Advertisement
asweigart

Untitled

Oct 20th, 2021
146
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.38 KB | None | 0 0
  1. import random, time
  2.  
  3. random.seed(42) # Select which puzzle to solve.
  4. SIZE = 4 # Set this to either 3 or 4.
  5. BLANK = '__' # The string that represents the blank space.
  6.  
  7. # Constants for directions:
  8. UP = 'up'
  9. DOWN = 'down'
  10. LEFT = 'left'
  11. RIGHT = 'right'
  12.  
  13.  
  14.  
  15. def displayBoard(board):
  16. """Display `board` on the screen."""
  17.  
  18. for y in range(SIZE): # Iterate over each row.
  19. for x in range(SIZE): # Iterate over each column.
  20. print(board[x][y] + ' ', end='')
  21.  
  22. print() # Print a newline at the end of the row.
  23.  
  24.  
  25.  
  26.  
  27.  
  28. def getNewBoard():
  29. """Return a list of lists that represents a new tile puzzle."""
  30. # NOTE: All the single digit numbers have a space in front of them.
  31. if SIZE == 3:
  32. return [[' 1', ' 4', ' 7'], [' 2', ' 5', ' 8'],
  33. [' 3', ' 6', BLANK]]
  34. elif SIZE == 4:
  35. return [[' 1', ' 5', ' 9', '13'], [' 2', ' 6', '10', '14'],
  36. [' 3', ' 7', '11', '15'], [' 4', ' 8', '12', BLANK]]
  37.  
  38.  
  39.  
  40.  
  41. def findBlankSpace(board):
  42. """Return an [x, y] list of the blank space's location."""
  43. for x in range(SIZE):
  44. for y in range(SIZE):
  45. if board[x][y] == BLANK:
  46. return [x, y]
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53. def makeMove(b, move):
  54. """Carry out the given move on the given board in `b`."""
  55.  
  56. bx, by = findBlankSpace(b)
  57.  
  58. if move == UP:
  59. b[bx][by], b[bx][by+1] = b[bx][by+1], b[bx][by]
  60. elif move == LEFT:
  61. b[bx][by], b[bx+1][by] = b[bx+1][by], b[bx][by]
  62. elif move == DOWN:
  63. b[bx][by], b[bx][by-1] = b[bx][by-1], b[bx][by]
  64. elif move == RIGHT:
  65. b[bx][by], b[bx-1][by] = b[bx-1][by], b[bx][by]
  66.  
  67.  
  68.  
  69.  
  70. def undoMove(board, move):
  71. """Do the opposite move of `move` to undo it on `board`."""
  72. if move == UP:
  73. makeMove(board, DOWN)
  74. elif move == DOWN:
  75. makeMove(board, UP)
  76. elif move == LEFT:
  77. makeMove(board, RIGHT)
  78. elif move == RIGHT:
  79. makeMove(board, LEFT)
  80.  
  81.  
  82.  
  83.  
  84. def getValidMoves(board):
  85. """Returns a list of the valid moves to make on this board."""
  86.  
  87. blankx, blanky = findBlankSpace(board)
  88.  
  89. validMoves = []
  90. if blanky != SIZE - 1: # Blank space is not on the bottom row.
  91. validMoves.append(UP)
  92.  
  93. if blankx != SIZE - 1: # Blank space is not on the right column.
  94. validMoves.append(LEFT)
  95.  
  96. if blanky != 0: # Blank space is not on the top row.
  97. validMoves.append(DOWN)
  98.  
  99. if blankx != 0: # Blank space is not on the left column.
  100. validMoves.append(RIGHT)
  101.  
  102. return validMoves
  103.  
  104.  
  105.  
  106. def getNewPuzzle():
  107. """Get a new puzzle by making random slides from a solved state."""
  108. board = getNewBoard()
  109. for i in range(100):
  110. validMoves = getValidMoves(board)
  111. makeMove(board, random.choice(validMoves))
  112.  
  113. return board
  114.  
  115.  
  116. def solve(board, maxMoves):
  117. """Attempt to solve the puzzle in `board` in at most `maxMoves`
  118. moves. Returns True if solved, otherwise False."""
  119. print('Attempting to solve in at most', maxMoves, 'moves...')
  120. solutionMoves = [] # A list of UP, DOWN, LEFT, RIGHT values.
  121. cache = set()
  122. hitsMisses = {'hits': 0, 'misses': 0}
  123. st = time.time()
  124. solved = attemptMove(board, solutionMoves, cache, hitsMisses, maxMoves, 0)
  125. duration = round(time.time() - st, 2)
  126. if solved:
  127. displayBoard(board)
  128. for move in solutionMoves:
  129. print('Move', move)
  130. makeMove(board, move)
  131. print() # Print a newline.
  132. displayBoard(board)
  133. print() # Print a newline.
  134.  
  135. print('Solved in', len(solutionMoves), 'moves:')
  136. print(', '.join(solutionMoves))
  137. _log(maxMoves, duration, hitsMisses)
  138. return True # Puzzle was solved.
  139. else:
  140. _log(maxMoves, duration, hitsMisses)
  141. return False # Unable to solve in maxMoves moves.
  142.  
  143.  
  144. def _log(maxMoves, duration, hitsMisses):
  145. with open('_log.csv', 'a') as fo:
  146. fo.write(str(maxMoves) + '\t' + str(round(duration, 1)) + '\t' + str(hitsMisses['hits']) + '\t' + str(hitsMisses['misses']) + '\t' + str(round(hitsMisses['hits'] / (hitsMisses['hits'] + hitsMisses['misses']) * 100, 2)) + '%\n')
  147. print(str(maxMoves) + '\t' + str(round(duration, 1)) + '\t' + str(hitsMisses['hits']) + '\t' + str(hitsMisses['misses']) + '\t' + str(round(hitsMisses['hits'] / (hitsMisses['hits'] + hitsMisses['misses']) * 100, 2)) + '%')
  148.  
  149. def attemptMove(board, movesMade, stateCache, cacheHitsMisses, maxMoves, depth):
  150. """A recursive function that attempts all possible moves on `board`
  151. until a solution is found or we've reached the `maxMoves` limit.
  152. Returns True if a solution was found, in which case, movesMade
  153. contains the series of moves to solve the puzzle. Returns False
  154. if no solution was found in `maxMoves` moves."""
  155. if depth > maxMoves:
  156. return False # BASE CASE
  157.  
  158. if board == SOLVED_BOARD:
  159. # BASE CASE - Solved the puzzle:
  160. return True
  161.  
  162. if SIZE == 4:
  163. boardAsTuple = (tuple(board[0]), tuple(board[1]), tuple(board[2]), tuple(board[3]))
  164. elif SIZE == 3:
  165. boardAsTuple = (tuple(board[0]), tuple(board[1]), tuple(board[2]))
  166. if boardAsTuple in stateCache:
  167. cacheHitsMisses['hits'] += 1
  168. return False
  169. else:
  170. cacheHitsMisses['misses'] += 1
  171. stateCache.add(boardAsTuple)
  172. #breakpoint()
  173.  
  174. # Attempt each of the valid moves:
  175. for move in getValidMoves(board):
  176. # Make the move:
  177. makeMove(board, move)
  178. movesMade.append(move)
  179.  
  180. # RECURSIVE CASE - Attempt another move:
  181. if attemptMove(board, movesMade, stateCache, cacheHitsMisses, maxMoves, depth + 1):
  182. # If the puzzle is solved, return True:
  183. undoMove(board, move)
  184. return True
  185.  
  186. # Undo this last move to set up for the next move:
  187. undoMove(board, move)
  188. movesMade.pop() # Remove the last move since it was undone.
  189.  
  190. return False # BASE CASE - Unable to find a solution.
  191.  
  192.  
  193. random.seed(1); import os; os.unlink('_log.csv')
  194. # Start the program:
  195. SOLVED_BOARD = getNewBoard()
  196. gameBoard = getNewPuzzle()
  197. displayBoard(gameBoard)
  198. startTime = time.time()
  199.  
  200. maxMoves = 1
  201. while True:
  202. if solve(gameBoard, maxMoves):
  203. break # Break out of the loop when a solution is found.
  204. maxMoves += 1
  205. print('Run in', round(time.time() - startTime, 1), 'seconds.')
  206.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement