forked from FOSS/BangleApps
405 lines
10 KiB
JavaScript
405 lines
10 KiB
JavaScript
/**
|
|
* BangleJS MARIO CLOCK
|
|
*
|
|
* + Original Author: Paul Cockrell https://github.com/paulcockrell
|
|
* + Created: April 2020
|
|
* + Based on Espruino Mario Clock V3 https://github.com/paulcockrell/espruino-mario-clock
|
|
* + Online Image convertor: https://www.espruino.com/Image+Converter, Use transparency + compression + 8bit Web + export as Image String
|
|
* + Images must be drawn as PNGs with transparent backgrounds
|
|
*/
|
|
|
|
const locale = require("locale");
|
|
const storage = require('Storage');
|
|
const settings = (storage.readJSON('setting.json', 1) || {});
|
|
const timeout = settings.timeout || 10;
|
|
const is12Hour = settings["12hour"] || false;
|
|
|
|
// Screen dimensions
|
|
let W, H;
|
|
|
|
let intervalRef, displayTimeoutRef = null;
|
|
|
|
// Colours
|
|
const LIGHTEST = "#effedd";
|
|
const LIGHT = "#add795";
|
|
const DARK = "#588d77";
|
|
const DARKEST = "#122d3e";
|
|
const NIGHT = "#001818";
|
|
|
|
// Character names
|
|
const TOAD = "toad";
|
|
const MARIO = "mario";
|
|
|
|
const characterSprite = {
|
|
frameIdx: 0,
|
|
x: 35,
|
|
y: 55,
|
|
jumpCounter: 0,
|
|
jumpIncrement: Math.PI / 6,
|
|
isJumping: false,
|
|
character: MARIO,
|
|
};
|
|
|
|
const coinSprite = {
|
|
frameIdx: 0,
|
|
x: 34,
|
|
y: 18,
|
|
isAnimating: false,
|
|
yDefault: 18,
|
|
};
|
|
|
|
const pyramidSprite = {
|
|
x: 90,
|
|
height: 34,
|
|
};
|
|
|
|
const ONE_SECOND = 1000;
|
|
|
|
let timer = 0;
|
|
let backgroundArr = [];
|
|
let nightMode = false;
|
|
|
|
function genRanNum(min, max) {
|
|
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
}
|
|
|
|
function switchCharacter() {
|
|
const curChar = characterSprite.character;
|
|
let newChar;
|
|
if (curChar === MARIO) {
|
|
newChar = TOAD;
|
|
} else {
|
|
newChar = MARIO;
|
|
}
|
|
|
|
characterSprite.character = newChar;
|
|
}
|
|
|
|
function toggleNightMode() {
|
|
nightMode = !nightMode;
|
|
}
|
|
|
|
function incrementTimer() {
|
|
if (timer > 1000) {
|
|
timer = 0;
|
|
}
|
|
else {
|
|
timer += 50;
|
|
}
|
|
}
|
|
|
|
function drawBackground() {
|
|
// Clear screen
|
|
if (nightMode) {
|
|
g.setColor(NIGHT);
|
|
} else {
|
|
g.setColor(LIGHTEST);
|
|
}
|
|
g.fillRect(0, 10, W, H);
|
|
|
|
// set cloud colors
|
|
if (nightMode) {
|
|
g.setColor(DARKEST);
|
|
} else {
|
|
g.setColor(LIGHT);
|
|
}
|
|
// draw clouds
|
|
g.fillRect(0, 10, g.getWidth(), 15);
|
|
g.fillRect(0, 17, g.getWidth(), 17);
|
|
g.fillRect(0, 19, g.getWidth(), 19);
|
|
g.fillRect(0, 21, g.getWidth(), 21);
|
|
|
|
// Date bar
|
|
g.setColor(DARKEST);
|
|
g.fillRect(0, 0, W, 9);
|
|
}
|
|
|
|
function drawFloor() {
|
|
const fImg = require("heatshrink").decompress(atob("ikDxH+rgATCoIBQAQYDP")); // Floor image
|
|
for (let x = 0; x < 4; x++) {
|
|
g.drawImage(fImg, x * 20, g.getHeight() - 5);
|
|
}
|
|
}
|
|
|
|
function drawPyramid() {
|
|
const pPol = [pyramidSprite.x + 10, H - 6, pyramidSprite.x + 50, pyramidSprite.height, pyramidSprite.x + 90, H - 6]; // Pyramid poly
|
|
|
|
g.setColor(LIGHT);
|
|
g.fillPoly(pPol);
|
|
|
|
pyramidSprite.x -= 1;
|
|
// Reset and randomize pyramid if off-screen
|
|
if (pyramidSprite.x < - 100) {
|
|
pyramidSprite.x = 90;
|
|
pyramidSprite.height = Math.floor(Math.random() * (60 /* max */ - 25 /* min */ + 1) + 25 /* min */);
|
|
}
|
|
}
|
|
|
|
function drawTreesFrame(x, y) {
|
|
const tImg = require("heatshrink").decompress(atob("h8GxH+AAMHAAIFCAxADEBYgDCAQYAFCwobOAZAEFBxo=")); // Tree image
|
|
|
|
g.drawImage(tImg, x, y);
|
|
g.setColor(DARKEST);
|
|
g.drawLine(x + 6 /* Match stalk to palm tree */, y + 6 /* Match stalk to palm tree */, x + 6, H - 6);
|
|
}
|
|
|
|
function generateTreeSprite() {
|
|
return {
|
|
x: 90,
|
|
y: Math.floor(Math.random() * (60 /* max */ - 30 /* min */ + 1) + 30 /* min */)
|
|
};
|
|
}
|
|
|
|
function drawTrees() {
|
|
// remove first sprite if offscreen
|
|
let firstBackgroundSprite = backgroundArr[0];
|
|
if (firstBackgroundSprite) {
|
|
if (firstBackgroundSprite.x < -15) backgroundArr.splice(0, 1);
|
|
}
|
|
|
|
// set background sprite if array empty
|
|
let lastBackgroundSprite = backgroundArr[backgroundArr.length - 1];
|
|
if (!lastBackgroundSprite) {
|
|
const newSprite = generateTreeSprite();
|
|
lastBackgroundSprite = newSprite;
|
|
backgroundArr.push(lastBackgroundSprite);
|
|
}
|
|
|
|
// add random sprites
|
|
if (backgroundArr.length < 2 && lastBackgroundSprite.x < (16 * 7)) {
|
|
const randIdx = Math.floor(Math.random() * 25);
|
|
if (randIdx < 2) {
|
|
const newSprite = generateTreeSprite();
|
|
backgroundArr.push(newSprite);
|
|
}
|
|
}
|
|
|
|
for (x = 0; x < backgroundArr.length; x++) {
|
|
let scenerySprite = backgroundArr[x];
|
|
scenerySprite.x -= 5;
|
|
drawTreesFrame(scenerySprite.x, scenerySprite.y);
|
|
}
|
|
}
|
|
|
|
function drawCoinFrame(x, y) {
|
|
const cImg = require("heatshrink").decompress(atob("hkPxH+AAcHAAQIEBIXWAAQNEBIWHAAdcBgQLBA4IODBYQKEBAQMDBelcBaJUBM4QRBNYx1EBQILDR4QHBBISdIBIoA==")); // Coin image
|
|
g.drawImage(cImg, x, y);
|
|
}
|
|
|
|
function drawCoin() {
|
|
if (!coinSprite.isAnimating) return;
|
|
|
|
coinSprite.y -= 8;
|
|
if (coinSprite.y < (0 - 15 /*Coin sprite height*/)) {
|
|
coinSprite.isAnimating = false;
|
|
coinSprite.y = coinSprite.yDefault;
|
|
return;
|
|
}
|
|
|
|
drawCoinFrame(coinSprite.x, coinSprite.y);
|
|
}
|
|
|
|
function drawMarioFrame(idx, x, y) {
|
|
switch(idx) {
|
|
case 0:
|
|
const mFr1 = require("heatshrink").decompress(atob("h8UxH+AAkHAAYKFBolcAAIPIBgYPDBpgfGFIY7EA4YcEBIPWAAYdDC4gLDAII5ECoYOFDogODFgoJCBwYZCAQYOFBAhAFFwZKGHQpMDw52FSg2HAAIoDAgIOMB5AAFGQTtKeBLuNcQwOJFwgJFA=")); // Mario Frame 1
|
|
g.drawImage(mFr1, x, y);
|
|
break;
|
|
case 1:
|
|
const mFr2 = require("heatshrink").decompress(atob("h8UxH+AAkHAAYKFBolcAAIPIBgYPDBpgfGFIY7EA4YcEBIPWAAYdDC4gLDAII5ECoYOFDogODFgoJCBwYZCAQYOFBAhAFFwZKGHQpMDw+HCQYEBSowOBBQIdCCgTOIFgiVHFwYCBUhA9FBwz8HAo73GACQA=")); // Mario frame 2
|
|
g.drawImage(mFr2, x, y);
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
|
|
function drawToadFrame(idx, x, y) {
|
|
switch(idx) {
|
|
case 0:
|
|
const tFr1 = require("heatshrink").decompress(atob("iEUxH+ACkHAAoNJrnWAAQRGg/WrgACB4QEBCAYOBB44QFB4QICAg4QBBAQbDEgwPCHpAGCGAQ9KAYQPKCYg/EJAoADAwaKFw4BEP4YQCBIIABB468EB4QADYIoQGDwQOGBYYrCCAwbFFwgQEM4gAEeA4OIH4ghFAAYLD")); // Toad Frame 1
|
|
g.drawImage(tFr1, x, y);
|
|
break;
|
|
case 1:
|
|
const tFr2 = require("heatshrink").decompress(atob("iEUxH+ACkHAAoNJrnWAAQRGg/WrgACB4QEBCAYOBB44QFB4QICAg4QBBAQbDEgwPCHpAGCGAQ9KAYQPKCYg/EJAoADAwaKFw4BEP4YQCBIIABB468EB4QADYIoQGDwQOGBYQrDb4wcGFxYLDMoYgHRYgwKABAMBA")); // Mario frame 2
|
|
g.drawImage(tFr2, x, y);
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
|
|
function drawCharacter(date, character) {
|
|
// calculate jumping
|
|
const seconds = date.getSeconds(),
|
|
milliseconds = date.getMilliseconds();
|
|
|
|
if (seconds == 59 && milliseconds > 800 && !characterSprite.isJumping) {
|
|
characterSprite.isJumping = true;
|
|
}
|
|
|
|
if (characterSprite.isJumping) {
|
|
characterSprite.y = (Math.sin(characterSprite.jumpCounter) * -12) + 50 /* Character Y base value */;
|
|
characterSprite.jumpCounter += characterSprite.jumpIncrement;
|
|
|
|
if (parseInt(characterSprite.jumpCounter) === 2 && !coinSprite.isAnimating) {
|
|
coinSprite.isAnimating = true;
|
|
}
|
|
|
|
if (characterSprite.jumpCounter.toFixed(1) >= 4) {
|
|
characterSprite.jumpCounter = 0;
|
|
characterSprite.isJumping = false;
|
|
}
|
|
}
|
|
|
|
// calculate animation timing
|
|
if (timer % 50 === 0) {
|
|
// shift to next frame
|
|
characterSprite.frameIdx ^= 1;
|
|
}
|
|
|
|
switch(characterSprite.character) {
|
|
case("toad"):
|
|
drawToadFrame(characterSprite.frameIdx, characterSprite.x, characterSprite.y);
|
|
break;
|
|
case("mario"):
|
|
default:
|
|
drawMarioFrame(characterSprite.frameIdx, characterSprite.x, characterSprite.y);
|
|
}
|
|
}
|
|
|
|
function drawBrickFrame(x, y) {
|
|
const brk = require("heatshrink").decompress(atob("ikQxH+/0HACASB6wAQCoPWw4AOrgT/Cf4T/Cb1cAB8H/wVBAB/+A"));
|
|
g.drawImage(brk, x, y);
|
|
}
|
|
|
|
function drawTime(date) {
|
|
// draw hour brick
|
|
drawBrickFrame(20, 25);
|
|
// draw minute brick
|
|
drawBrickFrame(42, 25);
|
|
|
|
const h = date.getHours();
|
|
const hours = ("0" + ((is12Hour && h > 12) ? h - 12 : h)).substr(-2);
|
|
const mins = ("0" + date.getMinutes()).substr(-2);
|
|
|
|
g.setFont("6x8");
|
|
g.setColor(DARKEST);
|
|
g.drawString(hours, 25, 29);
|
|
g.drawString(mins, 47, 29);
|
|
}
|
|
|
|
function drawDate(date) {
|
|
g.setFont("6x8");
|
|
g.setColor(LIGHTEST);
|
|
let dateStr = locale.date(date, true);
|
|
dateStr = dateStr.replace(date.getFullYear(), "").trim().replace(/\/$/i,"");
|
|
dateStr = locale.dow(date, true) + " " + dateStr;
|
|
g.drawString(dateStr, (W - g.stringWidth(dateStr))/2, 1);
|
|
}
|
|
|
|
function redraw() {
|
|
const date = new Date();
|
|
|
|
// Update timers
|
|
incrementTimer();
|
|
|
|
// Draw frame
|
|
drawBackground();
|
|
drawFloor();
|
|
drawPyramid();
|
|
drawTrees();
|
|
drawTime(date);
|
|
drawDate(date);
|
|
drawCharacter(date);
|
|
drawCoin();
|
|
|
|
// Render new frame
|
|
g.flip();
|
|
}
|
|
|
|
function clearTimers(){
|
|
if(intervalRef) {
|
|
clearInterval(intervalRef);
|
|
intervalRef = null;
|
|
}
|
|
|
|
if(displayTimeoutRef) {
|
|
clearInterval(displayTimeoutRef);
|
|
displayTimeoutRef = null;
|
|
}
|
|
}
|
|
|
|
function resetDisplayTimeout() {
|
|
if (displayTimeoutRef) clearInterval(displayTimeoutRef);
|
|
|
|
displayTimeoutRef = setInterval(() => {
|
|
if (Bangle.isLCDOn()) Bangle.setLCDPower(false);
|
|
clearTimers();
|
|
}, ONE_SECOND * timeout);
|
|
}
|
|
|
|
function startTimers(){
|
|
if(intervalRef) clearTimers();
|
|
intervalRef = setInterval(redraw, 50);
|
|
|
|
resetDisplayTimeout();
|
|
|
|
redraw();
|
|
}
|
|
|
|
// Main
|
|
function init() {
|
|
clearInterval();
|
|
|
|
// Initialise display
|
|
Bangle.setLCDMode("80x80");
|
|
|
|
// Store screen dimensions
|
|
W = g.getWidth();
|
|
H = g.getHeight();
|
|
|
|
// Get Mario to jump!
|
|
setWatch(() => {
|
|
if (intervalRef && !characterSprite.isJumping) characterSprite.isJumping = true;
|
|
resetDisplayTimeout();
|
|
}, BTN1, {repeat:true});
|
|
|
|
setWatch(() => {
|
|
Bangle.setLCDMode();
|
|
Bangle.showLauncher();
|
|
}, BTN2, {repeat:false,edge:"falling"});
|
|
|
|
Bangle.on('lcdPower', (on) => {
|
|
if (on) {
|
|
startTimers();
|
|
} else {
|
|
clearTimers();
|
|
}
|
|
});
|
|
|
|
Bangle.on('faceUp', (up) => {
|
|
if (up && !Bangle.isLCDOn()) {
|
|
clearTimers();
|
|
Bangle.setLCDPower(true);
|
|
}
|
|
});
|
|
|
|
Bangle.on('swipe', (sDir) => {
|
|
resetDisplayTimeout();
|
|
|
|
switch(sDir) {
|
|
// Swipe right (1) - change character (on a loop)
|
|
case(1):
|
|
switchCharacter();
|
|
break;
|
|
// Swipe left (-1) - change day/night mode (on a loop)
|
|
case(-1):
|
|
default:
|
|
toggleNightMode();
|
|
}
|
|
});
|
|
|
|
startTimers();
|
|
}
|
|
|
|
// Initialise!
|
|
init(); |