Threes part 3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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