Merge branch 'master' of https://github.com/berkenbu/BangleApps into berkenbu-master

master
Gordon Williams 2022-03-17 08:27:54 +00:00
commit 1fe3b1e84b
8 changed files with 226 additions and 41 deletions

17
apps/bordle/README.md Normal file
View File

@ -0,0 +1,17 @@
# Bordle
The Bangle version of a popular word guessing game. The goal is to guess a 5 letter word in 6 tries or less. After each guess, the letters in the guess are
marked in colors: yellow for a letter that appears in the to-be-guessed word, but in a different location and green for a letter in the correct position.
Only words contained in the internal dictionary are allowed as valid guesses. At app launch, a target word is picked from the dictionary at random.
On startup, a grid of 6 lines with 5 (empty) letter boxes is displayed. Swiping left or right at any time switches between grid view and keyboard view.
The keyboad was inspired by the 'Scribble' app (it is a simplified version using the layout library). The letter group "Z ..." contains the delete key and
the enter key. Hitting enter after the 5th letter will add the guess to the grid view and color mark it.
The (English language) dictionary was derived from the the Unix ispell word list by filtering out plurals and past particples (and some hand editing) from all 5 letter words.
It is contained in the file 'wordlencr.txt' which contains one long string (no newline characters) of all the words concatenated. It would not be too difficult to swap it
out for a different language version. The keyboard currently only supports the 26 characters of the latin alphabet (no accents or umlauts).

1
apps/bordle/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AA/TADwoIFkYyOF0owIF04wGUSqvVBZQtZGJYJIFzomKF0onIF07EKF0owLF9wNEnwACE6oZILxovbMBov/F/4v/C54uWF/4vKBQQLLF/4YPFwYMLF7AZGF5Y5KF5xJIFwoMJD44vaBhwvcLQpgHF8gGRF6xYNBpQvTXBoNOF65QJBIgvjBywvUV5YOOF64OIB54v/cQwAKB5ov/F84wKADYuIF+AwkFIwwnE45hmExCSlEpTEiERr3KADw+PF0ownUSoseA=="))

BIN
apps/bordle/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

159
apps/bordle/bordle.app.js Normal file
View File

@ -0,0 +1,159 @@
var Layout = require("Layout");
var gameState = 0;
var keyState = 0;
var keyStateIdx = 0;
function buttonPushed(b) {
if (keyState==0) {
keyState++;
keyStateIdx = b;
if (b<6) {
for (i=1; i<=5; ++i) {
var c = String.fromCharCode(i+64+(b-1)*5);
layout["bt"+i.toString()].label = c;
layout["bt"+i.toString()].bgCol = wordle.keyColors[c]||g.theme.bg;
}
layout.bt6.label = "<";
}
else {
layout.bt1.label = "Z";
layout.bt1.bgCol = wordle.keyColors.Z||g.theme.bg;
layout.bt2.label = "<del>";
layout.bt4.label = "<ent>";
layout.bt3.label = layout.bt5.label = " ";
layout.bt6.label = "<";
}
}
else { // actual button pushed
inp = layout.input.label;
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);
layout.input.label = inp;
}
layout = getKeyLayout(inp);
keyState = 0;
if (inp.length==5 && keyStateIdx==6 && b==4) {
rc = wordle.addGuess(inp);
layout.input.label = "";
layout.update();
gameState = 0;
if (rc>0) return;
g.clear();
wordle.render();
return;
}
}
layout.update();
g.clear();
layout.render();
}
function getKeyLayout(text) {
return new Layout( {
type: "v", c: [
{type:"txt", font:"6x8:2", id:"input", label:text, pad: 3},
{type: "h", c: [
{type:"btn", font:"6x8:2", id:"bt1", label:"ABCDE", cb: l=>buttonPushed(1), pad:4, filly:1, fillx:1 },
{type:"btn", font:"6x8:2", id:"bt2", label:"FGHIJ", cb: l=>buttonPushed(2), pad:4, filly:1, fillx:1 },
]},
{type: "h", c: [
{type:"btn", font:"6x8:2", id:"bt3", label:"KLMNO", cb: l=>buttonPushed(3), pad:4, filly:1, fillx:1 },
{type:"btn", font:"6x8:2", id:"bt4", label:"PQRST", cb: l=>buttonPushed(4), pad:4, filly:1, fillx:1 },
]},
{type: "h", c: [
{type:"btn", font:"6x8:2", id:"bt5", label:"UVWXY", cb: l=>buttonPushed(5), pad:4, filly:1, fillx:1 },
{type:"btn", font:"6x8:2", id:"bt6", label:"Z ...", cb: l=>buttonPushed(6), pad:4, filly:1, fillx:1 },
]}
]});
}
class Wordle {
constructor(word) {
this.word = word;
this.guesses = [];
this.guessColors = [];
this.keyColors = [];
this.nGuesses = -1;
if (word == "rnd") {
this.words = require("Storage").read("wordlencr.txt");
i = Math.floor(Math.floor(this.words.length/5)*Math.random())*5;
this.word = this.words.slice(i, i+5).toUpperCase();
}
console.log(this.word);
}
render(clear) {
h = g.getHeight();
bh = Math.floor(h/6);
bbh = Math.floor(0.85*bh);
w = g.getWidth();
bw = Math.floor(w/5);
bbw = Math.floor(0.85*bw);
if (clear) g.clear();
g.setFont("Vector", Math.floor(bbh*0.95)).setFontAlign(0,0);
g.setColor(g.theme.fg);
for (i=0; i<6; ++i) {
for (j=0; j<5; ++j) {
if (i<=this.nGuesses) {
g.setColor(this.guessColors[i][j]).fillRect(j*bw+(bw-bbw)/2, i*bh+(bh-bbh)/2, (j+1)*bw-(bw-bbw)/2, (i+1)*bh-(bh-bbh)/2);
g.setColor(g.theme.fg).drawString(this.guesses[i][j], 2+j*bw+bw/2, 2+i*bh+bh/2);
}
g.setColor(g.theme.fg).drawRect(j*bw+(bw-bbw)/2, i*bh+(bh-bbh)/2, (j+1)*bw-(bw-bbw)/2, (i+1)*bh-(bh-bbh)/2);
}
}
}
addGuess(w) {
if ((this.words.indexOf(w.toLowerCase())%5)!=0) {
E.showAlert(w+"\nis not a word", "Invalid word").then(function() {
layout = getKeyLayout("");
wordle.render(true);
});
return 3;
}
this.guesses.push(w);
this.nGuesses++;
this.guessColors.push([]);
correct = 0;
var sol = this.word;
for (i=0; i<w.length; ++i) {
c = w[i];
col = g.theme.bg;
if (sol[i]==c) {
sol = sol.substr(0,i) + '?' + sol.substr(i+1);
col = "#0f0";
++correct;
}
else if (sol.includes(c)) col = "#ff0";
if (col!=g.theme.bg) this.keyColors[c] = this.keyColors[c] || col;
else this.keyColors[c] = "#00f";
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();});
return 2;
}
}
}
wordle = new Wordle("rnd");
layout = getKeyLayout("");
wordle.render(true);
Bangle.on('swipe', function (dir) {
if (dir==1 || dir==-1) {
g.clear();
if (gameState==0) {
layout.render();
gameState = 1;
}
else if (gameState==1) {
wordle.render();
gameState = 0;
}
}
});

15
apps/bordle/metadata.json Normal file
View File

@ -0,0 +1,15 @@
{ "id": "bordle",
"name": "Bordle",
"shortName":"Bordle",
"icon": "app.png",
"version":"0.01",
"description": "Bangle version of a popular word search game",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"tags": "game, text",
"storage": [
{"name":"bordle.app.js","url":"bordle.app.js"},
{"name":"wordlencr.txt","url":"wordlencr.txt"},
{"name":"bordle.img","url":"app-icon.js","evaluate":true}
]
}

File diff suppressed because one or more lines are too long

View File

@ -78,10 +78,27 @@ function parseDevice(d) {
})}
function connection_setup() {
NRF.setScan();
NRF.setScan(parseDevice, { filters: [{services:["FFE0"]}], timeout: 2000});
g.clearRect(0, 60, 239, 239).setFontVector(18).setFontAlign(0, 0, 0).setColor(0, 1, 0);
g.drawString("Scanning for relay...", 120, 120);
menu = {
"": { "title": "Select relay board" },
"re-scan": () => connection_setup()
};
waitMessage();
NRF.findDevices(devices => {
devices.forEach(device =>{
let deviceName = device.id.substring(0,17);
if (device.name) {
deviceName = device.name;
}
if (device.services!=undefined && device.services.find(e => e.toLowerCase()=="ffe0")) deviceName = "* "+deviceName;
menu[deviceName] = () => { E.showMenu(); parseDevice(device); }
});
E.showMenu(menu);
}, { active: true });
}
function waitMessage() {
E.showMenu();
E.showMessage("scanning");
}
function moveChannelFrame(oldc, newc) {

View File

@ -33,7 +33,7 @@
<div id="map">
</div>
<div id="controls">
<div style="display:inline-block;text-align:center;vertical-align: top;" id="3bitdiv"> <input type="checkbox" id="3bit"></input><br/><span>3 bit</span></div>
<div style="display:inline-block;text-align:center;vertical-align: top;"> <input type="checkbox" id="3bit"></input><br/><span>3 bit</span></div>
<button id="getmap" class="btn btn-primary">Get Map</button><br/>
<canvas id="maptiles" style="display:none"></canvas>
<div id="uploadbuttons" style="display:none"><button id="upload" class="btn btn-primary">Upload</button>
@ -52,16 +52,16 @@
TODO:
* Allow a larger tilesize. Currently we use 'evaluate:true' which means we can only send 64x64x8 bit, but with some tweaking we could send 128x128 which would reduce the number of files and make things a bit snappier
* Could maybe use palettised output?
* Could potentially use a custom 16 color palette?
* Allow user to choose size of map area to be uploaded (small/med/large)
* What is faster? Storing as a compressed image and decompressing, or storing decompressed?
*/
var TILESIZE = 96; // Size of our tiles
var OSMTILESIZE = 256; // Size of openstreetmap tiles
var MAPSIZE = TILESIZE*5; ///< 480 - Size of map we download
var OSMTILECOUNT = 3; // how many tiles do we download in each direction (Math.floor(MAPSIZE / OSMTILESIZE)+1)
var TILESIZE = 64;
var OSMTILESIZE = 256;
var OSMSUBTILES = OSMTILESIZE / TILESIZE;
/* Can see possible tiles on http://leaflet-extras.github.io/leaflet-providers/preview/
However some don't allow cross-origin use */
var TILELAYER = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'; // simple, high contrast
@ -82,37 +82,11 @@ TODO:
});
// Could optionally overlay trails: https://wiki.openstreetmap.org/wiki/Tiles
// Search box:
const searchProvider = new window.GeoSearch.OpenStreetMapProvider();
const searchControl = new GeoSearch.GeoSearchControl({
provider: searchProvider,
style: 'button',
updateMap: true,
autoClose: true,
showMarker: false,
keepResult: true,
autoComplete: false
});
map.addControl(searchControl);
function onInit(device) {
if (device && device.info && device.info.g) {
// On 3 bit devices, don't even offer the option. 3 bit is the only way
if (device.info.g.bpp==3) {
document.getElementById("3bit").checked = true;
document.getElementById("3bitdiv").style = "display:none";
}
}
}
var mapFiles = [];
previewTileLayer.addTo(map);
function tilesLoaded(ctx, width, height) {
var options = {
compression:false, output:"raw",
mode:"web"
};
var options = { compression:true, mode:"web", output:"string"};
if (document.getElementById("3bit").checked) {
options = {
compression:false, output:"raw",
@ -162,7 +136,8 @@ TODO:
imgstr = imgstr.slice(compress.length,-1);*/
tiles.push({
name:"openstmap-"+x+"-"+y+".img",
content:imgstr
content:imgstr,
evaluate:true
});
}
}
@ -211,10 +186,10 @@ TODO:
var canvas = document.getElementById("maptiles");
canvas.style.display="";
var ctx = canvas.getContext('2d');
canvas.width = MAPSIZE;
canvas.height = MAPSIZE;
for (var i = 0; i < OSMTILECOUNT; i++) {
for (var j = 0; j < OSMTILECOUNT; j++) {
canvas.width = OSMTILESIZE*2;
canvas.height = OSMTILESIZE*2;
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
(function(i,j){
var coords = new L.Point(center.x+i-1, center.y+j-1);
coords.z = zoom;