blob: 9ae6cc5c01b9039981f31f4c28a470ac249ee5dc [file] [log] [blame]
Benjamin Peterson852f3cc2010-03-19 20:58:52 +00001""" turtle-example-suite:
2
3 tdemo_nim.py
4
5Play nim against the computer. The player
6who takes the last stick is the winner.
7
8Implements the model-view-controller
9design pattern.
10"""
11
12
13import turtle
14import random
15import time
16
17SCREENWIDTH = 640
18SCREENHEIGHT = 480
19
20MINSTICKS = 7
21MAXSTICKS = 31
22
23HUNIT = SCREENHEIGHT // 12
24WUNIT = SCREENWIDTH // ((MAXSTICKS // 5) * 11 + (MAXSTICKS % 5) * 2)
25
26SCOLOR = (63, 63, 31)
27HCOLOR = (255, 204, 204)
28COLOR = (204, 204, 255)
29
30def randomrow():
31 return random.randint(MINSTICKS, MAXSTICKS)
32
33def 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
43def 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
53class 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
89class 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
116class 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
Benjamin Peterson852f3cc2010-03-19 20:58:52 +0000146 def setup(self):
147 self.screen.tracer(False)
148 for row in range(3):
149 for col in range(self.model.sticks[row]):
150 self.sticks[(row, col)].color(SCOLOR)
151 for row in range(3):
152 for col in range(self.model.sticks[row], MAXSTICKS):
153 self.sticks[(row, col)].color("white")
154 self.display("Your turn! Click leftmost stick to remove.")
155 self.screen.tracer(True)
156
157 def notify_move(self, row, col, maxspalte, player):
158 if player == 0:
159 farbe = HCOLOR
160 for s in range(col, maxspalte):
161 self.sticks[(row, s)].color(farbe)
162 else:
163 self.display(" ... thinking ... ")
164 time.sleep(0.5)
165 self.display(" ... thinking ... aaah ...")
166 farbe = COLOR
167 for s in range(maxspalte-1, col-1, -1):
168 time.sleep(0.2)
169 self.sticks[(row, s)].color(farbe)
170 self.display("Your turn! Click leftmost stick to remove.")
171
172 def notify_over(self):
173 if self.game.model.winner == 0:
174 msg2 = "Congrats. You're the winner!!!"
175 else:
176 msg2 = "Sorry, the computer is the winner."
177 self.display("To play again press space bar. To leave press ESC.", msg2)
178
179 def clear(self):
180 if self.game.state == Nim.OVER:
181 self.screen.clear()
182
Terry Jan Reedyf5ac57d2014-06-30 16:09:24 -0400183
Benjamin Peterson852f3cc2010-03-19 20:58:52 +0000184class 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
Terry Jan Reedyf5ac57d2014-06-30 16:09:24 -0400204
Benjamin Peterson852f3cc2010-03-19 20:58:52 +0000205class Nim(object):
206 CREATED = 0
207 RUNNING = 1
208 OVER = 2
209 def __init__(self, screen):
210 self.state = Nim.CREATED
211 self.screen = screen
212 self.model = NimModel(self)
213 self.view = NimView(self)
214 self.controller = NimController(self)
215
216
Benjamin Peterson852f3cc2010-03-19 20:58:52 +0000217def main():
Terry Jan Reedyf5ac57d2014-06-30 16:09:24 -0400218 mainscreen = turtle.Screen()
219 mainscreen.mode("standard")
220 mainscreen.setup(SCREENWIDTH, SCREENHEIGHT)
Benjamin Peterson852f3cc2010-03-19 20:58:52 +0000221 nim = Nim(mainscreen)
Terry Jan Reedy8450c532014-08-27 01:43:50 -0400222 return "EVENTLOOP"
Benjamin Peterson852f3cc2010-03-19 20:58:52 +0000223
224if __name__ == "__main__":
225 main()
226 turtle.mainloop()