Merge branch 'master' of https://github.com/berkenbu/BangleApps into berkenbu-master
commit
1fe3b1e84b
|
|
@ -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).
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AA/TADwoIFkYyOF0owIF04wGUSqvVBZQtZGJYJIFzomKF0onIF07EKF0owLF9wNEnwACE6oZILxovbMBov/F/4v/C54uWF/4vKBQQLLF/4YPFwYMLF7AZGF5Y5KF5xJIFwoMJD44vaBhwvcLQpgHF8gGRF6xYNBpQvTXBoNOF65QJBIgvjBywvUV5YOOF64OIB54v/cQwAKB5ov/F84wKADYuIF+AwkFIwwnE45hmExCSlEpTEiERr3KADw+PF0ownUSoseA=="))
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue