forked from FOSS/BangleApps
New app 'bee', add game statistics to 'bordle' app.
parent
172df0dc27
commit
6fe1a7e1d9
|
@ -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.
|
||||
|
||||

|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AE2JAAIKHnc7DyNPp4vRGAwuBGB4sBAAQvSGIovPFqYvHGAYvDGBYsGGhwvGGIQvEGBQnDMYhkNGBAvOvQABqyRTF5GJr4wLFwQACX6IwLsowJLYMrldVGAQvTsoADGBITD0YvDldPF6n+F4gyGGAdP5nMF4KKBGDJZDGI7EBcoOiGAK7DGAQvYRogxEr1Pp9VMAiSBBILBWeJIxCromBMAQwDAAZfTGBQyCxOCGAIvBGIV/F7AwMAAOIp95GAYACFqoyQMAIwGF7QADEQd5FgIADqvGF8DnEAAIvFGIWjF8CFE0QwHAAQudAAK0EGBQuecw3GqpemYIxiCGIa8cF4wwHdTwvJp9/F82jGA9VMQovf5jkHGIwvg4wvIAAgvg5miF9wwNF8QABF9QwF0YuoF4oxCqoulGBAAB42i0QvjGBPMF0gwIFswwHF1IA/AH4A/AH4AL"))
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
|
@ -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<w.length; ++i) if (ltrs.indexOf(w[i])===-1) ltrs += w[i];
|
||||
return ltrs.length==7;
|
||||
}
|
||||
|
||||
function checkWord (w) {
|
||||
if (w.indexOf(String.fromCharCode(97+letters[0]))==-1) return false; // does it contain central letter?
|
||||
if (foundWords.indexOf(w)>=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" && i<letterIdx[j].length) ++i;
|
||||
if (i<letterIdx[j].length-1) {
|
||||
++i;
|
||||
while (letterIdx[j][i]!=="\n") {
|
||||
var c = letterIdx[j][i];
|
||||
if (ltrs.indexOf(c)===-1) ltrs += c;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i=0; i<7; ++i) letters.push(ltrs.charCodeAt(i)-97);
|
||||
shuffleLetters(1);
|
||||
}
|
||||
|
||||
function drawWord(c) {
|
||||
g.clearRect(0, 0, g.getWidth()-1, 19);
|
||||
g.setColor(c).setFont("Vector", 20).setFontAlign(0, 0, 0).drawString(word, g.getWidth()/2, 11).flip();
|
||||
}
|
||||
|
||||
function touchHandler(e, x) {
|
||||
var hex = 0;
|
||||
var hex_d = 1e6;
|
||||
for (var i=0; i<7; ++i) {
|
||||
var d = (x.x-centers[2*i])*(x.x-centers[2*i]) + (x.y-centers[2*i+1])*(x.y-centers[2*i+1]);
|
||||
if (d < hex_d) {
|
||||
hex_d = d;
|
||||
hex = i+1;
|
||||
}
|
||||
}
|
||||
hex = hex%7;
|
||||
if (word.length <= 15) word += String.fromCharCode(letters[hex]+65);
|
||||
drawWord(g.theme.fg);
|
||||
}
|
||||
|
||||
function drawScore() {
|
||||
g.setColor(g.theme.fg).setFont("Vector", 20).setFontAlign(0, 0, 0);
|
||||
g.clearRect(0, g.getHeight()-22, 60, g.getHeight()-1);
|
||||
g.clearRect(g.getWidth()-60, g.getHeight()-22, g.getWidth(), g.getHeight()-1);
|
||||
g.drawString(foundWords.length.toString(), 30, g.getHeight()-11);
|
||||
g.drawString(score.toString(), g.getWidth()-30, g.getHeight()-11);
|
||||
}
|
||||
|
||||
function wordFound (c) {
|
||||
word = "";
|
||||
drawWord(g.theme.fg);
|
||||
drawScore();
|
||||
clearInterval(intervalID);
|
||||
intervalID = -1;
|
||||
}
|
||||
|
||||
function swipeHandler(d, e) {
|
||||
if (d==-1 && word.length>0) {
|
||||
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);
|
|
@ -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]
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
|||
{ "id": "bee",
|
||||
"name": "Bee",
|
||||
"shortName":"Bee",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Spelling bee",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"tags": "game,text",
|
||||
"storage": [
|
||||
{"name":"bee.app.js","url":"bee.app.js"},
|
||||
{"name":"bee.words","url":"bee_words_2of12"},
|
||||
{"name":"bee_lindex.json","url":"bee_lindex.json"},
|
||||
{"name":"bee.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -21,7 +21,8 @@ function buttonPushed(b) {
|
|||
layout.bt1.bgCol = wordle.keyColors.Z||g.theme.bg;
|
||||
layout.bt2.label = "<del>";
|
||||
layout.bt4.label = "<ent>";
|
||||
layout.bt3.label = layout.bt5.label = " ";
|
||||
layout.bt3.label = " ";
|
||||
layout.bt5.label = "<stat>";
|
||||
layout.bt6.label = "<";
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +31,10 @@ function buttonPushed(b) {
|
|||
if (b!=6) {
|
||||
if ((keyStateIdx<=5 || b<=1) && inp.length<5) inp += String.fromCharCode(b+(keyStateIdx-1)*5+64);
|
||||
else if (layout.input.label.length>0 && 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 (max<this.stats[i]) max = this.stats[i];
|
||||
var h = g.getHeight();
|
||||
var w = g.getWidth();
|
||||
g.setColor('#00f').setFontVector((h-40)/8).setFontAlign(-1, 0, 0);
|
||||
for (i=1; i<=6; ++i) {
|
||||
tw = this.stats[i]*(w-24)/max;
|
||||
g.setColor("#00f").fillRect(20, 52+(i-1)*(h-52)/6+2, 20+tw, 52+i*(h-52)/6-2);
|
||||
g.setColor("#fff").drawString(i.toString(), 1, 52+(i-0.5)*(h-52)/6);
|
||||
g.drawString(this.stats[i].toString(), tw>20 ? 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(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue