diff --git a/apps/angles/ChangeLog b/apps/angles/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/angles/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/angles/app.js b/apps/angles/app.js new file mode 100644 index 000000000..a07c29199 --- /dev/null +++ b/apps/angles/app.js @@ -0,0 +1,49 @@ +g.clear().setRotation(1); +// g.setRotation ALSO changes accelerometer axes +var avrAngle = undefined; +var history = []; + +var R = Bangle.appRect; +var W = g.getWidth(); +var H = g.getHeight(); +var relativeTo = undefined; + +function draw(v) { + if (v===undefined) v = Bangle.getAccel(); + // current angle + var d = Math.sqrt(v.y*v.y + v.z*v.z); + var ang = Math.atan2(-v.x, d)*180/Math.PI; + // Median filter + if (history.length > 10) history.shift(); // pull old reading off the start + history.push(ang); + avrAngle = history.slice().sort()[(history.length-1)>>1]; // median filter + // Render + var x = R.x + R.w/2; + var y = R.y + R.h/2; + g.reset().clearRect(R).setFontAlign(0,0); + var displayAngle = avrAngle; + g.setFont("6x15").drawString("ANGLE (DEGREES)", x, R.y2-8); + if (relativeTo!==undefined) { + g.drawString("RELATIVE TO", x,y-50); + g.setFont("Vector:30").drawString(relativeTo.toFixed(1),x,y-30); + y += 20; + displayAngle = displayAngle-relativeTo; + } + g.setFont("Vector:60").drawString(displayAngle.toFixed(1),x,y); + +} + +draw(); +Bangle.on('accel',draw); + +// Pressing the button turns relative angle on/off +Bangle.setUI({ + mode : "custom", + btn : function(n) { + if (relativeTo===undefined) + relativeTo = avrAngle; + else + relativeTo = undefined; + draw(); + } +}); \ No newline at end of file diff --git a/apps/angles/icon.js b/apps/angles/icon.js new file mode 100644 index 000000000..3f051f95f --- /dev/null +++ b/apps/angles/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA///ov+5lChWMyGuxdzpdj4/lKf4AUkgQPgm0wAiPy2QCBsBkmS6QRNhIRBrVACJlPu2+pdICBcCrVJlvJtIRLifStMl3MtkARKydUyMkzMl0CMKyWWyUk1MkSJXkyR7BogRLgVcydSrVGzLHKgdLyfSpdE3JYKklqTwNJknJYJVkxcSp+pnygKhMs1OSEQOSYhVJl1bCIbBK5Mq7gRCyARJiVbqyPBCIKMKuVM24yBCIIiJnVOqu5CISMKp9JlvJCIRXKpP3nxoCRhUSBwSMNBwaMMgn6yp6DRhUl0mypiMMgM9ksipaMMhMtCINKRhlJmoRBpJuBCBIRGRhUE5I1CpKMLgmZn5ZDGhUAycnRoNMRhTDCsn3tfkRhLnDTwYQLNgSMMUQkyRhbGEkyMKAApFOAH4AGA")) \ No newline at end of file diff --git a/apps/angles/icon.png b/apps/angles/icon.png new file mode 100644 index 000000000..1a4559d44 Binary files /dev/null and b/apps/angles/icon.png differ diff --git a/apps/angles/metadata.json b/apps/angles/metadata.json new file mode 100644 index 000000000..f8a90a305 --- /dev/null +++ b/apps/angles/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "angles", + "name": "Angles (Spirit Level)", + "shortName": "Angles", + "version": "0.01", + "description": "Shows Angle or Relative angle in degrees (Digital Protractor/Inclinometer). Place Bangle sideways against a surface with the button facing away for best readings.", + "icon": "icon.png", + "screenshots": [{"url":"screenshot.png"}], + "tags": "tool", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"angles.app.js","url":"app.js"}, + {"name":"angles.img","url":"icon.js","evaluate":true} + ] +} diff --git a/apps/angles/screenshot.png b/apps/angles/screenshot.png new file mode 100644 index 000000000..9d631cf74 Binary files /dev/null and b/apps/angles/screenshot.png differ diff --git a/apps/drained/ChangeLog b/apps/drained/ChangeLog index c7fd27981..8d196b10d 100644 --- a/apps/drained/ChangeLog +++ b/apps/drained/ChangeLog @@ -2,3 +2,4 @@ 0.02: Allow boot exceptions, e.g. to load DST 0.03: Permit exceptions to load in low-power mode, e.g. daylight saving time. Also avoid polluting global scope. +0.04: Enhance menu: enable bluetooth, visit settings & visit recovery diff --git a/apps/drained/app.js b/apps/drained/app.js index e27fcb1d1..37cc8c71d 100644 --- a/apps/drained/app.js +++ b/apps/drained/app.js @@ -61,14 +61,13 @@ var reload = function () { nextDraw = undefined; }, btn: function () { - E.showPrompt("Restore watch to full power?").then(function (v) { - if (v) { - drainedRestore(); - } - else { - reload(); - } - }); + var menu = { + "Restore to full power": drainedRestore, + "Enable BLE": function () { return NRF.wake(); }, + "Settings": function () { return load("setting.app.js"); }, + "Recovery": function () { return Bangle.showRecoveryMenu(); }, + }; + E.showMenu(menu); } }); Bangle.CLOCK = 1; diff --git a/apps/drained/app.ts b/apps/drained/app.ts index f4d33bc44..ed40262fa 100644 --- a/apps/drained/app.ts +++ b/apps/drained/app.ts @@ -79,13 +79,13 @@ const reload = () => { nextDraw = undefined; }, btn: () => { - E.showPrompt("Restore watch to full power?").then(v => { - if(v){ - drainedRestore(); - }else{ - reload(); - } - }) + const menu = { + "Restore to full power": drainedRestore, + "Enable BLE": () => NRF.wake(), + "Settings": () => load("setting.app.js"), + "Recovery": () => Bangle.showRecoveryMenu(), + }; + E.showMenu(menu); } }); Bangle.CLOCK=1; diff --git a/apps/drained/metadata.json b/apps/drained/metadata.json index 6dfdac78d..ea18f6fcf 100644 --- a/apps/drained/metadata.json +++ b/apps/drained/metadata.json @@ -1,12 +1,10 @@ { "id": "drained", "name": "Drained", - "version": "0.03", + "version": "0.04", "description": "Switches to displaying a simple clock when the battery percentage is low, and disables some peripherals", "readme": "README.md", "icon": "icon.png", - "type": "clock", - "tags": "clock", "supports": ["BANGLEJS2"], "allow_emulator": true, "storage": [ diff --git a/apps/intervals/ChangeLog b/apps/intervals/ChangeLog new file mode 100644 index 000000000..761350e07 --- /dev/null +++ b/apps/intervals/ChangeLog @@ -0,0 +1,2 @@ +0.01: First Release +0.02: Changing resolution to seconds instead of 5 seconds \ No newline at end of file diff --git a/apps/intervals/intervals.app.js b/apps/intervals/intervals.app.js index da436b104..db20818ad 100644 --- a/apps/intervals/intervals.app.js +++ b/apps/intervals/intervals.app.js @@ -149,9 +149,9 @@ function showMenu() "START" : function() { startSession(); }, "Sets" : { value : settings.sets,min:0,max:20,step:1,onchange : v => { settings.sets=v; } }, "Work minutes" : { value : settings.workmin,min:0,max:59,step:1,onchange : v => { settings.workmin=v; } }, - "Work seconds" : { value : settings.workseg,min:0,max:59,step:5,onchange : v => { settings.workseg=v; } }, + "Work seconds" : { value : settings.workseg,min:0,max:59,step:1,onchange : v => { settings.workseg=v; } }, "Rest minutes" : { value : settings.restmin,min:0,max:59,step:1,onchange : v => { settings.restmin=v; } }, - "Rest seconds" : { value : settings.restseg,min:0,max:59,step:5,onchange : v => { settings.restseg=v; } }, + "Rest seconds" : { value : settings.restseg,min:0,max:59,step:1,onchange : v => { settings.restseg=v; } }, "Signal type" : { value : settings.buzz,format : v => v?"Buzz":"Beep",onchange : v => { settings.buzz=v; }} }; diff --git a/apps/intervals/metadata.json b/apps/intervals/metadata.json index 32c18ae70..542fb1846 100644 --- a/apps/intervals/metadata.json +++ b/apps/intervals/metadata.json @@ -2,7 +2,7 @@ "id": "intervals", "name": "Intervals App", "shortName": "Intervals", - "version": "0.01", + "version": "0.02", "description": "Intervals for training. It is possible to configure work time and rest time and number of sets.", "icon": "intervals.png", "tags": "", diff --git a/apps/pebbled/ChangeLog b/apps/pebbled/ChangeLog index 9f5734152..fcc8b3254 100644 --- a/apps/pebbled/ChangeLog +++ b/apps/pebbled/ChangeLog @@ -4,3 +4,4 @@ Support for fast loading 0.04: Localisation request: added Miles and AM/PM 0.05: Prevent exceptions from halting the draw cycle +0.06: Fix Settings page to ensure that the currently set distance is displayed (not 0.75) diff --git a/apps/pebbled/metadata.json b/apps/pebbled/metadata.json index 483f6e0d3..50555a173 100644 --- a/apps/pebbled/metadata.json +++ b/apps/pebbled/metadata.json @@ -2,7 +2,7 @@ "id": "pebbled", "name": "Pebble Clock with distance", "shortName": "Pebble + distance", - "version": "0.05", + "version": "0.06", "description": "Fork of Pebble Clock with distance in KM. Both step count and the distance are on the main screen. Default step length = 0.75m (can be changed in settings).", "readme": "README.md", "icon": "pebbled.png", diff --git a/apps/pebbled/pebbled.settings.js b/apps/pebbled/pebbled.settings.js index f4ca1d394..d54517a70 100644 --- a/apps/pebbled/pebbled.settings.js +++ b/apps/pebbled/pebbled.settings.js @@ -36,7 +36,7 @@ }, }, 'Step length': { - value: 0.75 || s.avStep, + value: s.avStep || 0.75, min: 0.2, max: 1.5, step: 0.01, diff --git a/apps/planetarium/ChangeLog b/apps/planetarium/ChangeLog index 9eedad602..9ef1f5c5b 100644 --- a/apps/planetarium/ChangeLog +++ b/apps/planetarium/ChangeLog @@ -2,3 +2,4 @@ 0.02: Major speed improvement. Added more stars. Up to 500! 0.03: Added more stars and constellations. Now it shows 20 constellations. 0.04: Use default Bangle formatter for booleans +0.05: Added more constellations (scorpio and aguila) diff --git a/apps/planetarium/metadata.json b/apps/planetarium/metadata.json index 78add2cea..a0327b842 100644 --- a/apps/planetarium/metadata.json +++ b/apps/planetarium/metadata.json @@ -2,7 +2,7 @@ "id": "planetarium", "name": "Planetarium", "shortName": "Planetarium", - "version": "0.04", + "version": "0.05", "description": "Planetarium showing up to 500 stars using the watch location and time", "icon": "planetarium.png", "tags": "", diff --git a/apps/planetarium/planetarium.const.csv b/apps/planetarium/planetarium.const.csv index 972e4faff..8fb5466b2 100644 --- a/apps/planetarium/planetarium.const.csv +++ b/apps/planetarium/planetarium.const.csv @@ -38,3 +38,7 @@ Draco e_15 131,131 70,70 382,382 e_15,382 187,187 423,423 e_16,e_16 207,207 122,122 e_17,e_17 232,232 342,342 452,452 428 Pegasus 92 85,138 54,54 85,138 92,283 85,283 389,160 85,92 258,258 297,297 83 +Aguila +12 249,249 271,249 170,249 217,12 365,120 12 +Scorpius +14 105,14 80,14 152,14 137,137 76,332 239,239 41,76 188,188 332,41 181,181 27 diff --git a/apps/quoteclock/app-icon.js b/apps/quoteclock/app-icon.js new file mode 100644 index 000000000..5dcc91801 --- /dev/null +++ b/apps/quoteclock/app-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("MDAEEREREREREURERERERERERBERERERERERERERERERREREREREREREREEREREREREREREREREURERERERERERERERBERERERERERERERFEREREREREREREREREERERERERERERERREREREREREREREREREQREREREREREREURBEhEURERERERERERERBEREREREREREURBESERIkRERERERERERBERERERERERFEQhERIhEiREREREREREREERERERERERFERCERIiIiJEREREREREREQRERERERERREREERASIiEkREREREREREQRERERERERREREQQERIiEkRERERERERERBEREREREUREREQRERAiIiEkRERERERERBEREREREURERERAERERISEkRERERERERBEREREREUREREREEQAREiJEREREREREREERERERFEREREREIiIRESIUREREREREREERERERFEREREREERABERIRREREREREREERERERFEREREREQREREREhREREREREREERERERFEREREREIiJBERESFEREREREREQRERERFEREREREIiQRERERIUREREREREQRERERRERERERCIiIhEQEREhREREREREQRERERRERERERCIiIiIRERESFEREREREQRERERREREREQiIiIiIiQRESJEREREREQRERERREREREQiIiIiIiIkFBJEREREREQRERERREREREQiIiIiIiJBESEkREREREQRERERREREREQiIiIiIiIiRCIiREREREQRERERREREREQiIiIiIiIiIkQiREREREQRERERREREREQiIiIiIiIiIiIiJEREREQRERERREREREQiIiIiIyIiIiIiIkREREQRERERREREREQiIiIkQjMiIiIkIkREREQRERERREREREREREREQiIiIiJEQiIiMkQRERERREQiIkREQRESIiIiIiJCQiMzIkQRERERREIiIgAAABEBERRCMzMzIiIhARERERERFEIiIiIiIiIzMzMzMzMhAAJAEQVRERERQjMzMzMzMzMzMiIhEAAAAAEiBURBERFCIzMzIiIhERAAAAARERERERESFERBEREUERAAAAAAAAARERERERERFREUJEQhERERERFEREREREREREREREREREREJEQkEREREUREREREREREREREREREREREEkREERERFEREREREREREREREQiJEREREQkRBERERFEJERCIiRCIiIiJEIiIkREREQSERARERESREIiIiIiRERERERERERCREVSIAEREREUREREREREREREREREREREREVREBEREREURERERBFERERERERERERERVEAAREREREQAAABEREREREREUREREREEAAAAAABEREREREREREREREREAAAAAAAAAAAABERERERERERERRERERERBEREREREREREREREREREREREUREREREREREQRERERERERERERERERERERRERERERERBERERERERERER")) \ No newline at end of file diff --git a/apps/quoteclock/app.js b/apps/quoteclock/app.js new file mode 100644 index 000000000..63b3ea032 --- /dev/null +++ b/apps/quoteclock/app.js @@ -0,0 +1,196 @@ +const locale = require("locale"); + +const quotesshakespeare = ["Be not afraid of greatness. Some are born great, some achieve greatness, and others have greatness thrust upon them.", "We know what we are, but know not what we may be.", "Sweet are the uses of adversity which, like the toad, ugly and venomous, wears yet a precious jewel in his head.", "Our doubts are traitors and make us lose the good we oft might win by fearing to attempt.", "Give every man thy ear, but few thy voice.", "Uneasy lies the head that wears the crown.", "How poor are they that have not patience! What wound did ever heal but by degrees?", "Nothing can come of nothing.", "How far that little candle throws its beams! So shines a good deed in a naughty world.", "What's done can't be undone.", "Though she be but little, she is fierce.", "No legacy is so rich as honesty.", "This above all; to thine own self be true.", "I wasted time, and now doth time waste me.", "The robbed that smiles, steals something from the thief.", "The devil can cite Scripture for his purpose.", "One touch of nature makes the whole world kin.", "What is past is prologue.", "Small cheer and great welcome makes a merry feast.", "Sweet mercy is nobility's true badge.", "'Tis not enough to help the feeble up, but to support them after.", "Neither a borrower nor a lender be.", "Ambition should be made of sterner stuff.", "I bear a charmed life.", "Heat not a furnace for your foe so hot that it do singe yourself.", "Talking isn't doing. It is a kind of good deed to say well; and yet words are not deeds.", "In time we hate that which we often fear.", "Modest doubt is called the beacon of the wise.", "With mirth and laughter let old wrinkles come.", "Boldness be my friend.", "Words without thoughts never to heaven go.", "Wisely, and slow. They stumble that run fast.", "Pleasure and action make the hours seem short.", "When words are scarce they are seldom spent in vain.", "Such as we are made of, such we be.", "And oftentimes excusing of a fault doth make the fault the worse by the excuse.", "Reputation is an idle and most false imposition; oft got without merit, and lost without deserving.", "To be, or not to be: that is the question.", "All the world's a stage, and all the men and women merely players. They have their exits and their entrances; and one man in his time plays many parts.", "All that glisters is not gold.", "Words are easy, like the wind; faithful friends are hard to find.", "The fault is not in our stars, but in ourselves.", "And this, our life, exempt from public haunt, finds tongues in trees, books in the running brooks, sermons in stones, and good in everything.", "Expectation is the root of all heartache.", "I like this place and could willingly waste my time in it.", "Better three hours too soon than a minute too late.", "Life's but a walking shadow, a poor player, that struts and frets his hour upon the stage, and then is heard no more; it is a tale told by an idiot, full of sound and fury, signifying nothing.", "My tongue will tell the anger of my heart, or else my heart concealing it will break.", "Brevity is the soul of wit.", "Give sorrow words; the grief that does not speak knits up o-er wrought heart and bids it break.", "Look like the innocent flower, but be the serpent under it.", "One may smile, and smile, be a villain.", "Conscience doth make cowards of us all.", "Let me be that I am and seek not to alter me.", "Et tu, Brute?", "O, beware, my lord, of jealousy; it is the green-ey'd monster, which doth mock the meat it feeds on.", "If we are true to ourselves, we can not be false to anyone.", "Be great in act, as you have been in thought.", "Suspicion always haunts the guilty mind.", "All things are ready, if our mind be so.", "Many a true word hath been spoken in jest.", "For sweetest things turn sourest by their deeds; lillies that fester smell far worse than weeds.", "The Devil hath power to assume a pleasing shape.", "Thought is free.", "April hath put a spirit of youth in everything.", "Summer's lease hath all too short a date.", "Our bodies are our gardens to the which our wills are gardeners.", "The tempter or the tempted, who sins most?", "Men should be what they seem.", "He jests at scars that never felt a wound.", "I would not wish any companion in the world but you.", "Self-love, my liege, is not so vile a sin, as self-neglecting.", "Doubt thou the stars are fire, doubt that the sun doth move. Doubt truth to be a liar, but never doubt I love.", "I am one who loved not wisely but too well.", "A young woman in love always looks like patience on a monument smiling at grief.", "My bounty is as boundless as the sea, my love as deep; the more I give to thee, the more I have, for both are infinite.", "They do not love that do not show their love.", "I love you with so much of my heart that none is left to protest.", "Love is heavy and light, bright and dark, hot and cold, sick and healthy, asleep and awake.", "Shall I compare thee to a summer's day? Thou art more lovely and more temperate.", "Love all, trust a few, do wrong to none.", "Kindness in women, not their beauteous looks, shall win my love.", "Love looks not with the eyes, but with the mind, and therefore is winged Cupid painted blind.", "Do not swear by the moon, for she changes constantly. Then your love would also change.", "If music be the food of love, play on.", "Love is too young to know what conscience is.", "Did my heart love till now? Forswear it, sight! For I ne'er saw true beauty till this night.", "Don't waste your love on somebody, who doesn't value it.", "And yet, to say the truth, reason and love keep little company together nowadays.", "Love is a smoke made with the fume of sighs.", "Go to your bosom: Knock there, and ask your heart what it doth know.", "In black ink my love may still shine bright.", "Love alters not with his brief hours and weeks, but bears it out even to the edge of doom.", "See how she leans her cheek upon her hand. O, that I were a glove upon that hand that I might touch that cheek!", "The course of true love never did run smooth.", "Love sought is good, but given unsought, is better.", "For which of my bad parts didst thou first fall in love with me?", "Speak low, if you speak love.", "Love comforteth like sunshine after rain.", "Good night, good night! Parting is such sweet sorrow, that I shall say good night till it be morrow.", "So long as men can breathe or eyes can see, so long lives this and this gives life to thee.", "For you, in my respect, are all the world.", "Love is merely a madness.", "Love is not love which alters when it alteration finds.", "How art thou out of breath when thou hast breath to say to me that thou art out of breath?", "I wish my horse had the speed of your tongue.", "Do you not know I am a woman? When I think, I must speak.", "I had rather hear my dog bark at a crow, than a man swear he loves me.", "'I can see that he's not in your good books,' said the messenger. 'No, and if he were I would burn my library.'", "God has given you one face, and you make yourself another.", "Misery acquaints a man with strange bedfellows.", "He that loves to be flattered is worthy o' the flatterer.", "Life is as tedious as twice-told tale, vexing the dull ear of a drowsy man.", "Maids want nothing but husbands, and when they have them, they want everything.", "O thou invisible spirit of wine, if thou hast no name to be known by, let us call thee devil.", "Lord, what fools these mortals be!", "I will praise any man that will praise me.", "My pride fell with my fortunes.", "Better a witty fool than a foolish wit.", "Is it not strange that desire should so many years outlive performance?", "I dote on his very absence.", "There's many a man has more hair than wit.", "Cowards die many times before their deaths; the valiant never taste of death but once.", "A fool thinks himself to be wise, but a wise man knows himself to be a fool.", "I am not bound to please thee with my answer."]; + +const quotesjane = ["The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.", "Friendship is certainly the finest balm for the pangs of disappointed love", "Give a loose rein to your fancy, indulge your imagination in every possible flight which the subject will afford", "There is nothing I would not do for those who are really my friends. I have no notion of loving people by halves, it is not my nature.", "I declare after all there is no enjoyment like reading! How much sooner one tires of anything than of a book!", "In vain have I struggled. It will not do. My feelings will not be repressed. You must allow me to tell you how ardently I admire and love you.", "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.", "I cannot fix on the hour, or the spot, or the look or the words, which laid the foundation. It is too long ago. I was in the middle before I knew that I had begun.", "Angry people are not always wise.", "I hate to hear you talk about all women as if they were fine ladies instead of rational creatures. None of us want to be in calm waters all our lives.", "Vanity and pride are different things, though the words are often used synonymously.", "You pierce my soul. I am half agony, half hope. Tell me not that I am too late, that such precious feelings are gone for ever.", "There is a stubbornness about me that never can bear to be frightened at the will of others. My courage always rises at every attempt to intimidate me.", "What are men to rocks and mountains?", "I could easily forgive his pride, if he had not mortified mine.", "Ah! There is nothing like staying at home, for real comfort.", "If I loved you less, I might be able to talk about it more.", "I always deserve the best treatment because I never put up with any other.", "My good opinion once lost is lost forever.", "There could have been no two hearts so open, no tastes so similar, no feelings so in unison", "Nothing ever fatigues me, but doing what I do not like.", "I wish, as well as everybody else, to be perfectly happy; but, like everybody else, it must be in my own way.", "There are people, who the more you do for them, the less they will do for themselves.", "One half of the world cannot understand the pleasures of the other.", "...when pain is over, the remembrance of it often becomes a pleasure.", "Without music, life would be a blank to me.", "You must be the best judge of your own happiness.", "If adventures will not befall a young lady in her own village, she must seek them abroad.", "I am only resolved to act in that manner, which will, in my own opinion, constitute my happiness, without reference to you, or to any person so wholly unconnected with me.", "I am excessively diverted.", "We have all a better guide in ourselves, if we would attend to it, than any other person can be.", "A fondness for reading, properly directed, must be an education in itself.", "Indeed, I am very sorry to be right in this instance. I would much rather have been merry than wise.", "We do not suffer by accident.", "Anne hoped she had outlived the age of blushing; but the age of emotion she certainly had not.", "Do not let the behaviour of others destroy your inner peace.", "There are as many forms of love as there are moments in time.", "Indulge your imagination in every possible flight.", "Obstinate, headstrong girl!", "Why not seize the pleasure at once? -- How often is happiness destroyed by preparation, foolish preparation!", "Is not general incivility the very essence of love?", "She had a lively, playful disposition that delighted in anything ridiculous.", "Now I must give one smirk, and then we may be rational again.", "It is not what we think or feel that makes us who we are. It is what we do. Or fail to do...", "Money can only give happiness where there is nothing else to give it.", "I was so anxious to do what is right that I forgot to do what is right."]; + +const quotesother = ["The cure for anything is salt water, sweat, tears, or the salt sea.", "Stuff your eyes with wonder. Live as if you'd drop dead in ten seconds. See the world.", "It's opener, out there, in the wide, open air.", "Oh, the places you'll go!", "A little magic can take you a long way.", "Most of the really exciting things we do in our lives scare us to death. They wouldn't be exciting if they didn't.", "Somewhere inside all of us is the power to change the world.", "if you have good thoughts they will shine out of your face like sunbeams and you will always look lovely.", "I understand what you're saying, and your comments are valuable, but I'm gonna ignore your advice.", "Meanings is not important, said the BFG. I cannot be right all the time. Quite often I is left instead of right.", "It doesn't matter who you are or what you look like, so long as somebody loves you.", "A little nonsense now and then is relished by the wisest men.", "But in a solitary life, there are rare moments when another soul dips near yours, as stars once a year brush the earth. Such a constellation was he to me.", "He showed me his scars, and in return he let me pretend that I had none.", "Humbling women seems to me a chief pastime of poets. As if there can be no story unless we crawl and weep.", "I thought: I cannot bear this world a moment longer. Then, child, make another.", "Only that: we are here. This is what it means to swim in the tide, to walk the earth and feel it touch your feet. This is what it means to be alive.", "I will not be like a bird bred in a cage, I thought, too dull to fly even when the door stands open.", "The truth is, men make terrible pigs.", "It was my first lesson. Beneath the smooth, familiar face of things is another that waits to tear the world in two.", "As you set out for Ithaka, hope your road is a long one, Keep Ithaka always in your mind. Arriving there is what you're destined for. But don't hurry the journey.", "It takes a great deal of bravery to stand up to our enemies, but just as much to stand up to our friends.", "If you want to know what a man's like, take a good look at how he treats his inferiors, not his equals.", "When in doubt, go to the library.", "It is our choices, Harry, that show what we truly are, far more than our abilities.", "Don't worry. You're just as sane as I am.", "I have hated words and I have loved them, and I hope I have made them right.", "Like most misery, it started with apparent happiness.", "A DEFINITION NOT FOUND IN THE DICTIONARY: Not leaving: an act of trust and love, often deciphered by children", "One was a book thief. The other stole the sky.", "In his blue gardens men and girls came and went like moths among the whisperings and the champagne and the stars.", "So we beat on, boats against the current, borne back ceaselessly into the past.", "He looked at her the way all women want to be looked at by a man.", "The loneliest moment in someone's life is when they are watching their whole world fall apart, and all they can do is stare blankly.", "Whenever you feel like criticizing any one...just remember that all the people in this world haven't had the advantages that you've had.", "In the midst of winter, I found there was, within me, an invincible summer. And that makes me happy.", "And in me too the wave rises. It swells; it arches its back. I am aware once more of a new desire", "Lock up your libraries if you like; but there is no gate, no lock, no bolt that you can set upon the freedom of my mind.", "One cannot think well, love well, sleep well, if one has not dined well.", "People who love to eat are always the best people", "Why, sometimes I've believed as many as six impossible things before breakfast.", "It's no use going back to yesterday, because I was a different person then.", "To live is the rarest thing in the world. Most people exist, that is all.", "We are all in the gutter, but some of us are looking at the stars.", "The truth is rarely pure and never simple.", "Never love anyone who treats you like you're ordinary.", "Not all those who wander are lost", "When spring came, even the false spring, there were no problems except where to be happiest.", "I don't want comfort. (...) I want poetry. I want danger. I want freedom. I want goodness. I want sin.", "Morning without you is a dwindled dawn.", "I took a deep breath and listened to the old brag of my heart. I am, I am, I am.", "Earth laughs in flowers", "I felt my lungs inflate with the onrush of scener air, mountains, trees, people. I thought, This is what it is to be happy.", "She never went astray, never made an error. I had been jealous then. Now I thought: what a burden. What an ugly weight upon your back.", "Live in the sunshine, swim the sea, drink the wild air.", "I felt once more how simple and frugal a thing is happiness: a glass of wine, a roast chestnut, a wretched little brazier, the sound of the sea. Nothing else.", "I will clamber through the Clouds and exist.", "Just living isn't enough, said the butterfly, one must have sunshine, freedom and a little flower.", "It is a serious thing, to be alive, on this fresh morning, in this broken world", "We loved with a love that was more than love", "To young men contemplating a voyage I would say, go.", "Encuéntrate y pon la serendipia del revés. Haz del mundo tu pista de baile.", "I discover myself on the verge of a usual mistake", "I too am not a bit tamed I too am untranslatable; I sound my barbaric yawp over the roofs of the world.", "Do I contradict myself? Very well then, I contradict myself; I am large, I contain multitudes.", "No need to hurry. No need to sparkle. No need to be anybody but oneself.", "Procura guardar siempre por encima de tu vida un buen espacio de cielo.", "El mundo hay que fabricárselo uno mismo, hay que crear peldanos que te suban, que te saquen del pozo. Hay que inventar la vida porque acaba siendo verdad", "En vista de lo visto me desvisto, me desnudo a mí misma y me mantengo, me encanta este tener lo que no tengo", "Lo mejor del olvido es el recuerdo", "I suggest we learn to love ourselves before it's made illegal", "To unpathed waters, undreamed shores.", ]; + +var skullleft = { + width: 32, + height: 40, + bpp: 4, + buffer: require("heatshrink").decompress(atob("AAcBrXf//sygOJz//AAfmB48eBwgAB4QPGv4PG/8QB4vvB49gBwkDBw//0APEhQPI6APEt4PI+AODgX/23P/eOB5MM/EA1/xMYoGBAAU/wBQBHAMDMgYPEh8wl35+YGBlxPHhOk7+T/RlF2BPF/kSTIRlDyAPDgomC//pxRPDqAPDhqWC36/Kn4fCeAv9TwmP//5HYXfB4XlB4ldqQKC+l5AYO0dosIwwPC+f25+CBwusmn6FYX1/9AgEwB4ku/G/q39//2EINt/IPEgS4B+VQ7nJOIYiBN4v9qMRidPXwcC5yfEAAOSk2eMgcvQIlOTgoAB/e/B4kB14PG/ihGgZSBAAfJxYPGKQQADYgYPFgNZ5vFif/54PIAAnPB4X6UYoAEpRFB/19+wOIgwtCzICB6oPHr4LB+4xC/APHg4LBtwiCyAwInn3BwX3MBVvBwP9L5QPDwIOKgG/BxsAxdQAoY=")) +}; +var skullright = { + width: 32, + height: 40, + bpp: 4, + buffer: require("heatshrink").decompress(atob("AAtJ53//uliAOIt3/AAf5CA8sBwgAB8IOFgIOG//3B4sGB4/+B4sKB4//mAPEhoPI0APEh4PI+wPK9G//lr/8gB4cfLYsf/UAx/AB4/+JQJGBmEI+ZPH8QGBn+f8UD+APDhZZF185/tKyAPDhJZEUoMh55PFgpPD1Ge/44CqC/K/aoC6CAE77uFD4RfEgFeBoX9IYWf//4aItLBgOXp4UCkvVB4sAlH8t8/B4VowAOFgcAgn/r4zC19D5QPEz/WDoNv//fsv7x6nCAAIcBNoWc5sFl7GBSAXskDAD+kxiMVO4JvD7/yLQfpsUpAoSfD7/736gFAAPkTorHBAAv6iAPF3GcBwn+mBsEXQYPEXgoPD/n/mNc7mVFoibB14PC/i3GAAVv6/vHIOkBxFdDoOZGIVgB4+PFoV/AYP1B48JDgXmAQNwGBAcC/9/4ZPJgaPB//2BxIABjAPOCAX7B5kAgu4BZI=")) +}; +var janeright = { + width: 32, + height: 40, + bpp: 1, + transparent: -1, + palette: new Uint16Array([0, 65535]), + buffer: require("heatshrink").decompress(atob("AAM8AQMD/4DBg//4EAh//8EAj//+ADC/ALC/wDGDgIPBA4IDF/gjEAYIrDwADCG4ZDCv4LBgE/AYYXBAYIfBB4IDDI4IDFJYMAG4UBH4RnBAYIvBK4pPHNYYDBKYobBIYMDCwIDBCwIDBHQUDBwI")) +}; +var janeleft = { + width: 32, + height: 40, + bpp: 1, + transparent: -1, + palette: new Uint16Array([0, 65535]), + buffer: require("heatshrink").decompress(atob("gE8AYX/4EAgf/8EAg//+EAh///EAj4DCv4LCAYf/A4gPDAYM/AYQjDFYIDBgIDCDgIDDG4MB/wGBgP8AYMDAYUHAYYPCh4DGj4DCG4IDBH4OAAYRnBAYIvBLYpPFNYZTBAYJTFDYIYBEYIYBAYUPAYUBAYI=")) +}; +var bookleft = { + width: 32, + height: 40, + bpp: 4, + transparent: -1, + palette: new Uint16Array([0, 52857, 2113, 65535, 4258, 32, 25388, 29614, 4226, 6371, 48631, 16936, 2145, 8452, 12678, 52825]), + buffer: require("heatshrink").decompress(atob("AH8iAC9VlFJ7vu9IMGlHd9e2s931WjigPGxWq1/xiIACtAPGsYMDAAMTygOFlsxB4sXhOCB4knBwsRn0I9gODku/B42ihviB4cpuI+G2EO6QPDlY+GjXQk3CB4efFw0XkHbHwlzB437gFkB4cuFw0R1MMuCcEBw0RuEGJwmxBw0T6FXTgmhB40agHuB4cmHw87gDOE6I+H/EE3htDTgw+BssOVodbFw8R8FbyQPCzQOHi+Aww+DsIPHixeENpER1sAuQPC40a1Wv/4ABTocNsoPC5eczue92721nu938EHJwcp2EAAAkFrMAo9SJ4fWoAQFgkA3wODAAO3u92tfu7KXBzCtEAANQgGE7vrIANms2yB4oAEktVlGJwQPKABBMFAH4AJA=")) +}; +var bookright = { + width: 32, + height: 40, + bpp: 4, + transparent: -1, + palette: new Uint16Array([0, 52857, 2113, 65535, 4258, 32, 25388, 29614, 4226, 6371, 48631, 16936, 2145, 8452, 12678, 52825]), + buffer: require("heatshrink").decompress(atob("AH8iAB+e93dymCqoPJoM61V3u1r33dwQPGw0RAAUf/Wq1APGpMxCAYABmwOFlGQuIPFifSB4nOwHjB4sRuQPEl3QlQPG/dSB4ct8ELIA0XyQPDllihuhIA2yGAm9gRQGiP5B4lGgG/B403IAkH4GaB40T8RQEsEHB40RUQt1hpgGiMbB4nugBgGiMaUQmGgGzIA9iB4fLoGPIA8dWIngqxAHi5xDlO1hwfHie1EAdohChHiOpMAthB48WB4cngHaB45xEq3QUIf/AAP61WhthQDuEOu93u1r3fu9PZ5O8B4UlukAytQgAAEheSIAfrgFABwsEtrBEw2JQ4Od922s93vYOEkUrs1mHwO+7tIgEFB4sozGCqqpDACZIFAH4AJA")) +}; + + +let hour; +let minute; +let date; +let timer; +const watchtop = 55; +const hoursize = 32; +const datesize = 14; +let qnumber; +let quotehour; +let qtype; +let quotes; +let imageleft = bookleft; +let imageright = bookright; + +function draw() { + const d = new Date(); + const newHour = ('0' + d.getHours()).substr(-2); + const newMinute = ('0' + d.getMinutes()).substr(-2); + const newDate = locale.date(d).trim(); + g.setFontAlign(0, 0, 0); + if (newHour !== hour) { + g.setFont("Vector", hoursize); + g.setColor(0x0000); + g.drawString(hour, 60, watchtop); + g.setColor(0xFFFF); + g.drawString(newHour, 60, watchtop); + hour = newHour; + drawQuote(); + } + g.setFontAlign(0, 0, 0); + if (newMinute !== minute) { + g.setFont("Vector", hoursize); + g.setColor(0x0000); + g.drawString(minute, 120, watchtop); + g.setColor(0xFFFF); + g.drawString(newMinute, 120, watchtop); + minute = newMinute; + } + g.setFontAlign(0, 0, 0); + if (newDate !== date) { + g.setFont("Vector", datesize); + g.setColor(0x0000); + g.drawString(date, 88, 168); + g.setColor(0xFFFF); + g.drawString(newDate, 88, 168); + date = newDate; + } +} + +function drawStrCenter(str, colour) { + g.setColor(colour); + g.setFont("Vector", 13); + str = "\"" + str + "\""; + maxLength = 24; + var ta = []; + let index = 0; + let linestart = 0; + while (index < str.length) { + newIndex = str.indexOf(" ", index); + if (newIndex == -1) { + if ((str.length - linestart) > maxLength) { + ta.push(str.substring(linestart, index)); + ta.push(str.substring(index, str.length)); + } else ta.push(str.substring(linestart, str.length)); + break; + } else if (((newIndex - linestart) > maxLength)) { + ta.push(str.substring(linestart, index)); + linestart = index; + } else index = newIndex + 1; + } + y = 110 - ta.length * 5; + ta.forEach((e) => { + g.setFontAlign(0, -1).drawString(e.trim(), 88, y); + y += 12; + }); +} + +function drawQuote() { + if (qnumber == undefined || quotehour != hour) { + if (qnumber != undefined) drawStrCenter(quotes[qnumber], 0x0000); + qtype = Math.floor(Math.random() * 3); + if (qtype === 0) { + quotes = quotesshakespeare; + imageleft = skullleft; + imageright = skullright; + } + if (qtype === 1) { + quotes = quotesjane; + imageleft = janeleft; + imageright = janeright; + } + if (qtype === 2) { + quotes = quotesother; + imageleft = bookleft; + imageright = bookright; + } + drawImages(); + qnumber = Math.floor(Math.random() * quotes.length); + quotehour = hour; + } + drawStrCenter(quotes[qnumber], 0xFFFF); +} + +function drawImages() { + g.drawImage(imageleft, 0, 30); + g.drawImage(imageright, 142, 30); +} + +function startDrawing() { + hour = ''; + minute = ''; + date = ''; + g.setColor(0X0000); + g.fillRect(0, 0, 176, 176); + g.setFontAlign(0, 0, 0); + g.setFont("Vector", hoursize); + g.setColor(0xFFFF); + g.drawString(":", 88, watchtop); + drawImages(); + console.log("drawing image"); + Bangle.drawWidgets(); + draw(); + timer = setInterval(draw, 1000); +} + +function stopDrawing() { + if (timer) { + clearInterval(timer); + timer = undefined; + } +} +g.clear(); +Bangle.on('lcdPower', (on) => { + stopDrawing(); + if (on) { + startDrawing(); + } +}); + +Bangle.setUI("clock"); +Bangle.loadWidgets(); +startDrawing(); \ No newline at end of file diff --git a/apps/quoteclock/app.png b/apps/quoteclock/app.png new file mode 100644 index 000000000..5cb224198 Binary files /dev/null and b/apps/quoteclock/app.png differ diff --git a/apps/quoteclock/metadata.json b/apps/quoteclock/metadata.json new file mode 100644 index 000000000..0102f010e --- /dev/null +++ b/apps/quoteclock/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "quoteclock", + "name": "Quote Clock", + "version": "0.01", + "description": "A clock showing quotes every hour", + "icon": "app.png", + "type": "clock", + "tags": "clock,shakespeare", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"quoteclock.app.js","url":"app.js"}, + {"name":"quoteclock.img","url":"app-icon.js","evaluate":true} + ] + } \ No newline at end of file diff --git a/apps/rebble/ChangeLog b/apps/rebble/ChangeLog index 78ba0c5da..bde6fda82 100644 --- a/apps/rebble/ChangeLog +++ b/apps/rebble/ChangeLog @@ -14,3 +14,4 @@ 0.14: cleanup code and fix fastload issue 0.15: fix draw before widget hide 0.16: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps +0.17: Add fullscreen option (on by default) to show widgets, adjust sidebar 1 and 2 when fullscreen is off diff --git a/apps/rebble/README.md b/apps/rebble/README.md index 0ecb51d7a..e55978dc4 100644 --- a/apps/rebble/README.md +++ b/apps/rebble/README.md @@ -10,14 +10,22 @@ * Tap top or bottom right to instantly cycle to the next sidebar * Uses pedometer widget to get latest step count * Dependant apps are installed when Rebble installs -* Uses the whole screen, widgets are made invisible but still run in the background +* When in fullscreen widgets are made invisible but still run in the background * The icon is James Dean - 'Rebel Without a Cause' +## Fullscreen + ![](screenshot_rebble.png) ![](screenshot_rebble2.png) ![](screenshot_rebble3.png) ![](screenshot_rebble4.png) +## With widgets + +![](screenshot_rebble_w1.png) +![](screenshot_rebble_w2.png) +![](screenshot_rebble_w3.png) + ## Future Enhancements * Support for Weather Icons in the Steps Sidebar diff --git a/apps/rebble/metadata.json b/apps/rebble/metadata.json index 6242236c8..56fe014ff 100644 --- a/apps/rebble/metadata.json +++ b/apps/rebble/metadata.json @@ -2,12 +2,12 @@ "id": "rebble", "name": "Rebble Clock", "shortName": "Rebble", - "version": "0.16", + "version": "0.17", "description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion", "readme": "README.md", "icon": "rebble.png", "dependencies": {"mylocation":"app"}, - "screenshots": [{"url":"screenshot_rebble.png"}], + "screenshots": [{"url":"screenshot_rebble.png"}, {"url":"screenshot_rebble2.png"}, {"url":"screenshot_rebble3.png"}, {"url":"screenshot_rebble4.png"}, {"url":"screenshot_rebble_w1.png"}, {"url":"screenshot_rebble_w2.png"}, {"url":"screenshot_rebble_w3.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS2"], diff --git a/apps/rebble/rebble.app.js b/apps/rebble/rebble.app.js index 445c30125..bc18d997d 100644 --- a/apps/rebble/rebble.app.js +++ b/apps/rebble/rebble.app.js @@ -40,7 +40,7 @@ Graphics.prototype.setFontKdamThmor = function(scale) { } let loadSettings=function() { - settings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true,'sideTap':0}; + settings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'fullScreen': true, 'sideTap':0}; //sideTap 0 = on | 1 = sidebar1... let tmp = require('Storage').readJSON(SETTINGS_FILE, 1) || settings; @@ -118,32 +118,60 @@ Graphics.prototype.setFontKdamThmor = function(scale) { if (drawCount % 60 == 0) updateSunRiseSunSet(location.lat, location.lon); - + g.reset(); - g.setColor(g.theme.bg); - g.fillRect(0, 0, w2, h); - g.setColor(settings.bg); - g.fillRect(w2, 0, w, h); + + if (settings.fullScreen) { + g.setColor(g.theme.bg); + g.fillRect(0, 0, w2, h); + g.setColor(settings.bg); + g.fillRect(w2, 0, w, h); - // time - g.setColor(g.theme.fg); - g.setFontKdamThmor(); - g.setFontAlign(0, -1); - g.drawString(hh, w2/2, 10 + 0); - g.drawString(mm, w2/2, 10 + h/2); + // time + g.setColor(g.theme.fg); + g.setFontKdamThmor(); + g.setFontAlign(0, -1); + g.drawString(hh, w2/2, 10 + 0); + g.drawString(mm, w2/2, 10 + h/2); - switch(sideBar) { - case 0: - drawSideBar1(); - break; - case 1: - drawSideBar2(); - break; - case 2: - drawSideBar3(); - break; + switch(sideBar) { + case 0: + drawSideBar1(); + break; + case 1: + drawSideBar2(); + break; + case 2: + drawSideBar3(); + break; + } + } else { + g.setColor(g.theme.bg); + g.fillRect(0, 24, 113, 176); + g.setColor(settings.bg); + g.fillRect(113, 24, 176, 176); + + // time + g.setColor(g.theme.fg); + g.setFontKdamThmor(); + g.setFontAlign(0, -1); + g.drawString(hh, 57, 24); + g.drawString(mm, 57, 100); + + switch(sideBar) { + case 0: + drawSideBar1Alt(); + break; + case 1: + drawSideBar2Alt(); + break; + case 2: + drawSideBar3(); + break; + } } + drawCount++; queueDraw(); } @@ -164,6 +192,16 @@ Graphics.prototype.setFontKdamThmor = function(scale) { drawDateAndCalendar(w3, h/2, dy, dd, mm); } + + let drawSideBar1Alt=function() { + let date = new Date(); + let dy= require("date_utils").dow(date.getDay(),1).toUpperCase(); + let dd= date.getDate(); + let mm= require("date_utils").month(date.getMonth()+1,1).toUpperCase(); + let yy = date.getFullYear(); + + drawDateAndCalendarAlt(145, 46, dy, dd, mm, yy); + } let drawSideBar2=function() { drawBattery(w2 + (w-w2-wb)/2, h/10, wb, 17); @@ -178,6 +216,14 @@ Graphics.prototype.setFontKdamThmor = function(scale) { setSmallFont(); g.setFontAlign(0, -1); g.drawString(formatSteps(), w3, 7*h/8); + } + + let drawSideBar2Alt=function() { + // steps + g.drawImage(boot_img, 113, 59, { scale: 1 }); + setSmallFont(); + g.setFontAlign(0, -1); + g.drawString(formatSteps(), 145, 122); } // sunrise, sunset times @@ -212,6 +258,28 @@ Graphics.prototype.setFontKdamThmor = function(scale) { g.setFontAlign(0, -1); g.drawString(mm.toUpperCase(), x, y + 70); } + + let drawDateAndCalendarAlt=function(x, y, dy, dd, mm, yy) { + // day + setTextColor(); + setSmallFont(); + g.setFontAlign(0, -1); + g.drawString(dy.toUpperCase(), x, y); + + drawCalendar(x - 18, y + 28, 35, 3, dd); + + // month + setTextColor(); + setSmallFont(); + g.setFontAlign(0, -1); + g.drawString(mm.toUpperCase(), x, y + 70); + + // year + setTextColor(); + setSmallFont(); + g.setFontAlign(0, -1); + g.drawString(yy, x, y + 94); + } // at x,y width:wi thicknes:th let drawCalendar=function(x,y,wi,th,str) { @@ -311,7 +379,10 @@ Graphics.prototype.setFontKdamThmor = function(scale) { if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; delete Graphics.prototype.setFontKdamThmor; - Bangle.removeListener('charging',chargingListener); + + if (settings.fullScreen) { + Bangle.removeListener('charging',chargingListener); + } } let main=function(){ @@ -341,17 +412,17 @@ Graphics.prototype.setFontKdamThmor = function(scale) { }); } - - - Bangle.on('charging',chargingListener); - - Bangle.loadWidgets(); - require("widget_utils").hide(); + + if (settings.fullScreen) { + Bangle.on('charging',chargingListener); + require("widget_utils").hide(); + } else { + Bangle.drawWidgets(); + } + draw(); - } - main(); } diff --git a/apps/rebble/rebble.settings.js b/apps/rebble/rebble.settings.js index 46252d156..0048955c6 100644 --- a/apps/rebble/rebble.settings.js +++ b/apps/rebble/rebble.settings.js @@ -2,7 +2,7 @@ const SETTINGS_FILE = "rebble.json"; // initialize with default settings... - let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'sideTap':0}; + let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'fullScreen': true, 'sideTap':0}; //sideTap 0 = on| 1= sideBar1 | 2 = ... // ...and overwrite them with any saved values @@ -37,6 +37,14 @@ localSettings.bg = bg_code[v]; save(); }, + }, + 'Fullscreen': { + value: localSettings.fullScreen, + onchange: (v) => { + localSettings.fullScreen = v; + save(); + showMenu(); + } }, 'Auto Cycle': { value: localSettings.autoCycle, @@ -74,4 +82,4 @@ } showMenu(); -}) \ No newline at end of file +}) diff --git a/apps/rebble/screenshot_rebble_w1.png b/apps/rebble/screenshot_rebble_w1.png new file mode 100644 index 000000000..0057e862b Binary files /dev/null and b/apps/rebble/screenshot_rebble_w1.png differ diff --git a/apps/rebble/screenshot_rebble_w2.png b/apps/rebble/screenshot_rebble_w2.png new file mode 100644 index 000000000..09a558d6c Binary files /dev/null and b/apps/rebble/screenshot_rebble_w2.png differ diff --git a/apps/rebble/screenshot_rebble_w3.png b/apps/rebble/screenshot_rebble_w3.png new file mode 100644 index 000000000..5754e43a4 Binary files /dev/null and b/apps/rebble/screenshot_rebble_w3.png differ diff --git a/apps/slevel/spiritlevel-icon.js b/apps/slevel/spiritlevel-icon.js index 93a42fded..3f051f95f 100644 --- a/apps/slevel/spiritlevel-icon.js +++ b/apps/slevel/spiritlevel-icon.js @@ -1,2 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AF8OoMRC6nu8kRiAuT93uGCgWBGCguCGCgsBoMUGCQuBFYMRDAIwQFQUhDAIFCFx/hiUyC4NOGAKMP8MRmYwBjwwBCxkEFAIXBiYwBC4PuC5hxCC4IwCDwPgC5gpBpxxBiMSL4QWMgQpBIIKnEFxsikcxOISODCxkDmUiLIQADFxsjUIQWELp0iLwYuQgMzkUiFydBkcyFycOoMSXoIuST4YuTB4NBZwIuSABAuPAA5dQdSQuBoIXBLwouPiUxGAguOC4imDRh3hC4wuMgBABC44WMgBxBI4wuNgBxCC4MhAoQWNC4IwBU4guOgEBFQVBiguQGAi7PGBCMPGBAuRGAoWSGAYuTAH4AcA=")) - +require("heatshrink").decompress(atob("mEw4cA///ov+5lChWMyGuxdzpdj4/lKf4AUkgQPgm0wAiPy2QCBsBkmS6QRNhIRBrVACJlPu2+pdICBcCrVJlvJtIRLifStMl3MtkARKydUyMkzMl0CMKyWWyUk1MkSJXkyR7BogRLgVcydSrVGzLHKgdLyfSpdE3JYKklqTwNJknJYJVkxcSp+pnygKhMs1OSEQOSYhVJl1bCIbBK5Mq7gRCyARJiVbqyPBCIKMKuVM24yBCIIiJnVOqu5CISMKp9JlvJCIRXKpP3nxoCRhUSBwSMNBwaMMgn6yp6DRhUl0mypiMMgM9ksipaMMhMtCINKRhlJmoRBpJuBCBIRGRhUE5I1CpKMLgmZn5ZDGhUAycnRoNMRhTDCsn3tfkRhLnDTwYQLNgSMMUQkyRhbGEkyMKAApFOAH4AGA")) \ No newline at end of file diff --git a/apps/slevel/spiritlevel.png b/apps/slevel/spiritlevel.png index bd2f5b645..1a4559d44 100644 Binary files a/apps/slevel/spiritlevel.png and b/apps/slevel/spiritlevel.png differ diff --git a/apps/terminal/ChangeLog b/apps/terminal/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/terminal/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/terminal/app-icon.js b/apps/terminal/app-icon.js new file mode 100644 index 000000000..1c35a67d2 --- /dev/null +++ b/apps/terminal/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA///w8h7fOm2V/8fjkf/Gt58jKf4AtiVJkmSARFICK3PlnyCJ1t03aEcBZjCBVJCP4R/CK9N8gRPpmUEaGSuRHP+1KCJ8m5AjgI4PyNZ+cAQIQCwgjKCgQRNdP4R/CL4PJAQIRWAH4ApA==")) \ No newline at end of file diff --git a/apps/terminal/app.js b/apps/terminal/app.js new file mode 100644 index 000000000..79a50c73f --- /dev/null +++ b/apps/terminal/app.js @@ -0,0 +1,263 @@ +/* Espruino VT100 JS REPL + + +TODO: Add option to connect to a remote BLE device + +*/ + +var settings = Object.assign({ + // default values + textSize: 1, + loopAround: 1, + oneToOne: 0, + speedScaling: 24 +}, /*require('Storage').readJSON("repl.settings.json", true) || */{}); + +// Key Maps for Keyboard +var KEYMAPLOWER = [ + "`1234567890-=\b\b", + "\tqwertyuiop[]\n\n", + "\2asdfghjkl;'#\x82\n", + "\2\\zxcvbnm,./\x80\x83\x81", +]; +var KEYMAPUPPER = [ + "¬!\"£$%^&*()_+\b\b", + "\tQWERTYUIOP{}\n\n", + "\2ASDFGHJKL:@~\x82\n", + "\2|ZXCVBNM<>?\x80\x83\x81", +]; +var KEYIMGL = Graphics.createImage(` + + # # + ## # +###### + ## # + # # + + + # + ### + ##### + # + # + # + # + # + # + # + # + # + # +`); +KEYIMGL.transparent = 0; +var KEYIMGR = Graphics.createImage(` + + + # + ## + ######### + ## + # + + + + ####### + # + # + # + # + # + ##### + ### + # + + # + ### + ##### + + + # # + ## ## +### ##### ### + ## ### ## + # # # + +`); +KEYIMGR.transparent = 0; +/* If a char in the keymap is >=128, +subtract 128 and look in this array for +multi-character key codes*/ +var KEYEXTRA = [ + String.fromCharCode(27, 91, 68), // 0x80 left + String.fromCharCode(27, 91, 67), // 0x81 right + String.fromCharCode(27, 91, 65), // 0x82 up + String.fromCharCode(27, 91, 66), // 0x83 down + String.fromCharCode(27, 91, 53, 126), // 0x84 page up + String.fromCharCode(27, 91, 54, 126), // 0x85 page down +]; + +// state +const R = Bangle.appRect; +var kbx = 0, + kby = 0, + kbdx = 0, + kbdy = 0, + kbShift = false, + flashToggle = false; +const PX = 12, + PY = 16, + DRAGSCALE = settings.speedScaling; +var xoff = 0, + yoff = g.getHeight() - PY * 4; + +function draw() { + "ram"; + var map = kbShift ? KEYMAPUPPER : KEYMAPLOWER; + //g.drawImage(KEYIMG,0,yoff); + g.reset().setFont("6x8:2"); + g.clearRect(R.x, yoff, R.x2, R.y2); + if (kbx >= 0) + g.setColor(g.theme.bgH).fillRect(xoff + kbx * PX, yoff + kby * PY, xoff + (kbx + 1) * PX - 1, yoff + (kby + 1) * PY - 1).setColor(g.theme.fg); + g.drawImage(KEYIMGL, xoff - 1, yoff + PY, { scale: 2 }); + g.drawImage(KEYIMGR, xoff + PX * 12, yoff, { scale: 2 }); + var replace = /[\x80\x81\x82\x83\x84\x85]/g; + g.drawString(map[0].replace(replace," "), xoff, yoff); + g.drawString(map[1].replace(replace," "), xoff, yoff + PY); + g.drawString(map[2].replace(replace," "), xoff, yoff + PY * 2); + g.drawString(map[3].replace(replace," "), xoff, yoff + PY * 3); + g.flip(); +} + +function startTerminal(dataOutCallback) { + g.reset().clearRect(R); + // Set up the terminal + term = require("VT100").connect(g, { + charWidth: 6, + charHeight: 8 + }); + term.oy = R.y; + term.h = yoff; // we added this - it's not usually part of it + term.consoleHeight = 0 | ((term.h - term.oy) / term.charH); + term.scrollDown = function() { + g.setClipRect(R.x, term.y, R.x2, term.oy + term.h); + g.scroll(0, -this.charH); + g.setClipRect(R.x, R.y, R.x2, R.y2); + this.y--; + }; + term.fgCol = g.theme.fg; + term.bgCol = g.theme.bg; + draw(); + var flashInterval = setInterval(() => { + flashToggle = !flashToggle; + draw(); + }, 1000); + + function keyPress(kbx, kby) { + var map = kbShift ? KEYMAPUPPER : KEYMAPLOWER; + var ch = map[kby][kbx]; + if (ch == "\2") + kbShift = !kbShift; + else { + if (ch.charCodeAt(0) > 127) + ch = KEYEXTRA[ch.charCodeAt(0) - 128]; + dataOutCallback(ch); + } + Bangle.buzz(20); + draw(); + } + + Bangle.setUI({ + mode: "custom", + drag: e => { + if (settings.oneToOne) { + kbx = Math.max(Math.min(Math.floor((e.x - 16) / (6 * 2)), 13), 0); + kby = Math.max(Math.min(Math.floor((e.y - 120) / (8 * 2)), 3), 0); + //print(e.y, kby, e.x, kbx); + } + + if (!settings.oneToOne) { + kbdx += e.dx; + kbdy += e.dy; + var dx = Math.round(kbdx / DRAGSCALE), + dy = Math.round(kbdy / DRAGSCALE); + kbdx -= dx * DRAGSCALE; + kbdy -= dy * DRAGSCALE; + if (dx || dy) { + if (settings.loopAround) { + kbx = (kbx + dx + 15) % 15; + kby = (kby + dy + 4) % 4; + } else { + kbx = Math.max(Math.min((kbx + dx), 13), 0); + kby = Math.max(Math.min((kby + dy), 3), 0); + } + } + } + draw(); + + if (!e.b && e.y > Bangle.appRect.y && settings.oneToOne /*&& settings.releaseToSelect*/ ) + keyPress(kbx, kby); + }, + touch: () => { + if (!settings.oneToOne /*|| !settings.releaseToSelect*/ ) + keyPress(kbx, kby); + } + }); + let catchSwipe = () => { + E.stopEventPropagation && E.stopEventPropagation(); + }; + Bangle.prependListener && Bangle.prependListener('swipe', catchSwipe); // Intercept swipes on fw2v19 and later. Should not break on older firmwares. + return { + dataReceived : function(d) { + g.reset().setFont("6x8"); + // USB.write(e); // optionally mirror back to the PC + // Send characters to the terminal + for (var i in d) term.char(d[i]); + // update the screen + g.flip(); + } + }; +} + +function mainMenu() { + E.showMenu({ + "":{title:"Terminal"}, + /*LANG*/"JS REPL" : function() { + var t = startTerminal(function(d) { + LoopbackB.write(d); + }); + LoopbackB.on('data', function(d) { + t.dataReceived(d); + }); + // Now move the console to Loopback + LoopbackA.setConsole(); + }, + /*LANG*/"Bluetooth" : function() { + Bangle.setUI(); + E.showMessage(/*LANG*/"Scanning...", /*LANG*/"Bluetooth"); + NRF.findDevices(function(devices) { + if (!devices.length) + return E.showAlert("No devices found").then(() => mainMenu()); + var menu = { "" : { title: /*LANG*/"Bluetooth", back : () => mainMenu() } }; + devices.forEach(dev => { + var name = dev.name || dev.id.substr(0,17); + menu[name] = function() { + Bangle.setUI(); + E.showMessage(/*LANG*/"Connecting...", /*LANG*/"Bluetooth"); + require("ble_uart").connect(dev).then(function(uart) { + var t = startTerminal(function(d) { + uart.write(d); + }); + t.dataReceived("Connected to:\n "+name+"\n") + uart.on('data', function(d) { t.dataReceived(d); }); + }).catch(err => { + E.showAlert(err.toString()).then(() => mainMenu()); + }); + }; + }); + E.showMenu(menu); + }, { filters: [{ services: ['6e400001-b5a3-f393-e0a9-e50e24dcca9e'] }], timeout: 2000, active:true }); + } + }); +} + +mainMenu(); \ No newline at end of file diff --git a/apps/terminal/app.png b/apps/terminal/app.png new file mode 100644 index 000000000..ed883aede Binary files /dev/null and b/apps/terminal/app.png differ diff --git a/apps/terminal/metadata.json b/apps/terminal/metadata.json new file mode 100644 index 000000000..d32d00927 --- /dev/null +++ b/apps/terminal/metadata.json @@ -0,0 +1,14 @@ +{ "id": "terminal", + "name": "VT100 Terminal", + "shortName":"Terminal", + "version":"0.01", + "description": "Terminal and Keyboard that can be used as a REPL. You can type JS commands into Bangle.js's own REPL and execute them, or you can connect to other Bluetooth LE UART devices (like other Espruinos) and issue commands.", + "icon": "app.png", + "tags": "terminal,tool,bluetooth", + "screenshots" : [ { "url":"screenshot1.png" }, { "url":"screenshot2.png" } ], + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"terminal.app.js","url":"app.js"}, + {"name":"terminal.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/terminal/screenshot1.png b/apps/terminal/screenshot1.png new file mode 100644 index 000000000..e6d94d8d2 Binary files /dev/null and b/apps/terminal/screenshot1.png differ diff --git a/apps/terminal/screenshot2.png b/apps/terminal/screenshot2.png new file mode 100644 index 000000000..5b38b2ab7 Binary files /dev/null and b/apps/terminal/screenshot2.png differ diff --git a/apps/tetris/ChangeLog b/apps/tetris/ChangeLog index 86661f1b6..be909ae52 100644 --- a/apps/tetris/ChangeLog +++ b/apps/tetris/ChangeLog @@ -2,3 +2,5 @@ 0.02: Better controls, implement game over. 0.03: Implement mode and level selection screens. 0.04: Bring back old controls as "swipe" in menu, exit with button press +0.10: Major overhaul: added score, levels, bugfixes and misc, inspired by NES tetris +0.11: Save/Restore game state diff --git a/apps/tetris/metadata.json b/apps/tetris/metadata.json index ba73918d5..c82bb59f8 100644 --- a/apps/tetris/metadata.json +++ b/apps/tetris/metadata.json @@ -1,7 +1,7 @@ { "id": "tetris", "name": "Tetris", "shortName":"Tetris", - "version":"0.04", + "version":"0.11", "description": "Tetris", "icon": "tetris.png", "readme": "README.md", @@ -12,5 +12,8 @@ "storage": [ {"name":"tetris.app.js","url":"tetris.app.js"}, {"name":"tetris.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"tetris.json"} ] } diff --git a/apps/tetris/tetris.app.js b/apps/tetris/tetris.app.js index e4295732b..91dd20ab8 100644 --- a/apps/tetris/tetris.app.js +++ b/apps/tetris/tetris.app.js @@ -36,26 +36,45 @@ const tiles = [ const ox = 176/2 - 5*8; const oy = 8; +const FILE = "tetris.json"; + +const settings = Object.assign({ /* 0 .. simulated arrows 1 .. drag piece 2 .. accelerometer. 12 lines record. 3 .. altimeter */ -var control = 0, level = 0; + control: 0, + level: 0, + initialLevel: 0, + lines: 0, + score: 0, + pf: undefined, + ctn: Math.floor(Math.random()*7), // current tile number + ntn: Math.floor(Math.random()*7), // next tile number + ntr: Math.floor(Math.random()*4), // next tile rotation + dropInterval: undefined, +}, require('Storage').readJSON(FILE, true) || {}); + var alt_start = -9999; /* For altimeter control */ /* 0 .. menu 1 .. game 2 .. game over */ var state = 0; -var pf; +var px=4, py=0; + +function writeSettings() { + require('Storage').writeJSON(FILE, settings); +} + function initGame() { - pf = Array(23).fill().map(()=>Array(12).fill(0)); // field is really 10x20, but adding a border for collision checks - pf[20].fill(1); - pf[21].fill(1); - pf[22].fill(1); - pf.forEach((x,i) => { pf[i][0] = 1; pf[i][11] = 1; }); + settings.pf = Array(23).fill().map(()=>Array(12).fill(0)); // field is really 10x20, but adding a border for collision checks + settings.pf[20].fill(1); + settings.pf[21].fill(1); + settings.pf[22].fill(1); + settings.pf.forEach((x,i) => { settings.pf[i][0] = 1; settings.pf[i][11] = 1; }); } function rotateTile(t, r) { @@ -71,13 +90,18 @@ function rotateTile(t, r) { return nt; } +var time = Date.now(); +var ct = rotateTile(tiles[settings.ctn], Math.floor(Math.random()*4)); // current tile (rotated) + function drawBoundingBox() { g.setBgColor(0, 0, 0).clear().setColor(1, 1, 1); g.theme.bg = 0; for (i=0; i<4; ++i) g.drawRect(ox-i-1, oy-i-1, ox+10*8+i, oy+20*8+i); } -function drawTile (tile, n, x, y, qClear) { +function drawTile(tile, n, x, y, qClear) { + if (state != 1) // stops tile from being drawn on the game over screen + return; if (qClear) g.setColor(0); else g.setColor(tcols[n].r, tcols[n].g, tcols[n].b); for (i=0; i0) g.setColor(tcols[c-1].r, tcols[c-1].g, tcols[c-1].b).drawImage(block, ox+(x-1)*8, oy+y*8); else g.setColor(0, 0, 0).fillRect(ox+(x-1)*8, oy+y*8, ox+x*8-1, oy+(y+1)*8-1); } } function gameOver() { + state = 0; g.setColor(1, 1, 1).setFontAlign(0, 1, 0).setFont("Vector",22) .drawString("Game Over", 176/2, 76); - state = 0; - E.showAlert("Game Over").then(selectGame, print); + // this cannot allow changing game controls because it would set up duplicate events + E.showAlert("Game Over").then(newGame, print); + settings.lines = 0; + settings.score = 0; + settings.level = settings.initialLevel; +} + +function redrawStats(onlyScore) { + g.setColor(0).fillRect(5, 30, 41, 60) + .setColor(1, 1, 1).drawString(settings.score.toString(), 22, 50); + if (!onlyScore) { + g.setColor(0).fillRect(5, 80, 41, 110) + .setColor(1, 1, 1).drawString(settings.level.toString(), 22, 100) + .setColor(0).fillRect(5, 130, 41, 160) + .setColor(1, 1, 1).drawString(settings.lines.toString(), 22, 150); + } } function insertAndCheck() { - for (y=0; y0) pf[py+y][px+x+1] = ctn+1; + // stop pieces from falling into each other + for (let y=0; y0) settings.pf[py+y][px+x+1] = settings.ctn+1; + let clearCount = 0; + let linesToClear = []; + let yReal = 19; // the y for display purposes // check for full lines - for (y=19; y>0; y--) { + for (let y=19; y>0; y--) { var qFull = true; - for (x=1; x<11; ++x) qFull &= pf[y][x]>0; + for (let x=1; x<11; ++x) qFull &= settings.pf[y][x]>0; if (qFull) { - nlines++; - dropInterval -= 5; - Bangle.buzz(30); - for (ny=y; ny>0; ny--) pf[ny] = JSON.parse(JSON.stringify(pf[ny-1])); - redrawPF(y); - g.setColor(0).fillRect(5, 30, 41, 80).setColor(1, 1, 1).drawString(nlines.toString(), 22, 50); + clearCount++; + linesToClear.push([y, yReal]); + print(`linesToClear.push(${y})`); + // clear the line, but do not display it yet + for (ny=y; ny>0; ny--) settings.pf[ny] = JSON.parse(JSON.stringify(settings.pf[ny-1])); + y++; } + yReal--; + } + if (clearCount) { + settings.lines += clearCount; + let effectiveLevel = Math.max(settings.level, 1); + if (clearCount == 1) { // single + settings.score += 100 * effectiveLevel; + Bangle.buzz(80, 0.5); + } + else if (clearCount == 2) { // double + settings.score += 300 * effectiveLevel; + Bangle.buzz(80); + } + else if (clearCount == 3) { // triple + settings.score += 500 * effectiveLevel; + Bangle.buzz(200); + } + else if (clearCount >= 4) { // tetris + settings.score += 800 * effectiveLevel; + Bangle.buzz(500); + } + // the score will not be shown yet because redrawStats was not called + + // clear effect + let timer = getTime(); + g.setColor(0, 0, 0); + while (true) { + var rectLength = (getTime()-timer)/0.05 + 1; + if (rectLength > 6) + break; + var x1 = 6 - rectLength; + var x2 = 4 + rectLength; + for (let line of linesToClear) { + let y = line[1]; + g.fillRect(ox+x1*8, oy+y*8, ox+x2*8-1, oy+(y+1)*8-1); + } + g.flip(); + } + // display the cleared lines + for (let line of linesToClear) { + redrawPF(line[0]); + } + if (settings.lines != 0 && settings.lines % 10 == 0) { + settings.level++; + calculateSpeed(); + } + redrawStats(); } // spawn new tile px = 4; py = 0; - ctn = ntn; - ntn = Math.floor(Math.random()*7); - ct = rotateTile(tiles[ctn], ntr); - ntr = Math.floor(Math.random()*4); - showNext(ntn, ntr); + settings.ctn = settings.ntn; + settings.ntn = Math.floor(Math.random()*7); + ct = rotateTile(tiles[settings.ctn], settings.ntr); + settings.ntr = Math.floor(Math.random()*4); + showNext(settings.ntn, settings.ntr); if (!moveOk(ct, 0, 0)) { gameOver(); } @@ -149,48 +248,76 @@ function insertAndCheck() { function moveOk(t, dx, dy) { var ok = true; - for (y=0; y 0) ok = false; + if (t[y][x]*settings.pf[py+dy+y][px+dx+x+1] > 0) ok = false; return ok; } +function pauseGame() { + //print("Paused"); + state = 3; + E.showMenu({ + "" : { "title" : /*LANG*/"Pause menu" }, + "< Back" : () => resumeGame(), + /*LANG*/"Exit" : () => { + writeSettings(); + load(); + }, + /*LANG*/"New game": () => newGame(), + }); +} + +function resumeGame() { + //print("Resumed"); + state = 1; + drawGame(); + redrawPF(19); +} + function gameStep() { if (state != 1) return; if (Date.now()-time > dropInterval) { // drop one step time = Date.now(); - if (moveOk(ct, 0, 1)) { - drawTile(ct, ctn, ox+px*8, oy+py*8, true); + if (settings.level >= 15 && moveOk(ct, 0, 2)) { + // at level 15, pieces drop twile as quickly + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, true); + py += 2; + } + else if (moveOk(ct, 0, 1)) { + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, true); py++; } else { // reached the bottom - insertAndCheck(ct, ctn, px, py); + insertAndCheck(ct, settings.ctn, px, py); } - drawTile(ct, ctn, ox+px*8, oy+py*8, false); + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, false); } } function rotate() { t = rotateTile(ct, 3); if (moveOk(t, 0, 0)) { - drawTile(ct, ctn, ox+px*8, oy+py*8, true); + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, true); ct = t; - drawTile(ct, ctn, ox+px*8, oy+py*8, false); + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, false); } } function move(x, y) { - if (moveOk(ct, x, y)) { - drawTile(ct, ctn, ox+px*8, oy+py*8, true); + r = moveOk(ct, x, y); + if (r) { + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, true); px += x; py += y; - drawTile(ct, ctn, ox+px*8, oy+py*8, false); + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, false); } + return r; } function linear(x) { - print("Linear: ", x); + //print("Linear: ", x); let now = px / 10; if (x < now-0.06) move(-1, 0); @@ -198,126 +325,150 @@ function linear(x) { move(1, 0); } -function newGame() { - E.showMenu(); - Bangle.setUI({mode : "custom", btn: () => load()}); - if (control == 4) { // Swipe +function setupControls() { + if (settings.control == 4) { // Swipe Bangle.on("touch", (e) => { t = rotateTile(ct, 3); if (moveOk(t, 0, 0)) { - drawTile(ct, ctn, ox+px*8, oy+py*8, true); + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, true); ct = t; - drawTile(ct, ctn, ox+px*8, oy+py*8, false); + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, false); } }); Bangle.on("swipe", (x,y) => { if (y<0) y = 0; if (moveOk(ct, x, y)) { - drawTile(ct, ctn, ox+px*8, oy+py*8, true); + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, true); px += x; py += y; - drawTile(ct, ctn, ox+px*8, oy+py*8, false); + drawTile(ct, settings.ctn, ox+px*8, oy+py*8, false); } }); } else { // control != 4 - if (control == 2) { // Tilt + if (settings.control == 2) { // Tilt Bangle.on("accel", (e) => { if (state != 1) return; - if (control != 2) return; + if (settings.control != 2) return; print(e.x); linear((0.2-e.x) * 2.5); }); } - if (control == 3) { // Move - Bangle.setBarometerPower(true); - Bangle.on("pressure", (e) => { - if (state != 1) return; - if (control != 3) return; - let a = e.altitude; - if (alt_start == -9999) - alt_start = a; - a = a - alt_start; - print(e.altitude, a); - linear(a); - }); + if (settings.control == 3) { // Pressure + Bangle.setBarometerPower(true); + Bangle.on("pressure", (e) => { + if (state != 1) return; + if (settings.control != 3) return; + let a = e.altitude; + if (alt_start == -9999) + alt_start = a; + a = a - alt_start; + //print(e.altitude, a); + linear(a); + }); } Bangle.on("drag", (e) => { - let h = 176/2; - if (state == 2) { - if (e.b) - selectGame(); - return; - } - if (!e.b) - return; - if (state == 0) return; - if (e.y < h) { - if (e.x < h) - rotate(); - else { - let i = 0; - for (i=0; i<10; i++) { - move(0, 1); - g.flip(); - } - } - } else { - if (control == 1) - linear((e.x - 20) / 156); - if (control != 0) + let h = 176/2; + if (state == 2) { + if (e.b) + selectGame(); return; - if (e.x < h) - move(-1, 0); - else - move(1, 0); - } + } + if (!e.b) + return; + if (state == 0) return; + if (e.y < h) { + if (e.x < h) + rotate(); + else { + while (move(0, 1)) { + settings.score++; + g.flip(); + } + redrawStats(true); + } + } else { + if (settings.control == 1) + linear((e.x - 20) / 156); + if (settings.control != 0) + return; + if (e.x < h) + move(-1, 0); + else + move(1, 0); + } }); } +} +function newGame() { + settings.lines = 0; + settings.score = 0; + settings.level = settings.initialLevel; initGame(); - drawGame(); + startGame(); +} + +function startGame() { + calculateSpeed(); state = 1; - var step = 450 - 50*level; - if (control == 3) - step = step*2; - dropInterval = step; - var gi = setInterval(gameStep, 50); + drawGame(); + var gi = setInterval(gameStep, 20); } function drawGame() { + Bangle.setUI({mode : "custom", btn: () => { + if (state == 1) { + pauseGame(); + } + }}); drawBoundingBox(); g.setColor(1, 1, 1).setFontAlign(0, 1, 0) - .setFont("6x15", 1).drawString("Lines", 22, 30) + .setFont("6x15", 1).drawString("Score", 22, 30) + .drawString("Level", 22, 80) + .drawString("Lines", 22, 130) .drawString("Next", 176-22, 30); - showNext(ntn, ntr); - g.setColor(0).fillRect(5, 30, 41, 80) - .setColor(1, 1, 1).drawString(nlines.toString(), 22, 50); + redrawStats(); + showNext(settings.ntn, settings.ntr); } -function selectLevel() { - print("Level selection menu"); - - var menu = {}; - menu["< Back"] = () => {selectGame();}; - menu[/*LANG*/"Level 1"] = () => { level = 0; selectGame(); }; - menu[/*LANG*/"Level 2"] = () => { level = 1; selectGame(); }; - menu[/*LANG*/"Level 3"] = () => { level = 2; selectGame(); }; - E.showMenu(menu); -} function selectGame() { state = 0; - print("Game selection menu"); - //for (let i = 0; i < 100000; i++) ; - + //print("Game selection menu"); + var menu = {}; - menu[/*LANG*/"Normal"] = () => { control = 0; newGame(); }; - menu[/*LANG*/"Drag"] = () => { control = 1; newGame(); }; - menu[/*LANG*/"Tilt"] = () => { control = 2; newGame(); }; - menu[/*LANG*/"Move"] = () => { control = 3; newGame(); }; - menu[/*LANG*/"Swipe"] = () => { control = 4; newGame(); }; - menu[/*LANG*/"Level"] = () => { selectLevel(); }; + menu[/*LANG*/"New game"] = () => { + setupControls(); + newGame(); + resumeGame(); + }; + if (settings.pf !== undefined) { + menu[/*LANG*/"Resume game"] = () => { + setupControls(); + startGame(); + resumeGame(); + }; + } + menu[/*LANG*/"Controls"] = { + value: settings.control, + min: 0, max: 4, + format: v => [/*LANG*/"Normal", /*LANG*/"Drag", /*LANG*/"Tilt", /*LANG*/"Pressure", /*LANG*/"Swipe"][v], + onchange: v => { + settings.control = v; + writeSettings(); + } + }; + menu[/*LANG*/"Level"] = { + value : 1, + min : 0, + max : 10, + wrap : true, + onchange : (l) => { + settings.initialLevel = l; + writeSettings(); + } + }; E.showMenu(menu); } diff --git a/loader.js b/loader.js index 5f2283282..28ff540eb 100644 --- a/loader.js +++ b/loader.js @@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var RECOMMENDED_VERSION = "2v19"; +var RECOMMENDED_VERSION = "2v20"; // could check http://www.espruino.com/json/BANGLEJS.json for this // We're only interested in Bangles