Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Last active August 29, 2015 14:03
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save dermotbalson/894ecf6576c707bed068 to your computer and use it in GitHub Desktop.
Threes part 3
function setup()
Settings()
CreateBoard()
Initialise()
state=states.PLAY
end
function Settings()
states={PLAY=1,END=2}
dragXY=nil
moves={vec2(-1,0),vec2(1,0),vec2(0,-1),vec2(0,1)}
moveText={"Left","Right","Down","Up"}
cellWidth,cellHeight,cellGap,textSize=100,133,10,48
boardValues={ [0]={0,0,0},
[1]={color(255),color(0,0,255),0},
[2]={color(255),color(255,0,0),0},
[3]={color(0),color(255),3},
[6]={color(0),color(255),9},
[12]={color(255,0,0),color(255),27},
[24]={color(255,0,0),color(255),81},
[48]={color(255,0,0),color(255),243},
[96]={color(255,0,0),color(255),729},
[192]={color(255,0,0),color(255),2187},
[384]={color(255,0,0),color(255),6561}}
parameter.integer("MoveDepth",1,15,3) --NEW parameters to control AI
parameter.integer("SimsPerMove",5,500,50)
parameter.action("Get Hint",GetHint)
parameter.text("Hint","")
parameter.action("AutoPlay",AutoPlay)
parameter.boolean("WeightBlankTiles",false)
end
function CreateBoard()
imgBoard=image(cellWidth*4+cellGap*5,cellHeight*4+cellGap*5)
setContext(imgBoard)
background(150, 192, 211, 255)
fill(177, 199, 210, 255)
local w=cellGap
for r=1,4 do
local h=cellGap
for c=1,4 do
rect(w,h,cellWidth,cellHeight)
h=h+cellHeight+cellGap
end
w=w+cellWidth+cellGap
end
setContext()
GetNextTile()
end
function Initialise()
--create 9 random tiles valued 1,2 or 3 and put them somewhere in the table called board
--board is a 4x4 table
--start off by creating an empty 4x4 table with all values = 0
board={}
for i=1,4 do
board[i]={0,0,0,0}
end
--now create the initial tiles
for i=1,9 do
local n=RandInt(3)
while true do
local r=RandInt(4)
local c=RandInt(4)
if board[c][r]==0 then
board[c][r]=n
break
end
end
end
NextTile=RandInt(3) --get the next tile to be added so we can show it to the player
--calculate the position of each tile on the screen
--first calculate bottom left position of board
bottomPos=vec2(WIDTH/2-cellWidth*2-cellGap*2.5,HEIGHT/2-cellHeight*2-cellGap*2.5)
boardPos={} --table to hold positions
for c=1,4 do
boardPos[c]={}
for r=1,4 do
boardPos[c][r]=vec2(bottomPos.x+(cellWidth+cellGap)*(c-1)+cellGap,
bottomPos.y+(cellHeight+cellGap)*(r-1)+cellGap)
end
end
nextTilePos=vec2(bottomPos.x-cellWidth-10,HEIGHT/2)
auto=false --NEW turn off autoplay
end
function draw()
background(135, 187, 213, 255)
DrawBoard()
end
function DrawBoard()
--draw board itself
sprite(imgBoard,WIDTH/2,HEIGHT/2)
--draw the tiles
for c=1,4 do
for r=1,4 do
if board[c][r]>0 then
DrawTile(boardPos[c][r],board[c][r])
end
end
end
--show next tile to come
DrawTile(nextTilePos,NextTile)
--show score --NEW
fontSize(24)
fill(color(0,0,255))
text("Score: " .. GetScore(),bottomPos.x+cellWidth*2,bottomPos.y-50)
if state==states.END then
fontSize(72)
fill(0,255,0)
text("Game Over\nTouch to Restart",WIDTH/2,HEIGHT/2)
elseif auto then --NEW keeps getting new moves if on autoplay
local m=GetHint()
if m then MoveCells(moves[m],board)
else
MoveCells(moves[1],board)
auto=false
end
end
end
function RandInt(a) --returns random integer between 1 and a (inclusive)
return math.floor(math.random()*a)+1
end
function DrawTile(p,v)
fill(boardValues[v][2])
rect(p.x,p.y,cellWidth,cellHeight)
fill(boardValues[v][1])
font("ArialRoundedMTBold")
fontSize(textSize)
text(v,p.x+cellWidth/2,p.y+cellHeight/2)
end
function GetScore(b)
b=b or board
blanks=0
local score=0
for c=1,4 do
for r=1,4 do
score=score+boardValues[b[c][r]][3]
if b[c][r]==0 then blanks=blanks+1 end
end
end
if simulating and WeightBlankTiles then score=score*16/(16-blanks) end
return score
end
function touched(t)
if t.state==BEGAN then --Started dragging (or touched a button)
if state==states.END then
Initialise()
state=states.PLAY
else
--reset movement variables
dragXY=vec2(t.x,t.y)
end
elseif t.state==ENDED and dragXY then --Ended
local dx=math.abs(t.x-dragXY.x)
local dy=math.abs(t.y-dragXY.y)
local d=dx-dy --is X or Y movement larger
local m=0
if d>0 then --X movement is larger
if dx<cellWidth/2 then return end
if t.x>dragXY.x then m=vec2(1,0) else m=vec2(-1,0) end
elseif d<0 then --Y movement is larger
if dy<cellHeight/2 then return end
if t.y>dragXY.y then m=vec2(0,1) else m=vec2(0,-1) end
end
MoveCells(m)
dragXY=nil
end
end
function MoveCells(m,b) --NEW provide the board object, used by simulation
b=b or board --default to original board if b not provided
local r1,r2,r3=1,4,1
local c1,c2,c3=1,4,1
if m.x<0 then c1,c2,c3=2,4,1
elseif m.x>0 then c1,c2,c3=3,1,-1
elseif m.y<0 then r1,r2,r3=2,4,1
elseif m.y>0 then r1,r2,r3=3,1,-1
end
for c=c1,c2,c3 do
for r=r1,r2,r3 do
if b[c][r]>0 then
if b[c+m.x][r+m.y]==0 then
b[c+m.x][r+m.y]=b[c][r]
b[c][r]=0
elseif (b[c+m.x][r+m.y]+b[c][r]==3) or
(b[c+m.x][r+m.y]==b[c][r] and b[c][r]>2) then
b[c+m.x][r+m.y]=b[c+m.x][r+m.y]+b[c][r]
b[c][r]=0
elseif b[c+m.x][r+m.y]==b[c][r] and b[c][r]>2 then
b[c+m.x][r+m.y]=b[c+m.x][r+m.y]+b[c][r]
b[c][r]=0
end
end
end
end
--add new cell at edge, get cell location
q={} --list of blank cells
a=nil
if m.x~=0 then
for r=1,4 do
if b[c2][r]==0 then table.insert(q,r) end
end
if #q>0 then a=vec2(c2,q[RandInt(#q)]) end
elseif m.y~=0 then
for c=1,4 do
if b[c][r2]==0 then table.insert(q,c) end
end
if #q>0 then a=vec2(q[RandInt(#q)],r2) end
end
if a then
b[a.x][a.y]=NextTile
GetNextTile()
end
local mov=CheckAvailableMoves()
if simulating then return mov --NEW return move list, don't change game state, if simulating
elseif #mov==0 then state=states.END end
end
function CheckAvailableMoves(b)
b=b or board
-- check for end of game
mov={}
--check if any blank squares left, if so, we aren't done
for c=1,4 do
for r=1,4 do
if b[c][r]==0 then
return {1,2,3,4} --we can move all four ways
end
end
end
--check if matching pairs are adjacent (or 1 & 2)
if #mov==0 then
for c=1,4 do
for r=2,4 do
if (b[c][r]>2 and b[c][r]==b[c][r-1]) or b[c][r]+b[c][r-1]==3 then
mov={3,4}
break
end
end
if #mov>0 then break end
end
for r=1,4 do
for c=2,4 do
if (b[c][r]>2 and b[c][r]==b[c-1][r]) or b[c][r]+b[c-1][r]==3 then
table.insert(mov,1,1)
table.insert(mov,2,2)
break
end
end
if #mov>0 and mov[1]==1 then break end
end
end
return mov
end
function GetNextTile()
NextTile=RandInt(3)
end
--all NEW below this point
function GetHint()
h=RunSims()
--return move (1,2,3 or 4) and put text in Hint textbox
if h then Hint= moveText[h] else Hint="No hint available" end
return h
end
function RunSims()
simulating=true --prevents MoveCells from ending game prematurely
local bestTest,bestScore=0,0
currNextTile=NextTile --store next tile number so we can put it back at the end
m=CheckAvailableMoves(board) --get available moves
for j,i in ipairs(m) do --iterate over moves
local testScore=0
for s=1,SimsPerMove do --run the sims
maxTest=0
--we need to start each sim at the current position
NextTile=currNextTile --reset next tile to the one currently on screen
local b=CloneBoard(board) --make a temporary copy of the current board
local mov=MoveCells(moves[i],b) --make the first move
TestMove(b,1,mov) --TestMove will keep on mking future moves up to the depth we set
testScore=testScore+maxTest --add the max score from this sim, to the total
end
if testScore>bestScore then --keep track of which of the initial moves has the best score
bestScore=testScore
bestTest=i
end
end
NextTile=currNextTile --put back next tile value
simulating=nil
--return the best move
if bestScore>0 then return bestTest else return nil end
end
--recursive function that makes moves until the required depth is reached, then gets the score
function TestMove(b,d,mov) --b is copy of board, d = depth, mov is list of valid moves
for n,i in ipairs(mov) do --iterate through the moves
local b1=CloneBoard(b) --make a copy of the board provided
local m=MoveCells(moves[i],b1) --try the move
if #m>0 and d<MoveDepth then --continue if valid moves>0 and we haven't reached the target depth
TestMove(b1,d+1,m)
else --otherwise get score and adjust best score if greater
local s=GetScore(b1,true)
if s>maxTest then maxTest=s end
end
end
end
--makes a copy of the board provided
function CloneBoard(bb)
b={}
for c=1,4 do
b[c]={}
for r=1,4 do
b[c][r]=bb[c][r]
end
end
return b
end
function AutoPlay()
auto=true
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment