Enhance Mario clock performance and graphics

master
Paul Cockrell 2020-04-04 11:03:34 +01:00
parent 28c8437cce
commit 16ea9a8167
3 changed files with 124 additions and 182 deletions

View File

@ -893,7 +893,7 @@
{ "id": "marioclock", { "id": "marioclock",
"name": "Mario Clock", "name": "Mario Clock",
"icon": "marioclock.png", "icon": "marioclock.png",
"version":"0.05", "version":"0.06",
"description": "Animated Mario clock, jumps to change the time!", "description": "Animated Mario clock, jumps to change the time!",
"tags": "clock,mario,retro", "tags": "clock,mario,retro",
"type": "clock", "type": "clock",

View File

@ -3,3 +3,4 @@
0.03: use short date format from locale, take timeout from settings 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.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.05: use 12/24 hour clock from settings
0.06: Performance refactor, and enhanced graphics!

View File

@ -3,6 +3,7 @@
+ Based on Espruino Mario Clock V3 https://github.com/paulcockrell/espruino-mario-clock + 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. + 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 + Online Image convertor: https://www.espruino.com/Image+Converter
+ Images must be converted 1Bit White/Black !!! Not Black/White
**********************************/ **********************************/
const locale = require("locale"); const locale = require("locale");
@ -16,110 +17,32 @@ let W, H;
let intervalRef, displayTimeoutRef = null; let intervalRef, displayTimeoutRef = null;
// Space to draw watch widgets (e.g battery, bluetooth status)
const WIDGETS_GUTTER = 10;
// Colours // Colours
const LIGHTEST = "#effedd"; const LIGHTEST = "#effedd";
const LIGHT = "#add795"; const LIGHT = "#add795";
const DARK = "#588d77"; const DARK = "#588d77";
const DARKEST = "#122d3e"; 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 = { const marioSprite = {
frameIdx: 0, frameIdx: 0,
frames: [
marioRunningImage1,
marioRunningImage2
],
negFrames: [
marioRunningImage1Neg,
marioRunningImage2Neg
],
x: 35, x: 35,
y: 55, y: 55,
jumpCounter: 0, jumpCounter: 0,
jumpIncrement: Math.PI / 10, jumpIncrement: Math.PI / 6,
isJumping: false isJumping: false
}; };
const STATIC_TILES = { const coinSprite = {
"_": {img: floor, x: 16 * 8, y: 75}, frameIdx: 0,
"X": {img: sky, x: 0, y: 10}, x: 34,
"#": {img: brick, x: 0, y: 0}, y: 18,
isAnimating: false,
yDefault: 18,
}; };
const TILES = { const pyramidSprite = {
"T": {img: pipe, x: 16 * 8, y: 69}, x: 90,
"^": {img: pyramid, x: 16 * 8, y: 55}, height: 34,
"*": {img: flower, x: 16 * 8, y: 68},
"V": {img: pipePlant, x: 16 * 8, y: 60}
}; };
const ONE_SECOND = 1000; const ONE_SECOND = 1000;
@ -136,95 +59,133 @@ function incrementTimer() {
} }
} }
function drawTile(sprite) {
g.drawImage(sprite.img, sprite.x, sprite.y);
}
function drawBackground() { function drawBackground() {
// Clear screen
g.setColor(LIGHTEST); g.setColor(LIGHTEST);
g.fillRect(0, 10, W, H); g.fillRect(0, 10, W, H);
// draw floor // Date bar
g.setColor(DARK); g.setColor(DARKEST);
for (var x = 0; x < 16; x++) { g.fillRect(0, 0, W, 9);
var floorSprite = Object.assign({}, STATIC_TILES._, {x: x * 8});
drawTile(floorSprite);
}
// draw sky // draw sky
var skySprite = STATIC_TILES.X;
g.setColor(LIGHT); 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() { function drawFloor() {
// new random sprite const fImg = require("heatshrink").decompress(atob("ikDxH+rgATCoIBQAQYDP")); // Floor image
const spriteKeys = Object.keys(TILES); for (let x = 0; x < 4; x++) {
const key = spriteKeys[Math.floor(Math.random() * spriteKeys.length)]; g.drawImage(fImg, x * 20, g.getHeight() - 5);
let newSprite = Object.assign({}, TILES[key]); }
}
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 // remove first sprite if offscreen
let firstBackgroundSprite = backgroundArr[0]; let firstBackgroundSprite = backgroundArr[0];
if (firstBackgroundSprite) { if (firstBackgroundSprite) {
if (firstBackgroundSprite.x < -20) backgroundArr.splice(0, 1); if (firstBackgroundSprite.x < -15) backgroundArr.splice(0, 1);
} }
// set background sprite if array empty // set background sprite if array empty
var lastBackgroundSprite = backgroundArr[backgroundArr.length - 1]; let lastBackgroundSprite = backgroundArr[backgroundArr.length - 1];
if (!lastBackgroundSprite) { if (!lastBackgroundSprite) {
lastBackgroundSprite = newSprite; lastBackgroundSprite = newSprite;
backgroundArr.push(lastBackgroundSprite); backgroundArr.push(lastBackgroundSprite);
} }
// add random sprites // add random sprites
if (backgroundArr.length < 6 && lastBackgroundSprite.x < (16 * 7)) { if (backgroundArr.length < 2 && lastBackgroundSprite.x < (16 * 7)) {
var randIdx = Math.floor(Math.random() * 25); const randIdx = Math.floor(Math.random() * 25);
if (randIdx < spriteKeys.length - 1) { if (randIdx < 2) {
backgroundArr.push(newSprite); backgroundArr.push(newSprite);
} }
} }
for (x = 0; x < backgroundArr.length; x++) { for (x = 0; x < backgroundArr.length; x++) {
let scenerySprite = backgroundArr[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; scenerySprite.x -= 5;
drawTile(scenerySprite); drawTreesFrame(scenerySprite.x, scenerySprite.y);
} }
} }
function drawMario() { function drawCoinFrame(x, y) {
// clear old mario frame const cImg = require("heatshrink").decompress(atob("hkPxH+AAcHAAQIEBIXWAAQNEBIWHAAdcBgQLBA4IODBYQKEBAQMDBelcBaJUBM4QRBNYx1EBQILDR4QHBBISdIBIoA==")); // Coin image
g.setColor(LIGHTEST); g.drawImage(cImg, x, y);
g.drawImage( }
marioSprite.negFrames[marioSprite.frameIdx],
marioSprite.x,
marioSprite.y
);
g.drawImage(
marioSprite.frames[marioSprite.frameIdx],
marioSprite.x,
marioSprite.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 // calculate jumping
const t = new Date(), const seconds = date.getSeconds(),
seconds = t.getSeconds(), milliseconds = date.getMilliseconds();
milliseconds = t.getMilliseconds();
if (seconds == 59 && milliseconds > 800 && !marioSprite.isJumping) { if (seconds == 59 && milliseconds > 800 && !marioSprite.isJumping) {
marioSprite.isJumping = true; marioSprite.isJumping = true;
} }
if (marioSprite.isJumping) { 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; marioSprite.jumpCounter += marioSprite.jumpIncrement;
if (parseInt(marioSprite.jumpCounter) === 2 && !coinSprite.isAnimating) {
coinSprite.isAnimating = true;
}
if (marioSprite.jumpCounter.toFixed(1) >= 4) { if (marioSprite.jumpCounter.toFixed(1) >= 4) {
marioSprite.jumpCounter = 0; marioSprite.jumpCounter = 0;
marioSprite.isJumping = false; marioSprite.isJumping = false;
@ -232,51 +193,28 @@ function drawMario() {
} }
// calculate animation timing // calculate animation timing
if (timer % 100 === 0) { if (timer % 50 === 0) {
// shift to next frame // shift to next frame
marioSprite.frameIdx ^= 1; marioSprite.frameIdx ^= 1;
} }
// colour in mario drawMarioFrame(marioSprite.frameIdx, marioSprite.x, marioSprite.y);
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
);
} }
function drawBrickFrame(x, y) {
function drawBrick(x, y) { const brk = require("heatshrink").decompress(atob("ikQxH+/0HACASB6wAQCoPWw4AOrgT/Cf4T/Cb1cAB8H/wVBAB/+A"));
const brickSprite = Object.assign({}, STATIC_TILES['#'], {x: x, y: y}); g.drawImage(brk, x, 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 drawTime() { function drawTime(date) {
// draw hour brick // draw hour brick
drawBrick(20, 25); drawBrickFrame(20, 25);
// draw minute brick // draw minute brick
drawBrick(42, 25); drawBrickFrame(42, 25);
const t = new Date(); const h = date.getHours();
const h = t.getHours();
const hours = ("0" + ((is12Hour && h > 12) ? h - 12 : h)).substr(-2); 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.setFont("6x8");
g.setColor(DARKEST); g.setColor(DARKEST);
@ -284,25 +222,30 @@ function drawTime() {
g.drawString(mins, 47, 29); g.drawString(mins, 47, 29);
} }
function drawDate() { function drawDate(date) {
g.setFont("6x8"); g.setFont("6x8");
g.setColor(LIGHTEST); g.setColor(LIGHTEST);
let d = new Date(); let dateStr = locale.date(date, true);
let dateStr = locale.date(d, true); dateStr = dateStr.replace(date.getFullYear(), "").trim().replace(/\/$/i,"");
dateStr = dateStr.replace(d.getFullYear(), "").trim().replace(/\/$/i,""); dateStr = locale.dow(date, true) + " " + dateStr;
dateStr = locale.dow(d, true) + " " + dateStr; g.drawString(dateStr, (W - g.stringWidth(dateStr))/2, 0);
g.drawString(dateStr, (W - g.stringWidth(dateStr))/2, 0, true);
} }
function redraw() { function redraw() {
const date = new Date();
// Update timers // Update timers
incrementTimer(); incrementTimer();
// Draw frame // Draw frame
drawScenery(); drawBackground();
drawTime(); drawFloor();
drawDate(); drawPyramid();
drawMario(); drawTrees();
drawTime(date);
drawDate(date);
drawMario(date);
drawCoin();
// Render new frame // Render new frame
g.flip(); g.flip();
@ -335,7 +278,6 @@ function startTimers(){
resetDisplayTimeout(); resetDisplayTimeout();
drawBackground();
redraw(); redraw();
} }
@ -345,7 +287,6 @@ function init() {
// Initialise display // Initialise display
Bangle.setLCDMode("80x80"); Bangle.setLCDMode("80x80");
g.clear();
// Store screen dimensions // Store screen dimensions
W = g.getWidth(); W = g.getWidth();