'''
November 2016 Puzzle
Below are two arrangements of six numbered disks. Your goal
is to convert the triangular arrangement to the linear arrangement
in exactly eight moves. Each move consists of moving a disk
to a position where it is adjacent to two other disks. As
you can see, initially the 1, 4, and 6 are each adjacent to two
other disks, while the 2, 3, and 5 are each adjacent to four
other disks.
1
2 3 ---> 1 2 3 4 5 6
4 5 6
'''
from __future__ import print_function
#Required: graphics.py module, written by John Zelle
# http://mcsp.wartburg.edu/zelle/python/graphics.py
try:
import graphics as g
except:
print('This program depends on graphics.py by John Zelle at\n' \
+ 'http://mcsp.wartburg.edu/zelle/python/graphics.py\n'\
+ 'Please load and try again.')
exit()
class LabeledCircle:
'''
A LabeledCircle is a graphics.py circle with a text label to uniquely
identify it. It location is defined by (x, y) which refers to the grid
whose x and y ar multiples of the size of the circle. Grid locations
are converted to pixel values in a method below.
'''
def __init__(self, x, y, label):
'''
Create the LabeledCircle which is a graphics.py Circle with a Text
label identifier. The coodinates are its position on a grid where
the cell sizes are the size of each circle with the circle centered
in the cell.
'''
self.x = x #Grid x value of circle
self.y = y #Grid y value of circle
(posX, posY)= self.loc2pixel(x, y) #Get pixel values for x and y
#Create the object
self.circle = g.Circle(g.Point(posX, posY), circleSize)
self.label = g.Text(g.Point(posX, posY),label)
def draw(self, window):
'''
Draw the object (the circle and its label) onto the window
'''
self.circle.draw(window)
self.label.draw(window)
def move(self, x, y):
'''
Move the object to (x, y). Calculate the original pixel location and
the new position's pixel location from the grid locations. Calcuate
the pixel displacement. Then move the circle and its label.
'''
(posX, posY) = self.loc2pixel(self.x, self.y) #Current pixel position
(newPosX, newPosY) = self.loc2pixel(x,y) #New pixel position
xDist = newPosX - posX #Calculate the displacement
yDist = newPosY - posY
self.circle.move(xDist, yDist) #Do the move
self.label.move(xDist, yDist)
self.x = x #Update the new grid position
self.y = y
def loc2pixel(self, x, y):
'''
Convert the grid location to a pixel location on the window
'''
pixelX = x * circleSize + 15
pixelY = windowSizeY - (y * circleSize) - 15
return (pixelX, pixelY)
######Main##########################
circleSize = 10
windowSizeX = 300
windowSizeY = 100
def doMove(cir):
'''
Display the move of the LabeledCircle object cir by
calling the LabeledCircle's move method and displaying
a text string describing which move.
'''
global counter, move
(circle, x, y) = cir
circles[circle-1].move(x, y) # and move it
#Display which move just took place
counter.undraw() #Remove previous text
counter = g.Text(g.Point(30,20),"Move " + str(move))
counter.draw(win)
if __name__ == "__main__":
win = g.GraphWin("November 2016 Puzzler", windowSizeX, windowSizeY)
#Initial position of the circles
# Circle positions are a grid with each grid element the size of the
# circles. For example, the final position of circle 1 is (0, 0).
# The initial position of 1 is (8, 4). The initial position of
# circle 4 is (6, 0).
circles = [] #List containing the LabeledCircles
circles.append(LabeledCircle(8, 4, "1")) #Circle 1 is list element 0
circles.append(LabeledCircle(7, 2, "2")) #Circle 2 is ... 1, etc.
circles.append(LabeledCircle(9, 2, "3"))
circles.append(LabeledCircle(6, 0, "4"))
circles.append(LabeledCircle(8, 0, "5"))
circles.append(LabeledCircle(10, 0, "6"))
#Display the forward/backward/exit option 'buttons'
g.Circle(g.Point(200, 40), 20).draw(win) #Go back 'button'
g.Text(g.Point(200, 40), "<").draw(win)
g.Circle(g.Point(260, 40), 20).draw(win) #Go forward 'button'
g.Text(g.Point(260, 40), ">").draw(win)
g.Rectangle(g.Point(180,70), g.Point(280,95)).draw(win) #Exit 'button'
g.Text(g.Point(230,83),"Exit").draw(win)
#The eight moves forward and in reverse. Each move is a tuple as follows:
# (circleToMove, xDestinationPosition, yDestinationPosition)
moves = ((0,0,0),(5,5,2),(3,4,0),(1,3,2),(2,2,0),(3,1,2),(1,0,0),(3,4,0),(5,8,0))
backmoves = ((5,8,0),(3,9,2),(1,8,4),(2,7,2),(3,4,0),(1,3,2),(3,1,2),(5,5,2))
#Draw the initial position of the puzzle
for n in range(6):
circles[n].draw(win)
#Draw the instructions
counter = g.Text(g.Point(130,10),"Click mouse on < or > to move back or forward")
counter.setSize(8)
counter.draw(win)
#Check mouse click for forward (>), backward (<) move
# or Exit
move = 0
while True:
click = win.getMouse() #Get position of mouse click
clickX = click.getX() # check for forward or back
clickY = click.getY() # check for exit
#Exit was clicked?
if clickY > 70:
break #Leave this loop and quit
#Forward was clicked?
elif clickX > 230:
if move < 8: #Only 8 moves, ignore past 8
move += 1
doMove(moves[move]) #Display this move
#Backward was clicked?
else:
if move != 0: #Ignore moves before the beginning
move -= 1
doMove(backmoves[move]) #Move circle back
#Close the window and quit
win.close()