diff --git a/apps.json b/apps.json index 2b28509cf..08677cd59 100644 --- a/apps.json +++ b/apps.json @@ -3410,5 +3410,33 @@ {"name":"thermomF.app.js","url":"app.js"}, {"name":"thermomF.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "nixie", + "name": "Nixie Clock", + "shortName":"Nixie", + "icon": "nixie.png", + "version":"0.01", + "description": "A nixie tube clock for both Bangle 1 and 2.", + "tags": "clock", + "type":"clock", + "readme": "README.md", + "storage": [ + {"name":"nixie.app.js","url":"app.js"}, + {"name":"nixie.img","url":"app-icon.js","evaluate":true}, + {"name":"m_vatch.js","url":"m_vatch.js"} + ] +}, +{ "id": "carcrazy", + "name": "Car Crazy", + "shortName":"Car Crazy", + "icon": "carcrash.png", + "version":"0.01", + "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.", + "tags": "game", + "readme": "README.md", + "storage": [ + {"name":"carcrazy.app.js","url":"app.js"}, + {"name":"carcrazy.img","url":"app-icon.js","evaluate":true} + ] } ] diff --git a/apps/carcrazy/ChangeLog b/apps/carcrazy/ChangeLog new file mode 100644 index 000000000..9a50abf42 --- /dev/null +++ b/apps/carcrazy/ChangeLog @@ -0,0 +1 @@ +0.01: Car Crazy is now avialable for testing in beta! diff --git a/apps/carcrazy/README.md b/apps/carcrazy/README.md new file mode 100644 index 000000000..25fa23b25 --- /dev/null +++ b/apps/carcrazy/README.md @@ -0,0 +1,18 @@ +# Car Crazy +Car crazy is a fun game where you tilt your wrist left and right to avoid incoming cars. If you get hit by a car you lose a heart. In the game you have three hearts, if you get hit 3 times you are sent to the game over screen. Your goal is to try to last as long as you can. Because this game is still in beta please report any bugs here: https://forms.office.com/r/HnwYzG9Sk7. + +### Images: +(Coming Soon) + +### Instructions: + +BNT2: Hold down this button to start the game if you are on the starting page and game over page. + +Tilting Left-Right: Tilt your wrist left and right to steer your car and try not to get hit by the enemy car. + +### Feautures Coming Soon: +0.02: Levels are creating making the game get harder as it goes along. + +0.03: Optional soundtrack in settings. More levels. + +0.04: With higher scores you can now unlock different colors of cars. diff --git a/apps/carcrazy/app-icon.js b/apps/carcrazy/app-icon.js new file mode 100644 index 000000000..b3f122ea8 --- /dev/null +++ b/apps/carcrazy/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4Ay90A8AWT8HuhwXSCQPuAAIXShwWCC6YuDAAQwWGKQWWFwoJDxAAFF5gHChAXODAhHCxGDmYAE3YAC2AwKxAWFC4gABC4YBCIwYXGoNb2tUpYxDRYsIIw0zqoXBqtVDAIXVDAJiEUYYXNqhhDUYgWGC41VSYgXSjYXHAA4XGJAULDQQXQJALFDGBTXFAAuwgQXV3cP+coC6m////nAXYiIAEC5m4FIYXN2gKBpYXFwIXMkIKBiQXSFwQwCC4hIFC42xiMjmURlYXFDAgXImcziMbC4ynKC4uxC5MAAAsBI4cQAoMRC50AKYYdCDYIQHGBASCC6QwCDgwA/AH4AVA==")) diff --git a/apps/carcrazy/app.js b/apps/carcrazy/app.js new file mode 100644 index 000000000..c7476d18a --- /dev/null +++ b/apps/carcrazy/app.js @@ -0,0 +1,240 @@ +Bangle.setLCDPower(1); +Bangle.setLCDTimeout(0); +var numberofHearts = 3; + +Bangle.setLCDMode("doublebuffered"); + + +//var popUp = require("heatshrink").decompress(atob("isFwMBCJoA==")); + +var backgroundImage = require("heatshrink").decompress(atob("isZxH+woAB6YBBBodXAgYLCCIQUJAgoUOFP4p/FP4p/FP4p/FP4pYA==")); + +var heartImage = require("heatshrink").decompress(atob("hUKxH+ABckAAYIDpFstlIudzpARCtmQyGXz2luYVBkgIBBQXCy4JCCYQKCtgpCE4IJCFQIyFBAwKDBAw/CAogA==")); + +var gameOverImage = require("heatshrink").decompress(atob("kcV4MB+Nj/4AJwATBgfwDAV+AYUP4ADBgP4BAU/AYUHERMP+AiBgP+EQU/4AiBg/4EQX/EQUf8EH8ED/kAAIN/wEfEQ9/FwX+A==")); + +var RedCar = +require("heatshrink").decompress(atob("ol7xH+ABWBqwAqwI5LABKAtIf4AClZASq0rp9zudIDYNIABgSBABd5AAtPEoNPAoUrqxDPlcAkmXy9OIYVsABYSBABWdzwAFIYVzAoMkRSJDCNAJDBkhDiuckFQOevMqklPZBo7DZAUkHagAKIoyKCkl5IwIwBwJDLYYRDrWoMAAgJDCqxDZyxDXzoABIcpAXRRRD/If5D/If5D/If5D9uYASId9OBoIAPpxDvBoRD2AAlOADlPAAckIbJ0QABjeNIf4ACkhDUIgNIUQQEBAAtyIaOXvIAFp4lBp4FBIapFCA4wACyxCRzo2BAAlzEoNzA4ZDVQ4ZDGQyRDHQ4RDgHyQAHIf5DhklIHoRDrudzA4RDNIoRDtFYRD/Iac5rsykgABaANyILQACzudIgTGClcrrowBIZ9ktdkRQhCdRQ8rGAxD/If5D/If4ACzpD/Io5D/IbOI2eIIdowBwJDP1nX1hDtGAJD/Ialkx7eCZdgwDIZ1rIdwwEIf5D/If5DZmU5mRDtGAhDNAAhDrAAhD/IaM5rrLmzpCCIYYwEIZtktdkIcpBCIYgwEIf5DZAARD/If8ylcrpFIIcd5uYpBmRDUAAUyA4JECIb9zEoNdFoZDVnJDkp5DZx+z2YHCIbudAAJDFFYOPIanX65DgIAIACIYWBFYJD/IYSHBx5DQ1idCx5DFAAjITIIjLFFYIvBwJDQLINrtc5IZOWIbq1DIaJXBslkmUHg9IAA1yRCV5AAtPg8rnIrBQ6VkbwcyDQMrAoIAEpxCRpwaGmSwBmUAg4wEIZ1rIYYdBg5DZpBDLGAt6II+BwJ8CIY85mQAFp9OACFPDQ1dIZMrHYJCFLghDHAA4lCAB85DpgwEAARD9rpDHq0rp9zAAcrmTQCIZVkroAQGoNr2YABIY4wCHAlPldWBgMky4ADkhTDIZQAV2fXIZA4JlYABQ4pDCg8HIcePAAJDCFYJDCYAoABZoOBdw0HoFAVoQAdIASKFslkIgSMEIAIACIZBBgIonXZwpDHgBDE/1Wq0rIe8rHYJDFIoRD3IJBD6vRD1AASH/AA5DWoFkAARD9IgIADoA+hrooEGQhDQAAk5IcM5FpJDNkgAGnNAaQjTTC4gACmUklQAEkhDPpFsAAoYCR68yDQ1PzwAFvJD/IbNyuVOAA1WnIAQp4aGuedzpDauWXABFIOgwAKpwdJIghDgpxD/AAVzACRDvADpD/If5D/If5D/If5D/If5D/If5D/If5DGthD/AApD/If8kkhFGuVyIMJCDp4ABIZ5FCRIyKgQgmekgzDIf5DRp1zpzOIZ4I+dZAUqldzvLLQueXuaKiIQ2elYrBAgN5IaSHBRAIADIcOdp8klSSBOgRDNHIQiBpzjDRjDIEvMkEYdzBAIPBIbUAIboiEIaWBq1WYYVyy9yZwTQGAAVOp9OABlzAAdPAAlzzuduYeBGoJCJAAcrYYZFBAAReCADUkRgeezomBaYMrIJpD/Z496YoRFEUoIAaYoQgBEIMkp+BwJCQAARkCRQKJDAD2WEoKEBgBBTZw1zIcNyIYbIRAA1WRQQABEQIAbEAQlBY6hDIAARCcpAiEIbWBlYADpAAcEQhCZAAyrCADI+hAAhCbIc+BqwAawIwS")); + +var OrangeCar = require("heatshrink").decompress(atob("ol7xH+ABXGAFfHHJYAJQFpD/Ia1OgEkpwDBAAUqABYSBABd4AAskEgQFBAgOcQiQXBlQdCGhdyGowAFznGAAnHNQVO4/GRSQSCGIJDPIRhDH4xDD0SHCkhAMCAQ7BIAQbBHaYAKIoyEDKII1C45DJLIRDw0XHIYXGIa5BYZwWiIZKHDIeZFCIf5D/If5D/If5D/If4ADBQgAMIeEkBoQAOlRDvISJDnAAkklQAWDAQbGIcB0JABreNIf5DYIgJFEW4xCRJZYeBznGIagABIQYHDAAQ7SGwQAD44mCp3HBARD64xDDA4ZDbYzRDdPghDs45DQgEkHoRDrE4MkIaCKDIdYyEIf5DRzjeDaAZBaAAOcAAI9B0RmBlQqB0QwDIZvP5/ORQhCcRRAqCGAhD1Q4RD/If5D/ABOiIf6KHIf5DZ63X55DtGAhDN6/X6xDtGAPQIf5DT5/WZdwwB5xDQ54SCIdgwDIf5D/If5DZvHGAgRDrGAhDNAApDqAAhD/IaOc494Icui0RCBIYeiGAZDN5/P5xDkQgRDFGAhD/IbIABuRD/AAZD658qkkAlUqIcdyuUkkkqIaoABvAHBIgRDfFoXGFoZD+45DW6HX6+cIb+czmiIYnOFYJDU6wXB4xDfIAIACIYvQIf5DVCgPX63WZYoAEZCZBEIYnHFYLNCIaSKBvBDoFYKJCIafOIYUklQAFZKecAAsqEoOi5xDCQ6BYBCQVO5zjCAApESkgaGpwqBIwQEBFYRDPCQRD54/HDARDHzlOAAdyuTTHABd4CwIACp3HIZMAHYJCEPQpDHAAjYCTQQAPzgWCAAxDIAAJDXIgRDi5pDHpy0BXokAdoZDK56lBAB4UC64ABIY4wCHAkkkl4BgV4AAZTEIZYAUIZY4LI4IACuQUFITxDD6HQA4TpFIY7NB47vHQj4AE6yKG5w1HIAIACBpBDk6DOHIZn+lTdFIekqpxDFAALeFIeXGII5D/IeHP63X6xD/ABBD/IbIAE45Dh44tJIfHGIb9445FCaCzrGvBDXlVOAAodIISI7HknGAAtyIeQzCIZhTCIad4vEqkgAFp2cACAZGDQZDaIQIAIeZIAIlQdJ0RDkkhD/AAbbEABgcKIcwAcIf5D/If5D/If5D/If5D/If5D/If5D/If5DKI1BDVIQxD8AAMkIc+cAAJBCp0qGYRDPRVBBDAAIyEIf5DRlSbDZwtOuQ+eAAIrCNQIECIZofBRgaJfIQyED43HvBDPPYJDKRC6GD0WcAAUkFQILBIaB9EToRIJRqDIEzgiFBAR0DIZsqPgZDp46FBIZ3OGAMkkkqCogACBQIAFKwIATEQl4aIIJBD4RCJAAZ/EN4olEADCMDZAQlDIJpD/Z47pCZwQACYSgAIzg/CvEqeAPH5xCQRRoAdEoKETIdlyIbQABcgZIDADYiE45BXAAPGIcMqEQnPIbPOEAkqADgiEITIAGEogAYH0BD/ABHH4wAa5wwSA")); + +var PurpleCar = require("heatshrink").decompress(atob("ol74UBitg///BIP/7lVqtUDJUVBwIABq2qABOVCCkolQJC0AwDgWolAQD1EqwBCH0EqCCdKxQuEAAkKwGhCH4Q/CGD7ECDINEAAoQIwBACgQQYwQ+EAAcC1EACAeAlQfDAAheBCG2oCBUCCB8qCCiQBdp3+fxW/CH4Q/CH4QxgQQKwAQD1QQK1QQUmQQKxiH/CBGqCB3/5WACA+j/4Qf14Q/KhWqRI0CCAv+x4QBEYcC1Wo/SpFCBtUlQQBBgISBAAUAgYQB1fVr2qCAP//8qB4WoAwIQB1WVCCEoFwIQCAA36GwMo1AQOlQQM+QQCqWq1YPIAAPqKgYQOqwQOiqpBCBi6DCH4QDYoIOH/gKBCAs/CD2AZoISEBwUAgAQYAYIABgWqfIIQGAAISBAAQHCCDIAICH4Q/CH4Q/CAWoB5UKCB8qCCmK0AxJwQQDgQQKwAQE1Q0ICAuAlQQHlQrBCCdy1EK1QABCAmq0EKgoQBqofBIoIAFWYMqB4QQRuQ5BCAxlBxoQDqyHKyoQUZpLJDCBmq1oQFvRTGAAOlCApFBB4xBFCCdUIY3VBgY=")); + +var LightGreenCar = require("heatshrink").decompress(atob("ol74UBocF///BIP1z9VqtUDJUVBwIABq2qABOVCCkolQJC0AwDgWolAQD1EqwBCH0EqCCdKxQuEAAkKwGhCH4Q/CGD7ECDINEAAoQIwBACgQQYwQ+EAAcC1EACAeAlQfDAAheBCG2oCBUCCB8qCCiQBdp3+fxW/CH4Q/CH4QxgQQKwAQD1QQK1QQUmQQKxiH/CBGqCB3/5WACA+j/4Qf14QxKjGqRI0CCAv+x4QBEYcC1Wo/SpFCBtUlQQBBgISBAAQGBCAOo6te1QQB///lQPC1AGBCAOqyoQQlAuBCAXPAQIAD/Q2BlGoCAgAGCAUqCBnyCAVS1WrB5AAB9RUDCB1WCB0VVIIQMXQYQ/CAbFBBw/8BQIQFn4QewDNBCQgOCgEACDADBAAMC1T5BCAwABCQIACA4QQZABAQ/CH4Q/CH4QC1APKhQQPlQQUxWgGJOCCAcCCBWACAmqGhAQFwEqCA8qFYIQTuWohWqAAIQE1WghUFCANVD4JFBAAqzBlQPCCCNyHIIQGMoONCAdWQ5WVCCjNJZIYQM1WtCAt6KYwAB0oQFIoIPGIIoQTqhDG6oMDA")); + +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive +} + +function moveEnemyPosition(){ + score += 1; + randomRoadPositionIndicator = getRandomInt(1, 4); + if ((randomRoadPositionIndicator == 1)) { + randomRoadPosition = 85; + }else if((randomRoadPositionIndicator == 2)){ + randomRoadPosition = 120; + }else { + randomRoadPosition = 155; + } +} + +function collision(){ + if(gameStatus == GAMEPLAYING){ + if + ( + (enemyCarFrontY > playerCarFrontY) + && + ( + (enemyCarLeftX > playerCarLeftX && enemyCarLeftX < playerCarRightX) + || + (enemyCarRightX > playerCarLeftX && enemyCarRightX < playerCarRightX) + ) + ){ + // hit + setTimeout(collision, 2500); // wait 2.5 second for the function to actiavte agian. + numberofHearts -= 1; + score -= 1; + Bangle.buzz(); + }else{ + // miss + setTimeout(collision, 1); // try again in 1 milliseconds. + } + } +} + +function storeMyData(data) { + // ensure there are less than 500 elements in the array + while (log.length >= 500) log.shift(); + // append a new item to the array + log.push(data); +} + + +var file = require("Storage").open("CarCrazy.csv","r"); +var currentHighScore = file.readLine(); +if (currentHighScore == undefined) currentHighScore = 0; +var BackgroundStartingPosition = 75; +var carScale = 0.5; +var accel = Bangle.getAccel(); +var playerCarPosition = 120-accel.x*40; +var BackgroundYPosition = BackgroundStartingPosition; +var randomRoadPositionIndicator = getRandomInt(1, 3); +var randomRoadPosition = 120; +var enemyPositonY = 30; +var carWidth = 30; +var carHeight = 60; +var playerCarY = 130; +var enemyCarLeftX; +var enemyCarRightX; +var playerCarLeftX; +var playerCarRightX; +var enemyCarFrontY; +var playerCarFrontY; +var GAMEPLAYING = 1; +var GAMEOVER = 2; +var GAMESTART = 3; +var gameStatus = GAMESTART; +var score = 0; + +moveEnemyPosition(); +collision(); + + +g.setFontAlign(-1,-1); + +function clearHighScore() { + currentHighScore = 0; + file = require("Storage").open("CarCrazy.csv","w"); + file.erase(); +} + +function draw(){ + if(gameStatus == GAMEPLAYING){ + BackgroundYPosition += 10; + accel = Bangle.getAccel(); + playerCarPosition = 120-accel.x*40; + g.flip(); + g.drawImage(backgroundImage,125,BackgroundYPosition, {scale:13,rotate:0}); + g.drawImage(RedCar,playerCarPosition,playerCarY, {scale:carScale,rotate:3.142}); + g.drawImage(OrangeCar,randomRoadPosition,enemyPositonY, {scale:carScale,rotate:0}); + + if(numberofHearts==3){ + g.drawImage(heartImage,10,10, {scale:2,rotate:0}); + g.drawImage(heartImage,10,50, {scale:2,rotate:0}); + g.drawImage(heartImage,10,30, {scale:2,rotate:0}); + }else if(numberofHearts==2){ + g.drawImage(heartImage,10,50, {scale:2,rotate:0}); + g.drawImage(heartImage,10,30, {scale:2,rotate:0}); + }else if(numberofHearts==1){ + g.drawImage(heartImage,10,50, {scale:2,rotate:0}); + }else{ + gameStatus = GAMEOVER; + //clearHighScore(); + if(score >= currentHighScore){ + currentHighScore = score; + file = require("Storage").open("CarCrazy.csv","w"); + file.erase(); + file = require("Storage").open("CarCrazy.csv","w"); + file.write(currentHighScore+"\n"); + } + } + + playerCarFrontY = playerCarY-carHeight/2; + playerCarBackY = playerCarY+carHeight/2; + playerCarLeftX = playerCarPosition-carWidth/2; + playerCarRightX = playerCarPosition+carWidth/2; + + enemyCarFrontY = enemyPositonY+carHeight/2; + enemyCarBackY = enemyPositonY-carHeight/2; + enemyCarLeftX = randomRoadPosition-carWidth/2; + enemyCarRightX = randomRoadPosition+carWidth/2; + + //g.drawRect(playerCarLeftX, playerCarFrontY, playerCarRightX, playerCarBackY); + //g.drawRect(enemyCarLeftX, enemyCarFrontY, enemyCarRightX, enemyCarBackY); + + g.setColor(0,0,0); + g.drawString("Score: "+score,180,5); + g.drawString("HighScore:",178,15); + g.drawString(currentHighScore,205,25); + + if(BackgroundYPosition > 170){ + BackgroundYPosition = BackgroundStartingPosition; + } + + + }else if(gameStatus == GAMEOVER){ + + BackgroundYPosition += 10; + g.flip(); + g.drawImage(backgroundImage,125,BackgroundYPosition, {scale:13,rotate:0}); + g.drawImage(gameOverImage,125,80, {scale:8,rotate:0}); + if(BackgroundYPosition > 170){ + BackgroundYPosition = BackgroundStartingPosition; + } + g.setColor(255,0,0); + g.setFont("6x8",4); + g.drawString("Game Over",13,17); + g.setFont("6x8",1.5); + g.drawString("Score: "+score,10,75); + g.drawString("High",10,100); + g.drawString("Score: " + currentHighScore,10,110); + g.drawString("Hold Button",10,130); + g.drawString("2 To Play",10,140); + g.drawImage(LightGreenCar,180,115, {scale:0.5,rotate:3}); + g.drawImage(PurpleCar,215,115, {scale:0.5,rotate:3}); + }else if(gameStatus == GAMESTART){ + g.flip(); + g.drawImage(backgroundImage,125,BackgroundYPosition, {scale:13,rotate:0}); + g.setColor(255,0,0); + BackgroundYPosition += 10; + g.setFont("6x8",3); + g.drawImage(gameOverImage,125,80, {scale:8,rotate:0}); + g.drawString("Welcome to",13,11); + g.drawString("Car Crazy",13,31); + g.setFont("6x8",1.8); + g.drawString("High",10,80); + g.drawString("Score: "+currentHighScore,10,90); + g.drawString("Hold Button",10,120); + g.drawString("2 To Start",10,130); + g.drawImage(LightGreenCar,180,115, {scale:0.5,rotate:3}); + g.drawImage(PurpleCar,215,115, {scale:0.5,rotate:3}); + //setTimeout(displayPopup, 3000); + } +} +setInterval(draw ,10); + + +function moveEnemyCar(){ + if(gameStatus == GAMEPLAYING){ + enemyPositonY = enemyPositonY + 10; + if((enemyPositonY > 200)){ + enemyPositonY = 30; + moveEnemyPosition(); + } + } +} +setInterval(moveEnemyCar,10); + +setWatch(() => { + if(gameStatus == GAMESTART){ + gameStatus = GAMEPLAYING; + collision(); + enemyPositonY = 0; + score = 0; + }else if(gameStatus == GAMEOVER){ + gameStatus = GAMEPLAYING; + collision(); + enemyPositonY = 0; + numberofHearts = 3; + score = 0; + } +}, BTN2, {repeat:true}); + + + + + + + diff --git a/apps/carcrazy/carcrash.png b/apps/carcrazy/carcrash.png new file mode 100644 index 000000000..1678c54c8 Binary files /dev/null and b/apps/carcrazy/carcrash.png differ diff --git a/apps/nixie/ChangeLog b/apps/nixie/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/nixie/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/nixie/README.md b/apps/nixie/README.md new file mode 100644 index 000000000..17a49ebad --- /dev/null +++ b/apps/nixie/README.md @@ -0,0 +1,17 @@ +## Nixie clock + +This clock displays the time in nixie-inspired numerals and works on both Bangle versions (1 and 2). It uses a generic +coordinate system (0 <= width < 1) and has helper functions to use inline. + +The app makes use of a module called "m_vatch" which manages all the timers, and makes calls to functions in the 'main' file +to manage drawing the background, time, and any data like sensor info, step counters, battery, etc. The idea is that it is +reusable if you write many watch apps... you just need to implement functions to draw the background (called on start, and every +time the 'mode' changes (regular and night mode), the time (which gets a call every second), and the data (also every second, +except not in night mode)). + +Night mode is a mode that can be set manually or automatically, allowing the watch code to adjust colors and detail. Mainly, +used as a night clock, you can draw no background, and use dim colors for your digits. If set to auto, the accelerometer is used so +when the watch is placed on its side, it switches to night mode (your watch may need a tweak... and Bangle 2 is a different story!) + +It also handles step counting so that it's stored on a daily +basis - survives a system reset, zeroes when the date changes and keeps a record in a history file by day. diff --git a/apps/nixie/app-icon.js b/apps/nixie/app-icon.js new file mode 100644 index 000000000..99de52534 --- /dev/null +++ b/apps/nixie/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkBIf4A/AH4A/AH4AtgtVAANQAwIFCAwYTGBIQDBqsF6AXCqFQroXDBQQXFAxNUBRAQKAAXVAYUEFBY7EBgtVC5UEDotdERwQBC4pGDTgQXJgoRDMoQGCqisFR5pICDQQwDCAxVJZAYXKQo7nEC6AtBCYgXGCYYDCJQYXBF5ThDKwoNCB4UMC4yyBToIGDAYNUDoRiBO5CyBLwi5Ea4RyGAH4A/AH4A/AH4A/ACQ=")) diff --git a/apps/nixie/app.js b/apps/nixie/app.js new file mode 100644 index 000000000..a62590f75 --- /dev/null +++ b/apps/nixie/app.js @@ -0,0 +1,429 @@ +const EMULATOR = false; +// which Bangle? +const isB2 = g.getWidth() < 200; + +// global coordinate system +const wX = g.getWidth(); +const wY = g.getHeight(); +const midX = wX/2, midY = wY/2; +// relative positioning: send 0 <= coord < 1 +function relX(x) { return Math.floor(x*wX); } +function relY(y) { return Math.floor(y*wY); } + +// colors +const col_bg = 0; +const col_nm =isB2 ? 1 :"#206040"; +const col_sep = isB2 ? 6 :"#202020"; +const col_off = isB2 ? 1 : "#202020"; +const col_shad1 = isB2 ? 4 :"#FF0000"; +const col_shad2 = isB2 ? 6 :"#FF6000"; +const col_hi =isB2 ? 7 : "#FFC000"; +const col_data = isB2 ? 6 :"#C06000"; + +g.setBgColor(col_bg); +g.clear(); + +var imgTube = { + width : 64, height : 128, bpp : 2, + buffer : require("heatshrink").decompress(atob("AE9AB7sQD54AOiFQB5tVsgPN0uoBxkByEFB5kGyIPNhVVB5tpLwKAMoJuOgNQggMJgtVDhsVqtEZ5cVrWlEBcFtWq1WlJxUaBwOq1IgJgIdCqoABEBEC1WVBwTkGKgUGFYIOCgIRDC4kaFoVUOQQKCQ4IgCB4YKDCYIgCq2QgEqHwJLIEoOkgFqB4KaIEoNkB4Z7JHQVqquqD5EVDYQPCVRIPE1IPKgsAtJTCAA8GyEBD4TrKqAPOgNRB5sRB5wfPgAPOiA/RP4IPaiD6BB5oCBB5kAdQIPNH5wPCvIPMBgIPMR4QPcL4QPNgIPQvS/MqtAB59+B9cVB91VL91BF91RF79RB4OVD5wPsH59BB51FB5sQB/0AD7xvPV4elD5wPLqIPOgJPeX/6//X8YPMH5wPPL74PfN55PQB6TfPB5afDB51/D57P/Z/7P/B97vOB5kAB58VoAA=")) +}; +var imgTubeBW = { + width : 46, height : 92, bpp : 1, + buffer : require("heatshrink").decompress(atob("AD0EAomAAgcCBQkQEykwAgcP/gFD/wKECok4AgcB4A7DgwQEjAFEsYWExg2DhkgAoVAE4kA8AEDgZqEhw+JgA+DCwIKEhhrJCyJELFqBbQIiByLIk6gWZyC3WOSItWOVq3nCywA=")) +}; + +require("Font8x12").add(Graphics); +g.setFont("8x12", 1); +let interval = null; + +let alarming = false; +let nightMode = false; + +// our scale factor +let xs = 0.5 * wX/240; +let ys = 0.75 * wY/240; + +let prevH1 = -1; +let prevH2 = -1; +let prevM1 = -1; +let prevM2 = -1; + + +let points0 = new Uint8Array([ + 0, 40, + 1, 35, + 7, 20, + 16, 8, + 28, 2, + 40, 0, + + 51, 2, + 63, 10, + 72, 20, + 77, 35, + 78, 40, + + 78, 59, + 77, 64, + 72, 79, + 63, 89, + 51, 97, + + 40, 99, + 28, 97, + 16, 91, + 7, 79, + 1, 64, + 0, 59, + 0, 40 +]); + +let points1 = new Uint8Array([ 40, 99, 40, 0]); + +let points2 = new Uint8Array([ 0, 25, + 2, 22, + 6, 13, + 17, 5, + 28, 2, + 40, 0, + 52, 2, + 63, 5, + 74, 13, + 79, 23, + 79, 28, + 74, 38, + 63, 46, + 51, 54, + 40, 58, + 29, 62, + 17, 68, + 8, 80, + 0, 99, + 79, 99 + ]); + +let points4 = new Uint8Array([ 60, 99, 60, 0, 0, 75, 79, 75 ]); + +let points8 = new Uint8Array([ + 40, 40, + 26, 42, + 15, 46, + 4, 56, + 1, 66, + 1, 77, + 6, 87, + 17, 94, + 28, 97, + 38, 99, + 42, 99, + 52, 97, + 63, 94, + 74, 87, + 79, 77, + 79, 66, + 75, 56, + 64, 46, + 54, 42, + 40, 40, + + 52, 39, + 62, 34, + 69, 29, + 72, 23, + 72, 19, + 69, 12, + 62, 6, + 52, 2, + 40, 0, + + 28, 2, + 18, 6, + 11, 12, + 8, 19, + 8, 23, + 11, 29, + 18, 34, + 28, 39, + 40, 40, + ]); + +let points6 = new Uint8Array([ + 50, 0, + 4, 56, + 1, 66, + 1, 77, + 6, 87, + 17, 94, + 28, 97, + 40, 99, + 52, 97, + 63, 94, + 74, 87, + 79, 77, + 79, 66, + 75, 56, + 64, 46, + 52, 42, + 40, 40, + 26, 42, + 15, 46, + 4, 56, + ]); + +let points3 = new Uint8Array([ + 1, 77, + 6, 87, + 17, 94, + 28, 97, + 40, 99, + 52, 97, + 63, 94, + 74, 87, + 79, 77, + 79, 66, + 75, 56, + 64, 46, + 52, 42, + 39, 40, + 79, 0, + 1, 0 + ]); + +let points7 = new Uint8Array([ 0, 0, 79, 0, 30, 99 ]); + +let points9 = new Uint8Array(points6.length); +let points5 = new Uint8Array([ + 1, 77, + 6, 87, + 17, 94, + 28, 97, + 38, 99, + 42, 99, + 52, 97, + 63, 94, + 74, 87, + 79, 77, + 79, 66, + 75, 56, + 64, 46, + 54, 42, + 40, 40, + 26, 42, + 15, 46, + 27, 0, + 79, 0, +]); + +function drawPoints(points, x0, y0) { + let x = points[0]*xs+x0, y = points[1]*ys+y0; + //g.drawEllipse(x-2, y-2, x+2, y+2); + g.moveTo(x, y); + for(let idx=1; idx*2 < points.length; idx ++) { + let x = points[idx*2]*xs+x0; + let y = points[idx*2+1]*ys+y0; + //g.drawEllipse(x-2, y-2, x+2, y+2); + g.lineTo(x, y); + } +} + +/* create 5 from 2 */ +/* uncomment if you want the 5 to look more authentic (but uglier) +for (let idx=0; idx*2 < points2.length; idx++) { + points5[idx*2] = points2[idx*2]; + points5[idx*2+1] = 99-points2[idx*2+1]; +} +*/ +/* create 9 from 6 */ +for (let idx=0; idx*2 < points6.length; idx++) { + points9[idx*2] = 79-points6[idx*2]; + points9[idx*2+1] = 99-points6[idx*2+1]; +} + +pointsArray = [points0, points1, points2, points3, points4, points5, points6, points7, points8, points9]; + +function eraseDigit(d, x, y) { + if(d < 0 || d > 9) return; + g.setColor(col_bg); + if(nightMode) { + drawPoints(pointsArray[d], x, y); + return; + } + drawPoints(pointsArray[d], x-2, y-2); + drawPoints(pointsArray[d], x+2, y-2); + drawPoints(pointsArray[d], x-2, y+2); + drawPoints(pointsArray[d], x+2, y+2); + drawPoints(pointsArray[d], x-1, y-1); + drawPoints(pointsArray[d], x+1, y-1); + drawPoints(pointsArray[d], x-1, y+1); + drawPoints(pointsArray[d], x+1, y+1); +} + +function drawDigit(d, x, y) { + if(nightMode) { + g.setColor(col_nm); + drawPoints(pointsArray[d], x, y); + return; + } + g.setColor(col_off); + for (let idx = pointsArray.length - 1; idx >= 0 ; idx--) { + if(idx == d) { + g.setColor(col_shad1); + drawPoints(pointsArray[d], x-2, y-2); + drawPoints(pointsArray[d], x+2, y-2); + drawPoints(pointsArray[d], x-2, y+2); + drawPoints(pointsArray[d], x+2, y+2); + g.setColor(col_shad2); + drawPoints(pointsArray[d], x-1, y-1); + drawPoints(pointsArray[d], x+1, y-1); + drawPoints(pointsArray[d], x-1, y+1); + drawPoints(pointsArray[d], x+1, y+1); + + g.setColor(col_hi); + drawPoints(pointsArray[d], x, y); + + g.setColor(col_off); + } else { + drawPoints(pointsArray[idx], x, y); + } + } +} + +function drawBkgd(nm) { + g.clear(); + prevH1=-1;prevH2=-1;prevM1=-1;prevM2=-1; + if(nm) return; + + if(!isB2) { + // tube images + g.setColor(col_shad2); + + [relX(0),relX(0.25),relX(0.5),relX(0.75)].forEach((v,i,a) => { + g.drawImage(imgTube,v,relY(0.225)); + }); + // something to sit on + g.setColor(col_shad2); + g.fillRect(0, relY(0.76),wX,relY(0.76)); + } else { + // simple tubes + [1,45,89,133].forEach((v,i,a) => { + g.setColor(col_shad1); + g.drawEllipse(v, 52, v+41, 90); + g.drawRect(v,66,v+41,125); + g.clearRect(v+1,66,v+40,124); + }); + } + g.setColor(col_shad2); + g.moveTo(relX(0.125), 0); + g.lineTo(relX(0.25), relY(0.125)); + g.lineTo(relX(0.75), relY(0.125)); + g.lineTo(relX(0.875),0); + + g.moveTo(relX(0.125), wY); + g.lineTo(relX(0.25), relY(0.875)); + g.lineTo(relX(0.75), relY(0.875)); + g.lineTo(relX(0.875), wY); + +} + +function drawTime(d,nm) { + const dx = [relX(0.042), relX(0.29), relX(0.55), relX(0.791)]; //[ 10, 65, 135, 190]; + const dy = [relY(0.38),relY(0.38),relY(0.38),relY(0.38)]; + + let h1 = Math.floor(d.hour / 10); + let h2 = d.hour % 10; + let m1 = Math.floor(d.min / 10); + let m2 = d.min % 10; + + if(h1 == prevH1 && h2 == prevH2 && m1 == prevM1 && m2 == prevM2) { + return; + } + nightMode = nm; + + if(h1 != prevH1) { + eraseDigit(prevH1, dx[0], dy[0]); + drawDigit(h1, dx[0], dy[0]); + } + if(h2 != prevH2) { + eraseDigit(prevH2, dx[1], dy[1]); + drawDigit(h2, dx[1], dy[1]); + } + if(m1 != prevM1) { + eraseDigit(prevM1, dx[2], dy[2]); + drawDigit(m1, dx[2], dy[2]); + } + if(m2 != prevM2) { + eraseDigit(prevM2, dx[3], dy[3]); + drawDigit(m2, dx[3], dy[3]); + } + prevH1 = h1; + prevH2 = h2; + prevM1 = m1; + prevM2 = m2; + +} + +function drawData(d) { + if(!nightMode) { + g.setColor(col_data); + g.setFontAlign(0, -1); + g.drawString(` ${d.dow}, ${d.mon3} ${d.date} `, wX/2, relX(0.042), true); + g.setFontAlign(-1,-1); + g.drawString("STEP ", 0, relY(0.82), true); + g.drawString(`${d.steps} `,0, relY(0.875), true); + g.setFontAlign(1,-1); + g.drawString(" BTY", relX(0.999), relY(0.82), true); + g.drawString(` ${d.batt}`, relX(0.999), relY(0.875), true); + g.setFontAlign(0,-1); + g.setColor(col_shad2); + g.drawString('BANGLE.JS', wX/2, relY(0.925)); + } +} + +//setWatch(E.showLauncher, BTN1, {repeat:true,edge:"falling"}); +if(EMULATOR) { + let d = new Date(); + + let hour = d.getHours(); + let minute = d.getMinutes(); + + let h1 = Math.floor(hour / 10); + let h2 = hour % 10; + let m1 = Math.floor(minute / 10); + let m2 = minute % 10; + + let data = { + h1: h1, + h2: h2, + m1: m1, + m2: m2, + hour: hour, + min: minute, + }; + + drawBkgd(nightMode); + + drawTime(data, nightMode); + const mstr="JanFebMarAprMayJunJulAugSepOctNovDec"; + const dowstr = "SunMonTueWedThuFriSat"; + + let month = d.getMonth(); + let dow = d.getDay(); + data.month = month; + data.date = d.getDate(); + + data.mon3 = mstr.slice(month*3,month*3+3); + data.dow = dowstr.substr(dow*3,3); + data.dateStr = data.dow + " " + data.mon3 + " " + data.date; + data.steps = 12345; + data.batt = E.getBattery() + (Bangle.isCharging() ? "+" : ""); + data.charging = Bangle.isCharging(); + + drawData(data); +} else { + Bangle.setUI("clock"); + let v = require("m_vatch.js"); + v.setDrawTime(drawTime); + v.setDrawBackground(drawBkgd); + v.setDrawData(drawData); + v.begin(); +} diff --git a/apps/nixie/m_vatch.js b/apps/nixie/m_vatch.js new file mode 100644 index 000000000..430892424 --- /dev/null +++ b/apps/nixie/m_vatch.js @@ -0,0 +1,317 @@ +const _Storage = require('Storage'); + +let interval = null; + +let nightMode = false; + +let stepFile = 'v.steps.json'; +let stepArchiveFile = 'v.stephist.json'; + +let _Options = {}; +let optsFile = 'm_vatch.opts.json'; + +let _Alarm = { + inAlarm: false, + reload: () => {}, + scheduleAlarms: () => {}, + showMsg: (title, msg) => {}, + showNotes: () => {}, +}; + +let _StepData = {}; + +const pad0 = (n) => (n > 9) ? n : ("0"+n); + +const getToday = () => { + let d = new Date(); + return d.getFullYear()+'-'+ pad0(d.getMonth()+1) + '-' + pad0(d.getDate()); +}; + +function reload() { + _StepData = _Storage.readJSON(stepFile); + if(!_StepData) { + _StepData = { + lastDate: '2020-01-01', + stepCache: 0, + lastStepCount: 0, + updated: true, + }; + } + if(getToday() === _StepData.lastDate) { + _StepData.stepCache += _StepData.lastStepCount; + _StepData.lastStepCount = 0; + } +} + +function stringFromArray(data) +{ + var count = data.length; + var str = ""; + + for(var index = 0; index < count; index += 1) + str += String.fromCharCode(data[index]); + + return str; +} + +function logD(str) { + if(_Options.debug) console.log(str); +} + + +let lastH1 = -1; +let lastH2 = -1; +let lastM1 = -1; +let lastM2 = -1; + + +let drawBackground = () => {}; +let drawTime = () => {}; +let drawData = () => {}; + + +function timeCheck() { + + if(_Alarm.inAlarm) return; + + logD('Again, ' + JSON.stringify(_Options)); + logD('opt.nm = '+_Options.autoNightMode); + if(_Options.autoNightMode) { + // this may vary by Bangle.. adjust to taste + let a = Bangle.getAccel(); + a.x = Math.floor(a.x * 100); + logD('a.x = ' + a.x); + if(a.x <= 101 && a.x >= 99) { + if(!nightMode) { + nightMode = ! nightMode; + redrawScreen(); + } + } else { + if(nightMode) { + nightMode = ! nightMode; + redrawScreen(); + } + } + } + + let d = new Date(); + + let hour = d.getHours(); + let minute = d.getMinutes(); + + let h1 = Math.floor(hour / 10); + let h2 = hour % 10; + let m1 = Math.floor(minute / 10); + let m2 = minute % 10; + + logD("lastH1 = "+lastH1+": lastM2 = "+lastM2); + if(h1 == lastH1 && h2 == lastH2 && m1 == lastM1 && m2 == lastM2) { + return; + } + + logD("drawing time"); + let data = { + h1: h1, + h2: h2, + m1: m1, + m2: m2, + hour: hour, + min: minute, + }; + drawTime(data, nightMode); + + lastH1 = h1; + lastH2 = h2; + lastM1 = m1; + lastM2 = m2; + + if(!nightMode && !_Alarm.inAlarm) { + logD("drawing data..."); + const mstr="JanFebMarAprMayJunJulAugSepOctNovDec"; + const dowstr = "SunMonTueWedThuFriSat"; + + let month = d.getMonth(); + let dow = d.getDay(); + data.month = month; + data.date = d.getDate(); + + data.mon3 = mstr.slice(month*3,month*3+3); + data.dow = dowstr.substr(dow*3,3); + data.dateStr = data.dow + " " + data.mon3 + " " + data.date; + data.steps = _StepData.stepCache + _StepData.lastStepCount; + data.batt = E.getBattery() + (Bangle.isCharging() ? "+" : ""); + data.charging = Bangle.isCharging(); + + drawData(data); + } + + if(_StepData.updated) { + _Storage.writeJSON(stepFile, _StepData); + logD(JSON.stringify(_StepData)); + _StepData.updated = false; + } +} + +function stop () { + if (interval) { + clearInterval(interval); + } +} + +function start () { + if (interval) { + clearInterval(interval); + } + // first time init + interval = setInterval(timeCheck, 1000); + timeCheck(); +} + + +function btn1Func() { + logD("btn1Func"); + + if(_Alarm.inAlarm ) { + _Alarm.inAlarm = false; + } else { + if( ! _Options.autoNightMode) { + nightMode = ! nightMode; + logD('nm is '+nightMode); + } + } + redrawScreen(); +} + +function redrawScreen() { + logD("redrawScreen"); + + if(nightMode) { + g.setRotation(1,0); + } else { + g.setRotation(0,0); + } + lastM1 = -1; + lastM2 = -1; + lastH1 = -1; + lastH2 = -1; + drawBackground(nightMode); + timeCheck(); +} + +function btn2Func() { + _Alarm.reload(); + _Alarm.scheduleAlarms(); + _Alarm.showNotes(); +} + +Bangle.on('step', function(cnt) { + if(!_StepData.lastDate) return; + if(_StepData.lastDate !== getToday()) { + // save previous day's step count + try { + let sf = _Storage.readJSON(stepArchiveFile); + if(!sf) sf = []; + logD('sf is '+ (typeof sf) +':'+sf); + // trim to 30 + if(sf.length >= 30 ) sf.shift(); + let steps = _StepData.stepCache +_StepData.lastStepCount; + let sd = `${_StepData.lastDate},${steps}`; + sf.push(sd); + _Storage.writeJSON(stepArchiveFile, sf); + } catch (err) { + _Storage.write('err.txt',err); + } + /* individual step files by date + _Storage.write(_StepData.lastDate +'.steps', JSON.stringify( + _StepData.stepCache +_StepData.lastStepCount + )); + */ + _StepData.stepCache = 0 - cnt; + _StepData.lastDate = getToday(); + } + _StepData.lastStepCount = cnt; + _StepData.updated = true; +}); + +/* +** Advertise a writeable characteristic. Accepts text (in 20 char +** chunks) terminated with __EOM__ by itself. If there's text, show +** it (as an alarm), otherwise reload the alarm & msg files (empty +** string signals another BLE process updated those files) +*/ +/* +var BLEMessage = ""; +NRF.setServices({ + "feb10001-f00d-ea75-7192-abbadabadebb": { + "feb10002-f00d-ea75-7192-abbadabadebb": { + value : [0], + maxLen : 20, + writable : true, + onWrite : function(evt) { + let str = stringFromArray(evt.data); + if(str === "__EOM__") { + if(BLEMessage) { + showMsg('Message',BLEMessage); + } else { + reload(); + scheduleAlarms(); + showMsg('', 'Reloading...'); + } + BLEMessage = ''; + } else { + BLEMessage += str; + } + } + } + } +}, { }); +*/ + +exports.setDrawBackground = function(dBkgd) { + drawBackground = dBkgd; +}; +exports.setDrawTime = function(dTime) { + drawTime = dTime; +}; +exports.setDrawData = function( dData) { + drawData = dData; +}; +exports.begin = function() { + _Options = _Storage.readJSON(optsFile); + if(!_Options) _Options = { + autoNightMode: true, + useAlarms: false, + stepManager: true, + debug: true, + }; + + console.log(JSON.stringify(_Options)); + + if(_Options.useAlarms) { + _Alarm = require('m_alarms'); + _Alarm.reload(); + _Alarm.scheduleAlarms(); + } + // separate the Bangles now + const isB2 = g.getWidth() < 200; + + if(!isB2) { + Bangle.on('lcdPower', function (on) { + if (on) { + start(); + } else { + stop(); + } + }); + setWatch(btn1Func, BTN1, {repeat:true,edge:"falling"}); + + if(_Options.useAlarms) { + setWatch(btn2Func, BTN2, {repeat:true,edge:"falling"}); + } + setWatch(Bangle.showLauncher, BTN3, {repeat:false,edge:"falling"}); + } + reload(); + drawBackground(nightMode); + start(); +}; + diff --git a/apps/nixie/nixie.info b/apps/nixie/nixie.info new file mode 100644 index 000000000..66f5ff2a5 --- /dev/null +++ b/apps/nixie/nixie.info @@ -0,0 +1,10 @@ +{ +"id":"jvNixie", +"name":"Nixie Clock", +"type":"clock", +"src":"nixie.app.js", +"icon": "nixie.img", +"sortorder":1, +"version":"1.1", +"files":"nixie.info,nixie.app.js,nixie.img, m_vatch.js" +} diff --git a/apps/nixie/nixie.png b/apps/nixie/nixie.png new file mode 100644 index 000000000..d21714191 Binary files /dev/null and b/apps/nixie/nixie.png differ