16 numbered pieces of the puzzle are placed out of order on a 4 X 4 grid. The correct order to win is to order the piecesas 1 through 16, read left to right, top to bottom:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
How to Play: The aim is to get the pieces back in order by clicking on the yellow arrows (choosing a location to rotate a row or column) to slide the pieces left or right, up or down.
1 14 3 4 1 2 3 4 5 2 7 8 --> 5 6 7 8 9 6 11 12 9 10 11 12 13 10 15 16 13 14 15 16 ^
The Easy puzzle target is 3 moves, for the Hard puzzle it is 12 moves (or less!). Can it be that simple?
Create 16 Puzzle Game.
See details:[1]
Video:16 Puzzle Game
With Solver, See16_puzzle_game/autohotkey
Consteasy=1,hard=4DimSharedAsByten(1To16)SubinitGrid()ForiAsByte=0To15n(i)=i+1NextEndSubSubrotate(ix()AsByte)DimAsBytelast=n(ix(3))ForiAsByte=3To1Step-1n(ix(i))=n(ix(i-1))Nextn(ix(0))=lastEndSubFunctionhasWon()AsBooleanForiAsByte=0To15Ifn(i)<>i+1ThenReturnFalseNextReturnTrueEndFunctionSubsetDiff(levelAsByte)DimAsBytemoves=Iif(level=easy,3,12)DimAsByterc(),j,iFori=0TomovesRedimAsByterc(20)DimAsByter=Int(Rnd*2)DimAsBytes=Int(Rnd*4)Ifr=0Then' rotate random rowForj=s*4To(s+1)*4rc(j)=jNextElse' rotate random columnForj=sTos+16Step4rc(j)=jNextEndIfrotate(rc())IfhasWon()Then' do it againi=-1EndIfi+=1NextPrintUsing!"\nTarget is ## moves.";movesEndSubSubdrawGrid()Print!"\n D1 D2 D3 D4"Print" +-------------------+"PrintUsing"R1 | ## | ## | ## | ## | L1";n(0);n(1);n(2);n(3)Print" |----+----+----+----|"PrintUsing"R2 | ## | ## | ## | ## | L2";n(4);n(5);n(6);n(7)Print" |----+----+----+----|"PrintUsing"R3 | ## | ## | ## | ## | L3";n(8);n(9);n(10);n(11)Print" |----+----+----+----|"PrintUsing"R4 | ## | ## | ## | ## | L4";n(12);n(13);n(14);n(15)Print" +-------------------+"Print!" U1 U2 U3 U4\n"EndSub'--- Programa Principal ---RandomizeTimerDimAsBytelevel=easyDimAsStringdiffPrint"Enter difficulty level easy or hard E/H: ";DoInput;"",diffLoopUntilInstr("eEhH",diff)IfUcase(diff)="H"Thenlevel=hardClsPrint"Sixteen Puzzle"initGrid()setDiff(level)DimAsByteix(0To3)Print"When entering moves, you can also enter Q to quit or S to start again."DimAsBytec,moves=0DimAsString*2moveDodrawGrid()IfhasWon()ThenPrint"Congratulations, you have won the game in";moves;" moves!!"WhileInkey="":WendExitDoEndIfDoPrint"Moves so far = ";movesInput"Enter move : ",moveSelectCaseTrim(Ucase(move))Case"D1","D2","D3","D4"c=Cint(Right(move,1))-49ix(0)=0+cix(1)=4+cix(2)=8+cix(3)=12+crotate(ix())moves+=1ExitDoCase"L1","L2","L3","L4"c=Cint(Right(move,1))-49ix(0)=3+4*cix(1)=2+4*cix(2)=1+4*cix(3)=0+4*crotate(ix())moves+=1ExitDoCase"U1","U2","U3","U4"c=Cint(Right(move,1))-49ix(0)=12+cix(1)=8+cix(2)=4+cix(3)=0+crotate(ix())moves+=1ExitDoCase"R1","R2","R3","R4"c=Cint(Right(move,1))-49ix(0)=0+4*cix(1)=1+4*cix(2)=2+4*cix(3)=3+4*crotate(ix())moves+=1ExitDoCase"Q"ExitDo,DoCase"S"ClsinitGrid()setDiff(level)moves=0ExitDoCaseElsePrint"Invalid move, try again."EndSelectLoopLoop'--------------------------Sleep
begin enum _down = 1 _right _up _left _new = 100 _restrt _help _endend enumstr63 board, startPos, winBoardvoid local fn init window 1,,(0,0,340,340) int x for x = 1 to 4 button 10+x,,,@"⬇️",(x*50+20,270,50,50),,NSBezelStyleTexturedSquare ControlSetFontWithName( 10+x, @"Menlo", 32 ) button 30+x,,,@"⬆️",(x*50+20, 20,50,50),,NSBezelStyleTexturedSquare ControlSetFontWithName( 30+x, @"Menlo", 32 ) button 25-x,,,@"➡️",( 20,x*50+20,50,50),,NSBezelStyleTexturedSquare ControlSetFontWithName( 25-x, @"Menlo", 32 ) button 45-x,,,@"⬅️",(270,x*50+20,50,50),,NSBezelStyleTexturedSquare ControlSetFontWithName( 45-x, @"Menlo", 32 ) next button _new ,,,@"New", ( 20,270,50,50),,NSBezelStyleTexturedSquare button _end ,,,@"Quit", ( 20, 20,50,50),,NSBezelStyleTexturedSquare button _restrt,,,@"Redo", (270,270,50,50),,NSBezelStyleTexturedSquare button _help ,,,@"Help", (270, 20,50,50),,NSBezelStyleTexturedSquare for x = 1 to 16 winBoard += chr$(x) next board = winBoardend fnvoid local fn drawBoard int c = 1, x, y, z cls for y = 70 to 220 step 50 for x = 70 to 220 step 50 rect fill (x,y,48,48), fn coloryellow if board[c] > 9 then z = x-3 else z = x+6 print %(z,y+6) str(board[c]); c++ next nextend fnvoid local fn move( tag as int ) int r, d, rc = (tag mod 10) select tag / 10 case _up : d = +4 case _right : d = -1 : rc *= 4 case _down : d = -4 : rc += 12 case else : d = +1 : rc = rc * 4 - 3 end select for r = rc to rc + 2 * d step d swap board[r], board[r+d] next if board == winBoard then window 1, @"!!! YOU WON !!!" : text,,fn colorRed fn drawBoardend fnvoid local fn newGame window 1, @"16 PUZZLE GAME" int r for r = 1 to 16 swap board[r], board[rnd(16)] next startPos = board text @"Arial bold", 32, fn colorblue fn drawBoardend fnvoid local fn ask( tag as long ) CFStringRef s1, s2 : int btn select tag case _help s1 = @"Use the arrow buttons to move numbers in rows and columns.\n¬ Win by arranging the numbers in order, left to right, top to bottom:" s2 = @" 1 2 3 4 \n 5 6 7 8 \n 9 10 11 12\n 13 14 15 16" btn = alert 1, NSAlertStyleInformational, s1, s2, @"Okay" case _new : if board == winBoard then fn newGame : exit fn s1 = @"Leave this puzzle\nand start a new one?" btn = alert 1, NSAlertStyleInformational, s1,,@"New puzzle;Cancel" if btn == NSAlertFirstButtonReturn then fn newGame case _restrt s1 = @"Restart this puzzle\nfrom the beginning?" btn = alert 1, NSAlertStyleInformational,s1,,@"Restart;Cancel" if btn == NSAlertFirstButtonReturn then board = startPos : fn drawBoard case _end btn = alert 1, NSAlertStyleInformational,@"Quit 16 GAME?",,@"Quit;Cancel" if btn == NSAlertFirstButtonReturn then end end selectend fnvoid local fn doDialog(ev as long, tag as long) select ev case _btnClick : if tag < _new then fn move( tag ) else fn ask( tag ) case _windowWillClose : end end selectend fnon dialog fn doDialogfn initfn newGamehandleevents
packagemainimport("bufio""fmt""log""math/rand""os""strings""time")const(easy=1hard=4)varn[16]intfuncinitGrid(){fori:=0;i<16;i++{n[i]=i+1}}funcsetDiff(levelint){moves:=3iflevel==hard{moves=12}rc:=make([]int,0,4)fori:=0;i<moves;i++{rc=rc[:0]r:=rand.Intn(2)s:=rand.Intn(4)ifr==0{// rotate random rowforj:=s*4;j<(s+1)*4;j++{rc=append(rc,j)}}else{// rotate random columnforj:=s;j<s+16;j+=4{rc=append(rc,j)}}varrca[4]intcopy(rca[:],rc)rotate(rca)ifhasWon(){// do it againi=-1}}fmt.Println("Target is",moves,"moves.")}funcdrawGrid(){fmt.Println()fmt.Println(" D1 D2 D3 D4")fmt.Println(" ╔════╦════╦════╦════╗")fmt.Printf("R1 ║ %2d ║ %2d ║ %2d ║ %2d ║ L1\n",n[0],n[1],n[2],n[3])fmt.Println(" ╠════╬════╬════╬════╣")fmt.Printf("R2 ║ %2d ║ %2d ║ %2d ║ %2d ║ L2\n",n[4],n[5],n[6],n[7])fmt.Println(" ╠════╬════╬════╬════╣")fmt.Printf("R3 ║ %2d ║ %2d ║ %2d ║ %2d ║ L3\n",n[8],n[9],n[10],n[11])fmt.Println(" ╠════╬════╬════╬════╣")fmt.Printf("R4 ║ %2d ║ %2d ║ %2d ║ %2d ║ L4\n",n[12],n[13],n[14],n[15])fmt.Println(" ╚════╩════╩════╩════╝")fmt.Println(" U1 U2 U3 U4\n")}funcrotate(ix[4]int){last:=n[ix[3]]fori:=3;i>=1;i--{n[ix[i]]=n[ix[i-1]]}n[ix[0]]=last}funchasWon()bool{fori:=0;i<16;i++{ifn[i]!=i+1{returnfalse}}returntrue}funccheck(errerror){iferr!=nil{log.Fatal(err)}}funcmain(){initGrid()rand.Seed(time.Now().UnixNano())level:=easyscanner:=bufio.NewScanner(os.Stdin)for{fmt.Print("Enter difficulty level easy or hard E/H : ")scanner.Scan()diff:=strings.ToUpper(strings.TrimSpace(scanner.Text()))ifdiff!="E"&&diff!="H"{fmt.Println("Invalid response, try again.")}else{ifdiff=="H"{level=hard}break}}check(scanner.Err())setDiff(level)varix[4]intfmt.Println("When entering moves, you can also enter Q to quit or S to start again.")moves:=0outer:for{drawGrid()ifhasWon(){fmt.Println("Congratulations, you have won the game in",moves,"moves!!")return}for{fmt.Println("Moves so far =",moves,"\n")fmt.Print("Enter move : ")scanner.Scan()move:=strings.ToUpper(strings.TrimSpace(scanner.Text()))check(scanner.Err())switchmove{case"D1","D2","D3","D4":c:=int(move[1]-49)ix[0]=0+cix[1]=4+cix[2]=8+cix[3]=12+crotate(ix)moves++continueoutercase"L1","L2","L3","L4":c:=int(move[1]-49)ix[0]=3+4*cix[1]=2+4*cix[2]=1+4*cix[3]=0+4*crotate(ix)moves++continueoutercase"U1","U2","U3","U4":c:=int(move[1]-49)ix[0]=12+cix[1]=8+cix[2]=4+cix[3]=0+crotate(ix)moves++continueoutercase"R1","R2","R3","R4":c:=int(move[1]-49)ix[0]=0+4*cix[1]=1+4*cix[2]=2+4*cix[3]=3+4*crotate(ix)moves++continueoutercase"Q":returncase"S":initGrid()setDiff(level)moves=0continueouterdefault:fmt.Println("Invalid move, try again.")}}}}
Sample game:
Enter difficulty level easy or hard E/H : eTarget is 3 moves.When entering moves, you can also enter Q to quit or S to start again. D1 D2 D3 D4 ╔════╦════╦════╦════╗R1 ║ 1 ║ 2 ║ 3 ║ 4 ║ L1 ╠════╬════╬════╬════╣R2 ║ 7 ║ 8 ║ 5 ║ 6 ║ L2 ╠════╬════╬════╬════╣R3 ║ 9 ║ 10 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣R4 ║ 16 ║ 13 ║ 14 ║ 15 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4Moves so far = 0 Enter move : l4 D1 D2 D3 D4 ╔════╦════╦════╦════╗R1 ║ 1 ║ 2 ║ 3 ║ 4 ║ L1 ╠════╬════╬════╬════╣R2 ║ 7 ║ 8 ║ 5 ║ 6 ║ L2 ╠════╬════╬════╬════╣R3 ║ 9 ║ 10 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣R4 ║ 13 ║ 14 ║ 15 ║ 16 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4Moves so far = 1 Enter move : l2 D1 D2 D3 D4 ╔════╦════╦════╦════╗R1 ║ 1 ║ 2 ║ 3 ║ 4 ║ L1 ╠════╬════╬════╬════╣R2 ║ 8 ║ 5 ║ 6 ║ 7 ║ L2 ╠════╬════╬════╬════╣R3 ║ 9 ║ 10 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣R4 ║ 13 ║ 14 ║ 15 ║ 16 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4Moves so far = 2 Enter move : l2 D1 D2 D3 D4 ╔════╦════╦════╦════╗R1 ║ 1 ║ 2 ║ 3 ║ 4 ║ L1 ╠════╬════╬════╬════╣R2 ║ 5 ║ 6 ║ 7 ║ 8 ║ L2 ╠════╬════╬════╬════╣R3 ║ 9 ║ 10 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣R4 ║ 13 ║ 14 ║ 15 ║ 16 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4Congratulations, you have won the game in 3 moves!!
Assumes a recent release of jqt:
require'ide/qt/gl2'coinsert'jgl2'NB. event handlersgame_reset_button=:{{drawCOUNT=:#SETUP=:EMPTY[BOARD=:WIN}}game_restart_button=:{{drawCOUNT=:0[rotate"1SETUP[BOARD=:WIN}}game_easy_button=:{{setup3}}game_hard_button=:{{setup15}}game_board_mbldown=:{{loc=.(20+40*i.5)I.2{._".sysdataif.1=#ndx=.loc-.05do.side=.2#.<:>.loc%4NB. _2: left, _1 top, 1 bottom, 2 rightdrawrotateside,ndxend.}}NB. game logicBOARD=:WIN=:1+i.44message=:{{color=.(COUNT>#SETUP){::;:'black red'A=.'<span style="color: ',color,'">'[Z=.'</span>'if.BOARD-:WINdo.A,'You win',Zreturn.end.A,(":COUNT),Z,' of ',":#SETUP}}setup=:{{game_restart_buttonSETUP=:(_2_112{~?y#4),.1+?y#4}}rotate=:{{COUNT=:COUNT+1'side ndx'=.y-01flip=.|:if.2=|sidedo.flip=.]end.BOARD=:flip((*side)|.ndx{flipBOARD)ndx}flipBOARD}}NB. renderingwd{{)npcgamecloseok;ccboardisidraw;setboardwh200200;ccmsgstaticcenter;binh;cceasybutton;seteasytooltipstartgamewhichcanbecompletedin3moves;cchardbutton;sethardtooltipstartgamewhichcanbecompletedin15moves;binz;binh;ccrestartbutton;ccresetbutton;setresettooltipsetboardtoinitialposition,endinganycurrentgame;pshow;}}draw=:{{glclear''glbrushglrgb3#224NB. silverglrect00200200glbrushglrgb00255NB. blueglrectT=:20204040+"1]40*4411#:i.44glbrushglrgb2552550NB. yellowglpolygon(,200-])(,;"1@(_2<@|.\"1]))0300501040+"1(i.4)*/6$040gltextcolorglrgb3#255NB. whiteglfont'"lucidia console" 16'BOARD{{gltext":x[gltextxyy+50*_1^1<#":x}}"_2(30+40*44#:|:i.44)if.EMPTY-:SETUPdo.wd{{)nsetmsgtext<b>easy</b>or<b>hard</b>tostart;setrestartenable0;setrestartcaption;}}else.wd{{)nsetmsgtextMESSAGE;setrestartenable1;setrestartcaptionrestart;}}rplc'MESSAGE';message''end.glpaint''}}NB. start:game_reset_button''
Try ithere.
You'll also need a html file:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <link href="https://fonts.googleapis.com/css?family=Ubuntu+Mono&display=swap" rel="stylesheet"> <link rel="stylesheet" type="text/css" media="screen" href="./css/main.css"> <title>16 Puzzle</title></head><body> <div>WELL DONE!</div> <div></div> <div></div> <button>SHUFFLE</button> <script src="./src/index.js" type="module"></script></body></html>
And css file:
* { margin: 0; border: 0; text-align: center; font-family: 'Ubuntu Mono', monospace; user-select: none;}button { border-radius: 5px; width: 300px; height: 80px; font-size: 40px; margin-top: 60px;}#board { width: 410px; height: 410px; margin: 120px auto 30px auto;}#done { font-size: 140px; padding: 20px; color: #fff; background-color: rgba(0, 23, 56, .5); border: 1px solid rgb(0, 90, 220); width: 700px; position: absolute; top: 250px; left: calc(50% - 380px);}#moves { font-size: 40px; line-height: 80px; height: 80px; width: 300px; margin: auto; border: 1px solid #000; border-radius: 5px;}.btn,.numbers,.hide { float: left; width: 64px; height: 64px; line-height: 65px; font-size: 40px; border: 1px solid black; color: black; background-color: white; cursor: none; margin: 1px; transition: all .3s;}.btn:hover { background-color: rgba(71, 231, 71, 0.5); cursor: pointer;}.hide { border: 1px solid white; cursor: none;}
classPuzzle{constructor(){this.moves;this.started;this.board=document.getElementById("board");document.getElementById("shuffle").addEventListener("click",()=>{this.shuffle();});this.reset();}reset(){while(this.board.hasChildNodes()){this.board.removeChild(this.board.firstChild);}this.moves=0;this.started=false;document.getElementById("moves").innerText=this.moves;document.getElementById("done").style.visibility="hidden";lett=1;for(lety=0;y<6;y++){for(letx=0;x<6;x++){constd=document.createElement("div");d.id=`${x}_${y}`;if(y===0||x===0||y===5||x===5){if(((y===0||y===5)&&(x===0||x===5))){d.className="hide";}else{d.className="btn";if(y===0){d.innerText="🡇";d.func=()=>{this.rollDownRight(x,true);};}elseif(y===5){d.innerText="🡅";d.func=()=>{this.rollUpLeft(x,true);};}if(x===0){d.innerText="🡆";d.func=()=>{this.rollDownRight(y,false);};}elseif(x===5){d.innerText="🡄";d.func=()=>{this.rollUpLeft(y,false);};}d.addEventListener("click",(ev)=>{ev.srcElement.func();})}}else{d.className="numbers";d.innerText=`${t}`;t++;}this.board.appendChild(d);}}document.getElementById("shuffle").innerText="SHUFFLE";}shuffle(){if(this.started){this.reset();}else{this.started=true;conste=Math.floor(Math.random()*30)+30;for(letz=0;z<e;z++){switch(Math.floor(Math.random()*4)){case0:this.rollDownRight(Math.floor(Math.random()*4)+1,false);break;case1:this.rollUpLeft(Math.floor(Math.random()*4)+1,true);break;case2:this.rollUpLeft(Math.floor(Math.random()*4)+1,false);break;case3:this.rollDownRight(Math.floor(Math.random()*4)+1,true);break;}}this.moves=0;document.getElementById("moves").innerText=this.moves;document.getElementById("shuffle").innerText="RESTART";}}getElements(l,col){constz=Array.from(document.querySelectorAll(".numbers"));for(lete=15;e>-1;e--){if(z[e].id[(col?0:2)]!=l){z.splice(e,1)}}returnz;}rollDownRight(x,col){if(!this.started)return;constz=this.getElements(x,col),a=z[3].innerText;for(letr=3;r>0;r--){z[r].innerText=z[r-1].innerText;}z[0].innerText=a;this.updateMoves();this.checkSolved();}rollUpLeft(x,col){if(!this.started)return;constz=this.getElements(x,col),a=z[0].innerText;for(letr=0;r<3;r++){z[r].innerText=z[r+1].innerText;}z[3].innerText=a;this.updateMoves();this.checkSolved();}updateMoves(){this.moves++;document.getElementById("moves").innerText=this.moves;}checkSolved(){functioncheck(){constz=document.querySelectorAll(".numbers");letu=1;for(letrofz){if(r.innerText!=u)returnfalse;u++;}returntrue;}if(this.started&&check()){document.getElementById("done").style.visibility="visible";}}}newPuzzle();
Adapted fromWren
Works with jq, the C implementation of jq
Works with gojq, the Go implementation of jq (*)
The following implementation illustrates the use of "stderr"to simplify interaction with the user.
The program also illustrates the use of the MRG32k3a pseudo-randomnumber generator, a jq implementation of which which is available fromRosettaCode.org.An alternative that would probably be acceptable here wouldbe simply to use the built-in `now`.
include "MRG32k3a" {search: "."}; # see comment above### Generic functionsdef lpad($len): tostring | ($len - length) as $l | (" " * $l) + .;def inform(msg): . as $in | msg | stderr | $in;### The 16-Puzzle Gamedef easy: 1;def hard: 4;def drawGrid: def row($left; $array; $right): ([$left, ($array[] | lpad(2) ), $right] | join(" ║ ")) + "\n"; inform( " D1 D2 D3 D4\n" + " ╔════╦════╦════╦════╗\n" + row("R1"; .n[0:4]; "L1") + " ╠════╬════╬════╬════╣\n" + row("R2"; .n[4:8]; "L2") + " ╠════╬════╬════╬════╣\n" + row("R3"; .n[8:12];" L3") + " ╠════╬════╬════╬════╣\n" + row("R4"; .n[12:16];"L4") + " ╚════╩════╩════╩════╝\n" + " U1 U2 U3 U4\n" );# If $check is a string, interpret it as a regex to be checked.# Otherwise, allow any non-blank inputdef prompt($prompt; $check): def p: inform($prompt) | try input catch halt | if ($check|type) == "string" then if (try test($check) catch false) then . else p end elif trim != "" then . else p end; p; def initGrid: {n: [range ( 0; 16) | . + 1]};# Input: {n}# $ix should be an array of length 4def rotate($ix): .n[$ix[3]] as $last | reduce range (3; 0; -1) as $i (.; .n[$ix[$i]] = .n[$ix[$i-1]]) | .n[$ix[0]] = $last;def hasWon: .n as $n | all( range(0;15); . as $i | $n[$i] == $i + 1);# Input: {prng} as per MRG32k3a# Output: {prng, prn} where .prn is randomly selected from range(0;$n)def prn($n): .prng |= nextFloat | .prn = (.prng|.nextFloat * $n | trunc) ;# Set difficulty level# Input: {n}def setDiff($level): (if $level == easy then 3 else 12 end) as $moves | .rc = [] | .i = 0 | .prng = (seed(now | tostring | sub("^.*[.]";"") | tonumber)) | until(.i >= $moves; .rc = [] | prn(2) | .prn as $r | prn(4) | .prn as $s | if ($r == 0) then reduce range($s*4; ($s+1)*4) as $j (.; .rc += [$j] ) else # rotate random column reduce range($s; $s+16; 4) as $j (.; .rc += [$j] ) end | rotate(.rc) | if hasWon then # start again .i = -1 end | .i += 1 ) | inform("Target is \($moves) moves.\n");def play: def trim: gsub(" ";""); def difficulty: prompt("Enter difficulty level (E for easy, H for hard): "; "^[eEhH]") | .[:1] | ascii_upcase; initGrid | .level = (if difficulty == "H" then hard else easy end) | setDiff(.level) | inform("When entering moves, you can also enter Q to quit or S to start again.\n") | .moves = 0 | label $out | recurse( drawGrid | if hasWon then inform( "Congratulations, you have won the game in \(.moves) moves!\n") | break $out end | inform("Moves so far = \(.moves)\n") | (prompt("Enter move : "; "^ *([qsQS]|[drluDRLU] *[1234])") | ascii_upcase | trim) as $move | $move[:1] as $a | (if $a | test("[SQ]") then null else (($move[1:]|explode[0]) - 49) end ) as $c | if $a == "D" then rotate([0,4,8,12] | map(. + $c)) | .moves += 1 elif $a == "L" then rotate([3,2,1,0] | map(. + 4*$c)) | .moves += 1 elif $a == "U" then rotate([12,8,4,0] | map(. + $c)) | .moves += 1 elif $a == "R" then rotate([0,1,2,3] | map(. + 4*$c)) | .moves += 1 elif $move == "Q" then break $out elif $move == "S" then inform("Restarting...\n") | initGrid | setDiff(.level) | .moves = 0 else inform("Invalid move, try again.\n") end ) | empty ; play
Invocation: jq -nRr -f 16-puzzle-game.jq
Enter difficulty level (E for easy, H for hard): eTarget is 3 moves.When entering moves, you can also enter Q to quit or S to start again. D1 D2 D3 D4 ╔════╦════╦════╦════╗R1 ║ 4 ║ 1 ║ 13 ║ 3 ║ L1 ╠════╬════╬════╬════╣R2 ║ 5 ║ 2 ║ 7 ║ 8 ║ L2 ╠════╬════╬════╬════╣R3 ║ 9 ║ 6 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣R4 ║ 16 ║ 10 ║ 14 ║ 15 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4Moves so far = 0Enter move : d1 D1 D2 D3 D4 ╔════╦════╦════╦════╗R1 ║ 16 ║ 1 ║ 13 ║ 3 ║ L1 ╠════╬════╬════╬════╣R2 ║ 4 ║ 2 ║ 7 ║ 8 ║ L2 ╠════╬════╬════╬════╣R3 ║ 5 ║ 6 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣R4 ║ 9 ║ 10 ║ 14 ║ 15 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4Moves so far = 1Enter move : u1 D1 D2 D3 D4 ╔════╦════╦════╦════╗R1 ║ 4 ║ 1 ║ 13 ║ 3 ║ L1 ╠════╬════╬════╬════╣R2 ║ 5 ║ 2 ║ 7 ║ 8 ║ L2 ╠════╬════╬════╬════╣R3 ║ 9 ║ 6 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣R4 ║ 16 ║ 10 ║ 14 ║ 15 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4Moves so far = 2Enter move : sRestarting...Target is 12 moves. D1 D2 D3 D4 ╔════╦════╦════╦════╗R1 ║ 1 ║ 9 ║ 6 ║ 4 ║ L1 ╠════╬════╬════╬════╣R2 ║ 8 ║ 5 ║ 10 ║ 14 ║ L2 ╠════╬════╬════╬════╣R3 ║ 3 ║ 7 ║ 2 ║ 13 ║ L3 ╠════╬════╬════╬════╣R4 ║ 11 ║ 16 ║ 12 ║ 15 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4Moves so far = 0Enter move : q
usingGtk,Randomfunctionpuzzle16app(bsize)aclock,clock="\u27f2","\u27f3"win=GtkWindow("16 Game",300,300)|>(GtkFrame()|>(box=GtkBox(:v)))toolbar=GtkToolbar()newgame=GtkToolButton("New Game")set_gtk_property!(newgame,:label,"New Game")set_gtk_property!(newgame,:is_important,true)push!(toolbar,newgame)grid=GtkGrid()map(w->push!(box,w),[toolbar,grid])buttons=Array{Gtk.GtkButtonLeaf,2}(undef,bsize+2,bsize+2)foriin1:bsize+2,jin1:bsize+2grid[i,j]=buttons[i,j]=GtkButton()set_gtk_property!(buttons[i,j],:expand,true)endinorder=string.(reshape(1:bsize*bsize,bsize,bsize))puzzle=shuffle(inorder)rotatecol(puzzle,col,n)=puzzle[:,col].=circshift(puzzle[:,col],n)rotaterow(puzzle,row,n)=puzzle[row,:].=circshift(puzzle[row,:],n)iswon()=puzzle==inorderwon=falsefunctionfindrowcol(button)foriin1:bsize+2,jin1:bsize+2ifbuttons[i,j]==buttonreturni,jendendreturn0,0endfunctionplayerclicked(button)if!woni,j=findrowcol(button)ifi==1rotatecol(puzzle,j-1,1)elseifi==bsize+2rotatecol(puzzle,j-1,-1)elseifj==1rotaterow(puzzle,i-1,1)elseifj==bsize+2rotaterow(puzzle,i-1,-1)endendupdate!()endfunctionsetup!()foriin1:bsize+2,jin1:bsize+2if1<j<bsize+2ifi==1signal_connect(playerclicked,buttons[i,j],"clicked")elseifi==bsize+2signal_connect(playerclicked,buttons[i,j],"clicked")endelseif1<i<bsize+2ifj==1signal_connect(playerclicked,buttons[i,j],"clicked")elseifj==bsize+2signal_connect(playerclicked,buttons[i,j],"clicked")endendendendfunctionupdate!()foriin1:bsize+2,jin1:bsize+2if1<j<bsize+2ifi==1set_gtk_property!(buttons[i,j],:label,clock)elseifi==bsize+2set_gtk_property!(buttons[i,j],:label,aclock)elseset_gtk_property!(buttons[i,j],:label,puzzle[i-1,j-1])endelseif1<i<bsize+2ifj==1set_gtk_property!(buttons[i,j],:label,clock)elseifj==bsize+2set_gtk_property!(buttons[i,j],:label,aclock)endendendifiswon()won=trueinfo_dialog("Game over.\nScore:$score",win)endshowall(win)endfunctioninitialize!(w)puzzle=shuffle(inorder)won=falseupdate!()endsetup!()condition=Condition()endit(w)=notify(condition)signal_connect(initialize!,newgame,:clicked)signal_connect(endit,win,:destroy)initialize!(win)showall(win)wait(condition)endpuzzle16app(4)
Not a direct translation as there are a lot of differences, but the logic is the same.
importstd/[random,sequtils,strformat]importgtk2,glib2constBoardSize=4GridSize=BoardSize+2Right="⇨"Left="⇦"Up="⇧"Down="⇩"typeValue=1..16Puzzle=array[BoardSize,array[BoardSize,Value]]PuzzleApp=objectwindow:PWindow# Main window.inOrder:Puzzle# Target grid.puzzle:Puzzle# Current grid.buttons:array[GridSize,array[GridSize,PButton]]# Button grid.won:bool# True if game won.moves:Natural# Count of moves.procinitPuzzle(puzzle:varPuzzle;data:openArray[Value])=## Initialize the puzzle with a list of values.vari=0forrowinpuzzle.mitems:forcellinrow.mitems:cell=data[i]inciprocshowMessage(app:PuzzleApp)=## Show winning message.letmsg=&"You won in {app.moves} moves"letdialog=messageDialogNew(app.window,DIALOG_DESTROY_WITH_PARENT,MESSAGE_INFO,BUTTONS_CLOSE,msg.cstring)discarddialog.run()dialog.destroy()proconQuit(button:PWidget)=## Procedure called when clicking quit button.mainQuit()procrotateRow(puzzle:varPuzzle;row:Natural;left:bool)=## Rotate a row left or right.ifleft:letfirst=puzzle[row][0]foriin1..puzzle.high:puzzle[row][i-1]=puzzle[row][i]puzzle[row][^1]=firstelse:letlast=puzzle[row][^1]foriincountdown(puzzle.high,1):puzzle[row][i]=puzzle[row][i-1]puzzle[row][0]=lastprocrotateCol(puzzle:varPuzzle;col:Natural;up:bool)=## Rotate a column up or down.ifup:letfirst=puzzle[0][col]foriin1..puzzle.high:puzzle[i-1][col]=puzzle[i][col]puzzle[^1][col]=firstelse:letlast=puzzle[^1][col]foriincountdown(puzzle[0].high,1):puzzle[i][col]=puzzle[i-1][col]puzzle[0][col]=lastprocfindRowCol(app:PuzzleApp;button:PButton):(int,int)=## Find the row and column of a button.forrowin0..BoardSize+1:forcolin0..Boardsize+1:ifapp.buttons[row][col]==button:return(row,col)procupdate(app:varPuzzleApp)=## Update the grid.forrowin0..BoardSize+1:forcolin0..BoardSize+1:ifcolin1..BoardSize:ifrow==0:app.buttons[row][col].setLabel(Down)elifrow==BoardSize+1:app.buttons[row][col].setLabel(Up)else:app.buttons[row][col].setLabel(cstring($app.puzzle[row-1][col-1]))elifrowin1..BoardSize:ifcol==0:app.buttons[row][col].setLabel(Right)elifcol==BoardSize+1:app.buttons[row][col].setLabel(Left)ifapp.puzzle==app.inOrder:app.won=trueapp.showMessage()proconClick(button:PButton;app:varPuzzleApp)=## Procedure called when the user clicked a grid button.ifnotapp.won:incapp.moveslet(row,col)=app.findRowCol(button)ifrow==0:app.puzzle.rotateCol(col-1,false)elifrow==BoardSize+1:app.puzzle.rotateCol(col-1,true)elifcol==0:app.puzzle.rotateRow(row-1,false)elifcol==BoardSize+1:app.puzzle.rotateRow(row-1,true)app.update()procnewGame(button:PWidget;app:varPuzzleApp)=## Prepare a new game.varvalues=toSeq(Value.low..Value.high)values.shuffle()app.puzzle.initPuzzle(values)app.won=falseapp.update()proconDestroyEvent(widget:PWidget;data:pointer):gboolean{.cdecl.}=## Quit the application.mainQuit()randomize()nimInit()varapp:PuzzleAppapp.window=windowNew(WINDOW_TOPLEVEL)app.window.setTitle("16 puzzle game")app.window.setSizeRequest(328,365)discardapp.window.signalConnect("destroy",SIGNAL_FUNC(onDestroyEvent),nil)letvbox=vboxNew(false,0)app.window.addvboxlettoolbar=toolbarNew()discardtoolbar.insertItem("New game",nil,nil,nil,SIGNAL_FUNC(newGame),app.addr,0)discardtoolbar.insertItem("Quit",nil,nil,nil,SIGNAL_FUNC(onQuit),nil,1)vbox.packStart(toolbar,false,false,0)lethbox=hboxNew(false,5)vbox.packEnd(hbox,true,true,0)letgrid=tableNew(GridSize,GridSize,true)grid.setRowSpacings(5)grid.setColSpacings(5)hbox.packStart(grid,true,true,10)forrowinguint(0)..(BoardSize+1):forcolinguint(0)..(BoardSize+1):if(row,col)in[(0.guint,0.guint),(0,BoardSize+1),(BoardSize+1,0),(BoardSize+1,BoardSize+1)]:continueletbutton=buttonNew()button.setSizeRequest(45,45)app.buttons[row][col]=buttongrid.attach(button,col,col+1,row,row+1,0,0,1,1)varvalues=toSeq(Value.low..Value.high)app.inOrder.initPuzzle(values)values.shuffle()app.puzzle.initPuzzle(values)forrowin[0,BoardSize+1]:forcolin1..BoardSize:discardapp.buttons[row][col].signalConnect("clicked",SIGNAL_FUNC(onClick),app.addr)forcolin[0,BoardSize+1]:forrowin1..BoardSize:discardapp.buttons[row][col].signalConnect("clicked",SIGNAL_FUNC(onClick),app.addr)app.won=falseapp.update()app.window.showAll()main()
#!/usr/bin/perlusestrict;# http://www.rosettacode.org/wiki/16_Puzzle_Gameusewarnings;useList::Utilqw( any );useTk;my$size=$ARGV[0]//4;my$width=length$size**2;my$message='';my$steps;my@board;my@again;my$easy=3;my$mw=MainWindow->new(-title=>'16 Puzzle in Tk');$mw->geometry('+470+300');$mw->optionAdd('*font'=>'sans 14');my$frame=$mw->Frame(-bg=>'gray',-relief=>'ridge',-borderwidth=>5)->pack;my$label=$mw->Label(-textvariable=>\$message,-font=>'times-bold 30',)->pack;$mw->Button(-text=>"Exit",-command=>sub{$mw->destroy},)->pack(-side=>'right');$mw->Button(-text=>"Reset",-command=>sub{@board=@again;show();$message=$steps=0;$label->configure(-fg=>'black');},)->pack(-side=>'right');$mw->Button(-text=>"Easy",-command=>sub{$easy=3;generate()},)->pack(-side=>'left');$mw->Button(-text=>"Hard",-command=>sub{$easy=12;generate()},)->pack(-side=>'left');my@cells=map{$frame->Label(-text=>$_,-relief=>'sunken',-borderwidth=>2,-fg=>'white',-bg=>'blue',-font=>'sans 24',-padx=>7,-pady=>7,-width=>$width,)->grid(-row=>int($_/$size+2),-column=>$_%$size+2,-sticky=>"nsew",)}0..$size**2-1;formy$i(1..$size){$frame->Button(-text=>">",-command=>sub{move("R$i")},)->grid(-row=>$i+1,-column=>1,-sticky=>"nsew");$frame->Button(-text=>"<",-command=>sub{move("L$i")},)->grid(-row=>$i+1,-column=>$size+2,-sticky=>"nsew");$frame->Button(-text=>"v",-command=>sub{move("D$i")},)->grid(-row=>1,-column=>$i+1,-sticky=>"nsew");$frame->Button(-text=>"^",-command=>sub{move("U$i")},)->grid(-row=>$size+2,-column=>$i+1,-sticky=>"nsew");}generate();MainLoop;-M$0<0andexec$0,@ARGV;# restart if source file modified since startsubrandommove{move(qw(U D L R)[rand4].int1+rand$size)}subshow{$cells[$_]->configure(-text=>$board[$_])for0..$size**2-1}subsuccess{notany{$_+1!=$board[$_]}0..$size**2-1}submove{my($dir,$index)=split//,shift;$index--;my@from=map{$dir=~ /L|R/i?$_+$size*$index:$_*$size+$index}0..$size-1;@board[@from]=(@board[@from])[($dir=~ /L|U/i||-1)..$size-1,0];show();$message=++$steps;$label->configure(-fg=>success()?'red':'black');success()and$message="Solved in $steps";}subgenerate{@board=1..$size**2;randommove()for1..1+intrand$easy;success()andrandommove();@again=@board;$message=$steps=0;}
NB arrow keys not tested on linux, but "UDLR" should work...
constantlevel=5,ESC=27,UP=328,DOWN=336,LEFT=331,RIGHT=333sequenceboard=tagset(16),solve=boardprocedureprint_board()printf(1," 1 2 3 4\n")forr=1to4doprintf(1,"%d: %2d %2d %2d %2d\n",r&board[r*4-3..r*4])endforputs(1,"\n")endprocedureproceduremove(integerd,rc)-- d is 1..4 for up/down/left/right -- rc is 1..4 for row(d>=3)/column(d<=2)sequenceidx=repeat(0,4),tiles=repeat(0,4)fori=1to4doidx[i]=iff(d<=2?rc+(i-1)*4:(rc-1)*4+i)tiles[i]=board[idx[i]]endfor-- ?{d,rc,idx}idx=iff(mod(d,2)?idx[4]&idx[1..3]:idx[2..4]&idx[1])fori=1to4doboard[idx[i]]=tiles[i]endforendprocedurefori=1toleveldomove(rand(4),rand(4))endforwhile1doprint_board()ifboard=solvethenputs(1,"Solved!\n")exitendifputs(1,"Your move (escape|Up/Down|Left/Right & 1/2/3/4):")integerd,rcwhiletruedowhiletruedod=find(upper(wait_key()),{ESC,UP,DOWN,LEFT,RIGHT}&"UDLR")-1ifd!=-1thenexitendifendwhileifd=0thenputs(1,"\n\nYou gave up!\n")exitendififd>4thend-=4endifputs(1,"UDLR"[d])whiletruedorc=find(upper(wait_key()),ESC&"1234UDLR"&{UP,DOWN,LEFT,RIGHT})-1ifrc>