1"""      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
146    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
183
184class 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
205class 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
217def main():
218    mainscreen = turtle.Screen()
219    mainscreen.mode("standard")
220    mainscreen.setup(SCREENWIDTH, SCREENHEIGHT)
221    nim = Nim(mainscreen)
222    return "EVENTLOOP"
223
224if __name__ == "__main__":
225    main()
226    turtle.mainloop()
227