1 | # Reversi | 1 | # Reversi |
2 | | 2 | |
3 | import random | 3 | import random |
4 | import sys | 4 | import sys |
5 | | 5 | |
| | 6 | # NEW SAVE FEATURE CODE: |
| | 7 | import shelve |
| | 8 | |
6 | def drawBoard(board): | 9 | def drawBoard(board): |
7 | # This function prints out the board that it was passed. Returns None. | 10 | # This function prints out the board that it was passed. Returns None. |
8 | HLINE = ' +---+---+---+---+---+---+---+---+' | 11 | HLINE = ' +---+---+---+---+---+---+---+---+' |
9 | VLINE = ' | | | | | | | | |' | 12 | VLINE = ' | | | | | | | | |' |
10 | | 13 | |
11 | print(' 1 2 3 4 5 6 7 8') | 14 | print(' 1 2 3 4 5 6 7 8') |
12 | print(HLINE) | 15 | print(HLINE) |
13 | for y in range(8): | 16 | for y in range(8): |
14 | print(VLINE) | 17 | print(VLINE) |
15 | print(y+1, end=' ') | 18 | print(y+1, end=' ') |
16 | for x in range(8): | 19 | for x in range(8): |
17 | print('| %s' % (board[x][y]), end=' ') | 20 | print('| %s' % (board[x][y]), end=' ') |
18 | print('|') | 21 | print('|') |
19 | print(VLINE) | 22 | print(VLINE) |
20 | print(HLINE) | 23 | print(HLINE) |
21 | | 24 | |
22 | | 25 | |
23 | def resetBoard(board): | 26 | def resetBoard(board): |
24 | # Blanks out the board it is passed, except for the original starting position. | 27 | # Blanks out the board it is passed, except for the original starting position. |
25 | for x in range(8): | 28 | for x in range(8): |
26 | for y in range(8): | 29 | for y in range(8): |
27 | board[x][y] = ' ' | 30 | board[x][y] = ' ' |
28 | | 31 | |
29 | # Starting pieces: | 32 | # Starting pieces: |
30 | board[3][3] = 'X' | 33 | board[3][3] = 'X' |
31 | board[3][4] = 'O' | 34 | board[3][4] = 'O' |
32 | board[4][3] = 'O' | 35 | board[4][3] = 'O' |
33 | board[4][4] = 'X' | 36 | board[4][4] = 'X' |
34 | | 37 | |
35 | | 38 | |
36 | def getNewBoard(): | 39 | def getNewBoard(): |
37 | # Creates a brand new, blank board data structure. | 40 | # Creates a brand new, blank board data structure. |
38 | board = [] | 41 | board = [] |
39 | for i in range(8): | 42 | for i in range(8): |
40 | board.append([' '] * 8) | 43 | board.append([' '] * 8) |
41 | | 44 | |
42 | return board | 45 | return board |
43 | | 46 | |
44 | | 47 | |
45 | def isValidMove(board, tile, xstart, ystart): | 48 | def isValidMove(board, tile, xstart, ystart): |
46 | # Returns False if the player's move on space xstart, ystart is invalid. | 49 | # Returns False if the player's move on space xstart, ystart is invalid. |
47 | # If it is a valid move, returns a list of spaces that would become the player's if they made a move here. | 50 | # If it is a valid move, returns a list of spaces that would become the player's if they made a move here. |
48 | if board[xstart][ystart] != ' ' or not isOnBoard(xstart, ystart): | 51 | if board[xstart][ystart] != ' ' or not isOnBoard(xstart, ystart): |
49 | return False | 52 | return False |
50 | | 53 | |
51 | board[xstart][ystart] = tile # temporarily set the tile on the board. | 54 | board[xstart][ystart] = tile # temporarily set the tile on the board. |
52 | | 55 | |
53 | if tile == 'X': | 56 | if tile == 'X': |
54 | otherTile = 'O' | 57 | otherTile = 'O' |
55 | else: | 58 | else: |
56 | otherTile = 'X' | 59 | otherTile = 'X' |
57 | | 60 | |
58 | tilesToFlip = [] | 61 | tilesToFlip = [] |
59 | for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]: | 62 | for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]: |
60 | x, y = xstart, ystart | 63 | x, y = xstart, ystart |
61 | x += xdirection # first step in the direction | 64 | x += xdirection # first step in the direction |
62 | y += ydirection # first step in the direction | 65 | y += ydirection # first step in the direction |
63 | if isOnBoard(x, y) and board[x][y] == otherTile: | 66 | if isOnBoard(x, y) and board[x][y] == otherTile: |
64 | # There is a piece belonging to the other player next to our piece. | 67 | # There is a piece belonging to the other player next to our piece. |
65 | x += xdirection | 68 | x += xdirection |
66 | y += ydirection | 69 | y += ydirection |
67 | if not isOnBoard(x, y): | 70 | if not isOnBoard(x, y): |
68 | continue | 71 | continue |
69 | while board[x][y] == otherTile: | 72 | while board[x][y] == otherTile: |
70 | x += xdirection | 73 | x += xdirection |
71 | y += ydirection | 74 | y += ydirection |
72 | if not isOnBoard(x, y): # break out of while loop, then continue in for loop | 75 | if not isOnBoard(x, y): # break out of while loop, then continue in for loop |
73 | break | 76 | break |
74 | if not isOnBoard(x, y): | 77 | if not isOnBoard(x, y): |
75 | continue | 78 | continue |
76 | if board[x][y] == tile: | 79 | if board[x][y] == tile: |
77 | # There are pieces to flip over. Go in the reverse direction until we reach the original space, noting all the tiles along the way. | 80 | # There are pieces to flip over. Go in the reverse direction until we reach the original space, noting all the tiles along the way. |
78 | while True: | 81 | while True: |
79 | x -= xdirection | 82 | x -= xdirection |
80 | y -= ydirection | 83 | y -= ydirection |
81 | if x == xstart and y == ystart: | 84 | if x == xstart and y == ystart: |
82 | break | 85 | break |
83 | tilesToFlip.append([x, y]) | 86 | tilesToFlip.append([x, y]) |
84 | | 87 | |
85 | board[xstart][ystart] = ' ' # restore the empty space | 88 | board[xstart][ystart] = ' ' # restore the empty space |
86 | if len(tilesToFlip) == 0: # If no tiles were flipped, this is not a valid move. | 89 | if len(tilesToFlip) == 0: # If no tiles were flipped, this is not a valid move. |
87 | return False | 90 | return False |
88 | return tilesToFlip | 91 | return tilesToFlip |
89 | | 92 | |
90 | | 93 | |
91 | def isOnBoard(x, y): | 94 | def isOnBoard(x, y): |
92 | # Returns True if the coordinates are located on the board. | 95 | # Returns True if the coordinates are located on the board. |
93 | return x >= 0 and x <= 7 and y >= 0 and y <=7 | 96 | return x >= 0 and x <= 7 and y >= 0 and y <=7 |
94 | | 97 | |
95 | | 98 | |
96 | def getBoardWithValidMoves(board, tile): | 99 | def getBoardWithValidMoves(board, tile): |
97 | # Returns a new board with . marking the valid moves the given player can make. | 100 | # Returns a new board with . marking the valid moves the given player can make. |
98 | dupeBoard = getBoardCopy(board) | 101 | dupeBoard = getBoardCopy(board) |
99 | | 102 | |
100 | for x, y in getValidMoves(dupeBoard, tile): | 103 | for x, y in getValidMoves(dupeBoard, tile): |
101 | dupeBoard[x][y] = '.' | 104 | dupeBoard[x][y] = '.' |
102 | return dupeBoard | 105 | return dupeBoard |
103 | | 106 | |
104 | | 107 | |
105 | def getValidMoves(board, tile): | 108 | def getValidMoves(board, tile): |
106 | # Returns a list of [x,y] lists of valid moves for the given player on the given board. | 109 | # Returns a list of [x,y] lists of valid moves for the given player on the given board. |
107 | validMoves = [] | 110 | validMoves = [] |
108 | | 111 | |
109 | for x in range(8): | 112 | for x in range(8): |
110 | for y in range(8): | 113 | for y in range(8): |
111 | if isValidMove(board, tile, x, y) != False: | 114 | if isValidMove(board, tile, x, y) != False: |
112 | validMoves.append([x, y]) | 115 | validMoves.append([x, y]) |
113 | return validMoves | 116 | return validMoves |
114 | | 117 | |
115 | | 118 | |
116 | def getScoreOfBoard(board): | 119 | def getScoreOfBoard(board): |
117 | # Determine the score by counting the tiles. Returns a dictionary with keys 'X' and 'O'. | 120 | # Determine the score by counting the tiles. Returns a dictionary with keys 'X' and 'O'. |
118 | xscore = 0 | 121 | xscore = 0 |
119 | oscore = 0 | 122 | oscore = 0 |
120 | for x in range(8): | 123 | for x in range(8): |
121 | for y in range(8): | 124 | for y in range(8): |
122 | if board[x][y] == 'X': | 125 | if board[x][y] == 'X': |
123 | xscore += 1 | 126 | xscore += 1 |
124 | if board[x][y] == 'O': | 127 | if board[x][y] == 'O': |
125 | oscore += 1 | 128 | oscore += 1 |
126 | return {'X':xscore, 'O':oscore} | 129 | return {'X':xscore, 'O':oscore} |
127 | | 130 | |
128 | | 131 | |
129 | def enterPlayerTile(): | 132 | def enterPlayerTile(): |
130 | # Let's the player type which tile they want to be. | 133 | # Let's the player type which tile they want to be. |
131 | # Returns a list with the player's tile as the first item, and the computer's tile as the second. | 134 | # Returns a list with the player's tile as the first item, and the computer's tile as the second. |
132 | tile = '' | 135 | tile = '' |
133 | while not (tile == 'X' or tile == 'O'): | 136 | while not (tile == 'X' or tile == 'O'): |
134 | print('Do you want to be X or O?') | 137 | print('Do you want to be X or O?') |
135 | tile = input().upper() | 138 | tile = input().upper() |
136 | | 139 | |
137 | # the first element in the tuple is the player's tile, the second is the computer's tile. | 140 | # the first element in the tuple is the player's tile, the second is the computer's tile. |
138 | if tile == 'X': | 141 | if tile == 'X': |
139 | return ['X', 'O'] | 142 | return ['X', 'O'] |
140 | else: | 143 | else: |
141 | return ['O', 'X'] | 144 | return ['O', 'X'] |
142 | | 145 | |
143 | | 146 | |
144 | def whoGoesFirst(): | 147 | def whoGoesFirst(): |
145 | # Randomly choose the player who goes first. | 148 | # Randomly choose the player who goes first. |
146 | if random.randint(0, 1) == 0: | 149 | if random.randint(0, 1) == 0: |
147 | return 'computer' | 150 | return 'computer' |
148 | else: | 151 | else: |
149 | return 'player' | 152 | return 'player' |
150 | | 153 | |
151 | | 154 | |
152 | def playAgain(): | 155 | def playAgain(): |
153 | # This function returns True if the player wants to play again, otherwise it returns False. | 156 | # This function returns True if the player wants to play again, otherwise it returns False. |
154 | print('Do you want to play again? (yes or no)') | 157 | print('Do you want to play again? (yes or no)') |
155 | return input().lower().startswith('y') | 158 | return input().lower().startswith('y') |
156 | | 159 | |
157 | | 160 | |
158 | def makeMove(board, tile, xstart, ystart): | 161 | def makeMove(board, tile, xstart, ystart): |
159 | # Place the tile on the board at xstart, ystart, and flip any of the opponent's pieces. | 162 | # Place the tile on the board at xstart, ystart, and flip any of the opponent's pieces. |
160 | # Returns False if this is an invalid move, True if it is valid. | 163 | # Returns False if this is an invalid move, True if it is valid. |
161 | tilesToFlip = isValidMove(board, tile, xstart, ystart) | 164 | tilesToFlip = isValidMove(board, tile, xstart, ystart) |
162 | | 165 | |
163 | if tilesToFlip == False: | 166 | if tilesToFlip == False: |
164 | return False | 167 | return False |
165 | | 168 | |
166 | board[xstart][ystart] = tile | 169 | board[xstart][ystart] = tile |
167 | for x, y in tilesToFlip: | 170 | for x, y in tilesToFlip: |
168 | board[x][y] = tile | 171 | board[x][y] = tile |
169 | return True | 172 | return True |
170 | | 173 | |
171 | | 174 | |
172 | def getBoardCopy(board): | 175 | def getBoardCopy(board): |
173 | # Make a duplicate of the board list and return the duplicate. | 176 | # Make a duplicate of the board list and return the duplicate. |
174 | dupeBoard = getNewBoard() | 177 | dupeBoard = getNewBoard() |
175 | | 178 | |
176 | for x in range(8): | 179 | for x in range(8): |
177 | for y in range(8): | 180 | for y in range(8): |
178 | dupeBoard[x][y] = board[x][y] | 181 | dupeBoard[x][y] = board[x][y] |
179 | | 182 | |
180 | return dupeBoard | 183 | return dupeBoard |
181 | | 184 | |
182 | | 185 | |
183 | def isOnCorner(x, y): | 186 | def isOnCorner(x, y): |
184 | # Returns True if the position is in one of the four corners. | 187 | # Returns True if the position is in one of the four corners. |
185 | return (x == 0 and y == 0) or (x == 7 and y == 0) or (x == 0 and y == 7) or (x == 7 and y == 7) | 188 | return (x == 0 and y == 0) or (x == 7 and y == 0) or (x == 0 and y == 7) or (x == 7 and y == 7) |
186 | | 189 | |
187 | | 190 | |
188 | def getPlayerMove(board, playerTile): | 191 | def getPlayerMove(board, playerTile): |
189 | # Let the player type in their move. | 192 | # Let the player type in their move. |
190 | # Returns the move as [x, y] (or returns the strings 'hints' or 'quit') | 193 | # Returns the move as [x, y] (or returns the strings 'hints' or 'quit') |
191 | DIGITS1TO8 = '1 2 3 4 5 6 7 8'.split() | 194 | DIGITS1TO8 = '1 2 3 4 5 6 7 8'.split() |
192 | while True: | 195 | while True: |
193 | print('Enter your move, or type quit to end the game, or hints to turn off/on hints.') | 196 | |
| | 197 | # NEW SAVE FEATURE CODE: |
| | 198 | print('Enter your move, or type quit to end the game, or hints to turn off/on hints, or save to save the current game and load to load the previously saved game.') |
194 | move = input().lower() | 199 | move = input().lower() |
| | 200 | if move == 'save': |
| | 201 | return 'save' |
| | 202 | if move == 'load': |
| | 203 | return 'load' |
| | 204 | |
195 | if move == 'quit': | 205 | if move == 'quit': |
196 | return 'quit' | 206 | return 'quit' |
197 | if move == 'hints': | 207 | if move == 'hints': |
198 | return 'hints' | 208 | return 'hints' |
199 | | 209 | |
200 | if len(move) == 2 and move[0] in DIGITS1TO8 and move[1] in DIGITS1TO8: | 210 | if len(move) == 2 and move[0] in DIGITS1TO8 and move[1] in DIGITS1TO8: |
201 | x = int(move[0]) - 1 | 211 | x = int(move[0]) - 1 |
202 | y = int(move[1]) - 1 | 212 | y = int(move[1]) - 1 |
203 | if isValidMove(board, playerTile, x, y) == False: | 213 | if isValidMove(board, playerTile, x, y) == False: |
204 | continue | 214 | continue |
205 | else: | 215 | else: |
206 | break | 216 | break |
207 | else: | 217 | else: |
208 | print('That is not a valid move. Type the x digit (1-8), then the y digit (1-8).') | 218 | print('That is not a valid move. Type the x digit (1-8), then the y digit (1-8).') |
209 | print('For example, 81 will be the top-right corner.') | 219 | print('For example, 81 will be the top-right corner.') |
210 | | 220 | |
211 | return [x, y] | 221 | return [x, y] |
212 | | 222 | |
213 | | 223 | |
214 | def getComputerMove(board, computerTile): | 224 | def getComputerMove(board, computerTile): |
215 | # Given a board and the computer's tile, determine where to | 225 | # Given a board and the computer's tile, determine where to |
216 | # move and return that move as a [x, y] list. | 226 | # move and return that move as a [x, y] list. |
217 | possibleMoves = getValidMoves(board, computerTile) | 227 | possibleMoves = getValidMoves(board, computerTile) |
218 | | 228 | |
219 | # randomize the order of the possible moves | 229 | # randomize the order of the possible moves |
220 | random.shuffle(possibleMoves) | 230 | random.shuffle(possibleMoves) |
221 | | 231 | |
222 | # always go for a corner if available. | 232 | # always go for a corner if available. |
223 | for x, y in possibleMoves: | 233 | for x, y in possibleMoves: |
224 | if isOnCorner(x, y): | 234 | if isOnCorner(x, y): |
225 | return [x, y] | 235 | return [x, y] |
226 | | 236 | |
227 | # Go through all the possible moves and remember the best scoring move | 237 | # Go through all the possible moves and remember the best scoring move |
228 | bestScore = -1 | 238 | bestScore = -1 |
229 | for x, y in possibleMoves: | 239 | for x, y in possibleMoves: |
230 | dupeBoard = getBoardCopy(board) | 240 | dupeBoard = getBoardCopy(board) |
231 | makeMove(dupeBoard, computerTile, x, y) | 241 | makeMove(dupeBoard, computerTile, x, y) |
232 | score = getScoreOfBoard(dupeBoard)[computerTile] | 242 | score = getScoreOfBoard(dupeBoard)[computerTile] |
233 | if score > bestScore: | 243 | if score > bestScore: |
234 | bestMove = [x, y] | 244 | bestMove = [x, y] |
235 | bestScore = score | 245 | bestScore = score |
236 | return bestMove | 246 | return bestMove |
237 | | 247 | |
238 | | 248 | |
239 | def showPoints(playerTile, computerTile): | 249 | def showPoints(playerTile, computerTile): |
240 | # Prints out the current score. | 250 | # Prints out the current score. |
241 | scores = getScoreOfBoard(mainBoard) | 251 | scores = getScoreOfBoard(mainBoard) |
242 | print('You have %s points. The computer has %s points.' % (scores[playerTile], scores[computerTile])) | 252 | print('You have %s points. The computer has %s points.' % (scores[playerTile], scores[computerTile])) |
243 | | 253 | |
244 | | 254 | |
245 | | 255 | |
246 | print('Welcome to Reversi!') | 256 | print('Welcome to Reversi!') |
247 | | 257 | |
248 | while True: | 258 | while True: |
249 | # Reset the board and game. | 259 | # Reset the board and game. |
250 | mainBoard = getNewBoard() | 260 | mainBoard = getNewBoard() |
251 | resetBoard(mainBoard) | 261 | resetBoard(mainBoard) |
252 | playerTile, computerTile = enterPlayerTile() | 262 | playerTile, computerTile = enterPlayerTile() |
253 | showHints = False | 263 | showHints = False |
254 | turn = whoGoesFirst() | 264 | turn = whoGoesFirst() |
255 | print('The ' + turn + ' will go first.') | 265 | print('The ' + turn + ' will go first.') |
256 | | 266 | |
257 | while True: | 267 | while True: |
258 | if turn == 'player': | 268 | if turn == 'player': |
259 | # Player's turn. | 269 | # Player's turn. |
260 | if showHints: | 270 | if showHints: |
261 | validMovesBoard = getBoardWithValidMoves(mainBoard, playerTile) | 271 | validMovesBoard = getBoardWithValidMoves(mainBoard, playerTile) |
262 | drawBoard(validMovesBoard) | 272 | drawBoard(validMovesBoard) |
263 | else: | 273 | else: |
264 | drawBoard(mainBoard) | 274 | drawBoard(mainBoard) |
265 | showPoints(playerTile, computerTile) | 275 | showPoints(playerTile, computerTile) |
266 | move = getPlayerMove(mainBoard, playerTile) | 276 | move = getPlayerMove(mainBoard, playerTile) |
267 | if move == 'quit': | 277 | if move == 'quit': |
268 | print('Thanks for playing!') | 278 | print('Thanks for playing!') |
269 | sys.exit() # terminate the program | 279 | sys.exit() # terminate the program |
270 | elif move == 'hints': | 280 | elif move == 'hints': |
271 | showHints = not showHints | 281 | showHints = not showHints |
272 | continue | 282 | continue |
| | 283 | |
| | 284 | # NEW SAVE FEATURE CODE: |
| | 285 | elif move == 'save': |
| | 286 | saveGameShelfFile = shelve.open('reversiSavedGames') |
| | 287 | saveGameShelfFile['mainBoardVariable'] = mainBoard |
| | 288 | saveGameShelfFile['playerTileVariable'] = playerTile |
| | 289 | saveGameShelfFile['computerTileVariable'] = computerTile |
| | 290 | saveGameShelfFile['turnVariable'] = turn |
| | 291 | saveGameShelfFile['showHintsVariable'] = showHints |
| | 292 | saveGameShelfFile.close() |
| | 293 | print('Game saved.') |
| | 294 | continue |
| | 295 | elif move == 'load': |
| | 296 | saveGameShelfFile = shelve.open('reversiSavedGames') |
| | 297 | if 'mainBoardVariable' in saveGameShelfFile: |
| | 298 | mainBoard = saveGameShelfFile['mainBoardVariable'] |
| | 299 | playerTile = saveGameShelfFile['playerTileVariable'] |
| | 300 | computerTile = saveGameShelfFile['computerTileVariable'] |
| | 301 | turn = saveGameShelfFile['turnVariable'] |
| | 302 | showHints = saveGameShelfFile['showHintsVariable'] |
| | 303 | print('Game loaded.') |
| | 304 | else: |
| | 305 | print('\n\nThere is no previously saved game.\n') |
| | 306 | continue |
| | 307 | |
273 | else: | 308 | else: |
274 | makeMove(mainBoard, playerTile, move[0], move[1]) | 309 | makeMove(mainBoard, playerTile, move[0], move[1]) |
275 | | 310 | |
276 | if getValidMoves(mainBoard, computerTile) == []: | 311 | if getValidMoves(mainBoard, computerTile) == []: |
277 | break | 312 | break |
278 | else: | 313 | else: |
279 | turn = 'computer' | 314 | turn = 'computer' |
280 | | 315 | |
281 | else: | 316 | else: |
282 | # Computer's turn. | 317 | # Computer's turn. |
283 | drawBoard(mainBoard) | 318 | drawBoard(mainBoard) |
284 | showPoints(playerTile, computerTile) | 319 | showPoints(playerTile, computerTile) |
285 | input('Press Enter to see the computer\'s move.') | 320 | input('Press Enter to see the computer\'s move.') |
286 | x, y = getComputerMove(mainBoard, computerTile) | 321 | x, y = getComputerMove(mainBoard, computerTile) |
287 | makeMove(mainBoard, computerTile, x, y) | 322 | makeMove(mainBoard, computerTile, x, y) |
288 | | 323 | |
289 | if getValidMoves(mainBoard, playerTile) == []: | 324 | if getValidMoves(mainBoard, playerTile) == []: |
290 | break | 325 | break |
291 | else: | 326 | else: |
292 | turn = 'player' | 327 | turn = 'player' |
293 | | 328 | |
294 | # Display the final score. | 329 | # Display the final score. |
295 | drawBoard(mainBoard) | 330 | drawBoard(mainBoard) |
296 | scores = getScoreOfBoard(mainBoard) | 331 | scores = getScoreOfBoard(mainBoard) |
297 | print('X scored %s points. O scored %s points.' % (scores['X'], scores['O'])) | 332 | print('X scored %s points. O scored %s points.' % (scores['X'], scores['O'])) |
298 | if scores[playerTile] > scores[computerTile]: | 333 | if scores[playerTile] > scores[computerTile]: |
299 | print('You beat the computer by %s points! Congratulations!' % (scores[playerTile] - scores[computerTile])) | 334 | print('You beat the computer by %s points! Congratulations!' % (scores[playerTile] - scores[computerTile])) |
300 | elif scores[playerTile] < scores[computerTile]: | 335 | elif scores[playerTile] < scores[computerTile]: |
301 | print('You lost. The computer beat you by %s points.' % (scores[computerTile] - scores[playerTile])) | 336 | print('You lost. The computer beat you by %s points.' % (scores[computerTile] - scores[playerTile])) |
302 | else: | 337 | else: |
303 | print('The game was a tie!') | 338 | print('The game was a tie!') |
304 | | 339 | |
305 | if not playAgain(): | 340 | if not playAgain(): |
306 | break | 341 | break |