diff --git a/apps.json b/apps.json index f0bc4874a..32682282c 100644 --- a/apps.json +++ b/apps.json @@ -893,7 +893,7 @@ { "id": "marioclock", "name": "Mario Clock", "icon": "marioclock.png", - "version":"0.05", + "version":"0.06", "description": "Animated Mario clock, jumps to change the time!", "tags": "clock,mario,retro", "type": "clock", diff --git a/apps/marioclock/ChangeLog b/apps/marioclock/ChangeLog index 74db9bc18..dfad2d26a 100644 --- a/apps/marioclock/ChangeLog +++ b/apps/marioclock/ChangeLog @@ -3,3 +3,4 @@ 0.03: use short date format from locale, take timeout from settings 0.04: modify date to display to be more at the original idea but still localized 0.05: use 12/24 hour clock from settings +0.06: Performance refactor, and enhanced graphics! diff --git a/apps/marioclock/marioclock-app.js b/apps/marioclock/marioclock-app.js index 2eeb21c97..ed1d51cde 100644 --- a/apps/marioclock/marioclock-app.js +++ b/apps/marioclock/marioclock-app.js @@ -3,6 +3,7 @@ + Based on Espruino Mario Clock V3 https://github.com/paulcockrell/espruino-mario-clock + Converting images to 1bit BMP: Image > Mode > Indexed and tick the "Use black and white (1-bit) palette", Then export as BMP. + Online Image convertor: https://www.espruino.com/Image+Converter + + Images must be converted 1Bit White/Black !!! Not Black/White **********************************/ const locale = require("locale"); @@ -16,110 +17,32 @@ let W, H; let intervalRef, displayTimeoutRef = null; -// Space to draw watch widgets (e.g battery, bluetooth status) -const WIDGETS_GUTTER = 10; - // Colours const LIGHTEST = "#effedd"; const LIGHT = "#add795"; const DARK = "#588d77"; const DARKEST = "#122d3e"; -// Mario Images -const marioRunningImage1 = { - width : 15, height : 20, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("B8AfwH+B/8f/z4M+KExcnAUSCw87w4L8CJQRNB/YH+AxgCEAPAA=")) -}; - -const marioRunningImage1Neg = { - width : 15, height : 20, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("AAAAAAAAAAAAAHwB0DOgY/jt8PDAPAEAB2gOyAAgAAAOAB4AAAA=")) -}; - -const marioRunningImage2 = { - width : 15, height : 20, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("B8AfwH+B/8f/z4M+KExcnAUSCw87w4J6BEsPnSfyT+S+OMAAAAA=")) -}; - -const marioRunningImage2Neg = { - width : 15, height : 20, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("AAAAAAAAAAAAAHwB0DOgY/jt8PDAPAGEA7QAYhgMMBhAAAAAAAA=")) -}; - -const pyramid = { - width : 20, height : 20, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAkAAQgAIEAEAgCAEBAAggAEQAAoAAE=")) -}; - -const pipe = { - width : 9, height : 6, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("/8BxaCRSCA==")) -}; - -const floor = { - width : 8, height : 3, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("/6pE")) -}; - -const sky = { - width : 128, height : 30, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("VVVVVVVVVVVVVVVVVVVVVQAAAAAAAAAAAAAAAAAAAABVVVVVVVVVVVVVVVVVVVVVIiIiIiIiIiIiIiIiIiIiIlVVVVVVVVVVVVVVVVVVVVWIiIiIiIiIiIiIiIiIiIiIVVVVVVVVVVVVVVVVVVVVVSIiIiIiIiICIiIiIiIiIiJVVVVVVVVVAVVVVVVVVVVViIiIiIiIiACIiIiIiIiIiFVVVVVVVVQAVVVVVVVVVVUiIiIiIiIgACIiIiIiIiIiVVVVVVVVUAAVVVUBVVVVUKqqqqqqqggAKCqqAKqqqoBVUBVVVVQAABAVVABVVVUAIiACIiIgAAAgAiAAIiIiAFVABVVVUAAAUAVUABRVVQCqgAKCqqAAACAAAAAgCqoAVUABAVVAAAAAAAAAAAVQAKqAAACqoAAAAAAAAAACgABAAAAAUEAAAAAAAAAABQAAgAAAACAAAAAAAAAAAAIAAAAAAABQAAAAAAAAAAAEAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")) -}; - -const brick = { - width : 21, height : 15, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("f//0AABoAAsAABgAAMAABgAAMAABgAAMAABgAAMAABoAAsAABf//wA==")) -}; - -const flower = { - width : 7, height : 7, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("fY3wjW+OAA==")) -}; - -const pipePlant = { - width : 9, height : 15, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("FBsNhsHDWPn/gOLQSKQSKQQ=")) -}; - const marioSprite = { frameIdx: 0, - frames: [ - marioRunningImage1, - marioRunningImage2 - ], - negFrames: [ - marioRunningImage1Neg, - marioRunningImage2Neg - ], x: 35, y: 55, jumpCounter: 0, - jumpIncrement: Math.PI / 10, + jumpIncrement: Math.PI / 6, isJumping: false }; -const STATIC_TILES = { - "_": {img: floor, x: 16 * 8, y: 75}, - "X": {img: sky, x: 0, y: 10}, - "#": {img: brick, x: 0, y: 0}, +const coinSprite = { + frameIdx: 0, + x: 34, + y: 18, + isAnimating: false, + yDefault: 18, }; -const TILES = { - "T": {img: pipe, x: 16 * 8, y: 69}, - "^": {img: pyramid, x: 16 * 8, y: 55}, - "*": {img: flower, x: 16 * 8, y: 68}, - "V": {img: pipePlant, x: 16 * 8, y: 60} +const pyramidSprite = { + x: 90, + height: 34, }; const ONE_SECOND = 1000; @@ -136,95 +59,133 @@ function incrementTimer() { } } -function drawTile(sprite) { - g.drawImage(sprite.img, sprite.x, sprite.y); -} - function drawBackground() { + // Clear screen g.setColor(LIGHTEST); g.fillRect(0, 10, W, H); - // draw floor - g.setColor(DARK); - for (var x = 0; x < 16; x++) { - var floorSprite = Object.assign({}, STATIC_TILES._, {x: x * 8}); - drawTile(floorSprite); - } + // Date bar + g.setColor(DARKEST); + g.fillRect(0, 0, W, 9); // draw sky - var skySprite = STATIC_TILES.X; g.setColor(LIGHT); - drawTile(skySprite); + 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); } -function drawScenery() { - // new random sprite - const spriteKeys = Object.keys(TILES); - const key = spriteKeys[Math.floor(Math.random() * spriteKeys.length)]; - let newSprite = Object.assign({}, TILES[key]); +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 drawTrees() { + let newSprite = {x: 90, y: Math.floor(Math.random() * (40 /* max */ - 5 /* min */ + 1) + 15 /* min */)}; // remove first sprite if offscreen let firstBackgroundSprite = backgroundArr[0]; if (firstBackgroundSprite) { - if (firstBackgroundSprite.x < -20) backgroundArr.splice(0, 1); + if (firstBackgroundSprite.x < -15) backgroundArr.splice(0, 1); } // set background sprite if array empty - var lastBackgroundSprite = backgroundArr[backgroundArr.length - 1]; + let lastBackgroundSprite = backgroundArr[backgroundArr.length - 1]; if (!lastBackgroundSprite) { lastBackgroundSprite = newSprite; backgroundArr.push(lastBackgroundSprite); } // add random sprites - if (backgroundArr.length < 6 && lastBackgroundSprite.x < (16 * 7)) { - var randIdx = Math.floor(Math.random() * 25); - if (randIdx < spriteKeys.length - 1) { + if (backgroundArr.length < 2 && lastBackgroundSprite.x < (16 * 7)) { + const randIdx = Math.floor(Math.random() * 25); + if (randIdx < 2) { backgroundArr.push(newSprite); } } for (x = 0; x < backgroundArr.length; x++) { let scenerySprite = backgroundArr[x]; - - // clear sprite at previous position - g.setColor(LIGHTEST); - drawTile(scenerySprite); - - // draw sprite in new position - g.setColor(LIGHT); scenerySprite.x -= 5; - drawTile(scenerySprite); + drawTreesFrame(scenerySprite.x, scenerySprite.y); } } -function drawMario() { - // clear old mario frame - g.setColor(LIGHTEST); - g.drawImage( - marioSprite.negFrames[marioSprite.frameIdx], - marioSprite.x, - marioSprite.y - ); - g.drawImage( - marioSprite.frames[marioSprite.frameIdx], - marioSprite.x, - marioSprite.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) { + const mFr1 = require("heatshrink").decompress(atob("h8UxH+AAkHAAYKFBolcAAIPIBgYPDBpgfGFIY7EA4YcEBIPWAAYdDC4gLDAII5ECoYOFDogODFgoJCBwYZCAQYOFBAhAFFwZKGHQpMDw52FSg2HAAIoDAgIOMB5AAFGQTtKeBLuNcQwOJFwgJFA=")); // Mario Frame 1 + const mFr2 = require("heatshrink").decompress(atob("h8UxH+AAkHAAYKFBolcAAIPIBgYPDBpgfGFIY7EA4YcEBIPWAAYdDC4gLDAII5ECoYOFDogODFgoJCBwYZCAQYOFBAhAFFwZKGHQpMDw+HCQYEBSowOBBQIdCCgTOIFgiVHFwYCBUhA9FBwz8HAo73GACQA=")); // Mario frame 2 + + switch(idx) { + case 0: + g.drawImage(mFr1, x, y); + break; + case 1: + g.drawImage(mFr2, x, y); + break; + default: + } +} + +function drawMario(date) { // calculate jumping - const t = new Date(), - seconds = t.getSeconds(), - milliseconds = t.getMilliseconds(); + const seconds = date.getSeconds(), + milliseconds = date.getMilliseconds(); if (seconds == 59 && milliseconds > 800 && !marioSprite.isJumping) { marioSprite.isJumping = true; } if (marioSprite.isJumping) { - marioSprite.y = (Math.sin(marioSprite.jumpCounter) * -10) + 50 /* Mario Y base value */; + marioSprite.y = (Math.sin(marioSprite.jumpCounter) * -12) + 50 /* Mario Y base value */; marioSprite.jumpCounter += marioSprite.jumpIncrement; + if (parseInt(marioSprite.jumpCounter) === 2 && !coinSprite.isAnimating) { + coinSprite.isAnimating = true; + } + if (marioSprite.jumpCounter.toFixed(1) >= 4) { marioSprite.jumpCounter = 0; marioSprite.isJumping = false; @@ -232,51 +193,28 @@ function drawMario() { } // calculate animation timing - if (timer % 100 === 0) { + if (timer % 50 === 0) { // shift to next frame marioSprite.frameIdx ^= 1; } - // colour in mario - g.setColor(LIGHT); - g.drawImage( - marioSprite.negFrames[marioSprite.frameIdx], - marioSprite.x, - marioSprite.y - ); - - // draw mario - g.setColor(DARKEST); - g.drawImage( - marioSprite.frames[marioSprite.frameIdx], - marioSprite.x, - marioSprite.y - ); + drawMarioFrame(marioSprite.frameIdx, marioSprite.x, marioSprite.y); } - -function drawBrick(x, y) { - const brickSprite = Object.assign({}, STATIC_TILES['#'], {x: x, y: y}); - - // draw brick background colour - g.setColor(LIGHT); - g.fillRect(x, y, x + 20, y+14); - - // draw brick sprite - g.setColor(DARK); - drawTile(brickSprite); +function drawBrickFrame(x, y) { + const brk = require("heatshrink").decompress(atob("ikQxH+/0HACASB6wAQCoPWw4AOrgT/Cf4T/Cb1cAB8H/wVBAB/+A")); + g.drawImage(brk, x, y); } -function drawTime() { +function drawTime(date) { // draw hour brick - drawBrick(20, 25); + drawBrickFrame(20, 25); // draw minute brick - drawBrick(42, 25); + drawBrickFrame(42, 25); - const t = new Date(); - const h = t.getHours(); + const h = date.getHours(); const hours = ("0" + ((is12Hour && h > 12) ? h - 12 : h)).substr(-2); - const mins = ("0" + t.getMinutes()).substr(-2); + const mins = ("0" + date.getMinutes()).substr(-2); g.setFont("6x8"); g.setColor(DARKEST); @@ -284,25 +222,30 @@ function drawTime() { g.drawString(mins, 47, 29); } -function drawDate() { +function drawDate(date) { g.setFont("6x8"); g.setColor(LIGHTEST); - let d = new Date(); - let dateStr = locale.date(d, true); - dateStr = dateStr.replace(d.getFullYear(), "").trim().replace(/\/$/i,""); - dateStr = locale.dow(d, true) + " " + dateStr; - g.drawString(dateStr, (W - g.stringWidth(dateStr))/2, 0, true); + 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, 0); } function redraw() { + const date = new Date(); + // Update timers incrementTimer(); // Draw frame - drawScenery(); - drawTime(); - drawDate(); - drawMario(); + drawBackground(); + drawFloor(); + drawPyramid(); + drawTrees(); + drawTime(date); + drawDate(date); + drawMario(date); + drawCoin(); // Render new frame g.flip(); @@ -335,7 +278,6 @@ function startTimers(){ resetDisplayTimeout(); - drawBackground(); redraw(); } @@ -345,7 +287,6 @@ function init() { // Initialise display Bangle.setLCDMode("80x80"); - g.clear(); // Store screen dimensions W = g.getWidth(); @@ -381,4 +322,4 @@ function init() { } // Initialise! -init(); +init(); \ No newline at end of file