| """ turtle-example-suite: |
| |
| tdemo_nim.py |
| |
| Play nim against the computer. The player |
| who takes the last stick is the winner. |
| |
| Implements the model-view-controller |
| design pattern. |
| """ |
| |
| |
| import turtle |
| import random |
| import time |
| |
| SCREENWIDTH = 640 |
| SCREENHEIGHT = 480 |
| |
| MINSTICKS = 7 |
| MAXSTICKS = 31 |
| |
| HUNIT = SCREENHEIGHT // 12 |
| WUNIT = SCREENWIDTH // ((MAXSTICKS // 5) * 11 + (MAXSTICKS % 5) * 2) |
| |
| SCOLOR = (63, 63, 31) |
| HCOLOR = (255, 204, 204) |
| COLOR = (204, 204, 255) |
| |
| def randomrow(): |
| return random.randint(MINSTICKS, MAXSTICKS) |
| |
| def computerzug(state): |
| xored = state[0] ^ state[1] ^ state[2] |
| if xored == 0: |
| return randommove(state) |
| for z in range(3): |
| s = state[z] ^ xored |
| if s <= state[z]: |
| move = (z, s) |
| return move |
| |
| def randommove(state): |
| m = max(state) |
| while True: |
| z = random.randint(0,2) |
| if state[z] > (m > 1): |
| break |
| rand = random.randint(m > 1, state[z]-1) |
| return z, rand |
| |
| |
| class NimModel(object): |
| def __init__(self, game): |
| self.game = game |
| |
| def setup(self): |
| if self.game.state not in [Nim.CREATED, Nim.OVER]: |
| return |
| self.sticks = [randomrow(), randomrow(), randomrow()] |
| self.player = 0 |
| self.winner = None |
| self.game.view.setup() |
| self.game.state = Nim.RUNNING |
| |
| def move(self, row, col): |
| maxspalte = self.sticks[row] |
| self.sticks[row] = col |
| self.game.view.notify_move(row, col, maxspalte, self.player) |
| if self.game_over(): |
| self.game.state = Nim.OVER |
| self.winner = self.player |
| self.game.view.notify_over() |
| elif self.player == 0: |
| self.player = 1 |
| row, col = computerzug(self.sticks) |
| self.move(row, col) |
| self.player = 0 |
| |
| def game_over(self): |
| return self.sticks == [0, 0, 0] |
| |
| def notify_move(self, row, col): |
| if self.sticks[row] <= col: |
| return |
| self.move(row, col) |
| |
| |
| class Stick(turtle.Turtle): |
| def __init__(self, row, col, game): |
| turtle.Turtle.__init__(self, visible=False) |
| self.row = row |
| self.col = col |
| self.game = game |
| x, y = self.coords(row, col) |
| self.shape("square") |
| self.shapesize(HUNIT/10.0, WUNIT/20.0) |
| self.speed(0) |
| self.pu() |
| self.goto(x,y) |
| self.color("white") |
| self.showturtle() |
| |
| def coords(self, row, col): |
| packet, remainder = divmod(col, 5) |
| x = (3 + 11 * packet + 2 * remainder) * WUNIT |
| y = (2 + 3 * row) * HUNIT |
| return x - SCREENWIDTH // 2 + WUNIT // 2, SCREENHEIGHT // 2 - y - HUNIT // 2 |
| |
| def makemove(self, x, y): |
| if self.game.state != Nim.RUNNING: |
| return |
| self.game.controller.notify_move(self.row, self.col) |
| |
| |
| class NimView(object): |
| def __init__(self, game): |
| self.game = game |
| self.screen = game.screen |
| self.model = game.model |
| self.screen.colormode(255) |
| self.screen.tracer(False) |
| self.screen.bgcolor((240, 240, 255)) |
| self.writer = turtle.Turtle(visible=False) |
| self.writer.pu() |
| self.writer.speed(0) |
| self.sticks = {} |
| for row in range(3): |
| for col in range(MAXSTICKS): |
| self.sticks[(row, col)] = Stick(row, col, game) |
| self.display("... a moment please ...") |
| self.screen.tracer(True) |
| |
| def display(self, msg1, msg2=None): |
| self.screen.tracer(False) |
| self.writer.clear() |
| if msg2 is not None: |
| self.writer.goto(0, - SCREENHEIGHT // 2 + 48) |
| self.writer.pencolor("red") |
| self.writer.write(msg2, align="center", font=("Courier",18,"bold")) |
| self.writer.goto(0, - SCREENHEIGHT // 2 + 20) |
| self.writer.pencolor("black") |
| self.writer.write(msg1, align="center", font=("Courier",14,"bold")) |
| self.screen.tracer(True) |
| |
| |
| def setup(self): |
| self.screen.tracer(False) |
| for row in range(3): |
| for col in range(self.model.sticks[row]): |
| self.sticks[(row, col)].color(SCOLOR) |
| for row in range(3): |
| for col in range(self.model.sticks[row], MAXSTICKS): |
| self.sticks[(row, col)].color("white") |
| self.display("Your turn! Click leftmost stick to remove.") |
| self.screen.tracer(True) |
| |
| def notify_move(self, row, col, maxspalte, player): |
| if player == 0: |
| farbe = HCOLOR |
| for s in range(col, maxspalte): |
| self.sticks[(row, s)].color(farbe) |
| else: |
| self.display(" ... thinking ... ") |
| time.sleep(0.5) |
| self.display(" ... thinking ... aaah ...") |
| farbe = COLOR |
| for s in range(maxspalte-1, col-1, -1): |
| time.sleep(0.2) |
| self.sticks[(row, s)].color(farbe) |
| self.display("Your turn! Click leftmost stick to remove.") |
| |
| def notify_over(self): |
| if self.game.model.winner == 0: |
| msg2 = "Congrats. You're the winner!!!" |
| else: |
| msg2 = "Sorry, the computer is the winner." |
| self.display("To play again press space bar. To leave press ESC.", msg2) |
| |
| def clear(self): |
| if self.game.state == Nim.OVER: |
| self.screen.clear() |
| |
| class NimController(object): |
| |
| def __init__(self, game): |
| self.game = game |
| self.sticks = game.view.sticks |
| self.BUSY = False |
| for stick in self.sticks.values(): |
| stick.onclick(stick.makemove) |
| self.game.screen.onkey(self.game.model.setup, "space") |
| self.game.screen.onkey(self.game.view.clear, "Escape") |
| self.game.view.display("Press space bar to start game") |
| self.game.screen.listen() |
| |
| def notify_move(self, row, col): |
| if self.BUSY: |
| return |
| self.BUSY = True |
| self.game.model.notify_move(row, col) |
| self.BUSY = False |
| |
| class Nim(object): |
| CREATED = 0 |
| RUNNING = 1 |
| OVER = 2 |
| def __init__(self, screen): |
| self.state = Nim.CREATED |
| self.screen = screen |
| self.model = NimModel(self) |
| self.view = NimView(self) |
| self.controller = NimController(self) |
| |
| |
| mainscreen = turtle.Screen() |
| mainscreen.mode("standard") |
| mainscreen.setup(SCREENWIDTH, SCREENHEIGHT) |
| |
| def main(): |
| nim = Nim(mainscreen) |
| return "EVENTLOOP!" |
| |
| if __name__ == "__main__": |
| main() |
| turtle.mainloop() |