# Slide Puzzle # http://inventwithpython.com/blog # By Al Sweigart al@inventwithpython.com import pygame, sys, random, time from pygame.locals import * # Create the constants (change these to different values to modify the game.) COLS = 4 ROWS = 4 TILESIZE = 80 WINDOWWIDTH = 640 WINDOWHEIGHT = 480 FPS = 30 # Create the color constants BLACK = (0, 0, 0) WHITE = (255, 255, 255) BRIGHTBLUE = (0, 50, 255) BLUE = (0, 153, 153) GREEN = (0, 204, 0) BGCOLOR = BLUE TILECOLOR = GREEN TEXTCOLOR = WHITE BORDERCOLOR = BRIGHTBLUE FONTSIZE = 20 BUTTONCOLOR = WHITE BUTTONTEXTCOLOR = BLACK MESSAGECOLOR = WHITE # Other constants and global variables. RESET_SURF = None RESET_RECT = None NEW_SURF = None NEW_RECT = None SOLVE_SURF = None SOLVE_RECT = None XMARGIN = int((WINDOWWIDTH - (TILESIZE * COLS + (COLS - 1))) / 2) YMARGIN = int((WINDOWHEIGHT - (TILESIZE * ROWS + (ROWS - 1))) / 2) UP = 1 DOWN = 2 LEFT = 3 RIGHT = 4 def main(): global MAINCLOCK, MAINSURF, FONT, RESET_SURF, RESET_RECT, NEW_SURF, NEW_RECT, SOLVE_SURF, SOLVE_RECT pygame.init() MAINCLOCK = pygame.time.Clock() MAINSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) pygame.display.set_caption('Slide Puzzle') FONT = pygame.font.Font('freesansbold.ttf', FONTSIZE) # Store the option buttons and their rectangles in OPTIONS. RESET_SURF, RESET_RECT = makeText('Reset', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 90) NEW_SURF, NEW_RECT = makeText('New Game', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 60) SOLVE_SURF, SOLVE_RECT = makeText('Solve', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 30) mainBoard, solutionSeq = generateNewPuzzle(80) solvedBoard = getStartingBoard() # a solved board is the same as the board in a start state. seq = [] while True: # The main game loop. sliding = None msg = '' if mainBoard == solvedBoard: msg = 'Solved!' drawBoard(mainBoard, message=msg) """This is the main game loop, which constantly loops while the program is playing. In this loop, we display the board on the screen and also handle any input events from the player. sliding will store a which direction we should slide the blank space (depending on what the player clicked on or which key the player pressed.) We reset the value to None each time the game loop loops. Also, we may want to display a text message in the upper left corner of the window. We will store the string of this message in msg. If the board is currently in a solved state, then we want this message to be Solved!""" # Handle any events. for event in pygame.event.get(): if event.type == QUIT: terminate() if event.type == MOUSEBUTTONUP: spotClicked = getSpotClicked(mainBoard, event.pos[0], event.pos[1]) if spotClicked is not None: spotx, spoty = spotClicked blankx, blanky = getBlankPosition(mainBoard) if spotx == blankx + 1 and spoty == blanky: sliding = LEFT if spotx == blankx - 1 and spoty == blanky: sliding = RIGHT if spotx == blankx and spoty == blanky + 1: sliding = UP if spotx == blankx and spoty == blanky - 1: sliding = DOWN else: # check if the user clicked on one of the option buttons if RESET_RECT.collidepoint(event.pos[0], event.pos[1]): resetAnimation(mainBoard, seq) seq = [] if NEW_RECT.collidepoint(event.pos[0], event.pos[1]): mainBoard, solutionSeq = generateNewPuzzle(80) seq = [] if SOLVE_RECT.collidepoint(event.pos[0], event.pos[1]): resetAnimation(mainBoard, solutionSeq + seq) seq = [] if event.type == KEYUP: if (event.key == K_LEFT or event.key == ord('a')) and isValidMove(mainBoard, LEFT): sliding = LEFT if (event.key == K_RIGHT or event.key == ord('d')) and isValidMove(mainBoard, RIGHT): sliding = RIGHT if (event.key == K_UP or event.key == ord('w')) and isValidMove(mainBoard, UP): sliding = UP if (event.key == K_DOWN or event.key == ord('s')) and isValidMove(mainBoard, DOWN): sliding = DOWN if event.key == K_ESCAPE: terminate() if event.key == K_r: resetAnimation(board, moves) if sliding: slideAnimation(mainBoard, sliding) makeMove(mainBoard, sliding) seq.append(sliding) pygame.display.update() MAINCLOCK.tick(FPS) def terminate(): pygame.quit() sys.exit() def getStartingBoard(): counter = 1 board = [] for i in range(COLS): column = [] for j in range(ROWS): column.append(counter) counter += COLS board.append(column) counter -= COLS * (ROWS - 1) + COLS - 1 board[COLS-1][ROWS-1] = 0 return board def generateNewPuzzle(numSlides): sequence = [] board = getStartingBoard() drawBoard(board) pygame.display.update() time.sleep(0.5) lastMove = -1 for i in range(numSlides): move = getRandomMove(board, lastMove) slideAnimation(board, move, animationSpeed=int(TILESIZE / 3), message='Generating new puzzle...') makeMove(board, move) sequence.append(move) lastMove = move return (board, sequence) def makeMove(board, move): # This function does not check if the move is valid. blankx, blanky = getBlankPosition(board) if move == UP: board[blankx][blanky], board[blankx][blanky + 1] = board[blankx][blanky + 1], board[blankx][blanky] elif move == DOWN: board[blankx][blanky], board[blankx][blanky - 1] = board[blankx][blanky - 1], board[blankx][blanky] elif move == LEFT: board[blankx][blanky], board[blankx + 1][blanky] = board[blankx + 1][blanky], board[blankx][blanky] elif move == RIGHT: board[blankx][blanky], board[blankx - 1][blanky] = board[blankx - 1][blanky], board[blankx][blanky] def getLeftTopOfTile(tilex, tiley): left = XMARGIN + (tilex * TILESIZE) + (tilex - 1) top = YMARGIN + (tiley * TILESIZE) + (tiley - 1) return (left, top) def isValidMove(board, move): blankx, blanky = getBlankPosition(board) return (move == UP and blanky != len(board[0]) - 1) or \ (move == DOWN and blanky != 0) or \ (move == LEFT and blankx != len(board) - 1) or \ (move == RIGHT and blankx != 0) def getBlankPosition(board): for x in range(len(board[0])): for y in range(len(board)): if not board[x][y]: return (x, y) def drawTile(tilex, tiley, number, adjx=0, adjy=0): left, top = getLeftTopOfTile(tilex, tiley) pygame.draw.rect(MAINSURF, TILECOLOR, (left + adjx, top + adjy, TILESIZE, TILESIZE)) textSurf = FONT.render(str(number), True, TEXTCOLOR) textRect = textSurf.get_rect() textRect.center = left + int(TILESIZE / 2) + adjx, top + int(TILESIZE / 2) + adjy MAINSURF.blit(textSurf, textRect) def drawBoard(board, message=''): MAINSURF.fill(BGCOLOR) if message: textSurf, textRect = makeText(message, MESSAGECOLOR, BGCOLOR, 5, 5) MAINSURF.blit(textSurf, textRect) for tilex in range(len(board[0])): for tiley in range(len(board)): if board[tilex][tiley]: drawTile(tilex, tiley, board[tilex][tiley]) left, top = getLeftTopOfTile(0, 0) width = (COLS * TILESIZE) + (COLS - 1) height = (ROWS * TILESIZE) + (ROWS - 1) pygame.draw.rect(MAINSURF, BORDERCOLOR, (left - 5, top - 5, width + 9, height + 9), 4) MAINSURF.blit(RESET_SURF, RESET_RECT) MAINSURF.blit(NEW_SURF, NEW_RECT) MAINSURF.blit(SOLVE_SURF, SOLVE_RECT) def slideAnimation(board, direction, animationSpeed=8, message=''): # This function does not check if the move is valid. # prepare the base surface drawBoard(board, message=message) baseSurf = MAINSURF.copy() blankx, blanky = getBlankPosition(board) if direction == UP: movex = blankx movey = blanky + 1 elif direction == DOWN: movex = blankx movey = blanky - 1 elif direction == LEFT: movex = blankx + 1 movey = blanky elif direction == RIGHT: movex = blankx - 1 movey = blanky moveLeft, moveTop = getLeftTopOfTile(movex, movey) pygame.draw.rect(baseSurf, BGCOLOR, (moveLeft, moveTop, TILESIZE, TILESIZE)) for i in range(0, TILESIZE, animationSpeed): checkForQuit() if direction == UP: adjx = 0 adjy = -i if direction == DOWN: adjx = 0 adjy = i if direction == LEFT: adjx = -i adjy = 0 if direction == RIGHT: adjx = i adjy = 0 MAINSURF.blit(baseSurf, (0, 0)) drawTile(movex, movey, board[movex][movey], adjx, adjy) pygame.display.update() MAINCLOCK.tick(FPS) def checkForQuit(): for event in pygame.event.get(QUIT): terminate() for event in pygame.event.get(KEYUP): if event.key == K_ESCAPE: terminate() pygame.event.post(event) def getRandomMove(board, lastMove=None): validMoves = [UP, DOWN, LEFT, RIGHT] if lastMove == UP or not isValidMove(board, DOWN): validMoves.remove(DOWN) if lastMove == DOWN or not isValidMove(board, UP): validMoves.remove(UP) if lastMove == LEFT or not isValidMove(board, RIGHT): validMoves.remove(RIGHT) if lastMove == RIGHT or not isValidMove(board, LEFT): validMoves.remove(LEFT) return random.choice(validMoves) def getSpotClicked(board, x, y): for tilex in range(len(board[0])): for tiley in range(len(board)): left, top = getLeftTopOfTile(tilex, tiley) rectangle = pygame.Rect(left, top, TILESIZE, TILESIZE) if rectangle.collidepoint(x, y): return (tilex, tiley) return None def resetAnimation(board, sequence): #TODO - can probably get rid of this. revSequence = sequence[:] revSequence.reverse() for move in revSequence: if move == UP: oppositeMove = DOWN elif move == DOWN: oppositeMove = UP elif move == RIGHT: oppositeMove = LEFT elif move == LEFT: oppositeMove = RIGHT slideAnimation(board, oppositeMove, animationSpeed=int(TILESIZE / 2)) makeMove(board, oppositeMove) def makeText(text, color, bgcolor, top, left): textSurf = FONT.render(text, True, color, bgcolor) textRect = textSurf.get_rect() textRect.topleft = (top, left) return (textSurf, textRect) if __name__ == '__main__': main()