{ // ~~ Variables for clock ~~ let clockDrawTimeout; let twelveHourTime = require('Storage').readJSON('setting.json', 1)['12hour']; let updateSeconds = !Bangle.isLocked(); let batteryLevel = E.getBattery(); // ~~ Variables for game logic ~~ const NUM_COLORS = 6; const NUISANCE_COLOR = 7; let grid = [ new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]) ]; let hiddenRow = new Uint8Array([0, 0, 0, 0, 0, 0]); let nextQueue = [{pivot: 1, leaf: 1}, {pivot: 1, leaf: 1}]; let currentPair = {pivot: 0, leaf: 0}; let dropCoordinates = {pivotX: 2, pivotY: 11, leafX: 2, leafY: 10}; let pairX = 2; let pairOrientation = 0; //0 is up, 1 is right, 2 is down, 3 is left let slotsToCheck = []; let selectedColors; let lastChain = 0; let gameLost = false; let gamePaused = false; let midChain = false; /* Sets up a new game. Must be called once before the first round. */ let restartGame = function() { grid = [ new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]) ]; hiddenRow = new Uint8Array([0, 0, 0, 0, 0, 0]); currentPair = {pivot: 0, leaf: 0}; pairX = 2; pairOrientation = 0; //0 is up, 1 is right, 2 is down, 3 is left slotsToCheck = []; gameLost = false; lastChain = 0; //Set up random colors selectedColors = new Uint8Array([1, 2, 3, 4, 5, 6]); for (let i = NUM_COLORS - 1; i > 0; i--) { let swap = selectedColors[i]; let swapIndex = Math.floor(Math.random() * (i + 1)); selectedColors[i] = selectedColors[swapIndex]; selectedColors[swapIndex] = swap; } //Create the first two pairs (Always in the first three colors) nextQueue[0].pivot = selectedColors[Math.floor(Math.random() * 3)]; nextQueue[0].leaf = selectedColors[Math.floor(Math.random() * 3)]; nextQueue[1].pivot = selectedColors[Math.floor(Math.random() * 3)]; nextQueue[1].leaf = selectedColors[Math.floor(Math.random() * 3)]; }; /* Readies the next pair and generates a new one for the queue. */ let newPair = function() { currentPair.pivot = nextQueue[0].pivot; currentPair.leaf = nextQueue[0].leaf; nextQueue[0].pivot = nextQueue[1].pivot; nextQueue[0].leaf = nextQueue[1].leaf; nextQueue[1].pivot = selectedColors[Math.floor(Math.random() * 4)]; nextQueue[1].leaf = selectedColors[Math.floor(Math.random() * 4)]; pairX = 2; pairOrientation = 0; calcDropCoordinates(); }; /* Calculates the coordinates at which the current pair will be placed when quick dropped. */ let calcDropCoordinates = function() { dropCoordinates.pivotX = pairX; //Find Y coordinate of pivot dropCoordinates.pivotY = -2; for (let i = 11; i >= 0; i--) { if (grid[i][pairX] == 0) { dropCoordinates.pivotY = i; break; } } if (dropCoordinates.pivotY == -2 && hiddenRow[pairX] == 0) dropCoordinates.pivotY = -1; //Find coordinates of leaf if (pairOrientation == 1) { dropCoordinates.leafX = pairX + 1; dropCoordinates.leafY = -2; for (let i = 11; i >= 0; i--) { if (grid[i][pairX + 1] == 0) { dropCoordinates.leafY = i; break; } } if (dropCoordinates.leafY == -2 && hiddenRow[pairX + 1] == 0) dropCoordinates.leafY = -1; } else if (pairOrientation == 3) { dropCoordinates.leafX = pairX - 1; dropCoordinates.leafY = -2; for (let i = 11; i >= 0; i--) { if (grid[i][pairX - 1] == 0) { dropCoordinates.leafY = i; break; } } if (dropCoordinates.leafY == -2 && hiddenRow[pairX - 1] == 0) dropCoordinates.leafY = -1; } else if (pairOrientation == 2) { dropCoordinates.leafX = pairX; dropCoordinates.leafY = dropCoordinates.pivotY; dropCoordinates.pivotY--; } else { dropCoordinates.leafX = pairX; dropCoordinates.leafY = dropCoordinates.pivotY - 1; } }; /* Moves the current pair a certain number of slots. */ let movePair = function(dx) { pairX += dx; if (dx < 0) { if (pairX < (pairOrientation == 3 ? 1 : 0)) pairX = (pairOrientation == 3 ? 1 : 0); } if (dx > 0) { if (pairX > (pairOrientation == 1 ? 4 : 5)) pairX = (pairOrientation == 1 ? 4 : 5); } calcDropCoordinates(); }; /* Rotates the pair in the given direction around the pivot. */ let rotatePair = function(clockwise) { pairOrientation += (clockwise ? 1 : -1); if (pairOrientation > 3) pairOrientation = 0; if (pairOrientation < 0) pairOrientation = 3; if (pairOrientation == 1 && pairX == 5) pairX = 4; if (pairOrientation == 3 && pairX == 0) pairX = 1; calcDropCoordinates(); }; /* Places the current pair at the drop coordinates. */ let quickDrop = function() { if (dropCoordinates.pivotY == -1) { hiddenRow[dropCoordinates.pivotX] = currentPair.pivot; } else if (dropCoordinates.pivotY > -1) { grid[dropCoordinates.pivotY][dropCoordinates.pivotX] = currentPair.pivot; } if (dropCoordinates.leafY == -1) { hiddenRow[dropCoordinates.leafX] = currentPair.leaf; } else if (dropCoordinates.leafY > -1) { grid[dropCoordinates.leafY][dropCoordinates.leafX] = currentPair.leaf; } currentPair.pivot = 0; currentPair.leaf = 0; }; /* Makes all blobs fall to the lowest available slot. All blobs that fall will be added to slotsToCheck. */ let settleBlobs = function() { for (let x = 0; x < 6; x++) { let lowestOpen = 11; for (let y = 11; y >= 0; y--) { if (grid[y][x] != 0) { if (y != lowestOpen) { grid[lowestOpen][x] = grid[y][x]; grid[y][x] = 0; addSlotToCheck(x, lowestOpen); } lowestOpen--; } } if (lowestOpen >= 0 && hiddenRow[x] != 0) { grid[lowestOpen][x] = hiddenRow[x]; hiddenRow[x] = 0; addSlotToCheck(x, lowestOpen); } } }; /* Adds a slot to slotsToCheck. This slot will be checked for a pop next time popAll is called. */ let addSlotToCheck = function(x, y) { slotsToCheck.push({x: x, y: y}); }; /* Checks for a pop at every slot in slotsToCheck. Pops at all locations. */ let popAll = function() { let result = {pops: 0}; while(slotsToCheck.length > 0) { let coord = slotsToCheck.pop(); if (grid[coord.y][coord.x] != 0 && grid[coord.y][coord.x] != NUISANCE_COLOR) { if (checkSlotForPop(coord.x, coord.y)) result.pops += 1; } } return result; }; /* Checks a specific slot for a pop. If there are four or more adjacent blobs of the same color, they are removed. */ let checkSlotForPop = function(x, y) { let toDelete = [ new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]), new Uint8Array([0, 0, 0, 0, 0, 0]) ]; let blobsInClump = 0; let color = grid[y][x]; let toCheck = [{x: x, y: y}]; //Count every blob in this clump while (toCheck.length > 0) { let coord = toCheck.pop(); if (grid[coord.y][coord.x] == color && toDelete[coord.y][coord.x] == 0) { blobsInClump++; toDelete[coord.y][coord.x] = 1; if (coord.x > 0) toCheck.push({x: coord.x - 1, y: coord.y}); if (coord.x < 5) toCheck.push({x: coord.x + 1, y: coord.y}); if (coord.y > 0) toCheck.push({x: coord.x, y: coord.y - 1}); if (coord.y < 11) toCheck.push({x: coord.x, y: coord.y + 1}); } if (grid[coord.y][coord.x] == NUISANCE_COLOR && toDelete[coord.y][coord.x] == 0) toDelete[coord.y][coord.x] = 1; //For erasing garbage } //If there are at least four blobs in this clump, remove them from the grid and draw a pop. if (blobsInClump >= 4) { for (let y = 0; y < 12; y++) { for (let x = 0; x < 6; x++) { if (toDelete[y][x] == 1) { grid[y][x] = 0; //Clear the blob out of the slot g.setBgColor(0, 0, 0); g.clearRect((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21); //Draw the pop let colorInfo = getColor(color); g.setColor(colorInfo.r, colorInfo.g, colorInfo.b); if (color < NUISANCE_COLOR) { //A fancy pop for popped colors! g.drawEllipse((x*18)+36, (y*14)+7, (x*18)+50, (y*14)+21); g.drawEllipse((x*18)+27, (y*14)-2, (x*18)+59, (y*14)+30); } else if (color == NUISANCE_COLOR) { //Nuisance Blobs are simply crossed out. //TODO: Nuisance Blobs are currently unusued, but also untested. Test before use. g.drawLine((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21); } } } } return true; } return false; }; // Variables for graphics let oldGhost = {pivotX: 0, pivotY: 0, leafX: 0, leafY: 0}; /* Draws the time on the side. */ let drawTime = function(scheduleNext) { //Change this to alter the y-coordinate of the top edge. let dy = 25; g.setBgColor(0, 0, 0); g.clearRect(2, dy, 30, dy + 121); //Draw the time let d = new Date(); let h = d.getHours(), m = d.getMinutes(); if (twelveHourTime) { let mer = 'A'; if (h >= 12) mer = 'P'; if (h >= 13) h -= 12; if (h == 0) h = 12; g.setColor(1, 1, 1); g.setFont("Vector", 12); g.drawString(mer, 23, dy + 63); } let hs = h.toString().padStart(2, 0); let ms = m.toString().padStart(2, 0); g.setFont("Vector", 24); g.setColor(1, 0.2, 1); g.drawString(hs, 3, dy + 21); g.setColor(0.5, 0.5, 1); g.drawString(ms, 3, dy + 42); //Draw seconds let s = d.getSeconds(); if (updateSeconds) { let ss = s.toString().padStart(2, 0); g.setFont("Vector", 12); g.setColor(0.2, 1, 0.2); g.drawString(ss, 3, dy + 63); } //Draw the date let dayString = d.getDate().toString(); let dayNames = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; let dayName = dayNames[d.getDay()]; let monthNames = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JLY", "AUG", "SEP", "OCT", "NOV", "DEC"]; let monthName = monthNames[d.getMonth()]; g.setColor(1, 1, 1); g.setFont("Vector", 12); g.drawString(monthName, 3, dy + 84); g.drawString(dayString, 3, dy + 97); g.setColor(0.5, 0.5, 0.5); g.drawString(dayName, 3, dy + 110); //Draw battery if (s == 0) batteryLevel = E.getBattery(); if (Bangle.isCharging()) { g.setColor(0, 0, 1); } else if (batteryLevel <= 15) { g.setColor(1, 0, 0); } else { g.setColor(0, 1, 0); } g.drawString(batteryLevel + "%", 3, dy + 1); //Schedule the next draw if requested. if (!scheduleNext) return; if (clockDrawTimeout) clearTimeout(clockDrawTimeout); let interval = updateSeconds ? 1000 : 60000; clockDrawTimeout = setTimeout(function() { clockDrawTimeout = undefined; drawTime(true); }, interval - (Date.now() % interval)); }; /* Returns a tuple in the format {r, g, b} with the color of the blob with the given ID. This saves memory compared to having the colors stored in an array. */ let getColor = function(color) { if (color == 1) return {r: 1, g: 0, b: 0}; if (color == 2) return {r: 0, g: 1, b: 0}; if (color == 3) return {r: 0, g: 0, b: 1}; if (color == 4) return {r: 1, g: 1, b: 0}; if (color == 5) return {r: 1, g: 0, b: 1}; if (color == 6) return {r: 0, g: 1, b: 1}; if (color == 7) return {r: 0.5, g: 0.5, b: 0.5}; return {r: 1, g: 1, b: 1}; }; /* Clears the screen and draws the background. */ let drawBackground = function() { //Background g.setBgColor(0.5, 0.2, 0.1); g.clear(); g.setBgColor(0, 0, 0); g.clearRect(33, 0, 142, 176); g.setBgColor(0.5, 0.5, 0.5); g.clearRect(33, 4, 142, 6); //Reset button g.setBgColor(0.5, 0.5, 0.5); g.setColor(0, 0, 0); g.clearRect(143, 150, 175, 175); g.setFont("Vector", 30); g.drawString("R", 152, 150); //Pause button g.clearRect(0, 150, 32, 175); g.fillRect(9, 154, 13, 171); g.fillRect(18, 154, 22, 171); }; /* Draws a box under the next queue that displays the current value of lastChain. */ let drawChainCount = function() { g.setBgColor(0, 0, 0); g.setColor(1, 0.2, 0.2); g.setFont("Vector", 23); g.clearRect(145, 42, 173, 64); if (lastChain > 0) { if (lastChain < 10) g.drawString(lastChain, 154, 44); if (lastChain >= 10) g.drawString(lastChain, 147, 44); } }; /* Draws the blob at the given slot. */ let drawBlobAtSlot = function(x, y) { //If this blob is in the hidden row, clear it out and stop. if (y < 0) { g.setBgColor(0, 0, 0); g.clearRect((x*18)+34, 0, (x*18)+52, 3); return; } //First, clear what was in that slot. g.setBgColor(0, 0, 0); g.clearRect((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21); let color = grid[y][x]; if (color != 0) { let myColor = getColor(color); g.setColor(myColor.r, myColor.g, myColor.b); g.fillEllipse((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21); g.setColor(1, 1, 1); g.drawEllipse((x*18)+34, (y*14)+7, (x*18)+52, (y*14)+21); } }; /* Draws the ghost piece. clearOld: if the previous location of the ghost piece should be cleared. */ let drawGhostPiece = function(clearOld) { if (clearOld) { g.setColor(0, 0, 0); g.fillRect((oldGhost.pivotX*18)+38, (oldGhost.pivotY*14)+8, (oldGhost.pivotX*18)+47, (oldGhost.pivotY*14)+17); g.fillRect((oldGhost.leafX*18)+38, (oldGhost.leafY*14)+8, (oldGhost.leafX*18)+47, (oldGhost.leafY*14)+17); } let pivotX = dropCoordinates.pivotX; let pivotY = dropCoordinates.pivotY; let leafX = dropCoordinates.leafX; let leafY = dropCoordinates.leafY; let pivotColor = getColor(currentPair.pivot); let leafColor = getColor(currentPair.leaf); g.setColor(pivotColor.r, pivotColor.g, pivotColor.b); g.fillRect((pivotX*18)+40, (pivotY*14)+10, (pivotX*18)+45, (pivotY*14)+15); g.setColor(1, 1, 1); g.drawRect((pivotX*18)+38, (pivotY*14)+8, (pivotX*18)+47, (pivotY*14)+17); g.setColor(leafColor.r, leafColor.g, leafColor.b); g.fillRect((leafX*18)+40, (leafY*14)+10, (leafX*18)+45, (leafY*14)+15); oldGhost = {pivotX: pivotX, pivotY: pivotY, leafX: leafX, leafY: leafY}; }; /* Draws the next queue. */ let drawNextQueue = function() { g.setBgColor(0, 0, 0); g.clearRect(145, 4, 173, 28); let p1 = nextQueue[0].pivot; let l1 = nextQueue[0].leaf; let p2 = nextQueue[1].pivot; let l2 = nextQueue[1].leaf; let p1C = getColor(p1); let l1C = getColor(l1); let p2C = getColor(p2); let l2C = getColor(l2); g.setColor(p1C.r, p1C.g, p1C.b); g.fillEllipse(146, 17, 157, 28); g.setColor(l1C.r, l1C.g, l1C.b); g.fillEllipse(146, 5, 157, 16); g.setColor(p2C.r, p2C.g, p2C.b); g.fillEllipse(162, 17, 173, 28); g.setColor(l2C.r, l2C.g, l2C.b); g.fillEllipse(162, 5, 173, 16); g.setColor(1, 1, 1); g.drawLine(159, 4, 159, 28); g.drawEllipse(146, 17, 157, 28); g.drawEllipse(146, 5, 157, 16); g.drawEllipse(162, 17, 173, 28); g.drawEllipse(162, 5, 173, 16); }; /* Redraws the screen, except for the ghost piece. */ let redrawBoard = function() { drawBackground(); drawNextQueue(); drawChainCount(); drawTime(false); for (let y = 0; y < 12; y++) { for (let x = 0; x < 6; x++) { drawBlobAtSlot(x, y); } } }; /* Toggles the pause screen. */ let togglePause = function() { gamePaused = !gamePaused; if (gamePaused) { g.setBgColor(0.5, 0.2, 0.1); g.clear(); drawTime(false); g.setBgColor(0, 0, 0); g.setColor(1, 1, 1); g.clearRect(48, 66, 157, 110); g.setFont("Vector", 20); g.drawString("Tap here\nto unpause", 50, 68); require("widget_utils").show(); Bangle.drawWidgets(); } else { require("widget_utils").hide(); redrawBoard(); drawGhostPiece(false); //Display the loss text if the game is lost. if (gameLost) { g.setBgColor(0, 0, 0); g.setColor(1, 1, 1); g.clearRect(33, 73, 142, 103); g.setFont("Vector", 20); g.drawString("You Lose", 43, 80); } } }; // ~~ Events ~~ let dragAmnt = 0; let onTouch = (z, e) => { if (midChain) return; if (gamePaused) { if (e.x >= 40 && e.y >= 58 && e.x <= 165 && e.y <= 118) { g.setBgColor(1, 1, 1); g.clearRect(48, 66, 157, 110); g.flip(); togglePause(); } } else { //Tap reset button if (e.x >= 143 && e.y >= 150) { restartGame(); newPair(); redrawBoard(); drawGhostPiece(false); g.flip(); return; } //Tap pause button if (e.x <= 32 && e.y >= 150) { togglePause(); return; } //While playing, rotate pieces. if (!gameLost && !gamePaused) { if (e.x < 88) { rotatePair(false); drawGhostPiece(true); } else { rotatePair(true); drawGhostPiece(true); } } } }; Bangle.on("touch", onTouch); let onDrag = (e) => { if (gameLost || gamePaused || midChain) return; //Do nothing if the user is dragging down so that they don't accidentally move while dropping if (e.dy >= 5) { return; } dragAmnt += e.dx; if (e.b == 0) { dragAmnt = 0; } if (dragAmnt >= 20) { movePair(Math.floor(dragAmnt / 20)); drawGhostPiece(true); dragAmnt = dragAmnt % 20; } if (dragAmnt <= -20) { movePair(Math.ceil(dragAmnt / 20)); drawGhostPiece(true); dragAmnt = dragAmnt % 20; } }; Bangle.on("drag", onDrag); let onSwipe = (x, y) => { if (gameLost || gamePaused || midChain) return; if (y > 0) { let pivotX = dropCoordinates.pivotX; let pivotY = dropCoordinates.pivotY; let leafX = dropCoordinates.leafX; let leafY = dropCoordinates.leafY; if (pivotY < -1 && leafY < -1) return; quickDrop(); drawBlobAtSlot(pivotX, pivotY); drawBlobAtSlot(leafX, leafY); g.flip(); //Check for pops if (pivotY >= 0) addSlotToCheck(pivotX, pivotY); if (leafY >= 0) addSlotToCheck(leafX, leafY); midChain = true; let currentChain = 0; while (popAll().pops > 0) { currentChain++; lastChain = currentChain; drawChainCount(); g.flip(); settleBlobs(); redrawBoard(); g.flip(); } newPair(); drawNextQueue(); drawGhostPiece(false); //If the top slot of the third column is taken, lose the game. if (grid[0][2] != 0) { gameLost = true; g.setBgColor(0, 0, 0); g.setColor(1, 1, 1); g.clearRect(33, 73, 142, 103); g.setFont("Vector", 20); g.drawString("You Lose", 43, 80); } midChain = false; } }; Bangle.on("swipe", onSwipe); let onLock = on => { updateSeconds = !on; drawTime(true); }; Bangle.on('lock', onLock); let onCharging = charging => { drawTime(false); }; Bangle.on('charging', onCharging); Bangle.setUI({mode:"clock", remove:function() { //Remove listeners Bangle.removeListener("touch", onTouch); Bangle.removeListener("drag", onDrag); Bangle.removeListener("swipe", onSwipe); Bangle.removeListener('lock', onLock); Bangle.removeListener('charging', onCharging); if (clockDrawTimeout) clearTimeout(clockDrawTimeout); require("widget_utils").show(); }}); g.reset(); Bangle.loadWidgets(); require("widget_utils").hide(); drawBackground(); drawTime(true); restartGame(); newPair(); drawGhostPiece(false); drawNextQueue(); drawChainCount(); }