import pygame
import math
COLORS = [ 'crimson', 'forestgreen', 'yellow', 'royalblue',
'saddlebrown', 'hotpink', 'darkorange', 'darkmagenta' ]
NUM_OF_DISKS = len(COLORS)
BASE_LENGTH = 200
C_WIDTH = 3.732 * BASE_LENGTH
C_HEIGHT = 3.500 * BASE_LENGTH
DISK_R = 0.9 * BASE_LENGTH
POLE_R = 15
POSITIONS = { 'Source' : [0.268, 0.714],
'Auxiliary' : [0.500, 0.286],
'Destination' : [0.732, 0.714] }
FLASHING_COUNTER = 20
STEPS = 30
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
class Position(Vector):
def __init__(self, x, y):
super().__init__(x, y)
def move(self, vec):
self.x += vec.x
self.y += vec.y
class Disk:
def __init__(self, level):
self.level = level
self.color = COLORS[level]
self.r = (DISK_R-POLE_R)*(NUM_OF_DISKS-level)/NUM_OF_DISKS + POLE_R
class MovingDisk(Disk):
def __init__(self, level, frm, to):
super().__init__(level)
[sx, sy] = [frm.pos.x, frm.pos.y]
[dx, dy] = [to.pos.x, to.pos.y]
self.pos = Position(sx,sy)
self.mvec = Vector((dx-sx)/STEPS,(dy-sy)/STEPS)
self.move_ctr = 0
self.frm = frm
self.to = to
def step_forward(self):
self.pos.move(self.mvec)
self.move_ctr += 1
def finish_p(self):
ret_flag = (self.move_ctr == STEPS)
if ret_flag:
self.to.disks.append(Disk(self.level))
return ret_flag
class Tower:
def __init__(self, name, disks, direction=None):
self.name = name
self.disks = []
for i in range(disks):
self.disks.append(Disk(i))
self.direction = direction
self.moving = False
self.flash_ctr = 0
def toplevel(self):
l = len(self.disks)
# '-1' means there is no disk.
return self.disks[l-1].level if l > 0 else -1
def setup():
pygame.init()
screen = pygame.display.set_mode((C_WIDTH, C_HEIGHT))
pygame.display.set_caption('GDHP')
for t in [src,aux,dst]:
[rx, ry] = POSITIONS[t.name]
t.pos = Position(rx * C_WIDTH, ry * C_HEIGHT)
return screen
def base_drawing():
screen.fill('beige')
for t in [src,aux,dst]:
# draw disks
for d in t.disks:
pygame.draw.circle(screen, d.color, (t.pos.x,t.pos.y), d.r)
pygame.draw.circle(screen, 'black', (t.pos.x,t.pos.y), d.r, 1)
# draw a pole
fillcolor = 'gold' \
if t.moving and t.flash_ctr < FLASHING_COUNTER/2 else 'white'
pygame.draw.circle(screen, fillcolor, (t.pos.x,t.pos.y), POLE_R)
pygame.draw.circle(screen, 'brown', (t.pos.x,t.pos.y), POLE_R, 1)
# draw a direction
[sx, sy] = [t.pos.x, t.pos.y]
[dx, dy] = [t.direction.pos.x, t.direction.pos.y]
r = POLE_R / math.sqrt((dx-sx)*(dx-sx)+(dy-sy)*(dy-sy))
[dx, dy] = [(dx-sx)*r+sx, (dy-sy)*r+sy]
pygame.draw.line(screen, (0,0,128), (sx,sy), (dx,dy), 3)
def flash_poles():
for t in [src,aux,dst]:
t.moving = (t.direction.direction == t)
t.flash_ctr += 1
t.flash_ctr %= FLASHING_COUNTER
def pop_disk(src,aux,dst):
towers = list(filter(lambda x: x.moving, [src,aux,dst]))
idx = 0 if towers[0].toplevel() > towers[1].toplevel() else 1
[frm, to] = [towers[idx], towers[1-idx]]
return MovingDisk(frm.disks.pop().level,frm,to) \
if len(frm.disks) > 0 else None
def draw_moving_disk():
d = moving_disk
d.step_forward()
pygame.draw.circle(screen, d.color, (d.pos.x,d.pos.y), d.r)
pygame.draw.circle(screen, 'black', (d.pos.x,d.pos.y), d.r, 1)
return d.finish_p()
def turn():
for t in [moving_disk.frm,moving_disk.to]:
t.direction = list(filter(
lambda x: (x != t) and (x != t.direction), [src,aux,dst]))[0]
t.moving = False
def draw():
# base drawing
base_drawing()
# find two exchange-towers out of three
flash_poles()
# start moving
finish_p = False
mdisk = moving_disk
if mdisk == None:
mdisk = pop_disk(src,aux,dst)
if mdisk == None:
finish_p = True
else:
if draw_moving_disk():
turn()
mdisk = None
return mdisk, finish_p
# main routine
if __name__ == '__main__':
src = Tower('Source', NUM_OF_DISKS)
aux = Tower('Auxiliary', 0, src)
dst = Tower('Destination', 0, src)
# In the case of NUM_OF_DISKS is odd,
# the src must face the src.
# Otherwise, the src faces the aux.
src.direction = dst if len(COLORS) % 2 == 1 else aux
# the reference to moving disk is stored to this variable.
moving_disk = None
screen = setup()
while True:
for event in pygame.event.get():
if event.type == pygame.TEXTINPUT and event.text == 'q':
pygame.quit()
exit()
moving_disk, finish_p = draw()
if finish_p:
pygame.quit()
exit()
pygame.display.flip()
pygame.time.delay(30)