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