mirror of https://github.com/espruino/BangleApps
create new app 'score'
parent
f3712e6676
commit
154652e51d
13
apps.json
13
apps.json
|
@ -3554,5 +3554,18 @@
|
|||
{"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, ...)",
|
||||
"tags": "b2",
|
||||
"type": "app",
|
||||
"storage": [
|
||||
{"name":"score.app.js","url":"score.app.js"},
|
||||
{"name":"score.settings.js","url":"score.settings.js"},
|
||||
{"name":"score.img","url":"score.app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -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,288 @@
|
|||
require('Font5x9Numeric7Seg').add(Graphics);
|
||||
require('Font7x11Numeric7Seg').add(Graphics);
|
||||
require('FontTeletext5x9Ascii').add(Graphics);
|
||||
require('FontTeletext10x18Ascii').add(Graphics);
|
||||
|
||||
let settingsMenu = eval(require('Storage').read('score.settings.js'));
|
||||
let settings = settingsMenu(null, true);
|
||||
|
||||
let scores = null;
|
||||
let cSet = null;
|
||||
|
||||
let firstShownSet = null;
|
||||
|
||||
let settingsMenuOpened = null;
|
||||
let correctionMode = false;
|
||||
|
||||
let w = g.getWidth();
|
||||
let h = g.getHeight();
|
||||
|
||||
function setupInputWatchers() {
|
||||
if (global.BTN4) {
|
||||
setWatch(() => handleInput(2), BTN2, { repeat: true });
|
||||
setWatch(() => handleInput(3), BTN1, { repeat: true });
|
||||
setWatch(() => handleInput(4), BTN3, { repeat: true });
|
||||
} else {
|
||||
setWatch(() => handleInput(2), BTN, { repeat: true });
|
||||
}
|
||||
Bangle.on('touch', (b, e) => {
|
||||
if (b) {
|
||||
if (b === 1) {
|
||||
handleInput(0);
|
||||
} else {
|
||||
handleInput(1);
|
||||
}
|
||||
} else {
|
||||
if (e.x < w/2) {
|
||||
handleInput(0);
|
||||
} else {
|
||||
handleInput(1);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setupMatch() {
|
||||
scores = [];
|
||||
for (let s = 0; s < sets(); s++) {
|
||||
scores.push([0,0,null]);
|
||||
}
|
||||
scores.push([0,0,null]);
|
||||
|
||||
scores[0][2] = getTime();
|
||||
|
||||
cSet = 0;
|
||||
firstShownSet = 0 - Math.floor(setsPerPage() / 2);
|
||||
}
|
||||
|
||||
function showSettingsMenu() {
|
||||
settingsMenuOpened = getTime();
|
||||
settingsMenu(function (s, reset) {
|
||||
E.showMenu();
|
||||
|
||||
settings = s;
|
||||
|
||||
if (reset) {
|
||||
setupMatch();
|
||||
} else if (getTime() - settingsMenuOpened < 0.5 || correctionMode) {
|
||||
correctionMode = !correctionMode;
|
||||
}
|
||||
|
||||
settingsMenuOpened = null;
|
||||
|
||||
draw();
|
||||
});
|
||||
}
|
||||
|
||||
function setsPerPage() {
|
||||
return Math.min(settings.setsPerPage, sets());
|
||||
}
|
||||
|
||||
function sets() {
|
||||
return settings.winSets * 2 - 1;
|
||||
}
|
||||
|
||||
function currentSet() {
|
||||
return matchEnded() ? cSet - 1 : cSet;
|
||||
}
|
||||
|
||||
function formatNumber(num, length) {
|
||||
return num.toString().padStart(length ? length : 2,"0");
|
||||
}
|
||||
|
||||
function formatDuration(duration) {
|
||||
let durS = Math.floor(duration);
|
||||
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 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 reachedMaxScore = settings.enableMaxScore && pScore >= settings.maxScore;
|
||||
|
||||
return reachedMaxScore || (winScoreReached && isTwoAhead);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function matchScore(player) {
|
||||
return scores.reduce((acc, val) => acc += val[player], 0);
|
||||
}
|
||||
|
||||
function score(player) {
|
||||
let updateCurrentSet = function (val) {
|
||||
cSet += val;
|
||||
firstShownSet = Math.max(0, currentSet() - settings.setsPerPage + 1);
|
||||
}
|
||||
|
||||
if (!matchEnded() || correctionMode) {
|
||||
firstShownSet = currentSet() - Math.floor(setsPerPage() / 2);
|
||||
}
|
||||
|
||||
if (correctionMode) {
|
||||
if (
|
||||
scores[cSet][0] === 0 && scores[cSet][1] === 0 &&
|
||||
cSet > 0
|
||||
) {
|
||||
updateCurrentSet(-1);
|
||||
}
|
||||
|
||||
if (scores[cSet][player] > 0) {
|
||||
scores[cSet][player]--;
|
||||
}
|
||||
} else {
|
||||
if (matchEnded()) return;
|
||||
|
||||
scores[cSet][player]++;
|
||||
|
||||
if (setEnded(cSet) && cSet < sets()) {
|
||||
updateCurrentSet(1);
|
||||
scores[cSet][2] = getTime();
|
||||
}
|
||||
|
||||
if (matchEnded()) {
|
||||
firstShownSet = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
let lLimit = 1 - setsPerPage();
|
||||
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('Teletext10x18Ascii',1);
|
||||
g.drawString("WINNER", p === 0 ? w/4 : w/4*3, 15);
|
||||
} else if (matchEnded()) {
|
||||
g.setFontAlign(0,-1);
|
||||
|
||||
let dur1 = formatDuration(scores[cSet][2] - scores[0][2]);
|
||||
g.setFont('5x9Numeric7Seg',1);
|
||||
g.drawString(dur1, p === 0 ? w/8 : w/8*5, 10);
|
||||
|
||||
g.setFont('Teletext5x9Ascii',1);
|
||||
g.drawString((currentSet()+1) + ' set' + (currentSet() > 1 ? 's' : ''), p === 0 ? w/8*3 : w/8*7, 12);
|
||||
|
||||
}
|
||||
|
||||
g.setFontAlign(p === 0 ? -1 : 1,1);
|
||||
g.setFont('7x11Numeric7Seg',2);
|
||||
g.drawString(setsWon(p), p === 0 ? 10 : w-8, h-5);
|
||||
|
||||
g.setFontAlign(p === 0 ? 1 : -1,1);
|
||||
g.setFont('7x11Numeric7Seg',2);
|
||||
g.drawString(formatNumber(matchScore(p), 3), p === 0 ? w/2 - 8 : w/2 + 11, h-5);
|
||||
}
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
if (correctionMode) {
|
||||
g.setFont('Teletext10x18Ascii',1);
|
||||
g.drawString("R", 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, 40, y-10);
|
||||
if (scores[set+1][2] != null) {
|
||||
let dur2 = formatDuration(scores[set+1][2] - scores[set][2]);
|
||||
g.drawString(dur2, 40, y+10);
|
||||
}
|
||||
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont('7x11Numeric7Seg',3);
|
||||
for (let p = 0; p < 2; p++) {
|
||||
if (!setWon(set, p === 0 ? 1 : 0) || matchEnded()) {
|
||||
g.drawString(
|
||||
formatNumber(scores[set][p]),
|
||||
p === 0 ? (w-20)/4+20 : (w-20)/4*3+20,
|
||||
y
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw separator
|
||||
g.drawLine(w/2,20,w/2,h-25);
|
||||
|
||||
g.flip();
|
||||
}
|
||||
|
||||
setupInputWatchers();
|
||||
setupMatch();
|
||||
draw();
|
Binary file not shown.
After Width: | Height: | Size: 897 B |
|
@ -0,0 +1,81 @@
|
|||
(function (back, ret) {
|
||||
|
||||
const fileName = 'score.json'
|
||||
let settings = require('Storage').readJSON(fileName, 1) || {};
|
||||
const offon = ['No', 'Yes'];
|
||||
|
||||
let changed = false;
|
||||
|
||||
function save(key, value) {
|
||||
changed = true;
|
||||
settings[key] = value;
|
||||
if (key === 'winScore' && settings.maxScore < value) {
|
||||
settings.maxScore = value;
|
||||
}
|
||||
require('Storage').writeJSON(fileName, settings);
|
||||
}
|
||||
|
||||
if (!settings.winSets) {
|
||||
settings.winSets = 1;
|
||||
}
|
||||
if (!settings.winScore) {
|
||||
settings.winScore = 21;
|
||||
}
|
||||
if (!settings.enableTwoAhead) {
|
||||
settings.enableTwoAhead = true;
|
||||
}
|
||||
if (!settings.enableMaxScore) {
|
||||
settings.enableMaxScore = true;
|
||||
}
|
||||
if (!settings.maxScore) {
|
||||
settings.maxScore = 30;
|
||||
}
|
||||
if (!settings.setsPerPage) {
|
||||
settings.setsPerPage = 5;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
const appMenu = {};
|
||||
appMenu[''] = {'title': 'Score Settings'},
|
||||
appMenu['< Back'] = function () { back(settings, changed); };
|
||||
if (reset) {
|
||||
appMenu['Reset match'] = function () { back(settings, true); };
|
||||
}
|
||||
appMenu['Sets to win'] = {
|
||||
value: settings.winSets,
|
||||
min:1,
|
||||
onchange: m => save('winSets', m)
|
||||
};
|
||||
appMenu['Sets per page'] = {
|
||||
value: settings.setsPerPage,
|
||||
min:1,
|
||||
max:5,
|
||||
onchange: m => save('setsPerPage', m)
|
||||
};
|
||||
appMenu['Score to win'] = {
|
||||
value: settings.winScore,
|
||||
min:1,
|
||||
onchange: m => save('winScore', m)
|
||||
};
|
||||
appMenu['2-point lead'] = {
|
||||
value: settings['enableTwoAhead'],
|
||||
format: m => offon[~~m],
|
||||
onchange: m => save('enableTwoAhead', m)
|
||||
};
|
||||
appMenu['Maximum score?'] = {
|
||||
value: settings['enableMaxScore'],
|
||||
format: m => offon[~~m],
|
||||
onchange: m => save('enableMaxScore', m)
|
||||
};
|
||||
appMenu['Maximum score'] = {
|
||||
value: settings.maxScore,
|
||||
min: settings.winScore,
|
||||
onchange: m => save('maxScore', m)
|
||||
};
|
||||
|
||||
E.showMenu(appMenu)
|
||||
|
||||
})
|
Loading…
Reference in New Issue