BangleApps/apps/waternet/app.js

2810 lines
76 KiB
JavaScript

// --------------------------------------------------------------------------------------------------
// global variables and consts
// --------------------------------------------------------------------------------------------------
const DEBUGMODERAMUSE = 0;
let memStart;
if (DEBUGMODERAMUSE)
memStart = process.memory(true);
const DEBUGMODE = 0;
const DEBUGMODEINPUT = 0;
const DEBUGMODESPEED = 0;
const TILESIZE = 10;
const SCREENWIDTH = g.getWidth();
const SCREENHEIGHT = g.getHeight();
const SCREENOFFSETX = ((SCREENWIDTH - 16 * TILESIZE) >> 1);
const MAXBOARDWIDTH = 10;
const MAXBOARDHEIGHT = 8;
const MAXBOARDSIZE = MAXBOARDWIDTH * MAXBOARDHEIGHT;
const GSGAME = 0;
const GSTITLE = 1;
const GSLEVELSELECT = 2;
const GSLEVELSCLEARED = 3;
const GSHELPROTATE = 4;
const GSHELPROTATE2 = 5;
const GSHELPROTATE3 = 6;
const GSHELPROTATESLIDE = 7;
const GSHELPROTATESLIDE2 = 8;
const GSHELPROTATESLIDE3 = 9;
const GSHELPROTATESLIDE4 = 10;
const GSHELPSLIDE = 11;
const GSHELPSLIDE2 = 12;
const GSHELPSLIDE3 = 13;
const GSINTRO = 14;
const GSINITDIFF = 50;
const GSINITGAME = GSINITDIFF + GSGAME;
const GSINITTITLE = GSINITDIFF + GSTITLE;
const GSINITLEVELSELECT = GSINITDIFF + GSLEVELSELECT;
const GSINITLEVELSCLEARED = GSINITDIFF + GSLEVELSCLEARED;
const GSINITHELPROTATE = GSINITDIFF + GSHELPROTATE;
const GSINITHELPROTATE2 = GSINITDIFF + GSHELPROTATE2;
const GSINITHELPROTATE3 = GSINITDIFF + GSHELPROTATE3;
const GSINITHELPROTATESLIDE = GSINITDIFF + GSHELPROTATESLIDE;
const GSINITHELPROTATESLIDE2 = GSINITDIFF + GSHELPROTATESLIDE2;
const GSINITHELPROTATESLIDE3 = GSINITDIFF + GSHELPROTATESLIDE3;
const GSINITHELPROTATESLIDE4 = GSINITDIFF + GSHELPROTATESLIDE4;
const GSINITHELPSLIDE = GSINITDIFF + GSHELPSLIDE;
const GSINITHELPSLIDE2 = GSINITDIFF + GSHELPSLIDE2;
const GSINITHELPSLIDE3 = GSINITDIFF + GSHELPSLIDE3;
const GSINITINTRO = GSINITDIFF + GSINTRO;
const DIFFVERYEASY = 0;
const DIFFEASY = 1;
const DIFFNORMAL = 2;
const DIFFHARD = 3;
const DIFFVERYHARD = 4;
const DIFFRANDOM = 5;
const DIFFCOUNT = 6;
const GMROTATE = 0;
const GMSLIDE = 1;
const GMROTATESLIDE = 2;
const GMCOUNT = 3;
const MMSTARTGAME = 0;
const MMHELP = 1;
const MMOPTIONS = 2;
const MMCREDITS = 3;
const MMCOUNT = 4;
const OPSOUND = 0;
const OPINPUTRECTS = 1;
const OPTHEMING = 2;
const OPCOUNT = 3;
const TSMAINMENU = 0;
const TSGAMEMODE = 1;
const TSDIFFICULTY = 2;
const TSOPTIONS = 3;
const TSCREDITS = 4;
const LEVELCOUNT = 25;
const ARROWDOWN = 122;
const ARROWUP = 120;
const ARROWLEFT = 123;
const ARROWRIGHT = 121;
const LEFTMENU = 118;
const EMPTY = 61;
let startPos;
let menuPos;
let maxLevel;
let selectedLevel;
let boardX;
let boardY;
let difficulty;
let gameState;
let boardWidth;
let boardHeight;
let boardSize;
let levelDone;
let titleStep;
let gameMode;
let posAdd;
let mainMenu;
let option;
let needRedraw;
let requiresFlip;
let selectionX, selectionY;
let moves;
let randomSeedGame;
let level = new Uint8Array(MAXBOARDSIZE);
let redrawPartial;
let screenOffsetY;
// Cursor
const cursorNumTiles = 16; //for the max 2 cursors shown at once (on help screens)
let showCursor;
let spritePos = [];
//intro
let frames;
let titlePosY;
//savestate
let levelLocks = new Uint8Array(GMCOUNT * DIFFCOUNT);
let options = new Uint8Array(OPCOUNT);
//sound
let soundon = 1;
//game
let paused;
let wasSoundOn;
let redrawLevelDoneBit;
let currentTiles = {};
//general input
let dragleft = false;
let dragright = false;
let dragup = false;
let dragdown = false;
let btna = false;
let btnb = false;
// --------------------------------------------------------------------------------------------------
// images
// --------------------------------------------------------------------------------------------------
const BLOCKTILESSIXTEEN = {
width:16,
height: 16,
bpp:1,
buffer: require("heatshrink").decompress(atob("hkwAIv8n4BBCY4LDC44TH/4ACD6Y/IAIUAAIwLDH6HwAIIfTP6ZrLCaYLLP6ZrEH55/KD5aBIaIIBBBoIBFBYa/Oh//AIIfTAL4/I+ABBFLh/KNYa/PP5YfLJaZrDH6B/KD5YLDAIf/ABwXHE44fXH5ABOH6AfWP66/PD65/XH6AfWQJDRBAJq/OD64BfM6ABXL5y/PD653fE64XLn/8AIP/ABwTDE44fbH5ABOH6AfWP7ajHD7Z/bH5gfWH48/KIIBNX5wfXH5ABSH5gfWP7a/LD65/bH5gfaADfwAA0PAAwPPEYf/AAQfX+H8AIsPhgBFB6BfWKYZbDmHwAIs/h4BFB57fE/4BCD7X//gBBP7APWGYZ/cOYQfDAH4A//4ACEDkDALo/EELZ7eP7ZbfH5Afa4EAALojggYBdH4ghbPbx/bLb77EAD3wAIUMAAUwAAX84ABBgc8AIMMgYBBmARBhkAn4BBmEAAIQLCD4cw4ABBD55ffh/wAIM//gBBngAKB4YXDD4a+acAIACn49BAIioDA4c8gABBCZAfSA44fHB4cDAIRvDngBCEY4BEH5RXHH54XDOZZDHP6azDh7xBAJBfMG4w/XTZYfM/gBFngACBZYADH5ATHIoYnHL57JIZZIfE4ABCD4YAKDogXCH54HLL6ZLEAIRnHAAcP/gBBT4gRDAoLHIh/wAIIfDWYj7GI4gBKD4cD4ABZL44BDgAtBAIhLHh/AAIJfEOYYBCL45jECYwfEAKTfLmEDAIM8h4BBn4AD/4BBnnPAIQPCAIY/EZ4YBDcYbfGAA6fEQ4TXPT5c/DoIfIOogPCQ44/EFIITBLYYAKB4bzEH5xDEH4YLGH5ABDFIKLEgAVBM5A/EJYQBDgfAAKLfEXJi/JAIYfbb488h4BO54BBn//AIIKDD4jTKOYbrLD57LDT56LIAIQTDh7lBCZIAGCYYBDBZ4PEKYUwngBBmfMAIIHLC4YgEMoIBBmABB/8MAIMAgYBFBYgTCDYYnDT4Y3EhgBBA4YBDH463DAQSMBCAS0DBYYBHBA//n4BCh4BBbA4LDCYYfHGZYfDJYY/LIAIBB+ABCH44LDCYYbDT4n8AIMwAIQHDBYjfKf4gbCn88AIIbDmYBCD5YA=="))
};
const BLOCKTILES = {
width: 10,
height: 10,
bpp: 2,
transparent: -1,
palette: new Uint16Array([0, 65535, 0, 0]),
buffer: require("heatshrink").decompress(atob("gsFgFQqFVgtVAYIOEBIgTEBwoADDBwxGgoiCBIIKBBIgxLFAQYOMZ5UIMZ4JIMZ5UEGJRjIDBBoGGoYXCBwQ/ESo57DDB4EYGIxmBESxjIKgaVJMZQYIHZ5UDGJZjIDBAJEHoIAMDpIYTGIx+FAgwxLDCJjTSpIYTMaYxLDCJoGGhzlJDCIEYJ5YETJRiVJDCZeYDqYTGgNVqg9BABkVqtADoYYWGI1FAhYxLDCJjWPwYYWgtUJSgxIDCJjEBINRIAYEJSo4YTGI1BAhoxJDCJjWSowYTMawxIDCYAWqEUgsBoEFoNQEwIEBKoNABwMABIYOCModVDCdQitFgNUBINAqg5BoIJCBwIJEAgRKQIAQABe4UQikBBwMVoNRDAIECBIIOCBIhjDVwLRBDCg7BMap8PoonBIQLgCMaJZCPgYA/AEblEACsQcQIETGIYYUIqsFF4JjVDARFVfYIxEDClAgEEA4IETDQNUDCoEBig7BAiYxDDChFVY4hjTDARFVVwT2SAAreBAQMUiEEagNAqraBglUgsAiIzBiAzCBoQTBDAsQDBpKYgtUZQJLBgojBAhAOBCYJkFHwIERAAMVGISjFBIQIEBwIJBDBzGEDA8AAgMVoEFBwNBSoITGG4gxGdooxNCYhZGGwUUDBBjJRYQlHAgJKHMYYxTDAxjMitVfI4JJAgIxGBwwsBCYYOCJRR0IfJVQDAcVFgoECBIIYCT4LgNGJoYJHYRPHAAUFRYcVBwhPDiqQBE4MFGIbCEawwJFAgYyDNAK1CAiBKDoFVHIJaFfwpoBBYRKCUgwTCXBQYDbZauLGIkVawQEBT4NEcowxELINRSAZFCSpsFLIizGXAwTBfIxAFJQQiCfJIYCqjvBoNQAgyuDYQQxCJRYTBfwwxGPgIHBFIMBegTlDBwRjEqtBfIQTCBQIELVwiQJfwRAEcAgYPPgYxEdQoEFfIKzDqEFPgbWJDYKfJDBhFCbwwECSoY7CAoKpCVwQaBBAIOCCYL6DYQa/CRAYJKAAgiBO4NBopOBqJTBqgEEgoxEIAJSBqsFUIJwCbgRYBotQAwILBcoQxDioxHAgYJBGIoYGCYMQgFBgEEBIVQJQwYGJQIYFXgIYPqgYEaoQYORYIYFJREFqkBqITBitBgNUioEBBITIBqtACYIYFCYVFoNUDAYMBDBIA=="))
};
const CONGRATSTILES = {
width: 10,
height: 10,
bpp: 2,
transparent: -1,
palette: new Uint16Array([0, 65535, 0, 0]),
buffer: require("heatshrink").decompress(atob("gtUgFQoEFggEFqoJIDAVQDBEQDowTDGIoEBA4IEJGwQTBGJhPHGKAJGGI4YJHYRPIAAQYJihKFgtACYQxCLxK4LAAQfBAwQEQJQkBAgLCBAgzMCDAzbHqJbDAgT5JbZauMgNAqlUgsRoFRigJCqAEIJQlUEQKpBqL+NAATWCCZpSEGIQYLqr5LGKL5GGJxjJDoKpGAQb3DDoRjEWwL5LAg75HAhbgHDCCuHd5b+Cor5KFgy4MGI0AoiNBXoZ8MNAQTBDQI9CGIILBMAR8GADAA=="))
};
const CONGRATSSCREEN = {
width: 160,
height: 80,
bpp: 2,
transparent: -1,
palette: new Uint16Array([0, 65535, 0, 0]),
buffer: require("heatshrink").decompress(atob("AFUECaVUAokVCRcFqoUFB5FAE40FqAn1CgQWCAQIgCDAMUE44KBE4I0EgInIKQVAqtVDINUgNVBIdUiodBBQVAipPCAYIIBqAUBE4wYCqoTBEAMBqkFooVBEAInDBAQnCqEUCYInMIgITDMoI9CE4gIDE4tACIQntSAgTFMQJ3HE5Z3FqAnKiqfHBAYnHOQYABHIIUBbgInCY4RPEBwhPGCYZPGBYIdBRwInCionFAgVRE4VQBAInCioOCE94AHgItBAEhKBE8oAegkBQoIEBggJCUwVAAgawCLQIEECYSbBoC/DE9gJBE4gDCE4IEDBwYJCCYcAFgIOCcwgTCE8htCE5R8DBwZ3BE46BCO4YxFJ5aaEJ6An5T6oJCiqVDAAIECRYifGKgQnGT4oA/AH4A/AH4A/AH4A/AH4A/AH4AOqoCBqgn/E88UE81VoAndgoGGoNQGZcFE6BHGE8YhDisAiAnTNowABgNQqgTEiEEZBQRBipdGWpEBdAMVgoMCE4MEE6RFCCQ8BisQiIUCiEBogNCIo0BqpHGgIdBE4xFBoInnqEQYQQCBO4S9HE5BtBS4InIoEEE46pEXwlVDpAGDHwQJBgIbBE458BE6BPDLwInOqCpDAAcUCIQnJPoIiBE97XBigEBVIh3RTwInJgoEDE47OBgpYFE4tQigNBD4InGoIZCBIjfFLoQnKCwIHBiogBDodVdQQnGgIyCKQInFR4InCgInCqFUE88QC4ZoEE46LCRoSoDCwhIEI4MVHYgnKDoMVE4LIEIoQnHCgMFBYYhEDoy9CaI6NDE44HCE4yIBSownIDoQnGCgIntA4SWBco4nNS4IhCD4QcGCoSpEBQUVIQgVGAgQhCgInKLIrvDQIIKFCokFRwYnCHQhyEDgoSCPIYKHAgNVBIonsDAgcGFwTRFE4rICD4iyFAAYcFC4cUBIonJDYQTGAAQxGJgMFQIp+GHwzUEE5cAqqRDMJInuABKpJABiYFA=="))
};
const SELECTORTILES = {
width: 10,
height: 10,
bpp: 2,
transparent: 0,
palette: new Uint16Array([63519, 0, 65535, 0]),
buffer: require("heatshrink").decompress(atob("ACeAgEogEC0AaUhQdGHCozYFSYWCAggApGd9AgEUgEBqAaUgodGHCozYFSYWCAggApGbYA=="))
};
const SELECTORTILESSIXTEEN = {
width: 16,
height: 16,
bpp: 2,
transparent: 0,
palette : new Uint16Array([63519, 0, 65535, 0]),
buffer : require("heatshrink").decompress(atob("AGegAQMKAY+oB4QDDAF43HJZaO2lQDGG/67wAZ4A/XeQDPAH4AkqACBgoDHqgPCAYYAvG45LLR20VAYw3/XeADPAH67yAZ4AwA=="))
};
const TITLE = {
width: 160,
height: 50,
bpp: 2,
transparent: 0,
palette: new Uint16Array([63519, 65535, 0, 0]),
buffer: require("heatshrink").decompress(atob("AAUC1WglQCBhWoE78KqtoktV0GprREXBQ8JE4mVE61VsAnSHoJ9PNgInL0onHqtawAnNlInMyonD1QABxVVqzTCwACBAAOgAQJMBLwJpBtQnP1NVqoCBq2qLYICBrVVytqBQQOBrQNBHIInOCQIADrICBtWlAgOVsoHB1QCBqwZBPBAnftSyGaQNqBoOiNAISCAAVpAQIxDBgaICE4eVE4zkCBoI7BE4YkBqxVDSgQnGAAQRBUA0COQJlBXINqywrCJYVWDwInBHIQGBAIIRBPwKFBUA4nByzhCtWqE4YVBE4mqfwYnDMIL9BE+CDBRQQhBAYQFEE4NWE4yxDE4KfGE4waBE5ahBE5DvHE4pCD0p3EP4J7DBgJ3D1QDCrQnHEgQnDEYQnUrAmGgEJE9CMCE4SfDqtlAQIiBE46fDtQnkBwNWE5TlCE4rSBE5QRDE4THIhVaSIKKCTATzBSAIMBBoNqSwWlBAKfDAoOVE48CE8+aCgOlAQNltQnCEIIuCAoVptWmy2qAgLvC1OlE48AIoOm1InBtJJCrSWCDgOVa4JHB02V1NqNATMCE5BMBzUKdQK7CJANW0x/BOIOW02KAYJjB0QTBq2JqwnJlVVtECEQOKFANW0RHBgQGB1ApBB4NYlNWwShBtUJUgInIgVVsB7BAQISBqwDBrAGCwElAoInBhINBDAQNB0AmHBwOqBYICChSfBAYOoAwWAlQFB1WoA4IYDE4NoE5AAblNVLYIAjkrdBT5Andd5IAagWlE4KsBE8QmBUEgnrrQnmqwn/E/4n/E4elE8sAkonprQnjlNWqtoE8cKJ4NgE8cCyta0AnjgGVrAmkgGq1AFDA="))
};
// --------------------------------------------------------------------------------------------------
// C Code
//
// random stuff
// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript#72732727
// https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf
//
// power2 function
// https://www.geeksforgeeks.org/write-a-c-program-to-calculate-powxn/
// --------------------------------------------------------------------------------------------------
//this will give an uncaught exception in emulator
//but code will still run fine as i'll run javascript versions then
var c = E.compiledC(`
//void srand(int)
//int random(int)
//void generateLevel(int,int,int)
unsigned int m;
unsigned int a;
unsigned int s;
int power2 (int x, unsigned int y)
{
int temp;
if (y == 0)
return 1;
temp = power2 (x, y / 2);
if ((y % 2) == 0)
return temp * temp;
else
return x * temp * temp;
}
void srand(int seed)
{
m = power2(2, 16) - 15;
a = 33285;
s = seed % m;
}
int random(int value)
{
s = s * a % m;
return s % value;
}
void generateLevel(unsigned char* level, int boardWidth, int boardHeight ) {
int cc = 0;
int currentPoint = 0;
int visitedRooms = 1;
int tmp, tmp2;
int selectedNeighbour;
int neighboursFound;
int lookUpX, lookUpY;
int rnd;
int neighbours[4];
int cellStack[(boardWidth*boardHeight) +1];
//intial all walls value in every room we will remove bits of this value to remove walls
for (int i = 0; i < boardWidth*boardHeight; i++)
level[i] = 0xf;
while (visitedRooms != boardWidth * boardHeight) {
neighboursFound = 0;
lookUpX = currentPoint % boardWidth;
lookUpY = (currentPoint / boardWidth) | 0;
tmp = currentPoint + 1;
//tile has neighbour to the right which we did not handle yet
if ((lookUpX + 1 < boardWidth) && (level[tmp] == 0xf))
neighbours[neighboursFound++] = tmp;
tmp = currentPoint - 1;
//tile has neighbour to the left which we did not handle yet
if ((lookUpX > 0) && (level[tmp] == 0xf))
neighbours[neighboursFound++] = tmp;
tmp = currentPoint - boardWidth;
//tile has neighbour the north which we did not handle yet
if ((lookUpY > 0) && (level[tmp] == 0xf))
neighbours[neighboursFound++] = tmp;
tmp = currentPoint + boardWidth;
//tile has neighbour the south which we did not handle yet
if ((lookUpY + 1 < boardHeight) && (level[tmp] == 0xf))
neighbours[neighboursFound++] = tmp;
if (neighboursFound == 0)
{
currentPoint = cellStack[--cc];
continue;
} else {
rnd = random(neighboursFound);
}
selectedNeighbour = neighbours[rnd];
tmp = (selectedNeighbour % boardWidth);
//tile has neighbour to the east
if (tmp > lookUpX) {
//remove west wall neighbour
level[selectedNeighbour] &= ~(8);
//remove east wall tile
level[currentPoint] &= ~(2);
} else {
// tile has neighbour to the west
if (tmp < lookUpX) {
//remove east wall neighbour
level[selectedNeighbour] &= ~(2);
//remove west wall tile
level[currentPoint] &= ~(8);
} else {
// tile has neighbour to the north
tmp2 = selectedNeighbour / boardWidth;
if (tmp2 < lookUpY) {
//remove south wall neighbour
level[selectedNeighbour] &= ~(4);
//remove north wall tile
level[currentPoint] &= ~(1);
} else {
// tile has neighbour to the south
if (tmp2 > lookUpY) {
//remove north wall neighbour
level[selectedNeighbour] &= ~(1);
//remove south wall tile
level[currentPoint] &= ~(4);
}
}
}
}
//add tile to the cellstack
if (neighboursFound > 1) {
cellStack[cc++] = currentPoint;
}
//set tile to the neighbour
currentPoint = selectedNeighbour;
visitedRooms++;
}
}`);
//when using C Code it will generate levels
//a lot faster on the device.
//levels generated by the C Code are not the same
//as levels generated by the JS Code!
const USECCODE = c !== undefined;
print("Using C Code:" + USECCODE.toString());
// --------------------------------------------------------------------------------------------------
// random stuff
// --------------------------------------------------------------------------------------------------
let randfunc;
function srandjs(seed) {
var m = Math.pow(2, 35) - 31;
var a = 185852;
var s = seed % m;
randfunc = function() {
return (s = s * a % m);
};
}
function randomjs(value) {
return Math.floor(randfunc()) % value;
}
function srand(seed) {
"RAM";
if(USECCODE)
c.srand(seed);
else
srandjs(seed);
}
function random(value) {
"RAM";
if(USECCODE)
return c.random(value);
else
return randomjs(value);
}
srand(Date().getTime());
// --------------------------------------------------------------------------------------------------
// Sound stuff
// --------------------------------------------------------------------------------------------------
function setSoundOn(val) {
soundOn = val;
}
function isSoundOn() {
return soundOn;
}
function playErrorSound() {
if (soundOn) {
Bangle.buzz(150, 1);
}
}
function playMenuAcknowlege() {
if (soundOn) {
Bangle.buzz(100, 0.75);
}
}
function playMenuBackSound() {
if (soundOn) {
Bangle.buzz(50, 0.4);
}
}
function playMenuSelectSound() {
if (soundOn) {
Bangle.buzz(50, 0.6);
}
}
function playGameMoveSound() {
if (soundOn) {
Bangle.buzz(50, 0.6);
}
}
function playGameAction() {
if (soundOn) {
Bangle.buzz(100, 0.75);
}
}
// --------------------------------------------------------------------------------------------------
// Cursor stuff
// --------------------------------------------------------------------------------------------------
function move_sprite(sprite, x, y) {
spritePos[sprite][0] = x;
spritePos[sprite][1] = y;
}
function drawCursors(clear, useSixteenSize) {
if (showCursor == 0)
return;
for (let i = 0; i < cursorNumTiles; i++)
if (spritePos[i][1] < SCREENHEIGHT)
if(!useSixteenSize)
g.drawImage(SELECTORTILES, SCREENOFFSETX + spritePos[i][0], screenOffsetY + spritePos[i][1], {
frame: ((clear ? 8 : 0) + (i % 8))
});
else
g.drawImage(SELECTORTILESSIXTEEN, 8 + spritePos[i][0], 12 + spritePos[i][1], {
frame: ((clear ? 8 : 0) + (i % 8))
});
}
function hideCursors() {
//HIDE CURSOR SPRITES
//cursor 0
setCursorPos(0, -10, -10);
//cursor 1
setCursorPos(1, -10, -10);
showCursor = 0;
}
function showCursors() {
showCursor = 1;
}
function setCursorPos(cursorNr, xPos, yPos, useSixteenSize) {
if (cursorNr > 1)
return;
let size = TILESIZE;
if (useSixteenSize)
size = 16;
move_sprite((cursorNr << 3) + 0, ((xPos) * size), ((yPos - 1) * size));
move_sprite((cursorNr << 3) + 1, ((xPos + 1) * size), ((yPos) * size));
move_sprite((cursorNr << 3) + 2, ((xPos) * size), ((yPos + 1) * size));
move_sprite((cursorNr << 3) + 3, ((xPos - 1) * size), ((yPos) * size));
//corners
move_sprite((cursorNr << 3) + 4, ((xPos + 1) * size), ((yPos - 1) * size));
move_sprite((cursorNr << 3) + 5, ((xPos + 1) * size), ((yPos + 1) * size));
move_sprite((cursorNr << 3) + 6, ((xPos - 1) * size), ((yPos - 1) * size));
move_sprite((cursorNr << 3) + 7, ((xPos - 1) * size), ((yPos + 1) * size));
}
function initCursors() {
hideCursors();
}
// --------------------------------------------------------------------------------------------------
// helper funcs
// --------------------------------------------------------------------------------------------------
function set_bkg_tile_xy(x, y, tile, noScreenOffset) {
"RAM";
if(!noScreenOffset)
g.drawImage(currentTiles, SCREENOFFSETX + x * TILESIZE, screenOffsetY + y * TILESIZE, {
frame: tile
});
else
g.drawImage(currentTiles, x * TILESIZE, y * TILESIZE, {
frame: tile
});
}
function set_bkg_tile_xy_sixteen(x, y, tile) {
"RAM";
g.drawImage(BLOCKTILESSIXTEEN, 8 + x * 16, 12+ y * 16, {
frame: tile
});
}
function set_bkg_data(tiles) {
currentTiles = tiles;
}
function get_bkg_data() {
return currentTiles;
}
function set_bkg_tiles(x, y, map) {
"RAM";
g.drawImage(map, SCREENOFFSETX + x, screenOffsetY + y);
}
function setBlockTilesAsBackground() {
set_bkg_data(BLOCKTILES);
}
// --------------------------------------------------------------------------------------------------
// help screens
// --------------------------------------------------------------------------------------------------
//LEGEND STATE
function inithelpLegend() {
setBlockTilesAsBackground();
needRedraw = 1;
}
//LEGEND STATE
function helpLegend(nextState) {
if ((gameState == GSINITHELPSLIDE) ||
(gameState == GSINITHELPROTATE) ||
(gameState == GSINITHELPROTATESLIDE)) {
inithelpLegend();
gameState -= GSINITDIFF;
}
if (btna) {
playMenuAcknowlege();
gameState = nextState;
}
if (btnb) {
playMenuBackSound();
gameState = GSINITTITLE;
}
if (needRedraw) {
g.clearRect(Bangle.appRect);
switch (gameState) {
case GSHELPSLIDE:
printMessage(2, 0, "HELP: SLIDE");
break;
case GSHELPROTATE:
printMessage(2, 0, "HELP: ROTATE");
break;
case GSHELPROTATESLIDE:
printMessage(2, 0, "HELP: ROSLID");
break;
}
set_bkg_tile_xy(0, 2, 33);
printMessage(1, 2, ":WATER SOURCE");
set_bkg_tile_xy(0, 3, 11);
set_bkg_tile_xy(1, 3, 6);
set_bkg_tile_xy(2, 3, 12);
printMessage(3, 3, ":NOT FILLED");
set_bkg_tile_xy(0, 4, 27);
set_bkg_tile_xy(1, 4, 22);
set_bkg_tile_xy(2, 4, 28);
printMessage(3, 4, ":FILLED");
if ((gameState == GSHELPROTATESLIDE) ||
(gameState == GSHELPSLIDE)) {
set_bkg_tile_xy(0, 5, 121);
printMessage(1, 5, ":SLID ROW RIGHT");
set_bkg_tile_xy(0, 6, 123);
printMessage(1, 6, ":SLID ROW LEFT");
set_bkg_tile_xy(0, 7, 122);
printMessage(1, 7, ":SLID COL DOWN");
set_bkg_tile_xy(0, 8, 120);
printMessage(1, 8, ":SLID COL UP");
}
needRedraw = 0;
requiresFlip = 1;
}
}
//FINISH LEVEL STATE
function initHelpFinishLevel() {
setBlockTilesAsBackground();
needRedraw = 1;
}
//FINISH LEVEL STATE
function helpFinishLevel(nextState) {
if ((gameState == GSINITHELPSLIDE2) ||
(gameState == GSINITHELPROTATE2) ||
(gameState == GSINITHELPROTATESLIDE2)) {
initHelpFinishLevel();
gameState -= GSINITDIFF;
}
if (btna) {
playMenuAcknowlege();
gameState = nextState;
}
if (btnb) {
playMenuBackSound();
gameState = GSINITTITLE;
}
if (needRedraw) {
g.clearRect(Bangle.appRect);
switch (gameState) {
case GSHELPSLIDE2:
printMessage(2, 0, "HELP: SLIDE");
break;
case GSHELPROTATE2:
printMessage(2, 0, "HELP: ROTATE");
break;
case GSHELPROTATESLIDE2:
printMessage(2, 0, "HELP: ROSLID");
break;
}
printMessage(0, 2, "LEVEL FINISH:");
if ((gameState == GSHELPSLIDE2) ||
(gameState == GSHELPROTATESLIDE2)) {
//arrows top
set_bkg_tile_xy(2, 3, 122);
set_bkg_tile_xy(3, 3, 122);
set_bkg_tile_xy(4, 3, 122);
//arrows left / right row 1
set_bkg_tile_xy(1, 4, 121);
set_bkg_tile_xy(5, 4, 123);
//arrows left / right row 2
set_bkg_tile_xy(1, 5, 121);
set_bkg_tile_xy(5, 5, 123);
//arrows left / right row 3
set_bkg_tile_xy(1, 6, 121);
set_bkg_tile_xy(5, 6, 123);
//arrows bottom
set_bkg_tile_xy(2, 7, 120);
set_bkg_tile_xy(3, 7, 120);
set_bkg_tile_xy(4, 7, 120);
}
set_bkg_tile_xy(2, 4, 25);
set_bkg_tile_xy(3, 4, 23);
set_bkg_tile_xy(4, 4, 27);
printMessage(7, 4, "ALL WATER");
set_bkg_tile_xy(2, 5, 28);
set_bkg_tile_xy(3, 5, 33);
set_bkg_tile_xy(4, 5, 22);
printMessage(7, 5, "PIPES ARE");
set_bkg_tile_xy(2, 6, 29);
set_bkg_tile_xy(3, 6, 20);
set_bkg_tile_xy(4, 6, 23);
printMessage(7, 6, "FILLED");
needRedraw = 0;
requiresFlip = 1;
}
}
function initHelpDoSlideRotate() {
setBlockTilesAsBackground();
//DRAW CURSOR SPRITES
initCursors();
if ((gameState == GSINITHELPROTATESLIDE4) ||
(gameState == GSINITHELPSLIDE3)) {
setCursorPos(0, 0, 5);
setCursorPos(1, 11, 5);
} else {
setCursorPos(0, 1, 4);
setCursorPos(1, 12, 4);
}
showCursors();
needRedraw = 1;
}
function helpDoSlideRotate(nextState) {
if ((gameState == GSINITHELPSLIDE3) ||
(gameState == GSINITHELPROTATE3) ||
(gameState == GSINITHELPROTATESLIDE3) ||
(gameState == GSINITHELPROTATESLIDE4)) {
initHelpDoSlideRotate();
gameState -= GSINITDIFF;
}
if (btna) {
playMenuAcknowlege();
gameState = nextState;
hideCursors();
}
if (btnb) {
playMenuBackSound();
gameState = GSINITTITLE;
}
if (needRedraw) {
g.clearRect(Bangle.appRect);
switch (gameState) {
case GSHELPSLIDE3:
printMessage(2, 0, "HELP: SLIDE");
break;
case GSHELPROTATE3:
printMessage(2, 0, "HELP: ROTATE");
break;
case GSHELPROTATESLIDE3:
case GSHELPROTATESLIDE4:
printMessage(2, 0, "HELP: ROSLID");
break;
}
if ((gameState == GSHELPROTATESLIDE3) ||
(gameState == GSHELPROTATE3))
printMessage(5, 2, "ROTATE");
else
printMessage(6, 2, "SLIDE");
// 'A' + '=>'
set_bkg_tile_xy(6, 5, 119);
printMessage(5, 5, "TOUCH");
set_bkg_tile_xy(10, 5, 118);
if ((gameState == GSHELPSLIDE3) ||
(gameState == GSHELPROTATESLIDE3) ||
(gameState == GSHELPROTATESLIDE4)) {
//Top Arrows
set_bkg_tile_xy(1, 3, 122);
set_bkg_tile_xy(2, 3, 122);
set_bkg_tile_xy(3, 3, 122);
//arrows 1st row
set_bkg_tile_xy(0, 4, 121);
set_bkg_tile_xy(4, 4, 123);
//arrows 2nd row
set_bkg_tile_xy(0, 5, 121);
set_bkg_tile_xy(4, 5, 123);
//arrows 3rd row
set_bkg_tile_xy(0, 6, 121);
set_bkg_tile_xy(4, 6, 123);
//arrows bottom
set_bkg_tile_xy(1, 7, 120);
set_bkg_tile_xy(2, 7, 120);
set_bkg_tile_xy(3, 7, 120);
//2nd grid
//Top Arrows
set_bkg_tile_xy(12, 3, 122);
set_bkg_tile_xy(13, 3, 122);
set_bkg_tile_xy(14, 3, 122);
//arrows 1st row
set_bkg_tile_xy(11, 4, 121);
set_bkg_tile_xy(15, 4, 123);
//arrows 2nd row
set_bkg_tile_xy(11, 5, 121);
set_bkg_tile_xy(15, 5, 123);
//arrows 3rd row
set_bkg_tile_xy(11, 6, 121);
set_bkg_tile_xy(15, 6, 123);
//bottoms arrows
set_bkg_tile_xy(12, 7, 120);
set_bkg_tile_xy(13, 7, 120);
set_bkg_tile_xy(14, 7, 120);
}
//1st grid
if ((gameState == GSHELPROTATE3) ||
(gameState == GSHELPROTATESLIDE3)) {
set_bkg_tile_xy(1, 4, 12);
set_bkg_tile_xy(2, 4, 7);
set_bkg_tile_xy(3, 4, 27);
set_bkg_tile_xy(1, 5, 28);
set_bkg_tile_xy(2, 5, 33);
set_bkg_tile_xy(3, 5, 22);
set_bkg_tile_xy(1, 6, 29);
set_bkg_tile_xy(2, 6, 20);
set_bkg_tile_xy(3, 6, 23);
} else {
set_bkg_tile_xy(1, 4, 9);
set_bkg_tile_xy(2, 4, 7);
set_bkg_tile_xy(3, 4, 11);
set_bkg_tile_xy(1, 5, 17);
set_bkg_tile_xy(2, 5, 38);
set_bkg_tile_xy(3, 5, 12);
set_bkg_tile_xy(1, 6, 13);
set_bkg_tile_xy(2, 6, 4);
set_bkg_tile_xy(3, 6, 7);
}
//2nd grid
set_bkg_tile_xy(12, 4, 25);
set_bkg_tile_xy(13, 4, 23);
set_bkg_tile_xy(14, 4, 27);
set_bkg_tile_xy(12, 5, 28);
set_bkg_tile_xy(13, 5, 33);
set_bkg_tile_xy(14, 5, 22);
set_bkg_tile_xy(12, 6, 29);
set_bkg_tile_xy(13, 6, 20);
set_bkg_tile_xy(14, 6, 23);
drawCursors();
needRedraw = 0;
requiresFlip = 1;
}
}
//LEGEND STATE
function helpRotateSlide() {
helpLegend(GSINITHELPROTATESLIDE2);
}
//FINISH LEVEL STATE
function helpRotateSlide2() {
helpFinishLevel(GSINITHELPROTATESLIDE3);
}
//SLIDE STATE
function helpRotateSlide3() {
helpDoSlideRotate(GSINITHELPROTATESLIDE4);
}
//ROTATE STATE
function helpRotateSlide4() {
helpDoSlideRotate(GSINITTITLE);
}
function helpRotate() {
helpLegend(GSINITHELPROTATE2);
}
//FINISH LEVEL STATE
function helpRotate2() {
helpFinishLevel(GSINITHELPROTATE3);
}
//ROTATE STATE
function helpRotate3() {
helpDoSlideRotate(GSINITTITLE);
}
//LEGEND STATE
function helpSlide() {
helpLegend(GSINITHELPSLIDE2);
}
//FINISH LEVEL STATE
function helpSlide2() {
helpFinishLevel(GSINITHELPSLIDE3);
}
//SLIDE STATE
function helpSlide3() {
helpDoSlideRotate(GSINITTITLE);
}
// --------------------------------------------------------------------------------------------------
// Intro
// --------------------------------------------------------------------------------------------------
function initIntro() {
setBlockTilesAsBackground();
titlePosY = g.getHeight();
frames = 0;
}
function intro() {
if (gameState == GSINITINTRO) {
initIntro();
gameState -= GSINITDIFF;
}
if (btna || btnb) {
gameState = GSINITTITLE;
}
frames++;
g.clearRect(Bangle.appRect);
if (frames < 16) {
//16-12
printMessage(4 >> 1, 4, "WILLEMS DAVY");
requiresFlip = 1;
} else {
if (frames < 16 * 2) {
//16-8
printMessage(8 >> 1, 4, "PRESENTS");
requiresFlip = 1;
} else {
requiresFlip = 1;
g.drawImage(TITLE, SCREENOFFSETX, titlePosY);
if (titlePosY > screenOffsetY) {
titlePosY -= 4;
} else {
gameState = GSINITTITLE;
}
}
}
}
// --------------------------------------------------------------------------------------------------
// Level Stuff
// --------------------------------------------------------------------------------------------------
function generateLeveljs() {
let neighbours = new Uint8Array(4);
let cellStack = [];//new Uint8Array(boardSize + 1);
let cc = 0;
let currentPoint = 0;
let visitedRooms = 1;
let tmp, tmp2;
let selectedNeighbour;
let neighboursFound;
let lookUpX, lookUpY;
let rnd;
//intial all walls value in every room we will remove bits of this value to remove walls
level.fill(0xf, 0, boardSize);
while (visitedRooms != boardSize) {
neighboursFound = 0;
lookUpX = currentPoint % boardWidth;
lookUpY = (currentPoint / boardWidth) | 0;
tmp = currentPoint + 1;
//tile has neighbour to the right which we did not handle yet
if ((lookUpX + 1 < boardWidth) && (level[tmp] == 0xf))
neighbours[neighboursFound++] = tmp;
tmp = currentPoint - 1;
//tile has neighbour to the left which we did not handle yet
if ((lookUpX > 0) && (level[tmp] == 0xf))
neighbours[neighboursFound++] = tmp;
tmp = currentPoint - boardWidth;
//tile has neighbour the north which we did not handle yet
if ((lookUpY > 0) && (level[tmp] == 0xf))
neighbours[neighboursFound++] = tmp;
tmp = currentPoint + boardWidth;
//tile has neighbour the south which we did not handle yet
if ((lookUpY + 1 < boardHeight) && (level[tmp] == 0xf))
neighbours[neighboursFound++] = tmp;
if (neighboursFound == 0)
{
currentPoint = cellStack[--cc];
continue;
} else {
rnd = random(neighboursFound);
}
selectedNeighbour = neighbours[rnd];
tmp = (selectedNeighbour % boardWidth);
//tile has neighbour to the east
if (tmp > lookUpX) {
//remove west wall neighbour
level[selectedNeighbour] &= ~(8);
//remove east wall tile
level[currentPoint] &= ~(2);
} else {
// tile has neighbour to the west
if (tmp < lookUpX) {
//remove east wall neighbour
level[selectedNeighbour] &= ~(2);
//remove west wall tile
level[currentPoint] &= ~(8);
} else {
// tile has neighbour to the north
tmp2 = selectedNeighbour / boardWidth;
if (tmp2 < lookUpY) {
//remove south wall neighbour
level[selectedNeighbour] &= ~(4);
//remove north wall tile
level[currentPoint] &= ~(1);
} else {
// tile has neighbour to the south
if (tmp2 > lookUpY) {
//remove north wall neighbour
level[selectedNeighbour] &= ~(1);
//remove south wall tile
level[currentPoint] &= ~(4);
}
}
}
}
//add tile to the cellstack
if (neighboursFound > 1) {
cellStack[cc++] = currentPoint;
}
//set tile to the neighbour
currentPoint = selectedNeighbour;
visitedRooms++;
}
}
function generateLevel()
{
"RAM";
if(USECCODE)
{
var cellstack = new Uint8Array(MAXBOARDSIZE + 1);
var neighbours = new Uint8Array(4);
var addrLevel = E.getAddressOf(level,true);
c.generateLevel(addrLevel, boardWidth, boardHeight);
}
else
{
generateLeveljs();
}
}
function moveBlockDown(aTile) {
let tmp = level[aTile + boardSize - boardWidth];
for (let i = boardSize - boardWidth; i != 0; i -= boardWidth)
level[aTile + i] = level[aTile + i - boardWidth];
level[aTile] = tmp;
}
function moveBlockUp(aTile) {
let tmp = level[aTile - boardSize + boardWidth];
for (let i = boardSize - boardWidth; i != 0; i -= boardWidth)
level[aTile - i] = level[aTile - i + boardWidth];
level[aTile] = tmp;
}
function moveBlockRight(aTile) {
let tmp = level[aTile + boardWidth - 1];
for (let i = 0; i < boardWidth - 1; i++)
level[aTile + boardWidth - 1 - i] = level[aTile + boardWidth - 2 - i];
level[aTile] = tmp;
}
function moveBlockLeft(aTile) {
let tmp = level[aTile - boardWidth + 1];
for (let i = 0; i < boardWidth - 1; i++)
level[aTile - boardWidth + 1 + i] = level[aTile - boardWidth + 2 + i];
level[aTile] = tmp;
}
//rotates a tile by change the tilenr in the level
//there are 16 tiles per set and there are 3 sets no water, water filled, and special start tiles
function rotateBlock(aTile) {
switch (level[aTile]) {
case 1:
case 17:
case 33:
level[aTile] = 2;
break;
case 2:
case 18:
case 34:
level[aTile] = 4;
break;
case 3:
case 19:
case 35:
level[aTile] = 6;
break;
case 4:
case 20:
case 36:
level[aTile] = 8;
break;
case 5:
case 21:
case 37:
level[aTile] = 10;
break;
case 6:
case 22:
case 38:
level[aTile] = 12;
break;
case 7:
case 23:
case 39:
level[aTile] = 14;
break;
case 8:
case 24:
case 40:
level[aTile] = 1;
break;
case 9:
case 25:
case 41:
level[aTile] = 3;
break;
case 10:
case 26:
case 42:
level[aTile] = 5;
break;
case 11:
case 27:
case 43:
level[aTile] = 7;
break;
case 12:
case 28:
case 44:
level[aTile] = 9;
break;
case 13:
case 29:
case 45:
level[aTile] = 11;
break;
case 14:
case 30:
case 46:
level[aTile] = 13;
break;
default:
break;
}
}
function shuffleSlide(aTile) {
let rnd = random(3);
switch (rnd) {
case 0:
moveBlockUp((aTile % boardWidth) + boardSize - boardWidth);
break;
case 1:
moveBlockDown((aTile % boardWidth));
break;
case 2:
moveBlockLeft(boardWidth - 1 + aTile - (aTile % boardWidth));
break;
case 3:
moveBlockRight(aTile - (aTile % boardWidth));
break;
}
}
function shuffleRotate(aTile) {
let rnd = random(3);
for (let i = 0; i < rnd; i++)
rotateBlock(aTile);
}
function shuffleLevel() {
let rnd;
let j = 0;
while (j < boardSize) {
switch (gameMode) {
case GMROTATE:
shuffleRotate(j);
j++;
break;
case GMSLIDE:
shuffleSlide(j);
//for speed up it should be fine as all slide levels are uneven in width / height (except random)
j += 2;
break;
case GMROTATESLIDE:
rnd = random(2);
if (rnd == 0) {
shuffleSlide(j);
//for speed up
j += 2;
} else {
shuffleRotate(j);
j++;
}
break;
}
}
}
function handleConnectPoint(currentPoint, cellStack, cc) {
let lookUpX = currentPoint % boardWidth;
let lookUpY = (currentPoint / boardWidth) | 0;
let tmp;
let tmp2;
if ((lookUpY > 0) && (!(level[currentPoint] & 1))) {
tmp = currentPoint - boardWidth;
tmp2 = level[tmp];
if (((tmp2 < 16) && (!(tmp2 & 4))) ||
((tmp2 > 15) && (!((tmp2 - 16) & 4)))) {
//adapt tile to filled tile
if (level[currentPoint] < 16) {
level[currentPoint] += 16;
}
//add neighbour to cellstack of to handle tiles
if (tmp2 < 16) {
cellStack[cc++] = tmp;
}
}
}
//if tile has passage to the east and east neigbour passage to the west
if ((lookUpX + 1 < boardWidth) && (!(level[currentPoint] & 2))) {
tmp = currentPoint + 1;
tmp2 = level[tmp];
if (((tmp2 < 16) && (!(tmp2 & 8))) ||
((tmp2 > 15) && (!((tmp2 - 16) & 8)))) {
//adapt tile to filled tile
if (level[currentPoint] < 16) {
level[currentPoint] += 16;
}
//add neighbour to cellstack of to handle tiles
if (tmp2 < 16) {
cellStack[cc++] = tmp;
}
}
}
//if tile has passage to the south and south neigbour passage to the north
if ((lookUpY + 1 < boardHeight) && (!(level[currentPoint] & 4))) {
tmp = currentPoint + boardWidth;
tmp2 = level[tmp];
if (((tmp2 < 16) && (!(tmp2 & 1))) ||
((tmp2 > 15) && (!((tmp2 - 16) & 1)))) {
//adapt tile to filled tile
if (level[currentPoint] < 16) {
level[currentPoint] += 16;
}
//add neighbour to cellstack of to handle tiles
if (tmp2 < 16) {
cellStack[cc++] = tmp;
}
}
}
//if tile has passage to the west and west neigbour passage to the east
if ((lookUpX > 0) && (!(level[currentPoint] & 8))) {
tmp = currentPoint - 1;
tmp2 = level[tmp];
if (((tmp2 < 16) && (!(tmp2 & 2))) ||
((tmp2 > 15) && (!((tmp2 - 16) & 2)))) {
//adapt tile to filled tile
if (level[currentPoint] < 16) {
level[currentPoint] += 16;
}
//add neighbour to cellstack of to handle tiles
if (tmp2 < 16) {
cellStack[cc++] = tmp;
}
}
}
return cc;
}
function updateConnected() {
let cellStack = [];//new Uint8Array(boardSize + 1);
//reset all tiles to default not filled one
for (let i = 0; i != boardSize; i++) {
if (level[i] > 31) {
level[i] -= 32;
} else {
if (level[i] > 15) {
level[i] -= 16;
}
}
}
//start with start tile
let cc = 1;
cc = handleConnectPoint(startPos, cellStack, cc);
while (--cc > 0) {
//if tile is bigger then 15 we already handled this one, continue with next one
if ((level[cellStack[cc]] < 16)) {
cc = handleConnectPoint(cellStack[cc], cellStack, cc);
}
}
//add start pos special tile
if (level[startPos] > 15) {
level[startPos] += 16;
} else {
if (level[startPos] < 16)
level[startPos] += 32;
}
}
//when all board tiles are not below 16, the level is cleared
//as there are 16 tiles per tilegroup (no water, water, special start with water)
function isLevelDone() {
for (let i = 0; i != boardSize; i++)
if (level[i] < 16)
return 0;
return 1;
}
function initLevel(aRandomSeed, noLoading) {
let startTime = Date().getTime();
if (!noLoading) {
printMessage(((16 - 10) >> 1), (MAXBOARDHEIGHT >> 1) - 1, "[*********]");
printMessage(((16 - 10) >> 1), (MAXBOARDHEIGHT >> 1) - 0, "| LOADING +");
printMessage(((16 - 10) >> 1), (MAXBOARDHEIGHT >> 1) + 1, "<#########>");
g.flip();
}
levelDone = 0;
moves = 0;
if (difficulty != DIFFRANDOM)
//use level number + fixed value based on difficulty as seed for the random function
//this makes sure every level from a difficulty will remain the same
srand(selectedLevel + (difficulty * 500) + (gameMode * 50));
else
srand(aRandomSeed);
maxLevel = LEVELCOUNT;
//set boardsize and max level based on difficulty
switch (difficulty) {
case DIFFVERYEASY:
boardWidth = 5;
boardHeight = 5;
break;
case DIFFEASY:
boardWidth = 6;
boardHeight = 6;
break;
case DIFFNORMAL:
boardWidth = 7;
boardHeight = 7;
break;
case DIFFHARD:
boardWidth = 8;
boardHeight = 8;
break;
case DIFFVERYHARD:
boardWidth = 10;
boardHeight = 8;
break;
case DIFFRANDOM:
let rnd = random(255);
boardWidth = 5 + (rnd % (MAXBOARDWIDTH - 5 + 1)); //5 is smallest level width from very easy
rnd = random(255);
boardHeight = 5 + (rnd % (MAXBOARDHEIGHT - 5 + 1)); //5 is smallest level height from very easy
maxLevel = 0; //special value with random
break;
}
//add space for arrows based on same posadd value (1 or 0 depending if sliding is allowed)
boardWidth -= posAdd + posAdd;
boardHeight -= posAdd + posAdd;
boardSize = boardWidth * boardHeight;
//generate the level
generateLevel();
//startpoint of of level in center of screen
boardX = (MAXBOARDWIDTH - boardWidth) >> 1;
boardY = (MAXBOARDHEIGHT - boardHeight) >> 1;
startPos = (boardWidth >> 1) + (boardHeight >> 1) * (boardWidth);
//startpoint of tile with water and our cursor
selectionX = boardWidth >> 1;
selectionY = boardHeight >> 1;
//level is currently the solution so we still need to shuffle it
shuffleLevel();
//update possibly connected tiles already starting from startpoint
updateConnected();
if (DEBUGMODESPEED)
debugLog("Level Generated in " + (Date().getTime() - startTime).toString() + " ms");
}
// --------------------------------------------------------------------------------------------------
// levels cleared
// --------------------------------------------------------------------------------------------------
function initLevelsCleared() {
set_bkg_data(CONGRATSTILES);
g.clearRect(Bangle.appRect);
g.drawImage(CONGRATSSCREEN, SCREENOFFSETX, screenOffsetY);
switch (difficulty) {
case DIFFVERYEASY:
printCongratsScreen(0, 3, "VERY EASY LEVELS");
break;
case DIFFEASY:
printCongratsScreen(3, 3, "EASY LEVELS");
break;
case DIFFNORMAL:
printCongratsScreen(2, 3, "NORMAL LEVELS");
break;
case DIFFHARD:
printCongratsScreen(3, 3, "HARD LEVELS");
break;
case DIFFVERYHARD:
printCongratsScreen(0, 3, "VERY HARD LEVELS");
break;
}
requiresFlip = 1;
}
function levelsCleared() {
if (gameState == GSINITLEVELSCLEARED) {
initLevelsCleared();
gameState -= GSINITDIFF;
}
if (btna || btnb) {
playMenuAcknowlege();
titleStep = TSMAINMENU;
gameState = GSINITTITLE;
}
needRedraw = 0;
}
// --------------------------------------------------------------------------------------------------
// level select
// --------------------------------------------------------------------------------------------------
function drawLevelSelect(partial) {
if (partial > 2) {
g.clearRect(Bangle.appRect);
//LEVEL:
printMessage(0, 15, "LEVEL:", true);
}
if (partial == 2) {
//clear parts of loading text
printMessage(((16 - 10) >> 1), (MAXBOARDHEIGHT >> 1) - 1, " ");
printMessage(((16 - 10) >> 1), (MAXBOARDHEIGHT >> 1) - 0, " ");
printMessage(((16 - 10) >> 1), (MAXBOARDHEIGHT >> 1) + 1, " ");
}
//[LEVEL NR] 2 chars
if (partial == 2)
set_bkg_tile_xy(7, 15, EMPTY, true);
printMessage(6, 15, selectedLevel.toString(), true);
if (partial > 2) {
//B:BACK
printMessage(9, 16, "BTN:BACK", true);
}
if (partial > 1) {
//A:PLAY
printMessage(0, 16, "TCH:PLAY", true);
}
//Locked & Unlocked keywoard
let tmpUnlocked = levelUnlocked(gameMode, difficulty, selectedLevel - 1);
if (!tmpUnlocked)
printMessage(9, 15, "LOCKED", true);
else
printMessage(9, 15, "OPEN ", true);
if (partial > 2) {
//Draw arrows for vertical / horizontal movement
if (gameMode != GMROTATE) {
for (let x = 0; x != boardWidth; x++) {
set_bkg_tile_xy_sixteen(boardX + x, boardY - 1, ARROWDOWN);
set_bkg_tile_xy_sixteen(boardX + x, boardY + boardHeight, ARROWUP);
}
for (let y = 0; y != boardHeight; y++) {
set_bkg_tile_xy_sixteen(boardX - 1, boardY + y, ARROWRIGHT);
set_bkg_tile_xy_sixteen(boardX + boardWidth, boardY + y, ARROWLEFT);
}
}
}
//only draw right and bottom arrows
if (partial == 2) {
//Draw arrows for vertical / horizontal movement
if (gameMode != GMROTATE) {
for (let x = 0; x != boardWidth; x++) {
set_bkg_tile_xy_sixteen(boardX + x, boardY + boardHeight, ARROWUP);
}
for (let y = 0; y != boardHeight; y++) {
set_bkg_tile_xy_sixteen(boardX + boardWidth, boardY + y, ARROWLEFT);
}
}
}
let i16 = 0;
for (let yy = 0; yy < boardHeight; yy++) {
for (let xx = 0; xx < boardWidth; xx++) {
set_bkg_tile_xy_sixteen(boardX + xx, boardY + yy, level[i16 + xx]);
}
i16 += boardWidth;
}
redrawPartial = 3;
}
function initLevelSelect() {
setBlockTilesAsBackground();
needRedraw = 1;
redrawPartial = 3;
}
function levelSelect() {
if (gameState == GSINITLEVELSELECT) {
initLevelSelect();
gameState -= GSINITDIFF;
}
let tmpUnlocked = levelUnlocked(gameMode, difficulty, selectedLevel - 1);
if (btnb) {
playMenuBackSound();
gameState = GSINITTITLE;
}
if (btna) {
if (tmpUnlocked) {
gameState = GSINITGAME;
playMenuAcknowlege();
} else {
playErrorSound();
}
}
if (dragleft) {
if (difficulty == DIFFRANDOM) {
playMenuSelectSound();
randomSeedGame = Date.now();
initLevel(randomSeedGame);
needRedraw = 1;
redrawPartial = 3;
} else {
if (selectedLevel > 1) {
playMenuSelectSound();
selectedLevel--;
initLevel(randomSeedGame);
needRedraw = 1;
redrawPartial = 2;
}
}
}
if (dragright) {
if (difficulty == DIFFRANDOM) {
playMenuSelectSound();
//need new seed based on time
randomSeedGame = Date.now();
initLevel(randomSeedGame);
needRedraw = 1;
redrawPartial = 3;
} else {
if (selectedLevel < maxLevel) {
playMenuSelectSound();
selectedLevel++;
initLevel(randomSeedGame);
needRedraw = 1;
redrawPartial = 2;
}
}
}
if (needRedraw) {
drawLevelSelect(redrawPartial);
needRedraw = 0;
requiresFlip = 1;
}
}
// --------------------------------------------------------------------------------------------------
// printing functions
// --------------------------------------------------------------------------------------------------
function setCharAt(str, index, chr) {
"RAM";
if (index > str.length - 1) return str;
return str.substring(0, index) + chr + str.substring(index + 1);
}
function formatInteger(valinteger) {
"RAM";
const maxDigits = 10;
let array = " ";
const maxCharacters = (maxDigits);
const lastIndex = (maxCharacters - 1);
if (valinteger == 0) {
array = setCharAt(array, lastIndex, '0');
return {
digits: 1,
string: array
};
}
let digits = 0;
let integer = valinteger;
do {
let digit = integer % 10;
integer = Math.floor(integer / 10);
array = setCharAt(array, lastIndex - digits, digit.toString());
++digits;
}
while (integer > 0);
return {
digits: digits,
string: array
};
}
//print a number on levelselect or game screen
function printNumber(ax, ay, aNumber, maxDigits, noScreenOffset) {
"RAM";
const buffSize = 10;
let ret = formatInteger(aNumber);
let maxFor = ret.digits;
if (ret.digits > maxDigits)
maxFor = maxDigits;
for (let c = 0; c < maxFor; c++) {
if (ret.string.charAt(buffSize - ret.digits + c) == '')
return;
set_bkg_tile_xy(ax + (maxDigits - ret.digits) + c, ay, ret.string.charCodeAt(buffSize - ret.digits + c) + 32, noScreenOffset);
}
}
//print a message on the title screen on ax,ay, the tileset from titlescreen contains an alphabet
function printMessage(ax, ay, amsg, noScreenOffset) {
"RAM";
let aCode = 'A'.charCodeAt(0);
let zCode = 'Z'.charCodeAt(0);
let zeroCode = '0'.charCodeAt(0);
let nineCode = '9'.charCodeAt(0);
for (let p = 0; p < amsg.length; p++) {
let fCharCode = amsg.charCodeAt(p);
let tile = 61;
switch (fCharCode) {
case -1:
case 0:
return;
// '['
case 91:
tile = 70;
break;
//']'
case 93:
tile = 64;
break;
//'<'
case 60:
tile = 73;
break;
//'>'
case 62:
tile = 67;
break;
//'+'
case 43:
tile = 63;
break;
//'*'
case 42:
tile = 62;
break;
//'|'
case 124:
tile = 69;
break;
//'#'
case 35:
tile = 65;
break;
//':'
case 58:
tile = 116;
break;
//'a'
case 97:
tile = 119;
break;
//'b'
case 98:
tile = 117;
break;
default:
if ((fCharCode >= aCode) && (fCharCode <= zCode)) {
tile = fCharCode + 25;
} else {
if ((fCharCode >= zeroCode) && (fCharCode <= nineCode))
tile = fCharCode + 32;
}
break;
}
set_bkg_tile_xy(ax + p, ay, tile, noScreenOffset);
}
}
//print a message on the CongratsScreen on ax,ay, the tileset from Congrats Screen contains an alphabet in another font
function printCongratsScreen(ax, ay, amsg) {
// based on input from @Pharap
let aCode = 'A'.charCodeAt(0);
let zCode = 'Z'.charCodeAt(0);
for (let p = 0; p < amsg.length; p++) {
let fCharCode = amsg.charCodeAt(p);
let tile = 26;
if ((fCharCode == 0) || (fCharCode == -1))
return;
if ((fCharCode >= aCode) && (fCharCode <= zCode))
tile = fCharCode - aCode;
set_bkg_tile_xy(ax + p, ay, tile);
}
}
// --------------------------------------------------------------------------------------------------
// save state
// --------------------------------------------------------------------------------------------------
function validateSaveState() {
for (let j = 0; j < GMCOUNT; j++) {
for (let i = 0; i < DIFFCOUNT; i++) {
if ((levelLocks[(j * DIFFCOUNT) + i] == 0) || (levelLocks[(j * DIFFCOUNT) + i] > LEVELCOUNT))
return 0;
}
}
if (options[OPSOUND] > 1)
return 0;
if (options[OPINPUTRECTS] > 1)
return 0;
if (options[OPTHEMING] > 1)
return 0;
return 1;
}
function initSaveState() {
//read from file
let file = require("Storage").open("waternet.data.dat", "r");
let index = 0;
for (index = 0; index < GMCOUNT * DIFFCOUNT; index++) {
tmp = file.readLine();
if (tmp !== undefined)
levelLocks[index] = Number(tmp);
}
for (index = 0; index < OPCOUNT; index++) {
tmp = file.readLine();
if (tmp !== undefined)
options[index] = Number(tmp);
}
//then
if (!validateSaveState()) {
for (let j = 0; j < GMCOUNT; j++)
for (let i = 0; i < DIFFCOUNT; i++)
levelLocks[(j * DIFFCOUNT) + i] = 1; //1st level unlocked
options[OPSOUND] = 1;
options[OPINPUTRECTS] = 0;
options[OPTHEMING] = 1;
}
}
function saveSaveState() {
//save to file
let file = require("Storage").open("waternet.data.dat", "w");
let index;
for (index = 0; index < GMCOUNT * DIFFCOUNT; index++)
file.write(levelLocks[index].toString() + "\n");
for (index = 0; index < OPCOUNT; index++)
file.write(options[index].toString() + "\n");
}
function setSoundOnSaveState(value) {
options[OPSOUND] = value;
saveSaveState();
}
function isSoundOnSaveState() {
return options[OPSOUND] == 1;
}
function setThemingOnSaveState(value) {
options[OPTHEMING] = value;
saveSaveState();
}
function isThemingOnSaveState() {
return options[OPTHEMING] == 1;
}
function setInputRectsOnSaveState(value) {
options[OPINPUTRECTS] = value;
saveSaveState();
}
function isInputRectsOnSaveState() {
return options[OPINPUTRECTS] == 1;
}
function levelUnlocked(mode, diff, level) {
return levelLocks[(mode * DIFFCOUNT) + diff] > level;
}
function lastUnlockedLevel(mode, diff) {
return levelLocks[(mode * DIFFCOUNT) + diff];
}
function unlockLevel(mode, diff, level) {
if (level + 1 > lastUnlockedLevel(mode, diff)) {
levelLocks[(mode * DIFFCOUNT) + diff] = level + 1;
saveSaveState();
}
}
// --------------------------------------------------------------------------------------------------
// titlescreen
// --------------------------------------------------------------------------------------------------
function drawMenuSelector(tile) {
//set menu tile
switch (titleStep) {
case TSMAINMENU:
set_bkg_tile_xy(4, 4 + mainMenu, tile);
break;
case TSGAMEMODE:
set_bkg_tile_xy(4, 4 + gameMode, tile);
break;
case TSDIFFICULTY:
set_bkg_tile_xy(2, 3 + difficulty, tile);
break;
case TSOPTIONS:
set_bkg_tile_xy(1, 4 + option, tile);
break;
}
}
function drawMenuItems(clear) {
if (clear) {
g.setColor(g.getBgColor());
switch (titleStep) {
case TSMAINMENU:
g.fillRect(SCREENOFFSETX + 5 * TILESIZE, screenOffsetY + 4 * TILESIZE, SCREENOFFSETX + 13 * TILESIZE, screenOffsetY + 8 * TILESIZE);
break;
case TSDIFFICULTY:
g.fillRect(SCREENOFFSETX + 3 * TILESIZE, screenOffsetY + 3 * TILESIZE, SCREENOFFSETX + 12 * TILESIZE, screenOffsetY + 9 * TILESIZE);
break;
case TSGAMEMODE:
g.fillRect(SCREENOFFSETX + 5 * TILESIZE, screenOffsetY + 4 * TILESIZE, SCREENOFFSETX + 12 * TILESIZE, screenOffsetY + 7 * TILESIZE);
break;
case TSCREDITS:
g.fillRect(SCREENOFFSETX + 2 * TILESIZE, screenOffsetY + 5 * TILESIZE, SCREENOFFSETX + 15 * TILESIZE, screenOffsetY + 8 * TILESIZE);
break;
case TSOPTIONS:
g.fillRect(SCREENOFFSETX + 3 * TILESIZE, screenOffsetY + 4 * TILESIZE, SCREENOFFSETX + 11 * TILESIZE, screenOffsetY + 5 * TILESIZE);
g.fillRect(SCREENOFFSETX + 1 * TILESIZE, screenOffsetY + 5 * TILESIZE, SCREENOFFSETX + 16 * TILESIZE, screenOffsetY + 11 * TILESIZE);
break;
}
} else {
switch (titleStep) {
case TSMAINMENU:
printMessage(5, 4, "START");
printMessage(5, 5, "HELP");
printMessage(5, 6, "OPTIONS");
printMessage(5, 7, "CREDITS");
break;
case TSDIFFICULTY:
printMessage(3, 3, "VERY EASY");
printMessage(3, 4, "EASY");
printMessage(3, 5, "NORMAL");
printMessage(3, 6, "HARD");
printMessage(3, 7, "VERY HARD");
printMessage(3, 8, "RANDOM");
break;
case TSGAMEMODE:
printMessage(5, 4, "ROTATE");
printMessage(5, 5, "SLIDE");
printMessage(5, 6, "ROSLID");
break;
case TSCREDITS:
printMessage(3, 5, "CREATED BY");
printMessage(2, 6, "WILLEMS DAVY");
printMessage(2, 7, "JOYRIDER3774");
break;
case TSOPTIONS:
if (isSoundOn())
printMessage(3, 4, "BUZZ ON");
else
printMessage(3, 4, "BUZZ OFF");
if (isInputRectsOnSaveState())
printMessage(3, 5, "INPUTRECT ON");
else
printMessage(3, 5, "INPUTRECT OFF");
if (isThemingOnSaveState()) {
printMessage(3, 6, "THEMING ON");
} else {
printMessage(3, 6, "THEMING OFF");
}
break;
}
}
}
function drawTitleScreen(partial) {
if (partial > 2) {
g.clearRect(Bangle.appRect);
g.drawImage(TITLE, SCREENOFFSETX, screenOffsetY);
}
if (partial > 1)
drawMenuItems(false);
drawMenuSelector(LEFTMENU);
redrawPartial = 3;
}
function initTitleScreen() {
setBlockTilesAsBackground();
needRedraw = 1;
redrawPartial = 3;
}
function titleScreen() {
if (gameState == GSINITTITLE) {
initTitleScreen();
gameState -= GSINITDIFF;
}
if (dragup) {
switch (titleStep) {
case TSMAINMENU:
if (mainMenu > MMSTARTGAME) {
playMenuSelectSound();
//clear
drawMenuSelector(EMPTY);
mainMenu--;
needRedraw = 1;
redrawPartial = 1;
}
break;
case TSGAMEMODE:
if (gameMode > GMROTATE) {
playMenuSelectSound();
//clear
drawMenuSelector(EMPTY);
gameMode--;
needRedraw = 1;
redrawPartial = 1;
}
break;
case TSDIFFICULTY:
if (difficulty > DIFFVERYEASY) {
playMenuSelectSound();
//clear
drawMenuSelector(EMPTY);
difficulty--;
needRedraw = 1;
redrawPartial = 1;
}
break;
case TSOPTIONS:
if (option > OPSOUND) {
playMenuSelectSound();
//clear
drawMenuSelector(EMPTY);
option--;
needRedraw = 1;
redrawPartial = 1;
}
break;
}
}
if (dragdown) {
switch (titleStep) {
case TSMAINMENU:
if (mainMenu < MMCOUNT - 1) {
playMenuSelectSound();
//clear
drawMenuSelector(EMPTY);
mainMenu++;
needRedraw = 1;
redrawPartial = 1;
}
break;
case TSGAMEMODE:
if (gameMode < GMCOUNT - 1) {
playMenuSelectSound();
//clear
drawMenuSelector(EMPTY);
gameMode++;
needRedraw = 1;
redrawPartial = 1;
}
break;
case TSDIFFICULTY:
if (difficulty < DIFFCOUNT - 1) {
playMenuSelectSound();
//clear
drawMenuSelector(EMPTY);
difficulty++;
needRedraw = 1;
redrawPartial = 1;
}
break;
case TSOPTIONS:
if (option < OPCOUNT - 1) {
playMenuSelectSound();
//clear
drawMenuSelector(EMPTY);
option++;
needRedraw = 1;
redrawPartial = 1;
}
break;
}
}
if (btnb) {
switch (titleStep) {
case TSOPTIONS:
case TSCREDITS:
//clear
drawMenuSelector(EMPTY);
drawMenuItems(true);
titleStep = TSMAINMENU;
playMenuBackSound();
needRedraw = 1;
redrawPartial = 2;
break;
case TSGAMEMODE:
case TSDIFFICULTY:
//clear
drawMenuSelector(EMPTY);
drawMenuItems(true);
titleStep--;
playMenuBackSound();
needRedraw = 1;
redrawPartial = 2;
break;
}
}
if (btna) {
playMenuAcknowlege();
switch (mainMenu) {
case MMOPTIONS:
if (titleStep != TSOPTIONS) {
//clear
drawMenuSelector(EMPTY);
drawMenuItems(true);
titleStep = TSOPTIONS;
needRedraw = 1;
redrawPartial = 2;
} else {
switch (option) {
case OPSOUND:
setSoundOn(!isSoundOn());
setSoundOnSaveState(isSoundOn());
//clear
drawMenuItems(true);
needRedraw = 1;
redrawPartial = 2;
break;
case OPINPUTRECTS:
setInputRectsOnSaveState(!isInputRectsOnSaveState());
needRedraw = 1;
//needs 3 because text crosses input rect lines
redrawPartial = 3;
break;
case OPTHEMING:
setThemingOnSaveState(!isThemingOnSaveState());
setThemingOn(isThemingOnSaveState());
//needs a clear to set background color
if (isThemingOnSaveState()) {
g.setBgColor(g.theme.bg);
} else {
g.setBgColor(0x0000);
}
g.clearRect(g.appRect);
//probably need to call this again as i think
//a copy is kept in currenttiles but i'm not sure of that
//but it's a small call so should be fine
setBlockTilesAsBackground();
needRedraw = 1;
//needs 3 because we need to redraw everything
redrawPartial = 3;
}
}
break;
case MMCREDITS:
if (titleStep != TSCREDITS) {
//clear
drawMenuSelector(EMPTY);
drawMenuItems(true);
titleStep = TSCREDITS;
needRedraw = 1;
redrawPartial = 2;
} else {
//clear
drawMenuItems(true);
titleStep = TSMAINMENU;
needRedraw = 1;
redrawPartial = 2;
}
break;
case MMHELP:
if (titleStep < TSGAMEMODE) {
//clear
drawMenuSelector(EMPTY);
drawMenuItems(true);
titleStep++;
needRedraw = 1;
redrawPartial = 2;
} else {
switch (gameMode) {
case GMROTATE:
gameState = GSINITHELPROTATE;
break;
case GMSLIDE:
gameState = GSINITHELPSLIDE;
break;
case GMROTATESLIDE:
gameState = GSINITHELPROTATESLIDE;
break;
}
}
break;
case MMSTARTGAME:
if (titleStep < TSDIFFICULTY) {
//clear
drawMenuSelector(EMPTY);
drawMenuItems(true);
titleStep++;
needRedraw = 1;
redrawPartial = 2;
} else {
if (difficulty == DIFFRANDOM)
selectedLevel = 1;
else
selectedLevel = lastUnlockedLevel(gameMode, difficulty);
if (gameMode == GMROTATE)
posAdd = 0;
else
posAdd = 1;
//set randomseet to systime here
//it will be reused all the time
//with the level generating
//but not when going back from
//level playing to level selector
//when calling init level there
randomSeedGame = Date.now();
initLevel(randomSeedGame);
gameState = GSINITLEVELSELECT;
}
break;
}
}
if (needRedraw) {
drawTitleScreen(redrawPartial);
needRedraw = 0;
requiresFlip = 1;
}
}
// --------------------------------------------------------------------------------------------------
// game
// --------------------------------------------------------------------------------------------------
function drawGame(partial) {
//background
if (!paused && !redrawLevelDoneBit) {
if (partial > 2)
g.clearRect(Bangle.appRect);
//LEVEL:
if (partial > 2) {
printMessage(0, 15, "LEVEL:", true);
//[LEVEL NR] 2 chars
printMessage(6, 15, selectedLevel.toString(), true);
}
//MOVES:
if (partial > 2)
printMessage(9, 15, "MVS:", true);
if (partial > 1)
printMessage(13, 15, moves.toString(), true);
//A:XXXXXX (XXXXXX="ROTATE" or XXXXXX="SLIDE " or XXXXXX="ROSLID")
if (partial > 2) {
switch (gameMode) {
case GMROTATE:
printMessage(0, 16, "TCH:ROTA BTN:BACK", true);
break;
case GMSLIDE:
printMessage(0, 16, "TCH:SLID BTN:BACK", true);
break;
case GMROTATESLIDE:
printMessage(0, 16, "TCH:ROSL BTN:BACK", true);
break;
}
}
if (partial > 2) {
//Draw arrows for vertical / horizontal movement
if (gameMode != GMROTATE) {
for (let x = 0; x != boardWidth; x++) {
set_bkg_tile_xy_sixteen(boardX + x, boardY - 1, ARROWDOWN);
set_bkg_tile_xy_sixteen(boardX + x, boardY + boardHeight, ARROWUP);
}
for (let y = 0; y != boardHeight; y++) {
set_bkg_tile_xy_sixteen(boardX - 1, boardY + y, ARROWRIGHT);
set_bkg_tile_xy_sixteen(boardX + boardWidth, boardY + y, ARROWLEFT);
}
}
}
//complete level
let i16 = 0;
let yy;
let xx;
if (partial > 1) {
for (yy = 0; yy < boardHeight; yy++) {
for (xx = 0; xx < boardWidth; xx++) {
set_bkg_tile_xy_sixteen(boardX + xx, boardY + yy, level[i16 + xx]);
}
i16 += boardWidth;
}
}
}
redrawPartial = 3;
}
function initGame() {
paused = 0;
//set background tiles
setBlockTilesAsBackground();
//set sprite for selector / cursor
initCursors();
setCursorPos(0, boardX + selectionX, boardY + selectionY, true);
showCursors();
redrawLevelDoneBit = 0;
needRedraw = 1;
redrawPartial = 3;
}
function doPause() {
paused = 1;
wasSoundOn = isSoundOn();
setSoundOn(0);
hideCursors();
printMessage(0, (MAXBOARDHEIGHT >> 1) - 3, "[**************]");
printMessage(0, (MAXBOARDHEIGHT >> 1) - 2, "|PLEASE CONFIRM+");
printMessage(0, (MAXBOARDHEIGHT >> 1) - 1, "| +");
printMessage(0, (MAXBOARDHEIGHT >> 1) + 0, "| TOUCH PLAY +");
printMessage(0, (MAXBOARDHEIGHT >> 1) + 1, "| BTN TO QUIT +");
printMessage(0, (MAXBOARDHEIGHT >> 1) + 2, "<##############>");
requiresFlip = 1;
}
function doUnPause() {
paused = 0;
setSoundOn(wasSoundOn);
setCursorPos(0, boardX + selectionX, boardY + selectionY, true);
showCursors();
}
function game() {
if (gameState == GSINITGAME) {
initGame();
gameState -= GSINITDIFF;
}
if (dragdown) {
if (!levelDone && !paused) {
playGameMoveSound();
//if not touching border on bottom
if (selectionY + 1 < boardHeight + posAdd) {
//clear cursor
drawCursors(true, true);
selectionY += 1;
needRedraw = 1;
redrawPartial = 0;
} else {
//set to border on top
//clear cursor
drawCursors(true, true);
selectionY = -posAdd;
needRedraw = 1;
redrawPartial = 0;
}
setCursorPos(0, boardX + selectionX, boardY + selectionY, true);
}
} else {
if (dragup) {
if (!levelDone && !paused) {
//if not touching border on top
playGameMoveSound();
if (selectionY - 1 >= -posAdd) {
//clear cursor
drawCursors(true, true);
selectionY -= 1;
needRedraw = 1;
redrawPartial = 0;
} else {
//set to border on bottom
//clear cursor
drawCursors(true, true);
selectionY = boardHeight - 1 + posAdd;
needRedraw = 1;
redrawPartial = 0;
}
setCursorPos(0, boardX + selectionX, boardY + selectionY, true);
}
} else {
if (dragright) {
if (!levelDone && !paused) {
playGameMoveSound();
//if not touching border on right
if (selectionX + 1 < boardWidth + posAdd) {
//clear cursor
drawCursors(true, true);
selectionX += 1;
needRedraw = 1;
redrawPartial = 0;
} else {
//set to border on left
//clear cursor
drawCursors(true, true);
selectionX = -posAdd;
needRedraw = 1;
redrawPartial = 0;
}
setCursorPos(0, boardX + selectionX, boardY + selectionY, true);
}
} else {
if (dragleft) {
if (!levelDone && !paused) {
playGameMoveSound();
//if not touching border on left
if (selectionX - 1 >= -posAdd) {
//clear cursor
drawCursors(true, true);
selectionX -= 1;
needRedraw = 1;
redrawPartial = 0;
} else { //set to border on right
//clear cursor
drawCursors(true, true);
selectionX = boardWidth - 1 + posAdd;
needRedraw = 1;
redrawPartial = 0;
}
setCursorPos(0, boardX + selectionX, boardY + selectionY, true);
}
}
}
}
}
if (btna) {
if (paused) {
doUnPause();
playMenuAcknowlege();
needRedraw = 1;
redrawPartial = 3;
} else {
if (!levelDone) {
if ((selectionX > -1) && (selectionX < boardWidth) &&
(selectionY > -1) && (selectionY < boardHeight)) {
if (gameMode != GMSLIDE) {
rotateBlock(selectionX + (selectionY * boardWidth));
moves++;
playGameAction();
needRedraw = 1;
redrawPartial = 2;
} else {
playErrorSound();
}
} else {
if ((selectionX > -1) && (selectionX < boardWidth)) {
if (selectionY == -1) {
moveBlockDown(selectionX + ((selectionY + 1) * boardWidth));
moves++;
playGameAction();
needRedraw = 1;
redrawPartial = 2;
} else {
if (selectionY == boardHeight) {
moveBlockUp(selectionX + ((selectionY - 1) * boardWidth));
moves++;
playGameAction();
needRedraw = 1;
redrawPartial = 2;
}
}
} else {
if ((selectionY > -1) && (selectionY < boardHeight)) {
if (selectionX == -1) {
moveBlockRight((selectionX + 1) + (selectionY * boardWidth));
moves++;
playGameAction();
needRedraw = 1;
redrawPartial = 2;
} else {
if (selectionX == boardWidth) {
moveBlockLeft((selectionX - 1) + (selectionY * boardWidth));
moves++;
playGameAction();
needRedraw = 1;
redrawPartial = 2;
}
}
} else {
playErrorSound();
}
}
}
updateConnected();
levelDone = isLevelDone();
if (levelDone) {
//update level one last time so we are at final state
//as it won't be updated anymore as long as level done is displayed
//2 forces level to be drawn (only) one last time the other call uses levelDone
drawGame(2);
//hide cursor it's only sprite we use
hideCursors();
printMessage(((16 - 13) >> 1), (MAXBOARDHEIGHT >> 1) - 2, "[************]");
printMessage(((16 - 13) >> 1), (MAXBOARDHEIGHT >> 1) - 1, "| LEVEL DONE +");
printMessage(((16 - 13) >> 1), (MAXBOARDHEIGHT >> 1) - 0, "| TOUCH TO +");
printMessage(((16 - 13) >> 1), (MAXBOARDHEIGHT >> 1) + 1, "| CONTINUE +");
printMessage(((16 - 13) >> 1), (MAXBOARDHEIGHT >> 1) + 2, "<############>");
redrawLevelDoneBit = 1;
}
} else {
redrawLevelDoneBit = 0;
//goto next level
if (difficulty == DIFFRANDOM) {
//ned new seed based on time
randomSeedGame = Date.now();
initLevel(randomSeedGame);
//show cursor again (it's actually to early but i'm not fixing that)
setCursorPos(0, boardX + selectionX, boardY + selectionY, true);
showCursors();
needRedraw = 1;
redrawPartial = 3;
} else {
//goto next level if any
if (selectedLevel < maxLevel) {
selectedLevel++;
unlockLevel(gameMode, difficulty, selectedLevel - 1);
initLevel(randomSeedGame);
//show cursor again (it's actually to early but i'm not fixing that)
setCursorPos(0, boardX + selectionX, boardY + selectionY, true);
showCursors();
needRedraw = 1;
redrawPartial = 3;
} else {
gameState = GSINITLEVELSCLEARED;
}
}
}
}
}
if (btnb) {
if (!levelDone) {
if (!paused) {
playMenuBackSound();
doPause();
needRedraw = 0;
} else {
//need to enable early again to play backsound
//normally unpause does it but we only unpause
//after fade
setSoundOn(wasSoundOn);
hideCursors();
playMenuBackSound();
gameState = GSINITLEVELSELECT;
doUnPause();
//unpause sets cursor visible !
hideCursors();
//need to reset the level to initial state when going back to level selector
//could not find a better way unfortunatly
//also we do not want to reset the randomseed used for random level generating
//or a new level would have been created when going back we only want the level
//with random to change when pressing left and right in the level selector
//this way it stays consistent with the normal levels
//and the player can replay the level if he wants to
initLevel(randomSeedGame);
}
}
}
if (needRedraw) {
drawGame(redrawPartial);
drawCursors(false, true);
needRedraw = 0;
requiresFlip = 1;
}
}
// --------------------------------------------------------------------------------------------------
// main game start
// --------------------------------------------------------------------------------------------------
function setThemingOn(value) {
//change color palette to theming options for the images
if (value && (g.theme.bg != g.theme.fg)) {
SELECTORTILES.palette[1] = g.theme.bg;
SELECTORTILES.palette[2] = g.theme.fg;
SELECTORTILESSIXTEEN.palette[1] = g.theme.bg;
SELECTORTILESSIXTEEN.palette[2] = g.theme.fg;
TITLE.palette[3] = g.theme.bg;
TITLE.palette[2] = g.theme.bg;
TITLE.palette[1] = g.theme.fg;
BLOCKTILES.palette[0] = g.theme.bg;
BLOCKTILES.palette[1] = g.theme.fg;
CONGRATSSCREEN.palette[0] = g.theme.bg;
CONGRATSSCREEN.palette[1] = g.theme.fg;
CONGRATSTILES.palette[0] = g.theme.bg;
CONGRATSTILES.palette[1] = g.theme.fg;
} else {
SELECTORTILES.palette[1] = 0x0000;
SELECTORTILES.palette[2] = 0xFFFF;
SELECTORTILESSIXTEEN.palette[1] = 0x0000;
SELECTORTILESSIXTEEN.palette[2] = 0xFFFF;
TITLE.palette[3] = 0x0000;
TITLE.palette[2] = 0x0000;
TITLE.palette[1] = 0xFFFF;
BLOCKTILES.palette[0] = 0x0000;
BLOCKTILES.palette[1] = 0xFFFF;
CONGRATSSCREEN.palette[0] = 0x0000;
CONGRATSSCREEN.palette[1] = 0xFFFF;
CONGRATSTILES.palette[0] = 0x0000;
CONGRATSTILES.palette[1] = 0xFFFF;
}
}
function setup() {
redrawPartial = 0;
option = 0;
difficulty = DIFFNORMAL;
selectedLevel = 1;
mainMenu = MMSTARTGAME;
gameState = GSINITINTRO;
titleStep = TSMAINMENU;
gameMode = GMROTATE;
posAdd = 0;
//has to be called first because initsound read savestate sound to set intial flags
initSaveState();
//initSound();
setSoundOn(isSoundOnSaveState());
setThemingOn(isThemingOnSaveState());
//has to be called after applying theming
setBlockTilesAsBackground();
//calculate screenoffset y position taking apprect into account
screenOffsetY = ((SCREENHEIGHT + Bangle.appRect.y - 8 * TILESIZE) >> 1);
}
function loop() {
//soundTimer();
let startTime = Date().getTime();
g.reset();
if (isThemingOnSaveState()) {
g.setColor(g.theme.fg);
g.setBgColor(g.theme.bg);
} else {
g.setColor(0xFFFF);
g.setBgColor(0x0000);
}
//gamestate handling
let prevGameState = gameState;
switch (gameState) {
case GSINITTITLE:
case GSTITLE:
clearInterval(intervalTimer);
titleScreen();
break;
case GSINITLEVELSELECT:
case GSLEVELSELECT:
levelSelect();
break;
case GSINITGAME:
case GSGAME:
game();
break;
case GSINITLEVELSCLEARED:
case GSLEVELSCLEARED:
levelsCleared();
break;
case GSINITHELPSLIDE:
case GSHELPSLIDE:
helpSlide();
break;
case GSINITHELPSLIDE2:
case GSHELPSLIDE2:
helpSlide2();
break;
case GSINITHELPSLIDE3:
case GSHELPSLIDE3:
helpSlide3();
break;
case GSHELPROTATESLIDE:
case GSINITHELPROTATESLIDE:
helpRotateSlide();
break;
case GSINITHELPROTATESLIDE2:
case GSHELPROTATESLIDE2:
helpRotateSlide2();
break;
case GSINITHELPROTATESLIDE3:
case GSHELPROTATESLIDE3:
helpRotateSlide3();
break;
case GSINITHELPROTATESLIDE4:
case GSHELPROTATESLIDE4:
helpRotateSlide4();
break;
case GSINITHELPROTATE:
case GSHELPROTATE:
helpRotate();
break;
case GSINITHELPROTATE2:
case GSHELPROTATE2:
helpRotate2();
break;
case GSINITHELPROTATE3:
case GSHELPROTATE3:
helpRotate3();
break;
case GSINITINTRO:
case GSINTRO:
intro();
break;
}
if (requiresFlip) {
if (isInputRectsOnSaveState()) {
const offsetvalue = 0.20;
let x1 = SCREENWIDTH * offsetvalue;
let x2 = SCREENWIDTH - SCREENWIDTH * offsetvalue;
let y1 = Bangle.appRect.y + SCREENHEIGHT * offsetvalue;
let y2 = SCREENHEIGHT - SCREENHEIGHT * offsetvalue;
g.setColor(1, 0, 1);
//up
g.drawRect(0, Bangle.appRect.y, SCREENWIDTH - 1, y1);
//down
g.drawRect(0, y2, SCREENWIDTH - 1, SCREENHEIGHT - 1);
//left
g.drawRect(0, Bangle.appRect.y, x1, SCREENHEIGHT - 1);
//right
g.drawRect(x2, Bangle.appRect.y, SCREENWIDTH - 1, SCREENHEIGHT - 1);
}
g.flip();
requiresFlip = 0;
}
//when switching gamestate we need a redraw
if ((gameState != prevGameState) && (gameState >= GSINITDIFF))
needRedraw = 1;
if (DEBUGMODESPEED)
debugLog("loop done: " + (Date().getTime() - startTime).toString());
else
debugLog("loop done");
if (DEBUGMODERAMUSE) {
let memTmp = process.memory(false);
let used = memTmp.usage - memStart.usage;
debugLog("Udiff:" + used.toString() + " used:" + memTmp.usage.toString() + " free:" + memTmp.free.toString() + " total:" + memTmp.total.toString());
}
}
function debugLog(val) {
if (DEBUGMODE)
print(val);
}
function handleTouch(button, data) {
const offsetvalue = 0.20;
let x1 = SCREENWIDTH * offsetvalue;
let x2 = SCREENWIDTH - SCREENWIDTH * offsetvalue;
let y1 = Bangle.appRect.y + SCREENHEIGHT * offsetvalue;
let y2 = SCREENHEIGHT - SCREENHEIGHT * offsetvalue;
dragleft = data.x < x1;
dragright = data.x > x2;
dragup = data.y < y1;
dragdown = data.y > y2;
btna = ((data.x <= x2) && (data.x >= x1) && (data.y >= y1) && (data.y <= y2) && (data.type == 0));
btnb = ((data.x <= x2) && (data.x >= x1) && (data.y >= y1) && (data.y <= y2) && (data.type == 2));
if (DEBUGMODEINPUT) {
debugLog("tap button:" + button.toString() + " x:" + data.x.toString() + " y:" + data.y.toString() + " x1:" + x1.toString() + " x2:" + x2.toString() + " y1:" + y1.toString() + " y2:" + y2.toString() + " type:" + data.type.toString());
debugLog("l:" + dragleft.toString() + " u:" + dragup.toString() + " r:" + dragright.toString() + " d:" + dragdown.toString() + " a:" + btna.toString() + " b:" + btnb.toString());
}
loop();
dragleft = false;
dragright = false;
dragdown = false;
dragup = false;
btna = false;
btnb = false;
while (needRedraw)
loop();
if (DEBUGMODEINPUT)
debugLog("handleTouch done");
}
function btnPressed() {
dragleft = false;
dragright = false;
dragdown = false;
dragup = false;
btna = false;
btnb = true;
loop();
btnb = false;
while (needRedraw)
loop();
if (DEBUGMODEINPUT)
debugLog("btnPressed done");
}
//initialize spritepos arrays
for (let i = 0; i < cursorNumTiles; i++)
spritePos.push(new Int16Array(2));
//clear one time entire screen
g.clear();
//setup game and run loop it will repeat during intro
//otherwise only as long as redraw is needed after input was detected
setup();
//for intro only
let intervalTimer = setInterval(loop, 66); // 15 fps
//for handling input
Bangle.on('touch', handleTouch);
setWatch(btnPressed, BTN, {
edge: "rising",
debounce: 50,
repeat: true
});