File: reversi.py

package info (click to toggle)
pythoncard 0.8.2-2
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 8,452 kB
  • sloc: python: 56,787; makefile: 56; sh: 22
file content (321 lines) | stat: -rw-r--r-- 11,813 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#!/usr/bin/python

"""
Simplistic implementation of the board game reversi, better known as Othello.

The algorithm for determining legal moves is not particularly efficient since
no attempt is made to cache legal moves.
"""

"""
__version__ = "$Revision: 1.4 $"
__date__ = "$Date: 2004/10/10 01:20:21 $"
"""

from PythonCard import dialog, model
from random import randint
import time
import wx

EMPTY = None
BLACK = True
WHITE = False
BOARDWIDTH = BOARDHEIGHT = 8

DIRECTIONS = ((-1, -1), (0, -1), (1, -1),
              (-1, 0),           (1, 0),
              (-1, 1),  (0, 1),  (1, 1))

BOARDCOLOR = 'dark green'
CELLWIDTH = CELLHEIGHT = 37

class GameBoard:
    def __init__(self):
        self.initializeBoard()

    def initializeBoard(self):
        self.board = {}
        # the board references are column, row
        # to simplify x, y translation
        for column in range(BOARDWIDTH):
            for row in range(BOARDHEIGHT):
                self.board[(column, row)] = EMPTY
        self.board[(3, 3)] = WHITE
        self.board[(4, 4)] = WHITE
        self.board[(3, 4)] = BLACK
        self.board[(4, 3)] = BLACK
        
        # black always goes first
        self.nextMove = BLACK
        self.buildLegalMoves(self.nextMove)
        self.gameOver = False

    def opponentColor(self, color):
        if color == BLACK:
            return WHITE
        else:
            return BLACK

    def legalMove(self, column, row, color):
        """returns the number of pieces flipped if the move is legal
        otherwise it returns 0"""
        totalFlipped = 0
        if self.board[(column, row)] == EMPTY:
            opponent = self.opponentColor(color)
            # to be a legal move 
            #   the position must be empty
            #   the position must be adjacent to an opponent
            #     color piece
            #   searching in the direction of the opposing
            #     color piece there must be a position matching
            #     the starting position color
            #   the edges of the board don't count
            board = self.board
            for dx, dy in DIRECTIONS:
                flipped = 0
                x = column + dx
                y = row + dy
                if board.get((x, y), EMPTY) == opponent:
                    # now check to see if we run into our
                    # own color so we have something to flip
                    # if we run into an empty space or off the board
                    # then it isn't a legal move
                    while board.get((x, y), EMPTY) == opponent:
                        x += dx
                        y += dy
                        flipped += 1
                        if board.get((x, y), EMPTY) == color:
                            totalFlipped += flipped
        return totalFlipped

    def buildLegalMoves(self, color):
        """build a dictionary of legal moves with the (column, row) as the key
        and the number of pieces flipped as the value"""

        self.legalMoves = {}
        for column, row in self.board.keys():
            flipped = self.legalMove(column, row, color)
            if flipped:
                self.legalMoves[(column, row)] = flipped
        
    def legalMovesAvailable(self, color):
        self.buildLegalMoves(color)
        # look at all empty positions on the board to determine
        # whether any legal moves exist
        if self.legalMoves == {}:
            return False
        else:
            return True

    def makeMove(self, column, row, color):
        self.board[(column, row)] = color
        # now flip all the pieces        
        
        opponent = self.opponentColor(color)
        # to be a legal move 
        #   the position must be empty
        #   the position must be adjacent to an opponent
        #     color piece
        #   searching in the direction of the opposing
        #     color piece there must be a position matching
        #     the starting position color
        #   the edges of the board don't count
        board = self.board
        for dx, dy in DIRECTIONS:
            x = column + dx
            y = row + dy
            if board.get((x, y), EMPTY) == opponent:
                flip = []
                # now check to see if we run into our
                # own color so we have something to flip
                # if we run into an empty space or off the board
                # then it isn't a legal move
                while board.get((x, y), EMPTY) == opponent:
                    # add pieces to flip
                    flip.append((x, y))
                    x += dx
                    y += dy
                    if board.get((x, y), EMPTY) == color:
                        for position in flip:
                            board[position] = color
                        break
                    

        # change who has the next move
        # if there are no legal moves left for the opponent
        # then we check whether there are any legal moves
        # left for the current player
        # if neither has a legal move then the game is over
        if self.legalMovesAvailable(opponent):
            self.nextMove = opponent
        elif self.legalMovesAvailable(color):
            self.nextMove = color
        else:
            self.gameOver = True

    # computer is currently stupid and just
    # randomly picks from the available legal moves
    # if you lose then you're Mr. Gumby <wink>
    # what it should do instead is be able to use
    # various strategies such as flip the most pieces
    # favor certain positions like the edges but avoid the
    # the spots next to the corners (weighted positions)
    # okay, I added the flip the most pieces strategy
    # but it is still pretty dumb
    
    def doRandomComputerMove(self, color):
        legalMoves = self.legalMoves.keys()
        column, row = legalMoves[randint(0, len(legalMoves) - 1)]
        self.makeMove(column, row, color)

    def doFlipMostPiecesComputerMove(self, color):
        flipped = 0
        for position in self.legalMoves.keys():
            #print "  ", position, self.legalMoves[position]
            if self.legalMoves[position] > flipped:
                best = position
                flipped = self.legalMoves[best]
        #print "picked:", best, self.legalMoves[best], "\n"
        self.makeMove(best[0], best[1], color)

    def getScore(self):
        """return a tuple containing the number of 
        empty, black, and white squares"""
        score = {BLACK:0, WHITE:0, EMPTY:0}
        for value in self.board.values():
            score[value] += 1
        return score


class Reversi(model.Background):

    def on_initialize(self, event):        
        self.boardModel = GameBoard()
        self.components.bufOff.size = (BOARDWIDTH * CELLWIDTH + 1, BOARDHEIGHT * CELLHEIGHT + 1)
        self.singleItemExpandingSizerLayout()
        
        self.drawBoard()
        self.updateStatus()
        
        self.player = BLACK
        self.computer = WHITE
        self.lastHover = None
        if self.computer == BLACK:
            self.boardModel.doComputerMove(BLACK)


    def computerMove(self):
        if self.menuBar.getChecked('menuStrategyFlipMostPieces'):
            self.boardModel.doFlipMostPiecesComputerMove(self.computer)
        else:
            self.boardModel.doRandomComputerMove(self.computer)
        # sleep for a second to make it appear
        # the computer thought long and hard on her choice :)
        time.sleep(1)
        self.drawBoard()
        self.updateStatus()

    def newGame(self):
        self.boardModel.initializeBoard()
        self.drawBoard()
        self.updateStatus()
        if self.computer == BLACK:
            self.computerMove()

    def drawCell(self, x, y, state):
        view = self.components.bufOff
        if state in [BLACK, WHITE]:
            if state == BLACK:
                color = 'black'
            else:
                color = 'white'
            view.fillColor = color
            center = (x * CELLWIDTH + CELLWIDTH / 2 + 1, y * CELLHEIGHT + CELLHEIGHT / 2 + 1)
            view.drawCircle(center, round((CELLWIDTH / 2.0) - 3))
        else:
            view.fillColor = BOARDCOLOR
            view.foregroundColor = BOARDCOLOR
            view.drawRectangle((x * CELLWIDTH + 1, y * CELLHEIGHT + 1), (CELLWIDTH - 2, CELLHEIGHT - 2))
            view.foregroundColor = 'black'
        
    def drawBoard(self):
        view = self.components.bufOff
        view.autoRefresh = False
        view.backgroundColor = BOARDCOLOR
        view.clear()
        # draw the right and bottom edge borders
        view.drawLine((0, BOARDHEIGHT * CELLHEIGHT), (BOARDWIDTH * CELLWIDTH, BOARDHEIGHT * CELLHEIGHT))
        view.drawLine((BOARDWIDTH * CELLWIDTH, 0), (BOARDWIDTH * CELLWIDTH, BOARDHEIGHT * CELLHEIGHT))
        for x in range(BOARDWIDTH):
            view.drawLine((x * CELLWIDTH, 0), (x * CELLWIDTH, BOARDHEIGHT * CELLHEIGHT))
            for y in range(BOARDHEIGHT):
                view.drawLine((0, y * CELLHEIGHT), (BOARDWIDTH * CELLWIDTH, y * CELLHEIGHT))
                state = self.boardModel.board[(x, y)]
                self.drawCell(x, y, state)
        view.autoRefresh = True
        view.refresh()
        if wx.Platform == '__WXMAC__':
            # Mac won't update screen even after a Blit 
            # until the event handler ends, so we have to force an update
            view.redraw()

    def updateStatus(self):
        if self.boardModel.gameOver:
            score = self.boardModel.getScore()
            playerScore = score[self.player]
            computerScore = score[self.computer]
            scoreString = "Black: %d  White: %d" % (score[BLACK], score[WHITE])
            if playerScore > computerScore:
                message = "Player won!"
            elif playerScore < computerScore:
                message = "Computer won!"
            else:
                message = "Tie Game"
            status = message + "  -  " + scoreString
        else:
            if self.boardModel.nextMove == BLACK:
                status = "Black's move"
            else:
                status = "White's move"
        self.statusBar.text = status

    def on_bufOff_mouseMove(self, event):
        x, y = event.position
        x = x / CELLWIDTH
        y = y / CELLHEIGHT
        #if self.boardModel.legalMove(x, y, self.boardModel.nextMove):
        if (x, y) != self.lastHover:
            # erase lastHover if needed
            if self.lastHover and self.boardModel.board[self.lastHover] is None:
                self.drawCell(self.lastHover[0], self.lastHover[1], EMPTY)
            # if the move is legal, show it
            if self.boardModel.legalMoves.get((x, y), None):
                self.drawCell(x, y, self.player)
            # don't track positions outside the valid range
            if (x >= 0 and x < BOARDWIDTH) and (y >= 0 and y < BOARDHEIGHT):
                self.lastHover = (x, y)

    def on_bufOff_mouseUp(self, event):
        x, y = event.position
        # this is a simplistic translation
        # when users click on the lines
        # separating cells they may get a cell
        # they didn't expect
        x = x / CELLWIDTH
        y = y / CELLHEIGHT
        if self.boardModel.legalMove(x, y, self.boardModel.nextMove):
            self.boardModel.makeMove(x, y, self.boardModel.nextMove)
            self.drawBoard()
            self.updateStatus()
            if not self.boardModel.gameOver:
                if self.boardModel.nextMove == self.computer:
                    self.computerMove()
        event.skip()

    def on_menuFileNewGame_select(self, event):
        self.newGame()


if __name__ == '__main__':
    app = model.Application(Reversi)
    app.MainLoop()