1 | """ turtle-example-suite:
|
---|
2 |
|
---|
3 | tdemo_nim.py
|
---|
4 |
|
---|
5 | Play nim against the computer. The player
|
---|
6 | who takes the last stick is the winner.
|
---|
7 |
|
---|
8 | Implements the model-view-controller
|
---|
9 | design pattern.
|
---|
10 | """
|
---|
11 |
|
---|
12 |
|
---|
13 | import turtle
|
---|
14 | import random
|
---|
15 | import time
|
---|
16 |
|
---|
17 | SCREENWIDTH = 640
|
---|
18 | SCREENHEIGHT = 480
|
---|
19 |
|
---|
20 | MINSTICKS = 7
|
---|
21 | MAXSTICKS = 31
|
---|
22 |
|
---|
23 | HUNIT = SCREENHEIGHT // 12
|
---|
24 | WUNIT = SCREENWIDTH // ((MAXSTICKS // 5) * 11 + (MAXSTICKS % 5) * 2)
|
---|
25 |
|
---|
26 | SCOLOR = (63, 63, 31)
|
---|
27 | HCOLOR = (255, 204, 204)
|
---|
28 | COLOR = (204, 204, 255)
|
---|
29 |
|
---|
30 | def randomrow():
|
---|
31 | return random.randint(MINSTICKS, MAXSTICKS)
|
---|
32 |
|
---|
33 | def computerzug(state):
|
---|
34 | xored = state[0] ^ state[1] ^ state[2]
|
---|
35 | if xored == 0:
|
---|
36 | return randommove(state)
|
---|
37 | for z in range(3):
|
---|
38 | s = state[z] ^ xored
|
---|
39 | if s <= state[z]:
|
---|
40 | move = (z, s)
|
---|
41 | return move
|
---|
42 |
|
---|
43 | def randommove(state):
|
---|
44 | m = max(state)
|
---|
45 | while True:
|
---|
46 | z = random.randint(0,2)
|
---|
47 | if state[z] > (m > 1):
|
---|
48 | break
|
---|
49 | rand = random.randint(m > 1, state[z]-1)
|
---|
50 | return z, rand
|
---|
51 |
|
---|
52 |
|
---|
53 | class NimModel(object):
|
---|
54 | def __init__(self, game):
|
---|
55 | self.game = game
|
---|
56 |
|
---|
57 | def setup(self):
|
---|
58 | if self.game.state not in [Nim.CREATED, Nim.OVER]:
|
---|
59 | return
|
---|
60 | self.sticks = [randomrow(), randomrow(), randomrow()]
|
---|
61 | self.player = 0
|
---|
62 | self.winner = None
|
---|
63 | self.game.view.setup()
|
---|
64 | self.game.state = Nim.RUNNING
|
---|
65 |
|
---|
66 | def move(self, row, col):
|
---|
67 | maxspalte = self.sticks[row]
|
---|
68 | self.sticks[row] = col
|
---|
69 | self.game.view.notify_move(row, col, maxspalte, self.player)
|
---|
70 | if self.game_over():
|
---|
71 | self.game.state = Nim.OVER
|
---|
72 | self.winner = self.player
|
---|
73 | self.game.view.notify_over()
|
---|
74 | elif self.player == 0:
|
---|
75 | self.player = 1
|
---|
76 | row, col = computerzug(self.sticks)
|
---|
77 | self.move(row, col)
|
---|
78 | self.player = 0
|
---|
79 |
|
---|
80 | def game_over(self):
|
---|
81 | return self.sticks == [0, 0, 0]
|
---|
82 |
|
---|
83 | def notify_move(self, row, col):
|
---|
84 | if self.sticks[row] <= col:
|
---|
85 | return
|
---|
86 | self.move(row, col)
|
---|
87 |
|
---|
88 |
|
---|
89 | class Stick(turtle.Turtle):
|
---|
90 | def __init__(self, row, col, game):
|
---|
91 | turtle.Turtle.__init__(self, visible=False)
|
---|
92 | self.row = row
|
---|
93 | self.col = col
|
---|
94 | self.game = game
|
---|
95 | x, y = self.coords(row, col)
|
---|
96 | self.shape("square")
|
---|
97 | self.shapesize(HUNIT/10.0, WUNIT/20.0)
|
---|
98 | self.speed(0)
|
---|
99 | self.pu()
|
---|
100 | self.goto(x,y)
|
---|
101 | self.color("white")
|
---|
102 | self.showturtle()
|
---|
103 |
|
---|
104 | def coords(self, row, col):
|
---|
105 | packet, remainder = divmod(col, 5)
|
---|
106 | x = (3 + 11 * packet + 2 * remainder) * WUNIT
|
---|
107 | y = (2 + 3 * row) * HUNIT
|
---|
108 | return x - SCREENWIDTH // 2 + WUNIT // 2, SCREENHEIGHT // 2 - y - HUNIT // 2
|
---|
109 |
|
---|
110 | def makemove(self, x, y):
|
---|
111 | if self.game.state != Nim.RUNNING:
|
---|
112 | return
|
---|
113 | self.game.controller.notify_move(self.row, self.col)
|
---|
114 |
|
---|
115 |
|
---|
116 | class NimView(object):
|
---|
117 | def __init__(self, game):
|
---|
118 | self.game = game
|
---|
119 | self.screen = game.screen
|
---|
120 | self.model = game.model
|
---|
121 | self.screen.colormode(255)
|
---|
122 | self.screen.tracer(False)
|
---|
123 | self.screen.bgcolor((240, 240, 255))
|
---|
124 | self.writer = turtle.Turtle(visible=False)
|
---|
125 | self.writer.pu()
|
---|
126 | self.writer.speed(0)
|
---|
127 | self.sticks = {}
|
---|
128 | for row in range(3):
|
---|
129 | for col in range(MAXSTICKS):
|
---|
130 | self.sticks[(row, col)] = Stick(row, col, game)
|
---|
131 | self.display("... a moment please ...")
|
---|
132 | self.screen.tracer(True)
|
---|
133 |
|
---|
134 | def display(self, msg1, msg2=None):
|
---|
135 | self.screen.tracer(False)
|
---|
136 | self.writer.clear()
|
---|
137 | if msg2 is not None:
|
---|
138 | self.writer.goto(0, - SCREENHEIGHT // 2 + 48)
|
---|
139 | self.writer.pencolor("red")
|
---|
140 | self.writer.write(msg2, align="center", font=("Courier",18,"bold"))
|
---|
141 | self.writer.goto(0, - SCREENHEIGHT // 2 + 20)
|
---|
142 | self.writer.pencolor("black")
|
---|
143 | self.writer.write(msg1, align="center", font=("Courier",14,"bold"))
|
---|
144 | self.screen.tracer(True)
|
---|
145 |
|
---|
146 |
|
---|
147 | def setup(self):
|
---|
148 | self.screen.tracer(False)
|
---|
149 | for row in range(3):
|
---|
150 | for col in range(self.model.sticks[row]):
|
---|
151 | self.sticks[(row, col)].color(SCOLOR)
|
---|
152 | for row in range(3):
|
---|
153 | for col in range(self.model.sticks[row], MAXSTICKS):
|
---|
154 | self.sticks[(row, col)].color("white")
|
---|
155 | self.display("Your turn! Click leftmost stick to remove.")
|
---|
156 | self.screen.tracer(True)
|
---|
157 |
|
---|
158 | def notify_move(self, row, col, maxspalte, player):
|
---|
159 | if player == 0:
|
---|
160 | farbe = HCOLOR
|
---|
161 | for s in range(col, maxspalte):
|
---|
162 | self.sticks[(row, s)].color(farbe)
|
---|
163 | else:
|
---|
164 | self.display(" ... thinking ... ")
|
---|
165 | time.sleep(0.5)
|
---|
166 | self.display(" ... thinking ... aaah ...")
|
---|
167 | farbe = COLOR
|
---|
168 | for s in range(maxspalte-1, col-1, -1):
|
---|
169 | time.sleep(0.2)
|
---|
170 | self.sticks[(row, s)].color(farbe)
|
---|
171 | self.display("Your turn! Click leftmost stick to remove.")
|
---|
172 |
|
---|
173 | def notify_over(self):
|
---|
174 | if self.game.model.winner == 0:
|
---|
175 | msg2 = "Congrats. You're the winner!!!"
|
---|
176 | else:
|
---|
177 | msg2 = "Sorry, the computer is the winner."
|
---|
178 | self.display("To play again press space bar. To leave press ESC.", msg2)
|
---|
179 |
|
---|
180 | def clear(self):
|
---|
181 | if self.game.state == Nim.OVER:
|
---|
182 | self.screen.clear()
|
---|
183 |
|
---|
184 | class NimController(object):
|
---|
185 |
|
---|
186 | def __init__(self, game):
|
---|
187 | self.game = game
|
---|
188 | self.sticks = game.view.sticks
|
---|
189 | self.BUSY = False
|
---|
190 | for stick in self.sticks.values():
|
---|
191 | stick.onclick(stick.makemove)
|
---|
192 | self.game.screen.onkey(self.game.model.setup, "space")
|
---|
193 | self.game.screen.onkey(self.game.view.clear, "Escape")
|
---|
194 | self.game.view.display("Press space bar to start game")
|
---|
195 | self.game.screen.listen()
|
---|
196 |
|
---|
197 | def notify_move(self, row, col):
|
---|
198 | if self.BUSY:
|
---|
199 | return
|
---|
200 | self.BUSY = True
|
---|
201 | self.game.model.notify_move(row, col)
|
---|
202 | self.BUSY = False
|
---|
203 |
|
---|
204 | class Nim(object):
|
---|
205 | CREATED = 0
|
---|
206 | RUNNING = 1
|
---|
207 | OVER = 2
|
---|
208 | def __init__(self, screen):
|
---|
209 | self.state = Nim.CREATED
|
---|
210 | self.screen = screen
|
---|
211 | self.model = NimModel(self)
|
---|
212 | self.view = NimView(self)
|
---|
213 | self.controller = NimController(self)
|
---|
214 |
|
---|
215 |
|
---|
216 | mainscreen = turtle.Screen()
|
---|
217 | mainscreen.mode("standard")
|
---|
218 | mainscreen.setup(SCREENWIDTH, SCREENHEIGHT)
|
---|
219 |
|
---|
220 | def main():
|
---|
221 | nim = Nim(mainscreen)
|
---|
222 | return "EVENTLOOP!"
|
---|
223 |
|
---|
224 | if __name__ == "__main__":
|
---|
225 | main()
|
---|
226 | turtle.mainloop()
|
---|
227 |
|
---|