1# Flippy (an Othello or Reversi clone)1# Flippy (an Othello or Reversi clone)
2# By Al Sweigart [email protected]2# By Al Sweigart [email protected]
3# http://inventwithpython.com/pygame3# http://inventwithpython.com/pygame
4# Released under a "Simplified BSD" license4# Released under a "Simplified BSD" license
55
6# Based on the "reversi.py" code that originally appeared in "Invent6# Based on the "reversi.py" code that originally appeared in "Invent
7# Your Own Computer Games with Python", chapter 15:7# Your Own Computer Games with Python", chapter 15:
8#   http://inventwithpython.com/chapter15.html8#   http://inventwithpython.com/chapter15.html
99
10import random, sys, pygame, time, copy10import random, sys, pygame, time, copy
11from pygame.locals import *11from pygame.locals import *
1212
 13# NEW SAVE FEATURE CODE:
 14import shelve
 15
13FPS = 10 # frames per second to update the screen16FPS = 10 # frames per second to update the screen
14WINDOWWIDTH = 640 # width of the program's window, in pixels17WINDOWWIDTH = 640 # width of the program's window, in pixels
15WINDOWHEIGHT = 480 # height in pixels18WINDOWHEIGHT = 480 # height in pixels
16SPACESIZE = 50 # width & height of each space on the board, in pixels19SPACESIZE = 50 # width & height of each space on the board, in pixels
17BOARDWIDTH = 8 # how many columns of spaces on the game board20BOARDWIDTH = 8 # how many columns of spaces on the game board
18BOARDHEIGHT = 8 # how many rows of spaces on the game board21BOARDHEIGHT = 8 # how many rows of spaces on the game board
19WHITE_TILE = 'WHITE_TILE' # an arbitrary but unique value22WHITE_TILE = 'WHITE_TILE' # an arbitrary but unique value
20BLACK_TILE = 'BLACK_TILE' # an arbitrary but unique value23BLACK_TILE = 'BLACK_TILE' # an arbitrary but unique value
21EMPTY_SPACE = 'EMPTY_SPACE' # an arbitrary but unique value24EMPTY_SPACE = 'EMPTY_SPACE' # an arbitrary but unique value
22HINT_TILE = 'HINT_TILE' # an arbitrary but unique value25HINT_TILE = 'HINT_TILE' # an arbitrary but unique value
23ANIMATIONSPEED = 25 # integer from 1 to 100, higher is faster animation26ANIMATIONSPEED = 25 # integer from 1 to 100, higher is faster animation
2427
25# Amount of space on the left & right side (XMARGIN) or above and below28# Amount of space on the left & right side (XMARGIN) or above and below
26# (YMARGIN) the game board, in pixels.29# (YMARGIN) the game board, in pixels.
27XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * SPACESIZE)) / 2)30XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * SPACESIZE)) / 2)
28YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * SPACESIZE)) / 2)31YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * SPACESIZE)) / 2)
2932
30#              R    G    B33#              R    G    B
31WHITE      = (255, 255, 255)34WHITE      = (255, 255, 255)
32BLACK      = (  0,   0,   0)35BLACK      = (  0,   0,   0)
33GREEN      = (  0, 155,   0)36GREEN      = (  0, 155,   0)
34BRIGHTBLUE = (  0,  50, 255)37BRIGHTBLUE = (  0,  50, 255)
35BROWN      = (174,  94,   0)38BROWN      = (174,  94,   0)
3639
37TEXTBGCOLOR1 = BRIGHTBLUE40TEXTBGCOLOR1 = BRIGHTBLUE
38TEXTBGCOLOR2 = GREEN41TEXTBGCOLOR2 = GREEN
39GRIDLINECOLOR = BLACK42GRIDLINECOLOR = BLACK
40TEXTCOLOR = WHITE43TEXTCOLOR = WHITE
41HINTCOLOR = BROWN44HINTCOLOR = BROWN
4245
4346
44def main():47def main():
45    global MAINCLOCK, DISPLAYSURF, FONT, BIGFONT, BGIMAGE48    global MAINCLOCK, DISPLAYSURF, FONT, BIGFONT, BGIMAGE
4649
47    pygame.init()50    pygame.init()
48    MAINCLOCK = pygame.time.Clock()51    MAINCLOCK = pygame.time.Clock()
49    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))52    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
50    pygame.display.set_caption('Flippy')53    pygame.display.set_caption('Flippy')
51    FONT = pygame.font.Font('freesansbold.ttf', 16)54    FONT = pygame.font.Font('freesansbold.ttf', 16)
52    BIGFONT = pygame.font.Font('freesansbold.ttf', 32)55    BIGFONT = pygame.font.Font('freesansbold.ttf', 32)
5356
54    # Set up the background image.57    # Set up the background image.
55    boardImage = pygame.image.load('flippyboard.png')58    boardImage = pygame.image.load('flippyboard.png')
56    # Use smoothscale() to stretch the board image to fit the entire board:59    # Use smoothscale() to stretch the board image to fit the entire board:
57    boardImage = pygame.transform.smoothscale(boardImage, (BOARDWIDTH * SPACESIZE, BOARDHEIGHT * SPACESIZE))60    boardImage = pygame.transform.smoothscale(boardImage, (BOARDWIDTH * SPACESIZE, BOARDHEIGHT * SPACESIZE))
58    boardImageRect = boardImage.get_rect()61    boardImageRect = boardImage.get_rect()
59    boardImageRect.topleft = (XMARGIN, YMARGIN)62    boardImageRect.topleft = (XMARGIN, YMARGIN)
60    BGIMAGE = pygame.image.load('flippybackground.png')63    BGIMAGE = pygame.image.load('flippybackground.png')
61    # Use smoothscale() to stretch the background image to fit the entire window:64    # Use smoothscale() to stretch the background image to fit the entire window:
62    BGIMAGE = pygame.transform.smoothscale(BGIMAGE, (WINDOWWIDTH, WINDOWHEIGHT))65    BGIMAGE = pygame.transform.smoothscale(BGIMAGE, (WINDOWWIDTH, WINDOWHEIGHT))
63    BGIMAGE.blit(boardImage, boardImageRect)66    BGIMAGE.blit(boardImage, boardImageRect)
6467
65    # Run the main game.68    # Run the main game.
66    while True:69    while True:
67        if runGame() == False:70        if runGame() == False:
68            break71            break
6972
7073
71def runGame():74def runGame():
72    # Plays a single game of reversi each time this function is called.75    # Plays a single game of reversi each time this function is called.
7376
74    # Reset the board and game.77    # Reset the board and game.
75    mainBoard = getNewBoard()78    mainBoard = getNewBoard()
76    resetBoard(mainBoard)79    resetBoard(mainBoard)
77    showHints = False80    showHints = False
78    turn = random.choice(['computer', 'player'])81    turn = random.choice(['computer', 'player'])
7982
80    # Draw the starting board and ask the player what color they want.83    # Draw the starting board and ask the player what color they want.
81    drawBoard(mainBoard)84    drawBoard(mainBoard)
82    playerTile, computerTile = enterPlayerTile()85    playerTile, computerTile = enterPlayerTile()
8386
84    # Make the Surface and Rect objects for the "New Game" and "Hints" buttons87    # Make the Surface and Rect objects for the "New Game" and "Hints" buttons
85    newGameSurf = FONT.render('New Game', True, TEXTCOLOR, TEXTBGCOLOR2)88    newGameSurf = FONT.render('New Game', True, TEXTCOLOR, TEXTBGCOLOR2)
86    newGameRect = newGameSurf.get_rect()89    newGameRect = newGameSurf.get_rect()
87    newGameRect.topright = (WINDOWWIDTH - 8, 10)90    newGameRect.topright = (WINDOWWIDTH - 8, 10)
88    hintsSurf = FONT.render('Hints', True, TEXTCOLOR, TEXTBGCOLOR2)91    hintsSurf = FONT.render('Hints', True, TEXTCOLOR, TEXTBGCOLOR2)
89    hintsRect = hintsSurf.get_rect()92    hintsRect = hintsSurf.get_rect()
90    hintsRect.topright = (WINDOWWIDTH - 8, 40)93    hintsRect.topright = (WINDOWWIDTH - 8, 40)
9194
 95    # NEW SAVE FEATURE CODE:
 96    # Make the Surface and Rect objects for the "Save game" and "Load game" slots
 97    saveButtonSurf = FONT.render('Save Game', True, TEXTCOLOR, TEXTBGCOLOR2)
 98    saveButtonRect = saveButtonSurf.get_rect()
 99    saveButtonRect.topright = (WINDOWWIDTH - 8, 70)
 100    loadButtonSurf = FONT.render('Load Game', True, TEXTCOLOR, TEXTBGCOLOR2)
 101    loadButtonRect = loadButtonSurf.get_rect()
 102    loadButtonRect.topright = (WINDOWWIDTH - 8, 100)
 103
92    while True: # main game loop104    while True: # main game loop
93        # Keep looping for player and computer's turns.105        # Keep looping for player and computer's turns.
94        if turn == 'player':106        if turn == 'player':
95            # Player's turn:107            # Player's turn:
96            if getValidMoves(mainBoard, playerTile) == []:108            if getValidMoves(mainBoard, playerTile) == []:
97                # If it's the player's turn but they109                # If it's the player's turn but they
98                # can't move, then end the game.110                # can't move, then end the game.
99                break111                break
100            movexy = None112            movexy = None
101            while movexy == None:113            while movexy == None:
102                # Keep looping until the player clicks on a valid space.114                # Keep looping until the player clicks on a valid space.
103115
104                # Determine which board data structure to use for display.116                # Determine which board data structure to use for display.
105                if showHints:117                if showHints:
106                    boardToDraw = getBoardWithValidMoves(mainBoard, playerTile)118                    boardToDraw = getBoardWithValidMoves(mainBoard, playerTile)
107                else:119                else:
108                    boardToDraw = mainBoard120                    boardToDraw = mainBoard
109121
110                checkForQuit()122                checkForQuit()
111                for event in pygame.event.get(): # event handling loop123                for event in pygame.event.get(): # event handling loop
112                    if event.type == MOUSEBUTTONUP:124                    if event.type == MOUSEBUTTONUP:
113                        # Handle mouse click events125                        # Handle mouse click events
114                        mousex, mousey = event.pos126                        mousex, mousey = event.pos
115                        if newGameRect.collidepoint( (mousex, mousey) ):127                        if newGameRect.collidepoint( (mousex, mousey) ):
116                            # Start a new game128                            # Start a new game
117                            return True129                            return True
118                        elif hintsRect.collidepoint( (mousex, mousey) ):130                        elif hintsRect.collidepoint( (mousex, mousey) ):
119                            # Toggle hints mode131                            # Toggle hints mode
120                            showHints = not showHints132                            showHints = not showHints
 133
 134                        # NEW SAVE FEATURE CODE:
 135                        elif saveButtonRect.collidepoint( (mousex, mousey) ):
 136                            # save the current game
 137                            saveGameShelfFile = shelve.open('flippySavedGames')
 138                            saveGameShelfFile['mainBoardVariable'] = mainBoard
 139                            saveGameShelfFile['playerTileVariable'] = playerTile
 140                            saveGameShelfFile['computerTileVariable'] = computerTile
 141                            saveGameShelfFile['turnVariable'] = turn
 142                            saveGameShelfFile['showHintsVariable'] = showHints
 143                            saveGameShelfFile.close()
 144
 145                            messageSurf = BIGFONT.render('Game Saved', True, TEXTCOLOR, TEXTBGCOLOR2)
 146                            messageRect = messageSurf.get_rect()
 147                            messageRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
 148                            DISPLAYSURF.blit(messageSurf, messageRect)
 149                            pygame.display.update()
 150                            time.sleep(1)
 151
 152                        elif loadButtonRect.collidepoint( (mousex, mousey) ):
 153                            # load the previously saved game
 154                            saveGameShelfFile = shelve.open('flippySavedGames')
 155                            if 'mainBoardVariable' in saveGameShelfFile:
 156                                mainBoard = saveGameShelfFile['mainBoardVariable']
 157                                playerTile = saveGameShelfFile['playerTileVariable']
 158                                computerTile = saveGameShelfFile['computerTileVariable']
 159                                turn = saveGameShelfFile['turnVariable']
 160                                showHints = saveGameShelfFile['showHintsVariable']
 161
 162                                messageSurf = BIGFONT.render('Game Loaded', True, TEXTCOLOR, TEXTBGCOLOR2)
 163                                messageRect = messageSurf.get_rect()
 164                                messageRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
 165                                DISPLAYSURF.blit(messageSurf, messageRect)
 166                                pygame.display.update()
 167                                time.sleep(1)
 168
121                        # movexy is set to a two-item tuple XY coordinate, or None value169                        # movexy is set to a two-item tuple XY coordinate, or None value
122                        movexy = getSpaceClicked(mousex, mousey)170                        movexy = getSpaceClicked(mousex, mousey)
123                        if movexy != None and not isValidMove(mainBoard, playerTile, movexy[0], movexy[1]):171                        if movexy != None and not isValidMove(mainBoard, playerTile, movexy[0], movexy[1]):
124                            movexy = None172                            movexy = None
125173
126                # Draw the game board.174                # Draw the game board.
127                drawBoard(boardToDraw)175                drawBoard(boardToDraw)
128                drawInfo(boardToDraw, playerTile, computerTile, turn)176                drawInfo(boardToDraw, playerTile, computerTile, turn)
129177
130                # Draw the "New Game" and "Hints" buttons.178                # Draw the "New Game" and "Hints" buttons.
131                DISPLAYSURF.blit(newGameSurf, newGameRect)179                DISPLAYSURF.blit(newGameSurf, newGameRect)
132                DISPLAYSURF.blit(hintsSurf, hintsRect)180                DISPLAYSURF.blit(hintsSurf, hintsRect)
133181
 182                # NEW SAVE FEATURE CODE:
 183                # Draw the "Save Game" and "Load Game" buttons.
 184                DISPLAYSURF.blit(saveButtonSurf, saveButtonRect)
 185                DISPLAYSURF.blit(loadButtonSurf, loadButtonRect)
 186
134                MAINCLOCK.tick(FPS)187                MAINCLOCK.tick(FPS)
135                pygame.display.update()188                pygame.display.update()
136189
137            # Make the move and end the turn.190            # Make the move and end the turn.
138            makeMove(mainBoard, playerTile, movexy[0], movexy[1], True)191            makeMove(mainBoard, playerTile, movexy[0], movexy[1], True)
139            if getValidMoves(mainBoard, computerTile) != []:192            if getValidMoves(mainBoard, computerTile) != []:
140                # Only set for the computer's turn if it can make a move.193                # Only set for the computer's turn if it can make a move.
141                turn = 'computer'194                turn = 'computer'
142195
143        else:196        else:
144            # Computer's turn:197            # Computer's turn:
145            if getValidMoves(mainBoard, computerTile) == []:198            if getValidMoves(mainBoard, computerTile) == []:
146                # If it was set to be the computer's turn but199                # If it was set to be the computer's turn but
147                # they can't move, then end the game.200                # they can't move, then end the game.
148                break201                break
149202
150            # Draw the board.203            # Draw the board.
151            drawBoard(mainBoard)204            drawBoard(mainBoard)
152            drawInfo(mainBoard, playerTile, computerTile, turn)205            drawInfo(mainBoard, playerTile, computerTile, turn)
153206
154            # Draw the "New Game" and "Hints" buttons.207            # Draw the "New Game" and "Hints" buttons.
155            DISPLAYSURF.blit(newGameSurf, newGameRect)208            DISPLAYSURF.blit(newGameSurf, newGameRect)
156            DISPLAYSURF.blit(hintsSurf, hintsRect)209            DISPLAYSURF.blit(hintsSurf, hintsRect)
 210
 211            # NEW SAVE FEATURE CODE:
 212            # Draw the "Save Game" and "Load Game" buttons.
 213            DISPLAYSURF.blit(saveButtonSurf, saveButtonRect)
 214            DISPLAYSURF.blit(loadButtonSurf, loadButtonRect)
157215
158            # Make it look like the computer is thinking by pausing a bit.216            # Make it look like the computer is thinking by pausing a bit.
159            pauseUntil = time.time() + random.randint(5, 15) * 0.1217            pauseUntil = time.time() + random.randint(5, 15) * 0.1
160            while time.time() < pauseUntil:218            while time.time() < pauseUntil:
161                pygame.display.update()219                pygame.display.update()
162220
163            # Make the move and end the turn.221            # Make the move and end the turn.
164            x, y = getComputerMove(mainBoard, computerTile)222            x, y = getComputerMove(mainBoard, computerTile)
165            makeMove(mainBoard, computerTile, x, y, True)223            makeMove(mainBoard, computerTile, x, y, True)
166            if getValidMoves(mainBoard, playerTile) != []:224            if getValidMoves(mainBoard, playerTile) != []:
167                # Only set for the player's turn if they can make a move.225                # Only set for the player's turn if they can make a move.
168                turn = 'player'226                turn = 'player'
169227
170    # Display the final score.228    # Display the final score.
171    drawBoard(mainBoard)229    drawBoard(mainBoard)
172    scores = getScoreOfBoard(mainBoard)230    scores = getScoreOfBoard(mainBoard)
173231
174    # Determine the text of the message to display.232    # Determine the text of the message to display.
175    if scores[playerTile] > scores[computerTile]:233    if scores[playerTile] > scores[computerTile]:
176        text = 'You beat the computer by %s points! Congratulations!' % \234        text = 'You beat the computer by %s points! Congratulations!' % \
177               (scores[playerTile] - scores[computerTile])235               (scores[playerTile] - scores[computerTile])
178    elif scores[playerTile] < scores[computerTile]:236    elif scores[playerTile] < scores[computerTile]:
179        text = 'You lost. The computer beat you by %s points.' % \237        text = 'You lost. The computer beat you by %s points.' % \
180               (scores[computerTile] - scores[playerTile])238               (scores[computerTile] - scores[playerTile])
181    else:239    else:
182        text = 'The game was a tie!'240        text = 'The game was a tie!'
183241
184    textSurf = FONT.render(text, True, TEXTCOLOR, TEXTBGCOLOR1)242    textSurf = FONT.render(text, True, TEXTCOLOR, TEXTBGCOLOR1)
185    textRect = textSurf.get_rect()243    textRect = textSurf.get_rect()
186    textRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))244    textRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
187    DISPLAYSURF.blit(textSurf, textRect)245    DISPLAYSURF.blit(textSurf, textRect)
188246
189    # Display the "Play again?" text with Yes and No buttons.247    # Display the "Play again?" text with Yes and No buttons.
190    text2Surf = BIGFONT.render('Play again?', True, TEXTCOLOR, TEXTBGCOLOR1)248    text2Surf = BIGFONT.render('Play again?', True, TEXTCOLOR, TEXTBGCOLOR1)
191    text2Rect = text2Surf.get_rect()249    text2Rect = text2Surf.get_rect()
192    text2Rect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 50)250    text2Rect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 50)
193251
194    # Make "Yes" button.252    # Make "Yes" button.
195    yesSurf = BIGFONT.render('Yes', True, TEXTCOLOR, TEXTBGCOLOR1)253    yesSurf = BIGFONT.render('Yes', True, TEXTCOLOR, TEXTBGCOLOR1)
196    yesRect = yesSurf.get_rect()254    yesRect = yesSurf.get_rect()
197    yesRect.center = (int(WINDOWWIDTH / 2) - 60, int(WINDOWHEIGHT / 2) + 90)255    yesRect.center = (int(WINDOWWIDTH / 2) - 60, int(WINDOWHEIGHT / 2) + 90)
198256
199    # Make "No" button.257    # Make "No" button.
200    noSurf = BIGFONT.render('No', True, TEXTCOLOR, TEXTBGCOLOR1)258    noSurf = BIGFONT.render('No', True, TEXTCOLOR, TEXTBGCOLOR1)
201    noRect = noSurf.get_rect()259    noRect = noSurf.get_rect()
202    noRect.center = (int(WINDOWWIDTH / 2) + 60, int(WINDOWHEIGHT / 2) + 90)260    noRect.center = (int(WINDOWWIDTH / 2) + 60, int(WINDOWHEIGHT / 2) + 90)
203261
204    while True:262    while True:
205        # Process events until the user clicks on Yes or No.263        # Process events until the user clicks on Yes or No.
206        checkForQuit()264        checkForQuit()
207        for event in pygame.event.get(): # event handling loop265        for event in pygame.event.get(): # event handling loop
208            if event.type == MOUSEBUTTONUP:266            if event.type == MOUSEBUTTONUP:
209                mousex, mousey = event.pos267                mousex, mousey = event.pos
210                if yesRect.collidepoint( (mousex, mousey) ):268                if yesRect.collidepoint( (mousex, mousey) ):
211                    return True269                    return True
212                elif noRect.collidepoint( (mousex, mousey) ):270                elif noRect.collidepoint( (mousex, mousey) ):
213                    return False271                    return False
214        DISPLAYSURF.blit(textSurf, textRect)272        DISPLAYSURF.blit(textSurf, textRect)
215        DISPLAYSURF.blit(text2Surf, text2Rect)273        DISPLAYSURF.blit(text2Surf, text2Rect)
216        DISPLAYSURF.blit(yesSurf, yesRect)274        DISPLAYSURF.blit(yesSurf, yesRect)
217        DISPLAYSURF.blit(noSurf, noRect)275        DISPLAYSURF.blit(noSurf, noRect)
218        pygame.display.update()276        pygame.display.update()
219        MAINCLOCK.tick(FPS)277        MAINCLOCK.tick(FPS)
220278
221279
222def translateBoardToPixelCoord(x, y):280def translateBoardToPixelCoord(x, y):
223    return XMARGIN + x * SPACESIZE + int(SPACESIZE / 2), YMARGIN + y * SPACESIZE + int(SPACESIZE / 2)281    return XMARGIN + x * SPACESIZE + int(SPACESIZE / 2), YMARGIN + y * SPACESIZE + int(SPACESIZE / 2)
224282
225283
226def animateTileChange(tilesToFlip, tileColor, additionalTile):284def animateTileChange(tilesToFlip, tileColor, additionalTile):
227    # Draw the additional tile that was just laid down. (Otherwise we'd285    # Draw the additional tile that was just laid down. (Otherwise we'd
228    # have to completely redraw the board & the board info.)286    # have to completely redraw the board & the board info.)
229    if tileColor == WHITE_TILE:287    if tileColor == WHITE_TILE:
230        additionalTileColor = WHITE288        additionalTileColor = WHITE
231    else:289    else:
232        additionalTileColor = BLACK290        additionalTileColor = BLACK
233    additionalTileX, additionalTileY = translateBoardToPixelCoord(additionalTile[0], additionalTile[1])291    additionalTileX, additionalTileY = translateBoardToPixelCoord(additionalTile[0], additionalTile[1])
234    pygame.draw.circle(DISPLAYSURF, additionalTileColor, (additionalTileX, additionalTileY), int(SPACESIZE / 2) - 4)292    pygame.draw.circle(DISPLAYSURF, additionalTileColor, (additionalTileX, additionalTileY), int(SPACESIZE / 2) - 4)
235    pygame.display.update()293    pygame.display.update()
236294
237    for rgbValues in range(0, 255, int(ANIMATIONSPEED * 2.55)):295    for rgbValues in range(0, 255, int(ANIMATIONSPEED * 2.55)):
238        if rgbValues > 255:296        if rgbValues > 255:
239            rgbValues = 255297            rgbValues = 255
240        elif rgbValues < 0:298        elif rgbValues < 0:
241            rgbValues = 0299            rgbValues = 0
242300
243        if tileColor == WHITE_TILE:301        if tileColor == WHITE_TILE:
244            color = tuple([rgbValues] * 3) # rgbValues goes from 0 to 255302            color = tuple([rgbValues] * 3) # rgbValues goes from 0 to 255
245        elif tileColor == BLACK_TILE:303        elif tileColor == BLACK_TILE:
246            color = tuple([255 - rgbValues] * 3) # rgbValues goes from 255 to 0304            color = tuple([255 - rgbValues] * 3) # rgbValues goes from 255 to 0
247305
248        for x, y in tilesToFlip:306        for x, y in tilesToFlip:
249            centerx, centery = translateBoardToPixelCoord(x, y)307            centerx, centery = translateBoardToPixelCoord(x, y)
250            pygame.draw.circle(DISPLAYSURF, color, (centerx, centery), int(SPACESIZE / 2) - 4)308            pygame.draw.circle(DISPLAYSURF, color, (centerx, centery), int(SPACESIZE / 2) - 4)
251        pygame.display.update()309        pygame.display.update()
252        MAINCLOCK.tick(FPS)310        MAINCLOCK.tick(FPS)
253        checkForQuit()311        checkForQuit()
254312
255313
256def drawBoard(board):314def drawBoard(board):
257    # Draw background of board.315    # Draw background of board.
258    DISPLAYSURF.blit(BGIMAGE, BGIMAGE.get_rect())316    DISPLAYSURF.blit(BGIMAGE, BGIMAGE.get_rect())
259317
260    # Draw grid lines of the board.318    # Draw grid lines of the board.
261    for x in range(BOARDWIDTH + 1):319    for x in range(BOARDWIDTH + 1):
262        # Draw the horizontal lines.320        # Draw the horizontal lines.
263        startx = (x * SPACESIZE) + XMARGIN321        startx = (x * SPACESIZE) + XMARGIN
264        starty = YMARGIN322        starty = YMARGIN
265        endx = (x * SPACESIZE) + XMARGIN323        endx = (x * SPACESIZE) + XMARGIN
266        endy = YMARGIN + (BOARDHEIGHT * SPACESIZE)324        endy = YMARGIN + (BOARDHEIGHT * SPACESIZE)
267        pygame.draw.line(DISPLAYSURF, GRIDLINECOLOR, (startx, starty), (endx, endy))325        pygame.draw.line(DISPLAYSURF, GRIDLINECOLOR, (startx, starty), (endx, endy))
268    for y in range(BOARDHEIGHT + 1):326    for y in range(BOARDHEIGHT + 1):
269        # Draw the vertical lines.327        # Draw the vertical lines.
270        startx = XMARGIN328        startx = XMARGIN
271        starty = (y * SPACESIZE) + YMARGIN329        starty = (y * SPACESIZE) + YMARGIN
272        endx = XMARGIN + (BOARDWIDTH * SPACESIZE)330        endx = XMARGIN + (BOARDWIDTH * SPACESIZE)
273        endy = (y * SPACESIZE) + YMARGIN331        endy = (y * SPACESIZE) + YMARGIN
274        pygame.draw.line(DISPLAYSURF, GRIDLINECOLOR, (startx, starty), (endx, endy))332        pygame.draw.line(DISPLAYSURF, GRIDLINECOLOR, (startx, starty), (endx, endy))
275333
276    # Draw the black & white tiles or hint spots.334    # Draw the black & white tiles or hint spots.
277    for x in range(BOARDWIDTH):335    for x in range(BOARDWIDTH):
278        for y in range(BOARDHEIGHT):336        for y in range(BOARDHEIGHT):
279            centerx, centery = translateBoardToPixelCoord(x, y)337            centerx, centery = translateBoardToPixelCoord(x, y)
280            if board[x][y] == WHITE_TILE or board[x][y] == BLACK_TILE:338            if board[x][y] == WHITE_TILE or board[x][y] == BLACK_TILE:
281                if board[x][y] == WHITE_TILE:339                if board[x][y] == WHITE_TILE:
282                    tileColor = WHITE340                    tileColor = WHITE
283                else:341                else:
284                    tileColor = BLACK342                    tileColor = BLACK
285                pygame.draw.circle(DISPLAYSURF, tileColor, (centerx, centery), int(SPACESIZE / 2) - 4)343                pygame.draw.circle(DISPLAYSURF, tileColor, (centerx, centery), int(SPACESIZE / 2) - 4)
286            if board[x][y] == HINT_TILE:344            if board[x][y] == HINT_TILE:
287                pygame.draw.rect(DISPLAYSURF, HINTCOLOR, (centerx - 4, centery - 4, 8, 8))345                pygame.draw.rect(DISPLAYSURF, HINTCOLOR, (centerx - 4, centery - 4, 8, 8))
288346
289347
290def getSpaceClicked(mousex, mousey):348def getSpaceClicked(mousex, mousey):
291    # Return a tuple of two integers of the board space coordinates where349    # Return a tuple of two integers of the board space coordinates where
292    # the mouse was clicked. (Or returns None not in any space.)350    # the mouse was clicked. (Or returns None not in any space.)
293    for x in range(BOARDWIDTH):351    for x in range(BOARDWIDTH):
294        for y in range(BOARDHEIGHT):352        for y in range(BOARDHEIGHT):
295            if mousex > x * SPACESIZE + XMARGIN and \353            if mousex > x * SPACESIZE + XMARGIN and \
296               mousex < (x + 1) * SPACESIZE + XMARGIN and \354               mousex < (x + 1) * SPACESIZE + XMARGIN and \
297               mousey > y * SPACESIZE + YMARGIN and \355               mousey > y * SPACESIZE + YMARGIN and \
298               mousey < (y + 1) * SPACESIZE + YMARGIN:356               mousey < (y + 1) * SPACESIZE + YMARGIN:
299                return (x, y)357                return (x, y)
300    return None358    return None
301359
302360
303def drawInfo(board, playerTile, computerTile, turn):361def drawInfo(board, playerTile, computerTile, turn):
304    # Draws scores and whose turn it is at the bottom of the screen.362    # Draws scores and whose turn it is at the bottom of the screen.
305    scores = getScoreOfBoard(board)363    scores = getScoreOfBoard(board)
306    scoreSurf = FONT.render("Player Score: %s    Computer Score: %s    %s's Turn" % (str(scores[playerTile]), str(scores[computerTile]), turn.title()), True, TEXTCOLOR)364    scoreSurf = FONT.render("Player Score: %s    Computer Score: %s    %s's Turn" % (str(scores[playerTile]), str(scores[computerTile]), turn.title()), True, TEXTCOLOR)
307    scoreRect = scoreSurf.get_rect()365    scoreRect = scoreSurf.get_rect()
308    scoreRect.bottomleft = (10, WINDOWHEIGHT - 5)366    scoreRect.bottomleft = (10, WINDOWHEIGHT - 5)
309    DISPLAYSURF.blit(scoreSurf, scoreRect)367    DISPLAYSURF.blit(scoreSurf, scoreRect)
310368
311369
312def resetBoard(board):370def resetBoard(board):
313    # Blanks out the board it is passed, and sets up starting tiles.371    # Blanks out the board it is passed, and sets up starting tiles.
314    for x in range(BOARDWIDTH):372    for x in range(BOARDWIDTH):
315        for y in range(BOARDHEIGHT):373        for y in range(BOARDHEIGHT):
316            board[x][y] = EMPTY_SPACE374            board[x][y] = EMPTY_SPACE
317375
318    # Add starting pieces to the center376    # Add starting pieces to the center
319    board[3][3] = WHITE_TILE377    board[3][3] = WHITE_TILE
320    board[3][4] = BLACK_TILE378    board[3][4] = BLACK_TILE
321    board[4][3] = BLACK_TILE379    board[4][3] = BLACK_TILE
322    board[4][4] = WHITE_TILE380    board[4][4] = WHITE_TILE
323381
324382
325def getNewBoard():383def getNewBoard():
326    # Creates a brand new, empty board data structure.384    # Creates a brand new, empty board data structure.
327    board = []385    board = []
328    for i in range(BOARDWIDTH):386    for i in range(BOARDWIDTH):
329        board.append([EMPTY_SPACE] * BOARDHEIGHT)387        board.append([EMPTY_SPACE] * BOARDHEIGHT)
330388
331    return board389    return board
332390
333391
334def isValidMove(board, tile, xstart, ystart):392def isValidMove(board, tile, xstart, ystart):
335    # Returns False if the player's move is invalid. If it is a valid393    # Returns False if the player's move is invalid. If it is a valid
336    # move, returns a list of spaces of the captured pieces.394    # move, returns a list of spaces of the captured pieces.
337    if board[xstart][ystart] != EMPTY_SPACE or not isOnBoard(xstart, ystart):395    if board[xstart][ystart] != EMPTY_SPACE or not isOnBoard(xstart, ystart):
338        return False396        return False
339397
340    board[xstart][ystart] = tile # temporarily set the tile on the board.398    board[xstart][ystart] = tile # temporarily set the tile on the board.
341399
342    if tile == WHITE_TILE:400    if tile == WHITE_TILE:
343        otherTile = BLACK_TILE401        otherTile = BLACK_TILE
344    else:402    else:
345        otherTile = WHITE_TILE403        otherTile = WHITE_TILE
346404
347    tilesToFlip = []405    tilesToFlip = []
348    # check each of the eight directions:406    # check each of the eight directions:
349    for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:407    for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:
350        x, y = xstart, ystart408        x, y = xstart, ystart
351        x += xdirection409        x += xdirection
352        y += ydirection410        y += ydirection
353        if isOnBoard(x, y) and board[x][y] == otherTile:411        if isOnBoard(x, y) and board[x][y] == otherTile:
354            # The piece belongs to the other player next to our piece.412            # The piece belongs to the other player next to our piece.
355            x += xdirection413            x += xdirection
356            y += ydirection414            y += ydirection
357            if not isOnBoard(x, y):415            if not isOnBoard(x, y):
358                continue416                continue
359            while board[x][y] == otherTile:417            while board[x][y] == otherTile:
360                x += xdirection418                x += xdirection
361                y += ydirection419                y += ydirection
362                if not isOnBoard(x, y):420                if not isOnBoard(x, y):
363                    break # break out of while loop, continue in for loop421                    break # break out of while loop, continue in for loop
364            if not isOnBoard(x, y):422            if not isOnBoard(x, y):
365                continue423                continue
366            if board[x][y] == tile:424            if board[x][y] == tile:
367                # There are pieces to flip over. Go in the reverse425                # There are pieces to flip over. Go in the reverse
368                # direction until we reach the original space, noting all426                # direction until we reach the original space, noting all
369                # the tiles along the way.427                # the tiles along the way.
370                while True:428                while True:
371                    x -= xdirection429                    x -= xdirection
372                    y -= ydirection430                    y -= ydirection
373                    if x == xstart and y == ystart:431                    if x == xstart and y == ystart:
374                        break432                        break
375                    tilesToFlip.append([x, y])433                    tilesToFlip.append([x, y])
376434
377    board[xstart][ystart] = EMPTY_SPACE # make space empty435    board[xstart][ystart] = EMPTY_SPACE # make space empty
378    if len(tilesToFlip) == 0: # If no tiles flipped, this move is invalid436    if len(tilesToFlip) == 0: # If no tiles flipped, this move is invalid
379        return False437        return False
380    return tilesToFlip438    return tilesToFlip
381439
382440
383def isOnBoard(x, y):441def isOnBoard(x, y):
384    # Returns True if the coordinates are located on the board.442    # Returns True if the coordinates are located on the board.
385    return x >= 0 and x < BOARDWIDTH and y >= 0 and y < BOARDHEIGHT443    return x >= 0 and x < BOARDWIDTH and y >= 0 and y < BOARDHEIGHT
386444
387445
388def getBoardWithValidMoves(board, tile):446def getBoardWithValidMoves(board, tile):
389    # Returns a new board with hint markings.447    # Returns a new board with hint markings.
390    dupeBoard = copy.deepcopy(board)448    dupeBoard = copy.deepcopy(board)
391449
392    for x, y in getValidMoves(dupeBoard, tile):450    for x, y in getValidMoves(dupeBoard, tile):
393        dupeBoard[x][y] = HINT_TILE451        dupeBoard[x][y] = HINT_TILE
394    return dupeBoard452    return dupeBoard
395453
396454
397def getValidMoves(board, tile):455def getValidMoves(board, tile):
398    # Returns a list of (x,y) tuples of all valid moves.456    # Returns a list of (x,y) tuples of all valid moves.
399    validMoves = []457    validMoves = []
400458
401    for x in range(BOARDWIDTH):459    for x in range(BOARDWIDTH):
402        for y in range(BOARDHEIGHT):460        for y in range(BOARDHEIGHT):
403            if isValidMove(board, tile, x, y) != False:461            if isValidMove(board, tile, x, y) != False:
404                validMoves.append((x, y))462                validMoves.append((x, y))
405    return validMoves463    return validMoves
406464
407465
408def getScoreOfBoard(board):466def getScoreOfBoard(board):
409    # Determine the score by counting the tiles.467    # Determine the score by counting the tiles.
410    xscore = 0468    xscore = 0
411    oscore = 0469    oscore = 0
412    for x in range(BOARDWIDTH):470    for x in range(BOARDWIDTH):
413        for y in range(BOARDHEIGHT):471        for y in range(BOARDHEIGHT):
414            if board[x][y] == WHITE_TILE:472            if board[x][y] == WHITE_TILE:
415                xscore += 1473                xscore += 1
416            if board[x][y] == BLACK_TILE:474            if board[x][y] == BLACK_TILE:
417                oscore += 1475                oscore += 1
418    return {WHITE_TILE:xscore, BLACK_TILE:oscore}476    return {WHITE_TILE:xscore, BLACK_TILE:oscore}
419477
420478
421def enterPlayerTile():479def enterPlayerTile():
422    # Draws the text and handles the mouse click events for letting480    # Draws the text and handles the mouse click events for letting
423    # the player choose which color they want to be.  Returns481    # the player choose which color they want to be.  Returns
424    # [WHITE_TILE, BLACK_TILE] if the player chooses to be White,482    # [WHITE_TILE, BLACK_TILE] if the player chooses to be White,
425    # [BLACK_TILE, WHITE_TILE] if Black.483    # [BLACK_TILE, WHITE_TILE] if Black.
426484
427    # Create the text.485    # Create the text.
428    textSurf = FONT.render('Do you want to be white or black?', True, TEXTCOLOR, TEXTBGCOLOR1)486    textSurf = FONT.render('Do you want to be white or black?', True, TEXTCOLOR, TEXTBGCOLOR1)
429    textRect = textSurf.get_rect()487    textRect = textSurf.get_rect()
430    textRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))488    textRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
431489
432    xSurf = BIGFONT.render('White', True, TEXTCOLOR, TEXTBGCOLOR1)490    xSurf = BIGFONT.render('White', True, TEXTCOLOR, TEXTBGCOLOR1)
433    xRect = xSurf.get_rect()491    xRect = xSurf.get_rect()
434    xRect.center = (int(WINDOWWIDTH / 2) - 60, int(WINDOWHEIGHT / 2) + 40)492    xRect.center = (int(WINDOWWIDTH / 2) - 60, int(WINDOWHEIGHT / 2) + 40)
435493
436    oSurf = BIGFONT.render('Black', True, TEXTCOLOR, TEXTBGCOLOR1)494    oSurf = BIGFONT.render('Black', True, TEXTCOLOR, TEXTBGCOLOR1)
437    oRect = oSurf.get_rect()495    oRect = oSurf.get_rect()
438    oRect.center = (int(WINDOWWIDTH / 2) + 60, int(WINDOWHEIGHT / 2) + 40)496    oRect.center = (int(WINDOWWIDTH / 2) + 60, int(WINDOWHEIGHT / 2) + 40)
439497
440    while True:498    while True:
441        # Keep looping until the player has clicked on a color.499        # Keep looping until the player has clicked on a color.
442        checkForQuit()500        checkForQuit()
443        for event in pygame.event.get(): # event handling loop501        for event in pygame.event.get(): # event handling loop
444            if event.type == MOUSEBUTTONUP:502            if event.type == MOUSEBUTTONUP:
445                mousex, mousey = event.pos503                mousex, mousey = event.pos
446                if xRect.collidepoint( (mousex, mousey) ):504                if xRect.collidepoint( (mousex, mousey) ):
447                    return [WHITE_TILE, BLACK_TILE]505                    return [WHITE_TILE, BLACK_TILE]
448                elif oRect.collidepoint( (mousex, mousey) ):506                elif oRect.collidepoint( (mousex, mousey) ):
449                    return [BLACK_TILE, WHITE_TILE]507                    return [BLACK_TILE, WHITE_TILE]
450508
451        # Draw the screen.509        # Draw the screen.
452        DISPLAYSURF.blit(textSurf, textRect)510        DISPLAYSURF.blit(textSurf, textRect)
453        DISPLAYSURF.blit(xSurf, xRect)511        DISPLAYSURF.blit(xSurf, xRect)
454        DISPLAYSURF.blit(oSurf, oRect)512        DISPLAYSURF.blit(oSurf, oRect)
455        pygame.display.update()513        pygame.display.update()
456        MAINCLOCK.tick(FPS)514        MAINCLOCK.tick(FPS)
457515
458516
459def makeMove(board, tile, xstart, ystart, realMove=False):517def makeMove(board, tile, xstart, ystart, realMove=False):
460    # Place the tile on the board at xstart, ystart, and flip tiles518    # Place the tile on the board at xstart, ystart, and flip tiles
461    # Returns False if this is an invalid move, True if it is valid.519    # Returns False if this is an invalid move, True if it is valid.
462    tilesToFlip = isValidMove(board, tile, xstart, ystart)520    tilesToFlip = isValidMove(board, tile, xstart, ystart)
463521
464    if tilesToFlip == False:522    if tilesToFlip == False:
465        return False523        return False
466524
467    board[xstart][ystart] = tile525    board[xstart][ystart] = tile
468526
469    if realMove:527    if realMove:
470        animateTileChange(tilesToFlip, tile, (xstart, ystart))528        animateTileChange(tilesToFlip, tile, (xstart, ystart))
471529
472    for x, y in tilesToFlip:530    for x, y in tilesToFlip:
473        board[x][y] = tile531        board[x][y] = tile
474    return True532    return True
475533
476534
477def isOnCorner(x, y):535def isOnCorner(x, y):
478    # Returns True if the position is in one of the four corners.536    # Returns True if the position is in one of the four corners.
479    return (x == 0 and y == 0) or \537    return (x == 0 and y == 0) or \
480           (x == BOARDWIDTH and y == 0) or \538           (x == BOARDWIDTH and y == 0) or \
481           (x == 0 and y == BOARDHEIGHT) or \539           (x == 0 and y == BOARDHEIGHT) or \
482           (x == BOARDWIDTH and y == BOARDHEIGHT)540           (x == BOARDWIDTH and y == BOARDHEIGHT)
483541
484542
485def getComputerMove(board, computerTile):543def getComputerMove(board, computerTile):
486    # Given a board and the computer's tile, determine where to544    # Given a board and the computer's tile, determine where to
487    # move and return that move as a [x, y] list.545    # move and return that move as a [x, y] list.
488    possibleMoves = getValidMoves(board, computerTile)546    possibleMoves = getValidMoves(board, computerTile)
489547
490    # randomize the order of the possible moves548    # randomize the order of the possible moves
491    random.shuffle(possibleMoves)549    random.shuffle(possibleMoves)
492550
493    # always go for a corner if available.551    # always go for a corner if available.
494    for x, y in possibleMoves:552    for x, y in possibleMoves:
495        if isOnCorner(x, y):553        if isOnCorner(x, y):
496            return [x, y]554            return [x, y]
497555
498    # Go through all possible moves and remember the best scoring move556    # Go through all possible moves and remember the best scoring move
499    bestScore = -1557    bestScore = -1
500    for x, y in possibleMoves:558    for x, y in possibleMoves:
501        dupeBoard = copy.deepcopy(board)559        dupeBoard = copy.deepcopy(board)
502        makeMove(dupeBoard, computerTile, x, y)560        makeMove(dupeBoard, computerTile, x, y)
503        score = getScoreOfBoard(dupeBoard)[computerTile]561        score = getScoreOfBoard(dupeBoard)[computerTile]
504        if score > bestScore:562        if score > bestScore:
505            bestMove = [x, y]563            bestMove = [x, y]
506            bestScore = score564            bestScore = score
507    return bestMove565    return bestMove
508566
509567
510def checkForQuit():568def checkForQuit():
511    for event in pygame.event.get((QUIT, KEYUP)): # event handling loop569    for event in pygame.event.get((QUIT, KEYUP)): # event handling loop
512        if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):570        if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
513            pygame.quit()571            pygame.quit()
514            sys.exit()572            sys.exit()
515573
516574
517if __name__ == '__main__':575if __name__ == '__main__':
518    main()576    main()