From 6fe1a7e1d94e881ff921de94704099a64b033796 Mon Sep 17 00:00:00 2001 From: marko Date: Sat, 2 Apr 2022 15:59:39 -0400 Subject: [PATCH] New app 'bee', add game statistics to 'bordle' app. --- apps/bee/README.md | 34 + apps/bee/app-icon.js | 1 + apps/bee/app.png | Bin 0 -> 2145 bytes apps/bee/bee.app.js | 192 + apps/bee/bee_lindex.json | 1 + apps/bee/bee_screenshot.png | Bin 0 -> 1338 bytes apps/bee/bee_words_2of12 | 74578 ++++++++++++++++++++++++++++++++++ apps/bee/metadata.json | 16 + apps/bordle/bordle.app.js | 46 +- 9 files changed, 74861 insertions(+), 7 deletions(-) create mode 100644 apps/bee/README.md create mode 100644 apps/bee/app-icon.js create mode 100644 apps/bee/app.png create mode 100644 apps/bee/bee.app.js create mode 100644 apps/bee/bee_lindex.json create mode 100644 apps/bee/bee_screenshot.png create mode 100644 apps/bee/bee_words_2of12 create mode 100644 apps/bee/metadata.json diff --git a/apps/bee/README.md b/apps/bee/README.md new file mode 100644 index 000000000..1beece1af --- /dev/null +++ b/apps/bee/README.md @@ -0,0 +1,34 @@ + +# Spelling bee game + +Word finding game inspired by the NYT spelling bee. Find as many words with 4 or more letters (must include the +letter at the center of the 'hive'). + + +Usage: + - tap on letters to type out word + - swipe left to delete last letter + - swipe right to enter; the word will turn blue while is being checked against the internal dictionary; once + checked, it will turn red if the word is invalid, does not contain the central letter or has been guessed before or + green if it is a valid word; in the latter case, points will be awarded + - swipe down to shuffle the 6 outer letters + - swipe up to view a list of already guessed words; tap on any of them to return to the regular game. + + +Scoring: +The number of correctly guessed words is displayed on the bottom left, the score on the bottom right. A single point +is awarded for a 4 letter word, or the number of letters if longer. A pangram is a word that contains all 7 letters at +least once and yields an additional 7 points. Each game contains at least one pangram. + + +Technical remarks: +The game uses an internal dictionary consisting of a newline separated list of English words ('bee.words'). The dictionary is fairly +large (~700kB of flash space) and thus requires appropriate space on the watch and will make installing the app somewhat +slow. Because of its size it cannot be compressed (heatshrink needs to hold the compressed/uncompressed data in memory). +In order to make checking for the validity of a guessed word faster, an index file ('bee_lindex.json') is installed with +the app that facilitates faster word lookups. This index file is specific to the dictionary file used. If one were to +replace the dictionary file with a different version (e.g. a different language) the index file has to be regenerated. The easiest +way to do so is to delete (via the Web IDE or the fileman app on the watch) the file 'bee_lindex.json' - it will be regenerated (and saved, +i.e. it only happens once) on app startup automatically, a process that takes roughly 30 seconds. + +![Screenshot](./bee_screenshot.png) diff --git a/apps/bee/app-icon.js b/apps/bee/app-icon.js new file mode 100644 index 000000000..f3bf5dbb2 --- /dev/null +++ b/apps/bee/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AE2JAAIKHnc7DyNPp4vRGAwuBGB4sBAAQvSGIovPFqYvHGAYvDGBYsGGhwvGGIQvEGBQnDMYhkNGBAvOvQABqyRTF5GJr4wLFwQACX6IwLsowJLYMrldVGAQvTsoADGBITD0YvDldPF6n+F4gyGGAdP5nMF4KKBGDJZDGI7EBcoOiGAK7DGAQvYRogxEr1Pp9VMAiSBBILBWeJIxCromBMAQwDAAZfTGBQyCxOCGAIvBGIV/F7AwMAAOIp95GAYACFqoyQMAIwGF7QADEQd5FgIADqvGF8DnEAAIvFGIWjF8CFE0QwHAAQudAAK0EGBQuecw3GqpemYIxiCGIa8cF4wwHdTwvJp9/F82jGA9VMQovf5jkHGIwvg4wvIAAgvg5miF9wwNF8QABF9QwF0YuoF4oxCqoulGBAAB42i0QvjGBPMF0gwIFswwHF1IA/AH4A/AH4AL")) diff --git a/apps/bee/app.png b/apps/bee/app.png new file mode 100644 index 0000000000000000000000000000000000000000..ed16c44b17415aba02d261f0a2b607713201f147 GIT binary patch literal 2145 zcmV-n2%h(eP)Wa%1m3q4yG15t;Mn9*n%jcZND%+5bH=Cs(m=LHV;Hh0tzBfAWDSr2!Vt=xJm9g z_ng%aF-XGA&5gNEXFB=MoDXO3|61$6_C9CtwKoi77{eF_!0oTA`M-!Ou{xir32y*y z0A2`NzSTd!PTHgKO8yj@FD`#Z<(aa(lYW7Sepx|F=FRiBb~fFlDz_utBtw%;8DNNx^Hbwc-cdR{?~gkR7GCxTn!pPboq@NJ1K(|gB9JS!giYDM7AG0apI(9 zMQ#I*09B&;ftq|^y4Hs;R_tyWR005NYr3{7a(ygL#8q~kayfaf&*Gyr7{2h-ckW1U z?Q8SYlKYn;y0&k7IBfAr-QhOZ3QdS6xG!S=1Aw^3BS0h;$72Yy#+u}(8@)$F$ad|( zFZ=SJT)a3H(I=XkTlo7s?^3p{hPtCCP*wbXANiL~ZnR``*4pFmfFRtt2@p^L41)S1^Bml6i(En>g^sR_05myP>&=^uaX>mE!>~N+-aop}? zE=d-yA8!)$o;UY-A}znUr?{xhvgK`Hb#_|Q4*{|=(;1VMah}+4x{199Kk0cbT~)ep zL_!h(C@_-lye!>lEXXjN$!P|cBnzYbf+{qGTn;uX4IvlHffS!Gcf3hfP(U@5_2xr! zTqxOjVAOtqtkkm@;v&G+Gp3P~opnCWk@}NFA`U>hVSB%oump}k#5BZb7-9xILVA+H zxDshx#@b5N79?T+XLwJuRP&2iRPeZ386v!_1s~007f8m^EV> zb7sw8!nm243YkG+(kO!|874umaJ=2+=-Ro~wMjBU4%V+x3$%x43`sLg}x&~E2&VS_b z?b_ws2#oL7p+L|pqy>b4M`*WQ%(3~TU3xif`?eW7K2EBickSs+L(~|Tb&8pJ2T4zE zLshUMKJ3Wn<(crFVNklIYFB40O#|)&eewK0O}0YTdVxCzV`n=MX+FmORd+FCN^AMU zch{9f7bq9r5@bbOJqidZ%@zxOxksxq@V?G6o0&0TU|&T(66%>KZ`| zQvjT<5W63p2f!71t(D6b)Xj~mLHLo(4gc{gO&}T`xcTZke*ehcH6D+f)6@3i-*Weq z?MS~IF%Z*4BVW`U8L9UBEAFiwJ37sp*xQpXmm93##uGDPr>F%bMZa?*^7Qm6?I$u) zt<0YGsT0qQ-tvCdNylX*uG`C|Gi;9kXH}MW$No|8`8*sQHPW^NJ~dF@e(XhOXFPEe zG6Rcln7u?rp9Q!wzqR70chwbN*njv~u;sD!d0us8z>Nv$^tf*R3tM;8#}j+VGjMlt z(KvxO5xf8m&4ILe*PM=cJnHM=q@~z_{M?SS+xMhJ6bZ!kDC=LUta&xw_;K$8{X3AA z2*I9rRZ^eYIO)T_yn;#XV^%FaAQ_|Vz5RRBzb1{eA0ABnA$>yKXW8aPgp|HY?!Wcu zj=5Jh7e=$&eK4i=FO}I%#~PC|ElUg)4jQ7bOe(K=Z6&zTZ2H5H5_o*k^luzG8vIv3 z-JMJBtUGXZL2ImXz{U0CNJ*&S%W4?XC$wT=ZLZ(z9_^<)Y29mI%iml+W?RhoVn^N3 z;>C~>F#YbBTZ>Pf?w240H&=|ku5@id)rm8KBhfjM6SX}~4DBa&<=p&hPhFLk64@P# z*4XTuy?ou&am&|Tw!Qp=jOwni`9+bY8jN8&Acpi0Xr=FaAa9&$^`0($y1;Q&qTNtL zgY7Z&xXvvFE_Bd$#B=<-ajJ(_fA zV0!-;@=mOk{#sx%z~SQ~4nDqqa?o+4-*USTj&G=_9uIu!yVN0XWQd%f{z*BZ{I$#W zC8dsxRL5njtY-+~ArT2UyZ~4|uPMoq%yp%Ei|(0U|E_6h^p&R@Exv$p(Y?@Mk&t3e zEB(&{Kfb74vA2DcQN6irj5M|R^7Mk6W)JeCBqo9Kk(n6QN5CbqXs6EjKWhzn zyIR}Kvcd-&A528-gdJ$KJW!0GTL51y8n6a$_*XKAm}lL1kMM|4O0dcyv4Is)-$>VGLs!|7Z9w XLWm}&-3OsD00000NkvXXu0mjfj8ZL+ literal 0 HcmV?d00001 diff --git a/apps/bee/bee.app.js b/apps/bee/bee.app.js new file mode 100644 index 000000000..4989296e0 --- /dev/null +++ b/apps/bee/bee.app.js @@ -0,0 +1,192 @@ + +const S = require("Storage"); +var letters = []; +var letterIdx = []; + +var centers = []; + +var word = ''; + +var foundWords = []; +var score = 0; + +var intervalID = -1; + +function prepareLetterIdx () { + "compile" + var li = [0]; + if (S.read("bee_lindex.json")!==undefined) li = S.readJSON("bee_lindex.json"); // check for cached index + else { + for (var i=1; i<26; ++i) { + var prefix = String.fromCharCode(97+i%26); + console.log(prefix); + li.push(words.indexOf("\n"+prefix, li[i-1])+1); + } + li.push(words.length); + S.writeJSON("bee_lindex.json", li); + } + for (var i=0; i<26; ++i) letterIdx[i] = S.read("bee.words", li[i], li[i+1]-li[i]); +} + +function findWord (w) { + "compile" + var ci = w.charCodeAt(0)-97; + var f = letterIdx[ci].indexOf(w); + if (f>=0 && letterIdx[ci][f+w.length]=="\n") return true; + return false; +} + +function isPangram(w) { + var ltrs = ''; + for (var i=0; i=0) return false; // already found + if (findWord(w)) { + foundWords.push(w); + if (w.length==4) score++; + else score += w.length; + if (isPangram(w)) score += 7; + return true; + } + return false; +} + +function getHexPoly(cx, cy, r, a) { + var p = []; + for (var i=0; i<6; ++i) p.push(cx+r*Math.sin((i+a)/3*Math.PI), cy+r*Math.cos((i+a)/3*Math.PI)); + return p; +} + +function drawHive() { + w = g.getWidth(); + h = g.getHeight(); + const R = w/3.3; + centers = getHexPoly(w/2, h/2+10, R, 0); + centers.push(w/2, h/2+10); + g.clear(); + g.setFont("Vector", w/7).setFontAlign(0, 0, 0); + g.setColor(g.theme.fg); + for (var i=0; i<6; ++i) { + g.drawPoly(getHexPoly(centers[2*i], centers[2*i+1], 0.9*R/Math.sqrt(3), 0.5), {closed:true}); + g.drawString(String.fromCharCode(65+letters[i+1]), centers[2*i]+2, centers[2*i+1]+2); + } + g.setColor(1, 1, 0).fillPoly(getHexPoly(w/2, h/2+10, 0.9*R/Math.sqrt(3), 0.5)); + g.setColor(0).drawString(String.fromCharCode(65+letters[0]), w/2+2, h/2+10+2); +} + +function shuffleLetters(qAll) { + for (var i=letters.length-1; i > 0; i--) { + var j = (1-qAll) + Math.floor(Math.random()*(i+qAll)); + var temp = letters[i]; + letters[i] = letters[j]; + letters[j] = temp; + } +} + +function pickLetters() { + var ltrs = ""; + while (ltrs.length!==7) { + ltrs = []; + var j = Math.floor(26*Math.random()); + var i = Math.floor((letterIdx[j].length-10)*Math.random()); + while (letterIdx[j][i]!="\n" && i0) { + word = word.slice(0, -1); + drawWord(g.theme.fg); + } + if (d==1 && word.length>=4) { + drawWord("#00f"); + drawWord((checkWord(word.toLowerCase()) ? "#0f0" : "#f00")); + if (intervalID===-1) intervalID = setInterval(wordFound, 800); + } + if (e===1) { + shuffleLetters(0); + drawHive(); + drawScore(); + drawWord(g.theme.fg); + } + if (e===-1 && foundWords.length>0) showWordList(); +} + +function showWordList() { + Bangle.removeListener("touch", touchHandler); + Bangle.removeListener("swipe", swipeHandler); + E.showScroller({ + h : 20, c : foundWords.length, + draw : (idx, r) => { + g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1); + g.setFont("6x8:2").drawString(foundWords[idx].toUpperCase(),r.x+10,r.y+4); + }, + select : (idx) => { + setInterval(()=> { + E.showScroller(); + drawHive(); + drawScore(); + Bangle.on("touch", touchHandler); + Bangle.on("swipe", swipeHandler); + clearInterval(); + }, 100); + } + }); +} + +prepareLetterIdx(); +pickLetters(); +drawHive(); +drawScore(); +Bangle.on("touch", touchHandler); +Bangle.on("swipe", swipeHandler); diff --git a/apps/bee/bee_lindex.json b/apps/bee/bee_lindex.json new file mode 100644 index 000000000..e4fa13037 --- /dev/null +++ b/apps/bee/bee_lindex.json @@ -0,0 +1 @@ +[0,41048,80445,152390,198606,228714,257919,279071,303726,337982,343582,348026,367246,404452,419780,438696,496250,499697,544600,624304,659085,680996,691270,708186,708341,709916,710883] \ No newline at end of file diff --git a/apps/bee/bee_screenshot.png b/apps/bee/bee_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..cd173b9978f4c906fb63e4957e41793a70da8350 GIT binary patch literal 1338 zcmV-A1;zS_P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00009 zP)t-s00030|NsC00A6}Z?f?J)0b)x>L;?Q-E#3eC00(qQO+^Rh1Of{l24Xi!e*gdk zE=fc|R9M5+nK7>1AP|OOWj9;7cEfy7MH_Z5 z=U_Dk|F3DbK8(yj&uTJI}*LlcS)t}I_#fTL;fk? zs7P=>ssZ%HsSeW>sSc-BB!y{qMGQ(vmi-<~8%e#IST=*5pZQ6h z!{{cguL!65;I*(XPjxu$g8{Z2UI?7J1zj~RWMbV?1deiqw*t(4Y7~aqf$)Oy2DFUu zQir+vIXp2q+m2lbD`Z$%XNFQa!c&A_qQ87aHfUGCbbJWhU2<;;-s>>&DGJwDev*4n@bd)5Wy+p&G zQ|>*%&u;Y9pM#fX_oa2?(u#CxtxK&)3e)V07$fU5eeA9LVNWq!yPfY#3|{NIRRQBZ zs6@FB76FxyzAWyJ8&l5Y+8u4+ZB)4(XWAXjYp_K-hubh zxjTd16k8kc!YT{!*m~H`@)kyfeQ9H*Bul^Iw2n$(+Lq&C0(`xRO*TytAhf z&Ee8FtO(Z_l}{wDBo-D)WxvgI30^XDJ_nzF5&I2s0{&02e=r6B001R)MObuXVRU6W zV{&C-bY%cCFfuYNFf=VNGE^}&Ix{djFg7hPG&(RaZ!PlY0000bbVXQnWMOn=I&E)c wX=Zr0 && b==2) inp = inp.slice(0,-1); + if (keyStateIdx==6 && b==5) { + wordle.drawStats(); + return; + } layout.input.label = inp; } layout = getKeyLayout(inp); @@ -82,6 +87,7 @@ class Wordle { this.word = this.words.slice(i, i+5).toUpperCase(); } console.log(this.word); + this.stats = require("Storage").readJSON("bordlestats.json") || {'1':0, '2':0, '3':0, '4':0, '5':0, '6':0, 'p':0, 'w':0, 's':0, 'ms':0}; } render(clear) { h = g.getHeight(); @@ -109,7 +115,7 @@ class Wordle { layout = getKeyLayout(""); wordle.render(true); }); - return 3; + return 1; } this.guesses.push(w); this.nGuesses++; @@ -130,13 +136,39 @@ class Wordle { this.guessColors[this.nGuesses].push(col); } if (correct==5) { - E.showAlert("The word is\n"+this.word, "You won in "+(this.nGuesses+1)+" guesses!").then(function(){load();}); - return 1; - } - if (this.nGuesses==5) { - E.showAlert("The word was\n"+this.word, "You lost!").then(function(){load();}); + E.showAlert("The word is\n"+this.word, "You won in "+(this.nGuesses+1)+" guesses!").then(function(){ + wordle.stats['p']++; wordle.stats['w']++; wordle.stats['s']++; wordle.stats[wordle.nGuesses+1]++; + if (wordle.stats['s']>wordle.stats['ms']) wordle.stats['ms'] = wordle.stats['s']; + require("Storage").writeJSON("bordlestats.json", wordle.stats); + wordle.drawStats(); + }); return 2; } + if (this.nGuesses==5) { + E.showAlert("The word was\n"+this.word, "You lost!").then(function(){ + wordle.stats['p']++; wordle.stats['s'] = 0; + require("Storage").writeJSON("bordlestats.json", wordle.stats); + wordle.drawStats(); + }); + return 3; + } + } + drawStats() { + E.showMessage(" ", "Statistics"); + var max = 1; + for (i=1; i<=6; ++i) if (max20 ? 25 : 25+tw, 52+(i-0.5)*(h-52)/6); + } + g.setFontVector((h-40)/9).setColor("#fff").drawString("P:"+this.stats["p"]+" W:"+this.stats["w"]+" S:"+this.stats["s"]+" M:"+this.stats["ms"], 4, 34); + Bangle.setUI(); + Bangle.on("touch", (e) => { load(); }); } }