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