|
""" 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) |
|
|
|
|
|
def main(): |
|
mainscreen = turtle.Screen() |
|
mainscreen.mode("standard") |
|
mainscreen.setup(SCREENWIDTH, SCREENHEIGHT) |
|
nim = Nim(mainscreen) |
|
return "EVENTLOOP" |
|
|
|
if __name__ == "__main__": |
|
main() |
|
turtle.mainloop() |
|
|