// Digilemma – By Frank Force
‘use strict’;
// for debugging
//let testSolution;
// shim variablessssss
const d = document, b = d.body;
// variables (remove from minified)
let level, piecesLeft, movePos, puzzle, puzzleTest, size, playerXY, rewind, moveOffset, input, seed, xy, xyOld;
// constants (will be replaced)
const seedToLevel = 1e5;
const maxLevel = 50;
// functions (remove const from minified)
const R = max=> Math.sin(++seed)**2 * 1e9 % max | 0;
// check if it is possible to get to player pos
const C = xy=> puzzleTest[xy] ? 0 : xy == playerXY || (puzzleTest[xy] = 1)&C(xy+1)|C(xy-1)|C(xy-size)|C(xy+size);
// keyboard input
onkeydown = e=> input[e.which] = 1;
// init
puzzle = [level = 0];
// set background color
d.bgColor = ‘#000’;
// load level from url hash
if (seed = location.hash.split`#`[1] | 0)
level = seed / seedToLevel | 0;
else
seed = Date.now();
// update loop
setInterval(o=>
{
// no pieces left or reload button
if (!piecesLeft || input[187] || input[82]) // R = reload, + = next level
{
// fix level if reload was pushed
piecesLeft && input[82] && –level;
// update url with level seed
location.hash = level * seedToLevel + (seed %= seedToLevel);
// limit max level
level = level%maxLevel + 1;
// set puzzle size
size = level<12 ? level+4 : 16;
// generate puzzle, keep trying until valid puzzle is made
for(o=0; !o;)
{
o = 1e5; // limit max move count (incase puzzle generator gets stuck)
//testSolution = [];
// clear playfield, add walls on sides and random blockers
for(xy=0; xy<size*size; ++xy)
puzzle[xy] = -((xy+1)%size<2 | xy%(size*size-size)<size | !R(99));
// try to make puzzle
for(piecesLeft = level; –o && piecesLeft;)
{
// get random place
xy = R(size*size);
// try again if wall or 9 (to prevent 10s) or if empty and not first and large chance (spawn new digits)
if (puzzle[xyOld = xy] < 9 & puzzle[xy] >= 0 && (piecesLeft == level | !R(1e3) | puzzle[xy]))
{
// random direction
moveOffset = (R(2)*2-1) * (R(2)? 1 : size);
// must be empty on push side
if (!puzzle[xy + moveOffset])
{
// move away while nothing is there, or randomly stop
for(; R(9) && !puzzle[xy+moveOffset*2]; ) xy+=moveOffset
// first piece must set player pos
piecesLeft < level || (playerXY = xy+moveOffset*2);
// check that it is possble to get to pushing location
puzzleTest = […puzzle];
if (C(xy+moveOffset*2))
{
// make move
if (puzzle[xyOld – moveOffset] > 0 & puzzle[xyOld – moveOffset] != puzzle[xyOld])
{
// bumped into another digit
puzzle[xy + moveOffset] = puzzle[xyOld];
puzzle[xyOld] = 0;
}
else if (!R(9) & piecesLeft < level)
{
// pushed into # wall (not for first piece)
puzzle[xy + moveOffset] = puzzle[xyOld];
puzzle[xyOld] = -1;
}
else if (!R(9) & piecesLeft < level & puzzle[xyOld] > 1)
{
// pushed into + piece (not for first piece and greater then 1)
puzzle[xy + moveOffset] = –puzzle[xyOld];
puzzle[xyOld] = -2;
}
else
{
// digit split
puzzle[xy + moveOffset] = ++puzzle[xyOld];
–piecesLeft;
}
// update player position
playerXY = xy+moveOffset*2;
//testSolution.push([[…puzzle], playerXY]);
}
}
}
}
}
// randomize player start
for(; puzzleTest = […puzzle], !C(xy = R(size)+ (R(size))*size); );
rewind = [[[…puzzle], playerXY = xy]];
}
else if (movePos) // update moving piece
{
// update moves
o = puzzle[movePos + moveOffset];
if (moveOffset == 1 & movePos%size == size-1 || moveOffset == -1 & movePos%size == 0)
{
// prevent wrapping on sides
movePos = 0;
}
else if (o == puzzle[movePos])
{
// hit same digit, join
puzzle[movePos + moveOffset] = o-1;
puzzle[movePos] = movePos = 0;
}
else if (o < -1)
{
// hit +, add 1
puzzle[movePos + moveOffset] = ++puzzle[movePos]%10; // prevent 10’s, 9 dissapear on +
puzzle[movePos] = movePos = 0;
}
else if (o < 1)
{
// open or wall, move into it
puzzle[movePos + moveOffset] = puzzle[movePos];
puzzle[movePos] = 0;
movePos = o<0 ? 0 : movePos + moveOffset; // stop if it hits a wall
}
else
{
// hit different digit, stop
movePos = 0;
}
}
else // player controls
{
// player input
//moveOffset = ((input[68]|0) – (input[65]|0)) || ((input[83]|0) – (input[87]|0))*size;
// enhancement: allow arrow keys and wasd
moveOffset = (((input[68]|0)||(input[39]|0)) – ((input[65]|0)||(input[37]|0))) || (((input[83]|0)||(input[40]|0)) – ((input[87]|0)||(input[38]|0)))*size;
// rewind, go back to start if first rewind
if (input[90]||input[32])
[[…puzzle], playerXY] = rewind[1] ? rewind.pop() : rewind[0];
//if (input[84] && testSolution[0]) [[…puzzle], playerXY] = testSolution.pop();
// player movement, check if move into empty space or push a digit
if (!puzzle[playerXY + moveOffset])
{
// empty space
puzzle[playerXY] = 0;
puzzle[playerXY += moveOffset] = -3;
}
else if (puzzle[playerXY + moveOffset] > 0) // push digit
{
// add to rewind, set move pos and offset
rewind.push([[…puzzle], playerXY, movePos = playerXY + moveOffset]);
}
}
// draw play area
o = `<pre><center><h1>`;
for(piecesLeft = xy=0; xy<size*size; ++xy % size ? input = [] : o += `n`) // new line, also clear input
o +=`<b style=color:hsl(${-40*puzzle[xy]},${ // set color
puzzle[xy]>0 ? piecesLeft = seedToLevel : 0}%,${ // check if digits are left
puzzle[xy]>0 ? 60 : 15-puzzle[xy]*50}%)>${ // draw digit
puzzle[xy]>0 ? puzzle[xy] : `·#+☻`[-puzzle[xy]] // draw icon
}`;
// set body html, display level
b.innerHTML = o + ‘<b style=color:#fff>’ + level + // show help (not part of 1k build)
`
<b style=color:#999>
Push numbers until none are left.
All puzzles are solvable!
– Controls –
WASD or Arrows = Move
Z or Space = Undo
R = Randomize
`
},
33); // 30 fps update
// Digilemma – By Frank Force
‘use strict’;
// for debugging
//let testSolution;
// shim variablessssss
const d = document, b = d.body;
// variables (remove from minified)
let level, piecesLeft, movePos, puzzle, puzzleTest, size, playerXY, rewind, moveOffset, input, seed, xy, xyOld;
// constants (will be replaced)
const seedToLevel = 1e5;
const maxLevel = 50;
// functions (remove const from minified)
const R = max=> Math.sin(++seed)**2 * 1e9 % max | 0;
// check if it is possible to get to player pos
const C = xy=> puzzleTest[xy] ? 0 : xy == playerXY || (puzzleTest[xy] = 1)&C(xy+1)|C(xy-1)|C(xy-size)|C(xy+size);
// keyboard input
onkeydown = e=> input[e.which] = 1;
// init
puzzle = [level = 0];
// set background color
d.bgColor = ‘#000’;
// load level from url hash
if (seed = location.hash.split`#`[1] | 0)
level = seed / seedToLevel | 0;
else
seed = Date.now();
// update loop
setInterval(o=>
{
// no pieces left or reload button
if (!piecesLeft || input[187] || input[82]) // R = reload, + = next level
{
// fix level if reload was pushed
piecesLeft && input[82] && –level;
// update url with level seed
location.hash = level * seedToLevel + (seed %= seedToLevel);
// limit max level
level = level%maxLevel + 1;
// set puzzle size
size = level<12 ? level+4 : 16;
// generate puzzle, keep trying until valid puzzle is made
for(o=0; !o;)
{
o = 1e5; // limit max move count (incase puzzle generator gets stuck)
//testSolution = [];
// clear playfield, add walls on sides and random blockers
for(xy=0; xy<size*size; ++xy)
puzzle[xy] = -((xy+1)%size<2 | xy%(size*size-size)<size | !R(99));
// try to make puzzle
for(piecesLeft = level; –o && piecesLeft;)
{
// get random place
xy = R(size*size);
// try again if wall or 9 (to prevent 10s) or if empty and not first and large chance (spawn new digits)
if (puzzle[xyOld = xy] < 9 & puzzle[xy] >= 0 && (piecesLeft == level | !R(1e3) | puzzle[xy]))
{
// random direction
moveOffset = (R(2)*2-1) * (R(2)? 1 : size);
// must be empty on push side
if (!puzzle[xy + moveOffset])
{
// move away while nothing is there, or randomly stop
for(; R(9) && !puzzle[xy+moveOffset*2]; ) xy+=moveOffset
// first piece must set player pos
piecesLeft < level || (playerXY = xy+moveOffset*2);
// check that it is possble to get to pushing location
puzzleTest = […puzzle];
if (C(xy+moveOffset*2))
{
// make move
if (puzzle[xyOld – moveOffset] > 0 & puzzle[xyOld – moveOffset] != puzzle[xyOld])
{
// bumped into another digit
puzzle[xy + moveOffset] = puzzle[xyOld];
puzzle[xyOld] = 0;
}
else if (!R(9) & piecesLeft < level)
{
// pushed into # wall (not for first piece)
puzzle[xy + moveOffset] = puzzle[xyOld];
puzzle[xyOld] = -1;
}
else if (!R(9) & piecesLeft < level & puzzle[xyOld] > 1)
{
// pushed into + piece (not for first piece and greater then 1)
puzzle[xy + moveOffset] = –puzzle[xyOld];
puzzle[xyOld] = -2;
}
else
{
// digit split
puzzle[xy + moveOffset] = ++puzzle[xyOld];
–piecesLeft;
}
// update player position
playerXY = xy+moveOffset*2;
//testSolution.push([[…puzzle], playerXY]);
}
}
}
}
}
// randomize player start
for(; puzzleTest = […puzzle], !C(xy = R(size)+ (R(size))*size); );
rewind = [[[…puzzle], playerXY = xy]];
}
else if (movePos) // update moving piece
{
// update moves
o = puzzle[movePos + moveOffset];
if (moveOffset == 1 & movePos%size == size-1 || moveOffset == -1 & movePos%size == 0)
{
// prevent wrapping on sides
movePos = 0;
}
else if (o == puzzle[movePos])
{
// hit same digit, join
puzzle[movePos + moveOffset] = o-1;
puzzle[movePos] = movePos = 0;
}
else if (o < -1)
{
// hit +, add 1
puzzle[movePos + moveOffset] = ++puzzle[movePos]%10; // prevent 10’s, 9 dissapear on +
puzzle[movePos] = movePos = 0;
}
else if (o < 1)
{
// open or wall, move into it
puzzle[movePos + moveOffset] = puzzle[movePos];
puzzle[movePos] = 0;
movePos = o<0 ? 0 : movePos + moveOffset; // stop if it hits a wall
}
else
{
// hit different digit, stop
movePos = 0;
}
}
else // player controls
{
// player input
//moveOffset = ((input[68]|0) – (input[65]|0)) || ((input[83]|0) – (input[87]|0))*size;
// enhancement: allow arrow keys and wasd
moveOffset = (((input[68]|0)||(input[39]|0)) – ((input[65]|0)||(input[37]|0))) || (((input[83]|0)||(input[40]|0)) – ((input[87]|0)||(input[38]|0)))*size;
// rewind, go back to start if first rewind
if (input[90]||input[32])
[[…puzzle], playerXY] = rewind[1] ? rewind.pop() : rewind[0];
//if (input[84] && testSolution[0]) [[…puzzle], playerXY] = testSolution.pop();
// player movement, check if move into empty space or push a digit
if (!puzzle[playerXY + moveOffset])
{
// empty space
puzzle[playerXY] = 0;
puzzle[playerXY += moveOffset] = -3;
}
else if (puzzle[playerXY + moveOffset] > 0) // push digit
{
// add to rewind, set move pos and offset
rewind.push([[…puzzle], playerXY, movePos = playerXY + moveOffset]);
}
}
// draw play area
o = `<pre><center><h1>`;
for(piecesLeft = xy=0; xy<size*size; ++xy % size ? input = [] : o += `n`) // new line, also clear input
o +=`<b style=color:hsl(${-40*puzzle[xy]},${ // set color
puzzle[xy]>0 ? piecesLeft = seedToLevel : 0}%,${ // check if digits are left
puzzle[xy]>0 ? 60 : 15-puzzle[xy]*50}%)>${ // draw digit
puzzle[xy]>0 ? puzzle[xy] : `·#+☻`[-puzzle[xy]] // draw icon
}`;
// set body html, display level
b.innerHTML = o + ‘<b style=color:#fff>’ + level + // show help (not part of 1k build)
`
<b style=color:#999>
Push numbers until none are left.
All puzzles are solvable!
– Controls –
WASD or Arrows = Move
Z or Space = Undo
R = Randomize
`
},
33); // 30 fps update