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