mirror of https://github.com/espruino/BangleApps
Merge branch 'espruino:master' into master
commit
5db16d190b
24
apps.json
24
apps.json
|
@ -511,7 +511,7 @@
|
|||
{ "id": "gpsrec",
|
||||
"name": "GPS Recorder",
|
||||
"icon": "app.png",
|
||||
"version":"0.23",
|
||||
"version":"0.24",
|
||||
"interface": "interface.html",
|
||||
"description": "Application that allows you to record a GPS track. Can run in background",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
|
@ -687,7 +687,7 @@
|
|||
{ "id": "widbt",
|
||||
"name": "Bluetooth Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.05",
|
||||
"version":"0.06",
|
||||
"description": "Show the current Bluetooth connection status in the top right of the clock",
|
||||
"tags": "widget,bluetooth,b2",
|
||||
"type":"widget",
|
||||
|
@ -1316,7 +1316,7 @@
|
|||
{ "id": "widid",
|
||||
"name": "Bluetooth ID Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Display the last two tuple of your Bangle.js MAC address in the widget section. This is useful for figuring out which Bangle.js to connect to if you have more than one Bangle.js!",
|
||||
"tags": "widget,address,mac",
|
||||
"type":"widget",
|
||||
|
@ -3571,5 +3571,23 @@
|
|||
{"name":"floralclk.app.js","url":"app.js"},
|
||||
{"name":"floralclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "score",
|
||||
"name": "Score Tracker",
|
||||
"icon": "score.app.png",
|
||||
"version":"0.01",
|
||||
"description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.",
|
||||
"readme": "README.md",
|
||||
"tags": "b2",
|
||||
"type": "app",
|
||||
"storage": [
|
||||
{"name":"score.app.js","url":"score.app.js"},
|
||||
{"name":"score.settings.js","url":"score.settings.js"},
|
||||
{"name":"score.presets.json","url":"score.presets.json"},
|
||||
{"name":"score.img","url":"score.app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"score.json"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -25,3 +25,4 @@
|
|||
0.21: Fix issue where a period of 1s recorded every 2s, 5s every 6s, and so on
|
||||
0.22: Ensure Bangle.setGPSPower uses 'gpsrec' as a tag
|
||||
0.23: Fix issue where tracks wouldn't record when running from OpenStMap if a period hadn't been set up first
|
||||
0.24: Better support for Bangle.js 2, avoid widget area for Graphs, smooth graphs more
|
||||
|
|
|
@ -102,7 +102,8 @@ function getTrackInfo(fn) {
|
|||
var lfactor = Math.cos(minLat*Math.PI/180);
|
||||
var ylen = (maxLat-minLat);
|
||||
var xlen = (maxLong-minLong)* lfactor;
|
||||
var scale = xlen>ylen ? 200/xlen : 200/ylen;
|
||||
var screenSize = g.getHeight()-48; // 24 for widgets, plus a border
|
||||
var scale = xlen>ylen ? screenSize/xlen : screenSize/ylen;
|
||||
return {
|
||||
fn : fn,
|
||||
filename : filename,
|
||||
|
@ -110,6 +111,7 @@ function getTrackInfo(fn) {
|
|||
records : nl,
|
||||
minLat : minLat, maxLat : maxLat,
|
||||
minLong : minLong, maxLong : maxLong,
|
||||
lat : (minLat+maxLat)/2, lon : (minLong+maxLong)/2,
|
||||
lfactor : lfactor,
|
||||
scale : scale,
|
||||
duration : Math.round(duration/1000)
|
||||
|
@ -180,16 +182,18 @@ function plotTrack(info) {
|
|||
getMapXY = osm.latLonToXY.bind(osm);
|
||||
} else {
|
||||
getMapXY = function(lat, lon) { "ram"
|
||||
var ix = 30 + Math.round((long - info.minLong)*info.lfactor*info.scale);
|
||||
var iy = 210 - Math.round((lat - info.minLat)*info.scale);
|
||||
return {x:ix, y:iy};
|
||||
return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale),
|
||||
y:cy + Math.round((info.lat - lat)*info.scale)};
|
||||
}
|
||||
}
|
||||
|
||||
E.showMenu(); // remove menu
|
||||
E.showMessage("Drawing...","GPS Track "+info.fn);
|
||||
g.flip(); // on buffered screens, draw a not saying we're busy
|
||||
g.clear(1);
|
||||
var s = require("Storage");
|
||||
var cx = g.getWidth()/2;
|
||||
var cy = g.getHeight()/2;
|
||||
var cy = 24 + (g.getHeight()-24)/2;
|
||||
g.setColor(1,0.5,0.5);
|
||||
g.setFont("Vector",16);
|
||||
g.drawString("Track"+info.fn.toString()+" - Loading",10,220);
|
||||
|
@ -203,8 +207,8 @@ function plotTrack(info) {
|
|||
g.drawString("N",2,40);
|
||||
g.setColor(1,1,1);
|
||||
} else {
|
||||
osm.lat = (info.minLat+info.maxLat)/2;
|
||||
osm.lon = (info.minLong+info.maxLong)/2;
|
||||
osm.lat = info.lat;
|
||||
osm.lon = info.lon;
|
||||
osm.draw();
|
||||
g.setColor(0, 0, 0);
|
||||
}
|
||||
|
@ -251,7 +255,8 @@ function plotTrack(info) {
|
|||
g.drawString("Back",230,200);
|
||||
setWatch(function() {
|
||||
viewTrack(info.fn, info);
|
||||
}, BTN3);
|
||||
}, global.BTN3||BTN1);
|
||||
Bangle.drawWidgets();
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
@ -260,8 +265,8 @@ function plotGraph(info, style) {
|
|||
E.showMenu(); // remove menu
|
||||
E.showMessage("Calculating...","GPS Track "+info.fn);
|
||||
var filename = getFN(info.fn);
|
||||
var infn = new Float32Array(200);
|
||||
var infc = new Uint16Array(200);
|
||||
var infn = new Float32Array(80);
|
||||
var infc = new Uint16Array(80);
|
||||
var title;
|
||||
var lt = 0; // last time
|
||||
var tn = 0; // count for each time period
|
||||
|
@ -278,7 +283,7 @@ function plotGraph(info, style) {
|
|||
title = "Altitude (m)";
|
||||
while(l!==undefined) {
|
||||
++nl;c=l.split(",");
|
||||
i = Math.round(200*(c[0]/1000 - strt)/dur);
|
||||
i = Math.round(80*(c[0]/1000 - strt)/dur);
|
||||
infn[i]+=+c[3];
|
||||
infc[i]++;
|
||||
l = f.readLine(f);
|
||||
|
@ -289,7 +294,7 @@ function plotGraph(info, style) {
|
|||
var t,dx,dy,d,lt = c[0]/1000;
|
||||
while(l!==undefined) {
|
||||
++nl;c=l.split(",");
|
||||
i = Math.round(200*(c[0]/1000 - strt)/dur);
|
||||
i = Math.round(80*(c[0]/1000 - strt)/dur);
|
||||
t = c[0]/1000;
|
||||
p = Bangle.project({lat:c[1],lon:c[2]});
|
||||
dx = p.x-lp.x;
|
||||
|
@ -320,9 +325,9 @@ function plotGraph(info, style) {
|
|||
// draw
|
||||
g.clear(1).setFont("6x8",1);
|
||||
var r = require("graph").drawLine(g, infn, {
|
||||
x:4,y:0,
|
||||
x:4,y:24,
|
||||
width: g.getWidth()-24,
|
||||
height: g.getHeight()-8,
|
||||
height: g.getHeight()-(24+8),
|
||||
axes : true,
|
||||
gridy : grid,
|
||||
gridx : 50,
|
||||
|
@ -334,7 +339,7 @@ function plotGraph(info, style) {
|
|||
g.drawString("Back",230,200);
|
||||
setWatch(function() {
|
||||
viewTrack(info.fn, info);
|
||||
}, BTN3);
|
||||
}, global.BTN3||BTN1);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,47 @@
|
|||
This app will allow you to keep scores for most kinds of sports.
|
||||
|
||||
# Keybinds
|
||||
To correct a falsely awarded point simply open and close the menu within .5 seconds. This will put the app into correction mode (indicated by the `R`).
|
||||
In this mode any score increments will be decrements. To move back a set, reduce both players scores to 0, then decrement one of the scores once again.
|
||||
|
||||
## Bangle.js 1
|
||||
| Keybinding | Description |
|
||||
|---------------------|------------------------------|
|
||||
| `BTN1` | Increment left player score |
|
||||
| `BTN3` | Increment right player score |
|
||||
| `BTN2` | Menu |
|
||||
| touch on left side | Scroll up |
|
||||
| touch on right side | Scroll down |
|
||||
|
||||
## Bangle.js 2
|
||||
| Keybinding | Description |
|
||||
|-------------------------------------|------------------------------|
|
||||
| `BTN1` | Menu |
|
||||
| touch on left side of divider line | Increment left player score |
|
||||
| touch on right side of divider line | Increment right player score |
|
||||
|
||||
# Settings
|
||||
| Setting | Description |
|
||||
|------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `Presets` | Enable a preset for one of the configured sports |
|
||||
| `Sets to win` | How many sets a player has to win before the match is won (Maximum sets: this*2-1) |
|
||||
| `Sets per page` | How many sets should be shown in the app. Further sets will be available by scrolling (ignored if higher than `Sets to win`) |
|
||||
| `Score to win` | What score ends a given set |
|
||||
| `2-point lead` | Does winning a set require a two-point lead |
|
||||
| `Maximum score?` | Should there be a maximum score, at which point the two-point lead rule falls away |
|
||||
| `Maximum score` | At which score should the two-point lead rule fall away (ignored if lower than Sets to win) |
|
||||
| `Tennis scoring` | If enabled, each point in a set will require a full tennis game |
|
||||
| `TB sets?` | Should sets that have reached `(maxScore-1):(maxScore-1)` be decided with a tiebreak |
|
||||
| All other options starting with TB | Equivalent to option with same name but applied to tiebreaks |
|
||||
|
||||
The settings can be changed both from within the app by simply pressing `BTN2` (`BTN1` on Bangle.js 2) or in the `App Settings` in the `Settings` app.
|
||||
|
||||
If changes are made to the settings from within the app, a new match will automatically be initialized upon exiting the settings.
|
||||
|
||||
By default the settings will reflect Badminton rules.
|
||||
|
||||
## Tennis Scoring
|
||||
While tennis scoring is available, correcting in this mode will reset to the beginning of the current game.
|
||||
Resetting at the beginning of the current game will reset to the beginning of the previous game, leaving the user to fast-forward to the correct score once again.
|
||||
|
||||
This might get changed at some point.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AE2IxAKSCigv/F/4vS44ABB4IECAAoKECgM7AAIJBAgQAFBQguJF6HHEhAvKGAwvy4wPB4wuGBQwdCmgJBmguGBQwvJ0ulF5AKFEgeCwQvIBQqPJ4wuHBQ4lEFw4KHF5IAQFJAALF+vNACYv/F/4v053P64vPxPXAAOJF6vP6wbCF52zCQQAB2YvTDIgvOLoWzMJQvOL6JeCss7spgIF5nPMQgvNCAQEBr4FEd6YvVAowv/F/4v4d9WzCANlndlAgOzF82JFQWJGgWJF8xgDAAReGF8RhDLo4vRABQiHABgv/F/4v/F4owTCgIuZAH4A/AH4A/ADgA=="))
|
|
@ -0,0 +1,478 @@
|
|||
require('Font5x9Numeric7Seg').add(Graphics);
|
||||
require('Font7x11Numeric7Seg').add(Graphics);
|
||||
require('FontTeletext5x9Ascii').add(Graphics);
|
||||
|
||||
let settingsMenu = eval(require('Storage').read('score.settings.js'));
|
||||
let settings = settingsMenu(null, null, true);
|
||||
|
||||
let tennisScores = ['00','15','30','40','DC','AD'];
|
||||
|
||||
let scores = null;
|
||||
let tScores = null;
|
||||
let cSet = null;
|
||||
|
||||
let firstShownSet = null;
|
||||
|
||||
let settingsMenuOpened = null;
|
||||
let correctionMode = false;
|
||||
|
||||
let w = g.getWidth();
|
||||
let h = g.getHeight();
|
||||
|
||||
let isBangle1 = process.env.BOARD === 'BANGLEJS';
|
||||
|
||||
function getXCoord(func) {
|
||||
let offset = 40;
|
||||
return func(w-offset)+offset;
|
||||
}
|
||||
|
||||
function getSecondsTime() {
|
||||
return Math.floor(getTime() * 1000);
|
||||
}
|
||||
|
||||
function setupDisplay() {
|
||||
// make sure LCD on Bangle.js 1 stays on
|
||||
if (isBangle1) {
|
||||
if (settings.keepDisplayOn) {
|
||||
Bangle.setLCDTimeout(0);
|
||||
Bangle.setLCDPower(true);
|
||||
} else {
|
||||
Bangle.setLCDTimeout(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupInputWatchers(init) {
|
||||
Bangle.setUI('updown', v => {
|
||||
if (v) {
|
||||
if (isBangle1) {
|
||||
let i = settings.mirrorScoreButtons ? v : v * -1;
|
||||
handleInput(Math.floor((i+2)/2));
|
||||
} else {
|
||||
handleInput(Math.floor((v+2)/2)+3);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (init) {
|
||||
setWatch(() => handleInput(2), isBangle1 ? BTN2 : BTN, { repeat: true });
|
||||
Bangle.on('touch', (b, e) => {
|
||||
if (isBangle1) {
|
||||
if (b === 1) {
|
||||
handleInput(3);
|
||||
} else {
|
||||
handleInput(4);
|
||||
}
|
||||
} else {
|
||||
if (e.x < getXCoord(w => w/2)) {
|
||||
handleInput(0);
|
||||
} else {
|
||||
handleInput(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setupMatch() {
|
||||
scores = [];
|
||||
for (let s = 0; s < sets(); s++) {
|
||||
scores.push([0,0,null,0,0]);
|
||||
}
|
||||
scores.push([0,0,null,0,0]);
|
||||
|
||||
if (settings.enableTennisScoring) {
|
||||
tScores = [0,0];
|
||||
} else {
|
||||
tScores = null;
|
||||
}
|
||||
|
||||
scores[0][2] = getSecondsTime();
|
||||
|
||||
cSet = 0;
|
||||
setFirstShownSet();
|
||||
|
||||
correctionMode = false;
|
||||
}
|
||||
|
||||
function showSettingsMenu() {
|
||||
settingsMenuOpened = getSecondsTime();
|
||||
l = null;
|
||||
settingsMenu(function (s, reset) {
|
||||
E.showMenu();
|
||||
|
||||
settings = s;
|
||||
|
||||
if (reset) {
|
||||
setupMatch();
|
||||
} else if (getSecondsTime() - settingsMenuOpened < 500 || correctionMode) {
|
||||
correctionMode = !correctionMode;
|
||||
}
|
||||
|
||||
settingsMenuOpened = null;
|
||||
|
||||
draw();
|
||||
|
||||
setupDisplay();
|
||||
setupInputWatchers();
|
||||
}, function (msg) {
|
||||
switch (msg) {
|
||||
case 'end_set':
|
||||
updateCurrentSet(1);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function maxScore() {
|
||||
return Math.max(settings.maxScore, settings.winScore);
|
||||
}
|
||||
|
||||
function tiebreakMaxScore() {
|
||||
return Math.max(settings.maxScoreTiebreakMaxScore, settings.maxScoreTiebreakWinScore);
|
||||
}
|
||||
|
||||
function setsPerPage() {
|
||||
return Math.min(settings.setsPerPage, sets());
|
||||
}
|
||||
|
||||
function sets() {
|
||||
return settings.winSets * 2 - 1;
|
||||
}
|
||||
|
||||
function currentSet() {
|
||||
return matchEnded() ? cSet - 1 : cSet;
|
||||
}
|
||||
|
||||
function shouldTiebreak() {
|
||||
return settings.enableMaxScoreTiebreak &&
|
||||
scores[cSet][0] + scores[cSet][1] === (maxScore() - 1) * 2;
|
||||
}
|
||||
|
||||
function formatNumber(num, length) {
|
||||
return num.toString().padStart(length ? length : 2,"0");
|
||||
}
|
||||
|
||||
function formatDuration(duration) {
|
||||
let durS = Math.floor(duration / 1000);
|
||||
let durM = Math.floor(durS / 60);
|
||||
let durH = Math.floor(durM / 60);
|
||||
durS = durS - durM * 60;
|
||||
durM = durM - durH * 60;
|
||||
|
||||
durS = formatNumber(durS);
|
||||
durM = formatNumber(durM);
|
||||
durH = formatNumber(durH);
|
||||
|
||||
let dur = null;
|
||||
if (durH > 0) {
|
||||
dur = durH + ':' + durM;
|
||||
} else {
|
||||
dur = durM + ':' + durS;
|
||||
}
|
||||
|
||||
return dur;
|
||||
}
|
||||
|
||||
function tiebreakWon(set, player) {
|
||||
let pScore = scores[set][3+player];
|
||||
let p2Score = scores[set][3+~~!player];
|
||||
|
||||
let winScoreReached = pScore >= settings.maxScoreTiebreakWinScore;
|
||||
let isTwoAhead = !settings.maxScoreTiebreakEnableTwoAhead || pScore - p2Score >= 2;
|
||||
let reachedMaxScore = settings.maxScoreTiebreakEnableMaxScore && pScore >= tiebreakMaxScore();
|
||||
|
||||
return reachedMaxScore || (winScoreReached && isTwoAhead);
|
||||
}
|
||||
|
||||
function setWon(set, player) {
|
||||
let pScore = scores[set][player];
|
||||
let p2Score = scores[set][~~!player];
|
||||
|
||||
let winScoreReached = pScore >= settings.winScore;
|
||||
let isTwoAhead = !settings.enableTwoAhead || pScore - p2Score >= 2;
|
||||
let tiebreakW = tiebreakWon(set, player);
|
||||
let reachedMaxScore = settings.enableMaxScore && pScore >= maxScore();
|
||||
let manuallyEndedWon = cSet > set ? pScore > p2Score : false;
|
||||
|
||||
return (
|
||||
(settings.enableMaxScoreTiebreak ? tiebreakW : reachedMaxScore) ||
|
||||
(winScoreReached && isTwoAhead) ||
|
||||
manuallyEndedWon
|
||||
);
|
||||
}
|
||||
|
||||
function setEnded(set) {
|
||||
return setWon(set, 0) || setWon(set, 1);
|
||||
}
|
||||
|
||||
function setsWon(player) {
|
||||
return Array(sets()).fill(0).map((_, s) => ~~setWon(s, player)).reduce((a,v) => a+v, 0);
|
||||
}
|
||||
|
||||
function matchWon(player) {
|
||||
return setsWon(player) >= settings.winSets;
|
||||
}
|
||||
|
||||
function matchEnded() {
|
||||
return (matchWon(0) || matchWon(1)) && cSet > (setsWon(0) + setsWon(1) - 1);
|
||||
}
|
||||
|
||||
function matchScore(player) {
|
||||
return scores.reduce((acc, val) => acc += val[player], 0);
|
||||
}
|
||||
|
||||
function setFirstShownSet() {
|
||||
firstShownSet = Math.max(0, currentSet() - setsPerPage() + 1);
|
||||
}
|
||||
|
||||
function updateCurrentSet(val) {
|
||||
if (val > 0) {
|
||||
cSet++;
|
||||
} else if (val < 0) {
|
||||
cSet--;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
setFirstShownSet();
|
||||
|
||||
if (val > 0) {
|
||||
scores[cSet][2] = getSecondsTime();
|
||||
|
||||
if (matchEnded()) {
|
||||
firstShownSet = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function score(player) {
|
||||
if (!matchEnded()) {
|
||||
setFirstShownSet();
|
||||
}
|
||||
|
||||
if (correctionMode) {
|
||||
if (
|
||||
scores[cSet][0] === 0 && scores[cSet][1] === 0 &&
|
||||
scores[cSet][3] === 0 && scores[cSet][4] === 0 &&
|
||||
cSet > 0
|
||||
) {
|
||||
updateCurrentSet(-1);
|
||||
}
|
||||
|
||||
if (scores[cSet][3] > 0 || scores[cSet][4] > 0) {
|
||||
if (scores[cSet][3+player] > 0) {
|
||||
scores[cSet][3+player]--;
|
||||
}
|
||||
} else if (scores[cSet][player] > 0) {
|
||||
if (
|
||||
!settings.enableTennisScoring ||
|
||||
(tScores[player] === 0 && tScores[~~!player] === 0)
|
||||
) {
|
||||
scores[cSet][player]--;
|
||||
} else {
|
||||
tScores[player] = 0;
|
||||
tScores[~~!player] = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (matchEnded()) return;
|
||||
|
||||
if (shouldTiebreak()) {
|
||||
scores[cSet][3+player]++;
|
||||
} else if (settings.enableTennisScoring) {
|
||||
if (tScores[player] === 4 && tScores[~~!player] === 5) { // DC : AD
|
||||
tScores[~~!player]--;
|
||||
} else if (tScores[player] === 2 && tScores[~~!player] === 3) { // 30 : 40
|
||||
tScores[0] = 4;
|
||||
tScores[1] = 4;
|
||||
} else if (tScores[player] === 3 || tScores[player] === 5) { // 40 / AD
|
||||
tScores[0] = 0;
|
||||
tScores[1] = 0;
|
||||
scores[cSet][player]++;
|
||||
} else {
|
||||
tScores[player]++;
|
||||
}
|
||||
} else {
|
||||
scores[cSet][player]++;
|
||||
}
|
||||
|
||||
if (setEnded(cSet) && cSet < sets()) {
|
||||
if (shouldTiebreak()) {
|
||||
scores[cSet][player]++;
|
||||
}
|
||||
updateCurrentSet(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleInput(button) {
|
||||
if (settingsMenuOpened) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (button) {
|
||||
case 0:
|
||||
case 1:
|
||||
score(button);
|
||||
break;
|
||||
case 2:
|
||||
showSettingsMenu();
|
||||
return;
|
||||
case 3:
|
||||
case 4:
|
||||
let hLimit = currentSet() - setsPerPage() + 1;
|
||||
let lLimit = 0;
|
||||
let val = (button * 2 - 7);
|
||||
firstShownSet += val;
|
||||
if (firstShownSet > hLimit) firstShownSet = hLimit;
|
||||
if (firstShownSet < lLimit) firstShownSet = lLimit;
|
||||
break;
|
||||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.setFontAlign(0,0);
|
||||
g.clear();
|
||||
|
||||
for (let p = 0; p < 2; p++) {
|
||||
if (matchWon(p)) {
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont('Teletext5x9Ascii',2);
|
||||
g.drawString(
|
||||
"WINNER",
|
||||
getXCoord(w => p === 0 ? w/4 : w/4*3),
|
||||
15
|
||||
);
|
||||
} else if (matchEnded()) {
|
||||
g.setFontAlign(1,0);
|
||||
|
||||
g.setFont('Teletext5x9Ascii',1);
|
||||
g.drawString(
|
||||
(currentSet()+1) + ' set' + (currentSet() > 0 ? 's' : ''),
|
||||
40,
|
||||
8
|
||||
);
|
||||
|
||||
let dur1 = formatDuration(scores[cSet][2] - scores[0][2]);
|
||||
g.setFont('5x9Numeric7Seg',1);
|
||||
g.drawString(
|
||||
dur1,
|
||||
40,
|
||||
18
|
||||
);
|
||||
}
|
||||
|
||||
g.setFontAlign(p === 0 ? -1 : 1,1);
|
||||
g.setFont('5x9Numeric7Seg',2);
|
||||
g.drawString(
|
||||
setsWon(p),
|
||||
getXCoord(w => p === 0 ? 5 : w-3),
|
||||
h-5
|
||||
);
|
||||
|
||||
if (!settings.enableTennisScoring) {
|
||||
g.setFontAlign(p === 0 ? 1 : -1,1);
|
||||
g.setFont('7x11Numeric7Seg',2);
|
||||
g.drawString(
|
||||
formatNumber(matchScore(p), 3),
|
||||
getXCoord(w => p === 0 ? w/2 - 3 : w/2 + 6),
|
||||
h-5
|
||||
);
|
||||
}
|
||||
}
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
if (correctionMode) {
|
||||
g.setFont('Teletext5x9Ascii',2);
|
||||
g.drawString(
|
||||
"R",
|
||||
getXCoord(w => w/2),
|
||||
h-10
|
||||
);
|
||||
}
|
||||
|
||||
let lastShownSet = Math.min(
|
||||
sets(),
|
||||
currentSet() + 1,
|
||||
firstShownSet+setsPerPage()
|
||||
);
|
||||
let setsOnCurrentPage = Math.min(
|
||||
sets(),
|
||||
setsPerPage()
|
||||
);
|
||||
for (let set = firstShownSet; set < lastShownSet; set++) {
|
||||
if (set < 0) continue;
|
||||
|
||||
let y = (h-15)/(setsOnCurrentPage+1)*(set-firstShownSet+1)+5;
|
||||
|
||||
g.setFontAlign(-1,0);
|
||||
g.setFont('7x11Numeric7Seg',1);
|
||||
g.drawString(set+1, 5, y-10);
|
||||
if (scores[set+1][2] != null) {
|
||||
let dur2 = formatDuration(scores[set+1][2] - scores[set][2]);
|
||||
g.drawString(dur2, 5, y+10);
|
||||
}
|
||||
|
||||
for (let p = 0; p < 2; p++) {
|
||||
if (!setWon(set, p === 0 ? 1 : 0) || matchEnded()) {
|
||||
let bigNumX = getXCoord(w => p === 0 ? w/4-12 : w/4*3+15);
|
||||
let smallNumX = getXCoord(w => p === 0 ? w/2-2 : w/2+3);
|
||||
|
||||
if (settings.enableTennisScoring && set === cSet && !shouldTiebreak()) {
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont('7x11Numeric7Seg',3);
|
||||
g.drawString(
|
||||
formatNumber(tennisScores[tScores[p]]),
|
||||
bigNumX,
|
||||
y
|
||||
);
|
||||
} else if (shouldTiebreak() && set === cSet) {
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont('7x11Numeric7Seg',3);
|
||||
g.drawString(
|
||||
formatNumber(scores[set][3+p], 3),
|
||||
bigNumX,
|
||||
y
|
||||
);
|
||||
} else {
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont('7x11Numeric7Seg',3);
|
||||
g.drawString(
|
||||
formatNumber(scores[set][p]),
|
||||
bigNumX,
|
||||
y
|
||||
);
|
||||
}
|
||||
|
||||
if ((shouldTiebreak() || settings.enableTennisScoring) && set === cSet) {
|
||||
g.setFontAlign(p === 0 ? 1 : -1,0);
|
||||
g.setFont('7x11Numeric7Seg',1);
|
||||
g.drawString(
|
||||
formatNumber(scores[set][p]),
|
||||
smallNumX,
|
||||
y
|
||||
);
|
||||
} else if ((scores[set][3] !== 0 || scores[set][4] !== 0) && set !== cSet) {
|
||||
g.setFontAlign(p === 0 ? 1 : -1,0);
|
||||
g.setFont('7x11Numeric7Seg',1);
|
||||
g.drawString(
|
||||
formatNumber(scores[set][3+p], 3),
|
||||
smallNumX,
|
||||
y
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw separator
|
||||
g.drawLine(getXCoord(w => w/2), 20, getXCoord(w => w/2), h-25);
|
||||
|
||||
g.flip();
|
||||
}
|
||||
|
||||
setupDisplay();
|
||||
setupInputWatchers(true);
|
||||
setupMatch();
|
||||
draw();
|
Binary file not shown.
After Width: | Height: | Size: 897 B |
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"Badminton": {
|
||||
"winScore": 21,
|
||||
"enableTwoAhead": true,
|
||||
"enableMaxScore": true,
|
||||
"maxScore": 30
|
||||
},
|
||||
"Tennis": {
|
||||
"winScore": 6,
|
||||
"enableTwoAhead": true,
|
||||
"enableMaxScore": true,
|
||||
"maxScore": 7,
|
||||
"enableMaxScoreTiebreak": true,
|
||||
"maxScoreTiebreakWinScore": 7,
|
||||
"maxScoreTiebreakEnableTwoAhead": true,
|
||||
"maxScoreTiebreakEnableMaxScore": false,
|
||||
"enableTennisScoring": true
|
||||
},
|
||||
"Soccer": {
|
||||
"winSets": 1,
|
||||
"winScore": 9999,
|
||||
"enableTwoAhead": false,
|
||||
"enableMaxScore": false
|
||||
},
|
||||
"Table Tennis": {
|
||||
"winScore": 11,
|
||||
"enableTwoAhead": true,
|
||||
"enableMaxScore": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
(function () {
|
||||
return (function (back, inApp, ret) {
|
||||
const isBangle1 = process.env.BOARD === 'BANGLEJS'
|
||||
|
||||
function fillSettingsWithDefaults(settings) {
|
||||
if (isBangle1) {
|
||||
if (settings.mirrorScoreButtons == null) {
|
||||
settings.mirrorScoreButtons = false;
|
||||
}
|
||||
if (settings.keepDisplayOn == null) {
|
||||
settings.keepDisplayOn = true;
|
||||
}
|
||||
}
|
||||
if (settings.winSets == null) {
|
||||
settings.winSets = 2;
|
||||
}
|
||||
if (settings.setsPerPage == null) {
|
||||
settings.setsPerPage = 5;
|
||||
}
|
||||
if (settings.winScore == null) {
|
||||
settings.winScore = 21;
|
||||
}
|
||||
if (settings.enableTwoAhead == null) {
|
||||
settings.enableTwoAhead = true;
|
||||
}
|
||||
if (settings.enableMaxScore == null) {
|
||||
settings.enableMaxScore = true;
|
||||
}
|
||||
if (settings.maxScore == null) {
|
||||
settings.maxScore = 30;
|
||||
}
|
||||
if (settings.enableTennisScoring == null) {
|
||||
settings.enableTennisScoring = false;
|
||||
}
|
||||
|
||||
if (settings.enableMaxScoreTiebreak == null) {
|
||||
settings.enableMaxScoreTiebreak = false;
|
||||
}
|
||||
if (settings.maxScoreTiebreakWinScore == null) {
|
||||
settings.maxScoreTiebreakWinScore = 6;
|
||||
}
|
||||
if (settings.maxScoreTiebreakEnableTwoAhead == null) {
|
||||
settings.maxScoreTiebreakEnableTwoAhead = true;
|
||||
}
|
||||
if (settings.maxScoreTiebreakEnableMaxScore == null) {
|
||||
settings.maxScoreTiebreakEnableMaxScore = false;
|
||||
}
|
||||
if (settings.maxScoreTiebreakMaxScore == null) {
|
||||
settings.maxScoreTiebreakMaxScore = 15;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
const fileName = 'score.json';
|
||||
let settings = require('Storage').readJSON(fileName, 1) || {};
|
||||
const offon = ['No', 'Yes'];
|
||||
|
||||
let presetsFileName = 'score.presets.json';
|
||||
let presets = require('Storage').readJSON(presetsFileName);
|
||||
let presetNames = Object.keys(presets);
|
||||
|
||||
let changed = false;
|
||||
|
||||
function save(settings) {
|
||||
require('Storage').writeJSON(fileName, settings);
|
||||
}
|
||||
|
||||
function setAndSave(key, value, notChanged) {
|
||||
if (!notChanged) {
|
||||
changed = true;
|
||||
}
|
||||
settings[key] = value;
|
||||
if (key === 'winScore' && settings.maxScore < value) {
|
||||
settings.maxScore = value;
|
||||
}
|
||||
save(settings);
|
||||
}
|
||||
|
||||
settings = fillSettingsWithDefaults(settings);
|
||||
|
||||
if (ret) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
const presetMenu = function (appMenuBack) {
|
||||
let ret = function (changed) { E.showMenu(appMenu(appMenuBack, changed ? 2 : null)); };
|
||||
let m = {
|
||||
'': {'title': 'Score Presets'},
|
||||
'< Back': ret,
|
||||
};
|
||||
for (let i = 0; i < presetNames.length; i++) {
|
||||
m[presetNames[i]] = (function (i) {
|
||||
return function() {
|
||||
changed = true;
|
||||
let mirrorScoreButtons = settings.mirrorScoreButtons;
|
||||
let keepDisplayOn = settings.keepDisplayOn;
|
||||
|
||||
settings = fillSettingsWithDefaults(presets[presetNames[i]]);
|
||||
|
||||
settings.mirrorScoreButtons = mirrorScoreButtons;
|
||||
settings.keepDisplayOn = keepDisplayOn;
|
||||
save(settings);
|
||||
ret(true);
|
||||
};
|
||||
})(i);
|
||||
}
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
const appMenu = function (back, selected) {
|
||||
let m = {};
|
||||
|
||||
m[''] = {'title': 'Score Settings'};
|
||||
if (selected != null) {
|
||||
m[''].selected = selected;
|
||||
}
|
||||
m['< Back'] = function () { back(settings, changed); };
|
||||
m['Presets'] = function () { E.showMenu(presetMenu(back)); };
|
||||
if (isBangle1) {
|
||||
m['Mirror Buttons'] = {
|
||||
value: settings.mirrorScoreButtons,
|
||||
format: m => offon[~~m],
|
||||
onchange: m => setAndSave('mirrorScoreButtons', m, true),
|
||||
};
|
||||
m['Keep display on'] = {
|
||||
value: settings.keepDisplayOn,
|
||||
format: m => offon[~~m],
|
||||
onchange: m => setAndSave('keepDisplayOn', m, true),
|
||||
}
|
||||
}
|
||||
m['Sets to win'] = {
|
||||
value: settings.winSets,
|
||||
min:1,
|
||||
onchange: m => setAndSave('winSets', m),
|
||||
};
|
||||
m['Sets per page'] = {
|
||||
value: settings.setsPerPage,
|
||||
min:1,
|
||||
max:5,
|
||||
onchange: m => setAndSave('setsPerPage', m),
|
||||
};
|
||||
m['Score to win'] = {
|
||||
value: settings.winScore,
|
||||
min:1,
|
||||
max: 999,
|
||||
onchange: m => setAndSave('winScore', m),
|
||||
};
|
||||
m['2-point lead'] = {
|
||||
value: settings.enableTwoAhead,
|
||||
format: m => offon[~~m],
|
||||
onchange: m => setAndSave('enableTwoAhead', m),
|
||||
};
|
||||
m['Maximum score?'] = {
|
||||
value: settings.enableMaxScore,
|
||||
format: m => offon[~~m],
|
||||
onchange: m => setAndSave('enableMaxScore', m),
|
||||
};
|
||||
m['Maximum score'] = {
|
||||
value: settings.maxScore,
|
||||
min: 1,
|
||||
max: 999,
|
||||
onchange: m => setAndSave('maxScore', m),
|
||||
};
|
||||
m['Tennis scoring'] = {
|
||||
value: settings.enableTennisScoring,
|
||||
format: m => offon[~~m],
|
||||
onchange: m => setAndSave('enableTennisScoring', m),
|
||||
};
|
||||
m['TB sets?'] = {
|
||||
value: settings.enableMaxScoreTiebreak,
|
||||
format: m => offon[~~m],
|
||||
onchange: m => setAndSave('enableMaxScoreTiebreak', m),
|
||||
};
|
||||
m['TB Score to win'] = {
|
||||
value: settings.maxScoreTiebreakWinScore,
|
||||
onchange: m => setAndSave('maxScoreTiebreakWinScore', m),
|
||||
};
|
||||
m['TB 2-point lead'] = {
|
||||
value: settings.maxScoreTiebreakEnableTwoAhead,
|
||||
format: m => offon[~~m],
|
||||
onchange: m => setAndSave('maxScoreTiebreakEnableTwoAhead', m),
|
||||
};
|
||||
m['TB max score?'] = {
|
||||
value: settings.maxScoreTiebreakEnableMaxScore,
|
||||
format: m => offon[~~m],
|
||||
onchange: m => setAndSave('maxScoreTiebreakEnableMaxScore', m),
|
||||
};
|
||||
m['TB max score'] = {
|
||||
value: settings.maxScoreTiebreakMaxScore,
|
||||
onchange: m => setAndSave('maxScoreTiebreakMaxScore', m),
|
||||
};
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
const inAppMenu = function () {
|
||||
let m = {
|
||||
'': {'title': 'Score Menu'},
|
||||
'< Back': function () { back(settings, changed); },
|
||||
'Reset match': function () { back(settings, true); },
|
||||
'End current set': function () { inApp('end_set'); back(settings, changed); },
|
||||
'Configuration': function () { E.showMenu(appMenu(function () {
|
||||
E.showMenu(inAppMenu());
|
||||
})); },
|
||||
};
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
if (inApp != null) {
|
||||
E.showMenu(inAppMenu());
|
||||
} else {
|
||||
E.showMenu(appMenu(back));
|
||||
}
|
||||
|
||||
});
|
||||
})();
|
|
@ -2,3 +2,4 @@
|
|||
0.03: Ensure redrawing works with variable size widget system
|
||||
0.04: Fix automatic update of Bluetooth connection status
|
||||
0.05: Make Bluetooth widget thinner, and when on a bright theme use light grey for disabled color
|
||||
0.06: Tweaking colors for dark/light themes and low bpp screens
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
function draw() {
|
||||
g.reset();
|
||||
if (NRF.getSecurityStatus().connected)
|
||||
g.setColor("#07f");
|
||||
g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
|
||||
else
|
||||
g.setColor(g.theme.bg ? "#AAA" : "#555");
|
||||
g.setColor(g.theme.dark ? "#666" : "#999");
|
||||
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y);
|
||||
}
|
||||
function changed() {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New Widget!
|
||||
0.02: Tweaks for variable size widget system
|
||||
0.03: Tweaking colors for dark/light themes
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
/* jshint esversion: 6 */
|
||||
(() => {
|
||||
function draw() {
|
||||
var id = NRF.getAddress().substr().substr(12).split(":");
|
||||
g.reset().setColor(0, 0.49, 1).setFont("6x8", 1);
|
||||
g.reset().setColor(g.theme.dark ? "#0ff" : "#00f").setFont("6x8", 1);
|
||||
g.drawString(id[0], this.x+2, this.y+4, true);
|
||||
g.drawString(id[1], this.x+2, this.y+14, true);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue