diff --git a/apps.json b/apps.json index 50a628676..931cfc2e5 100644 --- a/apps.json +++ b/apps.json @@ -2591,5 +2591,18 @@ {"name":"astral.app.js","url":"app.js"}, {"name":"astral.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "lifeclk", + "name": "Game of Life Clock", + "shortName":"Conway's Clock", + "icon": "app.png", + "version":"0.05", + "description": "Modification and clockification of Conway's Game of Life", + "tags": "clock", + "type" : "clock", + "storage": [ + {"name":"lifeclk.app.js","url":"app.js"}, + {"name":"lifeclk.img","url":"app-icon.js","evaluate":true} + ] } ] diff --git a/apps/lifeclk/ChangeLog b/apps/lifeclk/ChangeLog new file mode 100644 index 000000000..dfd8b8775 --- /dev/null +++ b/apps/lifeclk/ChangeLog @@ -0,0 +1,5 @@ +0.01: New App! 2021-01-07 +0.02: Faster algorithm, hours and minutes are now displayable whenever, using the upper button 2021-01-14 +0.03: Ah yes. Some people prefer the 12 hour system 2021-01-14 +0.04: Fixed a bug, doesn't run while display's on now 2021-01-18 +0.05: Fixed a bug, doesn't count the time it was asleep when calculating the update time 2021-01-19 \ No newline at end of file diff --git a/apps/lifeclk/app-icon.js b/apps/lifeclk/app-icon.js new file mode 100644 index 000000000..8127abd79 --- /dev/null +++ b/apps/lifeclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AFkLC6+wC6u73YuVC7JIUCwIZBLywwTIwYwTC4QwUC4OwGCgXCCoQwRXwYwTO4wwQO4wwQO44wPO44wPO5G7olACY9EAAVLO44LCCosECwYXDGAm0BQIvFCwoABCwIcCCoQuHCwwXEAAguFBQgFBoEEC451GIwQVDAgIXBIhQwECoYEBYAS5NCogEBC6BGFVAQXPGAoXRGAoXSO6owGC6Z3VGAoXUd4gWRGAYXVIwQXUIwReSC7gWUgELFyoXBCyoA/ACwA==")) \ No newline at end of file diff --git a/apps/lifeclk/app.js b/apps/lifeclk/app.js new file mode 100644 index 000000000..ec30b6973 --- /dev/null +++ b/apps/lifeclk/app.js @@ -0,0 +1,473 @@ +Bangle.setLCDTimeout(30); + +const is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; + +const squaresize = 5; +const padding = 2; +const bigSquareSize = squaresize+padding; +const noOfSquX = 27; +const noOfSquY = 23; +const screenWidth = noOfSquX * bigSquareSize - padding; +const screenHeight = noOfSquY * bigSquareSize - padding; +const ulCornerX = parseInt((220-screenWidth)/2); +const ulCornerY = parseInt(40 + (160-screenHeight)/2); +const textWidth = 27; +const textHeight = 7; +const sectextWidth = 9; +const textWidthScreen = textWidth*bigSquareSize; +const textHeightScreen = textHeight*bigSquareSize; +const textWidthScreenMin = (textWidth-sectextWidth)*bigSquareSize; +const textWidthScreenSec = sectextWidth*bigSquareSize; +const ulCornerTextX = parseInt((noOfSquX-textWidth)/2); +const ulCornerTextY = parseInt((noOfSquY-textHeight)/2); +const ulCornerTextXScreen = ulCornerX+parseInt((noOfSquX-textWidth) + *bigSquareSize/2); +const ulCornerTextYScreen = ulCornerY+parseInt((noOfSquY-textHeight) + *bigSquareSize/2); +const seculCornerTextX = ulCornerTextX+textWidth-sectextWidth; + +let buf = Graphics.createArrayBuffer(screenWidth,screenHeight,1,{msb:true}); +let textbufMin = Graphics.createArrayBuffer((textWidth-sectextWidth)*bigSquareSize, + textHeight*bigSquareSize, + 1,{msb:true}); +let textPreBufMin = Graphics.createArrayBuffer(textWidth-sectextWidth, + textHeight, + 8,{msb:true}); +let textbufSec = Graphics.createArrayBuffer(sectextWidth*bigSquareSize, + textHeight*bigSquareSize, + 1,{msb:true}); +let textPreBufSec = Graphics.createArrayBuffer(sectextWidth, + textHeight, + 8,{msb:true}); + +let genNow = new Uint8Array((noOfSquX+2)*(noOfSquY+2)); +let genFut = new Uint8Array((noOfSquX+2)*(noOfSquY+2)); +let generation=0; +const minuteGens = 10; +let genEnd = minuteGens; +let lastupdatetime=null; + +function fillLinesFirstFewGens(){ + let fade = (genEnd-generation)/minuteGens; + g.setColor(fade,fade,fade); + // vertical lines + g.fillRect(ulCornerTextXScreen, + ulCornerTextYScreen, + ulCornerTextXScreen, + ulCornerTextYScreen+textHeightScreen-padding); + // horizontal lines + g.fillRect(ulCornerTextXScreen, + ulCornerTextYScreen, + ulCornerTextXScreen+textWidthScreenMin, + ulCornerTextYScreen); + g.fillRect(ulCornerTextXScreen, + ulCornerTextYScreen+textHeightScreen-padding, + ulCornerTextXScreen+textWidthScreenMin, + ulCornerTextYScreen+textHeightScreen-padding); + g.setColor(1,1,1); +} + +function clearLinesFirstFewGens(){ + // vertical lines + g.clearRect(ulCornerTextXScreen, + ulCornerTextYScreen, + ulCornerTextXScreen, + ulCornerTextYScreen+textHeightScreen-padding); + // horizontal lines + g.clearRect(ulCornerTextXScreen, + ulCornerTextYScreen, + ulCornerTextXScreen+textWidthScreenMin, + ulCornerTextYScreen); + g.clearRect(ulCornerTextXScreen, + ulCornerTextYScreen+textHeightScreen-padding, + ulCornerTextXScreen+textWidthScreenMin, + ulCornerTextYScreen+textHeightScreen-padding); +} + +function fillLinesLaterGens(){ + g.setColor(1,1,1); + // horizontal lines + g.fillRect(ulCornerTextXScreen+textWidthScreenMin, + ulCornerTextYScreen, + ulCornerTextXScreen+textWidthScreenMin+textWidthScreenSec, + ulCornerTextYScreen); + g.fillRect(ulCornerTextXScreen+textWidthScreenMin, + ulCornerTextYScreen+textHeightScreen-padding, + ulCornerTextXScreen+textWidthScreenMin+textWidthScreenSec, + ulCornerTextYScreen+textHeightScreen-padding); + // vertical lines + g.fillRect(ulCornerTextXScreen+textWidthScreenMin, + ulCornerTextYScreen, + ulCornerTextXScreen+textWidthScreenMin, + ulCornerTextYScreen+textHeightScreen-padding); + g.fillRect(ulCornerTextXScreen+textWidthScreenMin+textWidthScreenSec, + ulCornerTextYScreen, + ulCornerTextXScreen+textWidthScreenMin+textWidthScreenSec, + ulCornerTextYScreen+textHeightScreen-padding); +} + +function renderSecText(){ + g.clearRect(ulCornerTextXScreen+18*bigSquareSize+padding, + ulCornerTextYScreen, + ulCornerTextXScreen+textWidthScreen, + ulCornerTextYScreen+textHeightScreen-1); + fillLinesLaterGens(); + g.drawImage({width:textWidthScreenSec, + height:textHeightScreen, + bpp:1,transparent:0,buffer:textbufSec.buffer}, + ulCornerTextXScreen+textWidthScreenMin,ulCornerTextYScreen); +} + +function flip(dontclear) { + g.setColor(1,1,1); + g.drawImage({width:screenWidth,height:screenHeight,bpp:1,buffer:buf.buffer}, + ulCornerX,ulCornerY); + if(generation=seculCornerTextX && x=ulCornerTextY && y=ulCornerTextX && x=ulCornerTextY && y4 && genNow[ind]<8){ + genFut[ind]+=1; + addNeighbors(ind); + let Xr=bigSquareSize*(x-1); + let Yr=bigSquareSize*(y-1); + buf.fillRect(Xr,Yr, Xr+squaresize,Yr+squaresize); + } + } + if(y == noOfSquY){ + if(generation == genEnd-1){writeMinuteText();} + flip(); + let tmp = genFut; genFut = genNow; genNow = tmp; // reference swapping + genFut.fill(0); + howlong(); + currentY = 1; + } + else{ + currentY++; + } + if(runOn){setTimeout(nextLineComp, 50);} +} + +function stopdraw() { + runOn = false; +} + +function startdraw(init) { + if (init===undefined) init=false; + Bangle.drawWidgets(); + g.reset(); + g.setColor(1,1,1); + g.setFont("6x8",1); + g.setFontAlign(0,0,3); + g.drawString("RESET",230,200); + g.drawString("LAUNCH",230,130); + g.drawString("CLOCK",230,60); + if(!init){ + updateSecondText(); + // stopdraw(); + runOn = true; + nextLineComp(); + } +} + +function regen(){ + g.setColor(1,1,1); + generation = 0; + genEnd = minuteGens; + currentY=1; + g.setFont("6x8",2); + g.setFontAlign(-1,-1,0); + g.clearRect(20,220,220,240); + g.drawString('Gen:'+generation+' 0ms ',20,220,true); + lastupdatetime=Date.now(); + updateSecondText(); + genNow.fill(0); + genFut.fill(0); + initDraw(); + // stopdraw(); + runOn = true; + nextLineComp(); +} + +function showMinAgain(){ + if(genEnd != generation+minuteGens){ + genEnd = generation+minuteGens; + lastDate = null; + } +} + +function setButtons(){ + setWatch(showMinAgain, BTN1, {repeat:true,edge:"falling"}); + setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); + setWatch(regen, BTN3, {repeat:true,edge:"falling"}); +} + +let wentToSleepAt; + +var SCREENACCESS = { + withApp:true, + request:function(){ + this.withApp=false; + stopdraw(); + wentToSleepAt = Date.now(); + }, + release:function(){ + this.withApp=true; + sleeptime = Date.now()-wentToSleepAt; + startdraw(); + } +}; + +Bangle.on('lcdPower',function(on) { + if (!SCREENACCESS.withApp) return; + if (on) { + startdraw(); + sleeptime = Date.now()-wentToSleepAt; + } else { + stopdraw(); + wentToSleepAt = Date.now(); + } +}); + +g.clear(); +Bangle.loadWidgets(); +setButtons(); +regen(); +startdraw(true); diff --git a/apps/lifeclk/app.png b/apps/lifeclk/app.png new file mode 100644 index 000000000..a88157f5c Binary files /dev/null and b/apps/lifeclk/app.png differ