-- Tower of Hanoi Puzzle -- By Andy Kemp (c) 2011 -- Tower of Hanoi Simulation by Andy Kemp is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. -- http://creativecommons.org/licenses/by-nc-sa/3.0/ gc=platform.gc() disks={{},{},{}} nodisks=5 Disk = class() x=0 y=0 moves=0 top={} pass=0 auto=0 shuffle=0 function on.create() h=platform.window:height() w=platform.window:width() cursor.show() diskwidth=math.floor((w-20)/3) diskheight=math.floor((h-12)/10) reset() end function on.paint(gc) --drawspindles drawspindle(gc,1) drawspindle(gc,2) drawspindle(gc,3) gc:setColorRGB(139,69,19) gc:fillRect(5,h-10,3*diskwidth+10,5) --Draw disks for i=1,3 do for j=1,table.getn(disks[i]) do Disk:draw(gc,i,disks[i][j],j) if disks[i][j]=="p" and moving==1 then Disk:moving(gc,x,y,movingsize) end end -- text Moves gc:setColorRGB(0,0,0) gc:setFont("sansserif","r",12) if auto~=1 then strwidth = gc:getStringWidth("Moves: "..moves) gc:drawString("Moves: "..moves,w-10-strwidth,20) else strwidth = gc:getStringWidth("Auto-Complete Mode") gc:drawString("Auto-Complete Mode",w-10-strwidth,20) end end -- End of Game if puzzlecomplete==1 then gc:setFont("sansserif","r",10) gc:setColorRGB(255, 255, 255) gc:fillRect(w/2-150,h/2-30,300,60) gc:setColorRGB(0, 0, 0) gc:setPen("thick","smooth") gc:drawRect(w/2-150,h/2-30,300,60) if shuffle==1 then strwidth = gc:getStringWidth("Well Done! You completed it in "..moves.. " moves!") strheight = gc:getStringHeight("Well Done! You completed it in "..moves.. " moves!") gc:drawString("Well Done! You completed it in "..moves.. " moves!",w/2-strwidth/2,h/2,"middle") else strwidth = gc:getStringWidth("Well Done! You completed it in "..moves.. " moves!") strheight = gc:getStringHeight("Well Done! You completed it in "..moves.. " moves!") gc:drawString("Well Done! You completed it in "..moves.. " moves!",w/2-strwidth/2,h/2-strheight/2,"middle") if moves == 2^(nodisks)-1 then strwidth = gc:getStringWidth("This is the minimum number of moves!!") gc:drawString("This is the minimum number of moves!!",w/2-strwidth/2,h/2+strheight/2,"middle") else if moves - (2^(nodisks)-1) == 1 then strwidth = gc:getStringWidth("But it can be done in ".. moves-(2^(nodisks)-1).." fewer moves!") gc:drawString("But it can be done in ".. moves-(2^(nodisks)-1).." fewer move!",w/2-strwidth/2,h/2+strheight/2,"middle") else strwidth = gc:getStringWidth("But it can be done in ".. moves-(2^(nodisks)-1).." fewer moves!") gc:drawString("But it can be done in ".. moves-(2^(nodisks)-1).." fewer moves!",w/2-strwidth/2,h/2+strheight/2,"middle") end end end end -- About Screen if about==1 then gc:setFont("sansserif","r",10) gc:setColorRGB(255, 255, 255) gc:fillRect(w/2-150,h/2-30,300,60) gc:setColorRGB(0, 0, 0) gc:setPen("thick","smooth") gc:drawRect(w/2-150,h/2-30,300,60) strwidth = gc:getStringWidth("Tower of Hanoi") strheight = gc:getStringHeight("Tower of Hanoi") gc:drawString("Tower of Hanoi",w/2-strwidth/2,h/2-strheight/2,"middle") strwidth = gc:getStringWidth("By Andy Kemp (c) 2011") gc:drawString("By Andy Kemp (c) 2011",w/2-strwidth/2,h/2+strheight/2,"middle") end end function on.mouseDown(mx,my) if auto~=1 then if puzzlecomplete==1 then puzzlecomplete=0 reset() elseif moving==1 then if mx>10 and mx<10+diskwidth then--first spindle topdisk = findtopdisk(1) if disks[1][1]=="p" or disks[1][1]==0 then if disks[1][1]==0 then moves=moves+1 document.markChanged() end disks[spindle][oldpos]=0 disks[1][1]=movingsize moving=0 platform.window:invalidate() elseif movingsize>disks[1][topdisk] then if disks[1][topdisk]==0 then topdisk=0 end if disks[1][topdisk+1]~="p" then moves=moves+1 document.markChanged() end disks[spindle][oldpos]=0 disks[1][topdisk+1]=movingsize moving=0 platform.window:invalidate() end elseif mx>10+diskwidth and mx<10+2*diskwidth then--second spindle topdisk = findtopdisk(2) if disks[2][1]=="p" or disks[2][1]==0 then if disks[2][1]==0 then moves=moves+1 document.markChanged() end disks[spindle][oldpos]=0 disks[2][1]=movingsize moving=0 platform.window:invalidate() elseif movingsize>disks[2][topdisk] then if disks[2][topdisk]==0 then topdisk=0 end if disks[2][topdisk+1]~="p" then moves=moves+1 document.markChanged() end disks[spindle][oldpos]=0 disks[2][topdisk+1]=movingsize moving=0 platform.window:invalidate() end elseif mx>10+2*diskwidth and mx<10+3*diskwidth then--third spindle topdisk = findtopdisk(3) if disks[3][1]=="p" or disks[3][1]==0 then if disks[3][1]==0 then moves=moves+1 document.markChanged() end disks[spindle][oldpos]=0 disks[3][1]=movingsize moving=0 platform.window:invalidate() elseif movingsize>disks[3][topdisk] then if disks[3][topdisk]==0 then topdisk=0 end if disks[3][topdisk+1]~="p" then moves=moves+1 document.markChanged() end disks[spindle][oldpos]=0 disks[3][topdisk+1]=movingsize moving=0 platform.window:invalidate() end end else if mx>10 and mx<10+diskwidth then--first spindle topdisk = findtopdisk(1) if topdisk~=0 then topdiskwidth = diskwidth*0.8^(disks[1][topdisk]-1) if (my>h-10-diskheight*topdisk and my10+(diskwidth-topdiskwidth)/2 and mx<10+(diskwidth-topdiskwidth)/2+topdiskwidth) then movingsize=disks[1][topdisk] disks[1][topdisk]="p" spindle=1 oldpos=topdisk xoff=mx-(10+(diskwidth-topdiskwidth)/2) yoff=my-(h+10-diskheight*topdisk) moving=1 cursor.set("drag grab") end end elseif mx>10+diskwidth and mx<10+2*diskwidth then--second spindle topdisk = findtopdisk(2) if topdisk~=0 then topdiskwidth = diskwidth*0.8^(disks[2][topdisk]-1) if (my>h-10-diskheight*topdisk and my10+diskwidth+(diskwidth-topdiskwidth)/2 and mx<10+diskwidth+(diskwidth-topdiskwidth)/2+topdiskwidth) then movingsize=disks[2][topdisk] disks[2][topdisk]="p" spindle=2 oldpos=topdisk xoff=mx-(10+diskwidth+(diskwidth-topdiskwidth)/2) yoff=my-(h+10-diskheight*topdisk) moving=1 cursor.set("drag grab") end end elseif mx>10+2*diskwidth and mx<10+3*diskwidth then--third spindle topdisk = findtopdisk(3) if topdisk~=0 then topdiskwidth = diskwidth*0.8^(disks[3][topdisk]-1) if (my>h-10-diskheight*topdisk and my10+2*diskwidth+(diskwidth-topdiskwidth)/2 and mx<10+2*diskwidth+(diskwidth-topdiskwidth)/2+topdiskwidth) then movingsize=disks[3][topdisk] disks[3][topdisk]="p" spindle=3 oldpos=topdisk xoff=mx-(10+2*diskwidth+(diskwidth-topdiskwidth)/2) yoff=my-(h+10-diskheight*topdisk) moving=1 cursor.set("drag grab") end end end end if disks[2][nodisks]==nodisks or disks[3][nodisks]==nodisks then puzzlecomplete = 1 end end end function Disk:draw(gc,spindle,disksize, diskpos) setdiskcolour(gc,disksize) if disksize~=0 and disksize~="p" then gc:fillRect((spindle-1)*diskwidth+10+(diskwidth - (0.8^(disksize-1))*diskwidth)/2,h-10-diskheight*diskpos,(0.8^(disksize-1))*diskwidth,diskheight) gc:setColorRGB(0,0,0) gc:drawRect((spindle-1)*diskwidth+10+(diskwidth - (0.8^(disksize-1))*diskwidth)/2,h-10-diskheight*diskpos,(0.8^(disksize-1))*diskwidth,diskheight) end end function Disk:moving(gc,x,y,disksize) setdiskcolour(gc,disksize) gc:fillRect(x-xoff,y-20-yoff,(0.8^(disksize-1))*diskwidth,diskheight) gc:setColorRGB(0,0,0) gc:drawRect(x-xoff,y-20-yoff,(0.8^(disksize-1))*diskwidth,diskheight) end function on.mouseMove(mx,my) topdisk=nil if moving==1 then x = mx y = my cursor.set("drag grab") platform.window:invalidate() elseif mx>10 and mx<10+diskwidth then--first spindle topdisk=findtopdisk(1) if topdisk~=0 then -- check there is a disk on the spindle topdiskwidth = diskwidth*0.8^(disks[1][topdisk]-1) if (my>h-10-diskheight*topdisk and my10+(diskwidth-topdiskwidth)/2 and mx<10+(diskwidth-topdiskwidth)/2+topdiskwidth) then cursor.set("hand open") end end elseif mx>10+diskwidth and mx<10+2*diskwidth then--second spindle topdisk=findtopdisk(2) if topdisk~=0 then -- check there is a disk on the spindle topdiskwidth = diskwidth*0.8^(disks[2][topdisk]-1) if (my>h-10-diskheight*topdisk and my10+diskwidth+(diskwidth-topdiskwidth)/2 and mx<10+diskwidth+(diskwidth-topdiskwidth)/2+topdiskwidth) then cursor.set("hand open") end end elseif mx>10+2*diskwidth and mx<10+3*diskwidth then--third spindle topdisk=findtopdisk(3) if topdisk~=0 then -- check there is a disk on the spindle topdiskwidth = diskwidth*0.8^(disks[3][topdisk]-1) if (my>h-10-diskheight*topdisk and my10+2*diskwidth+(diskwidth-topdiskwidth)/2 and mx<10+2*diskwidth+(diskwidth-topdiskwidth)/2+topdiskwidth) then cursor.set("hand open") end end else cursor.set("default") end end function drawspindle(gc,spindle) gc:setColorRGB(139,69,19) gc:fillRect((spindle-1)*diskwidth+5+diskwidth/2,30,10,h-40) end function reset() toolpalette.enable("Puzzle", "Auto-Complete", true) -- enable the autocomplete command timer.stop() -- check the timer is stopped disks={{},{},{}} -- clear the grid for j=1,nodisks do -- set the disks disks[1][j]=j disks[2][j]=0 disks[3][j]=0 end shuffle=0 moves=0 pass=0 cursor.show() platform.window:invalidate() end function on.enterKey() if puzzlecomplete==1 then puzzlecomplete=0 reset() end end function on.escapeKey() if puzzlecomplete==1 then puzzlecomplete=0 reset() end if auto==1 then auto=0 timer.stop() reset() end end function on.resize(width,height) w=width h=height diskwidth=math.floor((w-20)/3) diskheight=math.floor((h-12)/10) end function nextMove() -- a messy auto-complete algorithm! if disks[2][nodisks]~=nodisks and disks[3][nodisks]~=nodisks then if math.floor(nodisks/2)==nodisks/2 then -- find the parity of the disks parity = 0 else parity = 1 end top1 = findtopdisk(1) top2 = findtopdisk(2) top3 = findtopdisk(3) sp1top = disks[1][top1] sp2top = disks[2][top2] sp3top = disks[3][top3] if sp1top==nil then sp1top = 0 end if sp2top==nil then sp2top = 0 end if sp3top==nil then sp3top = 0 end if parity == 0 then --make the legal move between pegs A and B --make the legal move between pegs A and C --make the legal move between pegs B and C --repeat if pass==0 then if sp1top>sp2top then moveddisk = disks[1][top1] disks[1][top1]=0 disks[2][top2+1]=moveddisk pass=(pass+1)%3 else moveddisk = disks[2][top2] disks[2][top2]=0 disks[1][top1+1]=moveddisk pass=(pass+1)%3 end elseif pass==1 then if sp1top>sp3top then moveddisk = disks[1][top1] disks[1][top1]=0 disks[3][top3+1]=moveddisk pass=(pass+1)%3 else moveddisk = disks[3][top3] disks[3][top3]=0 disks[1][top1+1]=moveddisk pass=(pass+1)%3 end elseif pass==2 then if sp2top>sp3top then moveddisk = disks[2][top2] disks[2][top2]=0 disks[3][top3+1]=moveddisk pass=(pass+1)%3 else moveddisk = disks[3][top3] disks[3][top3]=0 disks[2][top2+1]=moveddisk pass=(pass+1)%3 end end elseif parity == 1 then --make the legal move between pegs A and C --make the legal move between pegs A and B --make the legal move between pegs B and C --repeat if pass==0 then if sp1top>sp3top then moveddisk = disks[1][top1] disks[1][top1]=0 disks[3][top3+1]=moveddisk pass=(pass+1)%3 else moveddisk = disks[3][top3] disks[3][top3]=0 disks[1][top1+1]=moveddisk pass=(pass+1)%3 end elseif pass==1 then if sp1top>sp2top then moveddisk = disks[1][top1] disks[1][top1]=0 disks[2][top2+1]=moveddisk pass=(pass+1)%3 else moveddisk = disks[2][top2] disks[2][top2]=0 disks[1][top1+1]=moveddisk pass=(pass+1)%3 end elseif pass==2 then if sp2top>sp3top then moveddisk = disks[2][top2] disks[2][top2]=0 disks[3][top3+1]=moveddisk pass=(pass+1)%3 else moveddisk = disks[3][top3] disks[3][top3]=0 disks[2][top2+1]=moveddisk pass=(pass+1)%3 end end end else auto=0 reset() timer.stop() end end function on.timer() if auto==1 then nextMove() platform.window:invalidate() end if about==1 then about = 0 timer.stop() platform.window:invalidate() end end function shuffledisks() shuffle=1 platform.window:invalidate() --math.randomseed(os.time()) -- seed random number generator toolpalette.enable("Puzzle", "Auto-Complete", false) -- disable auto-complete when in random mode as it fails! disks={{},{},{}} -- clear the grid for i=1,nodisks do -- zero the grid disks[1][i]=0 disks[2][i]=0 disks[3][i]=0 end for i=1,nodisks do spindle=math.random(1,3) topdisk = findtopdisk(spindle) disks[spindle][topdisk+1]=i end platform.window:invalidate() end function findtopdisk(spindle) topdisk=0 for j=1,nodisks do if disks[spindle][j]~=0 and disks[spindle][j]~="p" then topdisk=j end end return topdisk end function setdiskcolour(gc,disksize) if disksize==1 then gc:setColorRGB(255,0,0) elseif disksize==2 then gc:setColorRGB(0,255,0) elseif disksize==3 then gc:setColorRGB(0,0,255) elseif disksize==4 then gc:setColorRGB(255,255,0) elseif disksize==5 then gc:setColorRGB(0,255,255) elseif disksize==6 then gc:setColorRGB(255,0,255) elseif disksize==7 then gc:setColorRGB(100,0,0) elseif disksize==8 then gc:setColorRGB(0,150,0) end end menu={ {"Puzzle", {"Reset", function() reset() end}, {"Auto-Complete", function() auto=1 reset() timer.start(0.5) end}, {"Shuffle Disks", function() shuffledisks() end}, {"About", function() about=1 platform.window:invalidate() timer.start(4) end}, }, {"Disks", {"3 Disks", function() nodisks=3 reset() end}, {"4 Disks", function() nodisks=4 reset() end}, {"5 Disks", function() nodisks=5 reset() end}, {"6 Disks", function() nodisks=6 reset() end}, {"7 Disks", function() nodisks=7 reset() end}, {"8 Disks", function() nodisks=8 reset() end}, }, } toolpalette.register(menu)