Merge pull request #3835 from bobrippling/feat/score-fixes-and-squash

score: fix bangle 2 compat and add squash preset
master
Rob Pilling 2025-05-01 20:10:01 +01:00 committed by GitHub
commit 711c46c5f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 239 additions and 246 deletions

View File

@ -1,3 +1,4 @@
0.01: New App! 0.01: New App!
0.02: Minor code improvements 0.02: Minor code improvements
0.03: Bug fixes and some usability and performance improvements 0.03: Bug fixes and some usability and performance improvements
0.04: Add squash preset, simplify logic, fix compatibility with BangleJS 2

View File

@ -31,8 +31,7 @@ In this mode any score increments will be decrements. To move back a set, reduce
| `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`) | | `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 | | `Score to win` | What score ends a given set |
| `2-point lead` | Does winning a set require a two-point lead | | `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` | Should there be a maximum score, at which point the two-point lead rule falls away (ignored if lower than Sets to win) |
| `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 | | `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 | | `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 | | All other options starting with TB | Equivalent to option with same name but applied to tiebreaks |

View File

@ -1,7 +1,7 @@
{ {
"id": "score", "id": "score",
"name": "Score Tracker", "name": "Score Tracker",
"version": "0.03", "version": "0.04",
"description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.",
"icon": "score.app.png", "icon": "score.app.png",
"screenshots": [{"url":"screenshot_score.png"}], "screenshots": [{"url":"screenshot_score.png"}],

View File

@ -2,6 +2,12 @@ require('Font5x9Numeric7Seg').add(Graphics);
require('Font7x11Numeric7Seg').add(Graphics); require('Font7x11Numeric7Seg').add(Graphics);
require('FontTeletext5x9Ascii').add(Graphics); require('FontTeletext5x9Ascii').add(Graphics);
const KEY_SCORE_L = 0;
const KEY_SCORE_R = 1;
const KEY_MENU = 2;
const KEY_TENNIS_H = 3;
const KEY_TENNIS_L = 4;
let settingsMenu = eval(require('Storage').read('score.settings.js')); let settingsMenu = eval(require('Storage').read('score.settings.js'));
let settings = settingsMenu(null, null, true); let settings = settingsMenu(null, null, true);
@ -44,45 +50,55 @@ function setupDisplay() {
} }
function setupInputWatchers(init) { function setupInputWatchers(init) {
Bangle.setUI('updown', v => { Bangle.setUI('updown',
if (v) { isBangle1
if (isBangle1) { ? (v => {
if (v) {
let i = settings.mirrorScoreButtons ? v : v * -1; let i = settings.mirrorScoreButtons ? v : v * -1;
handleInput(Math.floor((i+2)/2)); handleInput(Math.floor((i+2)/2));
} else { }
})
: (v => {
if (v) {
// +1 -> 4
// -1 -> 3
handleInput(Math.floor((v+2)/2)+3); handleInput(Math.floor((v+2)/2)+3);
} }
} })
}); );
if (init) { if (init) {
if (isBangle1) { setWatch(
setWatch(() => handleInput(2), BTN2, { repeat: true }); () => handleInput(KEY_MENU),
} isBangle1 ? BTN2 : BTN,
Bangle.on('touch', (b, e) => { { repeat: true },
if (isBangle1) { );
Bangle.on('touch',
isBangle1
? ((b, e) => {
if (b === 1) { if (b === 1) {
handleInput(3); handleInput(KEY_TENNIS_H);
} else { } else {
handleInput(4); handleInput(KEY_TENNIS_L);
} }
} else { })
: ((b, e) => {
if (e.y > 18) { if (e.y > 18) {
if (e.x < getXCoord(w => w/2)) { if (e.x < getXCoord(w => w/2)) {
handleInput(0); handleInput(KEY_SCORE_L);
} else { } else {
handleInput(1); handleInput(KEY_SCORE_R);
} }
} else { } else {
// long press except if we have the menu opened or we are in the emulator (that doesn't // long press except if we have the menu opened or we are in the emulator (that doesn't
// seem to support long press events) // seem to support long press events)
if (e.type === 2 || settingsMenuOpened || process.env.BOARD === 'EMSCRIPTEN2') { if (e.type === 2 || settingsMenuOpened || process.env.BOARD === 'EMSCRIPTEN2') {
handleInput(2); handleInput(KEY_MENU);
} else { } else {
let p = null; let p = null;
if (matchWon(0)) p = 0; if (matchWon(0)) p = 0;
else if (matchWon(1)) p = 1; else if (matchWon(1)) p = 1;
// display full instructions if there is space available, or brief ones otherwise // display full instructions if there is space available, or brief ones otherwise
if (p === null) { if (p === null) {
drawInitialMsg(); drawInitialMsg();
@ -97,8 +113,8 @@ function setupInputWatchers(init) {
} }
} }
} }
} })
}); );
} }
} }
@ -137,14 +153,13 @@ function showSettingsMenu() {
if (reset) { if (reset) {
setupMatch(); setupMatch();
} }
if (isBangle1 || (!isBangle1 && back)) {
settingsMenuOpened = null;
draw(); settingsMenuOpened = null;
setupDisplay(); draw();
setupInputWatchers();
} setupDisplay();
setupInputWatchers();
}, function (msg) { }, function (msg) {
switch (msg) { switch (msg) {
case 'end_set': case 'end_set':
@ -213,8 +228,8 @@ function tiebreakWon(set, player) {
let p2Score = scores[set][3+~~!player]; let p2Score = scores[set][3+~~!player];
// reachedMaxScore || (winScoreReached && isTwoAhead); // reachedMaxScore || (winScoreReached && isTwoAhead);
return (settings.maxScoreTiebreakEnableMaxScore && pScore >= tiebreakMaxScore()) || return (settings.maxScoreTiebreakEnableMaxScore && pScore >= tiebreakMaxScore()) ||
((pScore >= settings.maxScoreTiebreakWinScore) && ((pScore >= settings.maxScoreTiebreakWinScore) &&
(!settings.maxScoreTiebreakEnableTwoAhead || pScore - p2Score >= 2)); (!settings.maxScoreTiebreakEnableTwoAhead || pScore - p2Score >= 2));
} }
@ -224,8 +239,8 @@ function setWon(set, player) {
// (tiebreak won / max score) || (winScoreReached && isTwoAhead) || manuallyEndedWon // (tiebreak won / max score) || (winScoreReached && isTwoAhead) || manuallyEndedWon
return ( return (
(settings.enableMaxScoreTiebreak ? tiebreakWon(set, player) : settings.enableMaxScore && pScore >= maxScore()) || (settings.enableMaxScoreTiebreak ? tiebreakWon(set, player) : settings.enableMaxScore && pScore >= maxScore()) ||
(pScore >= settings.winScore && (!settings.enableTwoAhead || pScore - p2Score >= 2)) || (pScore >= settings.winScore && (!settings.enableTwoAhead || pScore - p2Score >= 2)) ||
(cSet > set ? pScore > p2Score : false) (cSet > set ? pScore > p2Score : false)
); );
} }
@ -344,7 +359,7 @@ function handleInput(button) {
// console.log('button:', button); // console.log('button:', button);
if (settingsMenuOpened) { if (settingsMenuOpened) {
if (!isBangle1 && button == 2) { if (!isBangle1 && button == KEY_MENU) { // Bangle2 long press, hide menu
E.showMenu(); E.showMenu();
settingsMenuOpened = null; settingsMenuOpened = null;
@ -353,21 +368,21 @@ function handleInput(button) {
setupDisplay(); setupDisplay();
setupInputWatchers(); setupInputWatchers();
} }
return; return;
} }
switch (button) { switch (button) {
case 0: case KEY_SCORE_L:
case 1: case KEY_SCORE_R:
score(button); score(button);
break; break;
case 2: case KEY_MENU:
showSettingsMenu(); showSettingsMenu();
return; return;
case 3: case KEY_TENNIS_H:
case 4: { case KEY_TENNIS_L: {
let hLimit = currentSet() - setsPerPage() + 1; let hLimit = currentSet() - setsPerPage() + 1;
let lLimit = 0; let lLimit = 0;
let val = (button * 2 - 7); let val = (button * 2 - 7);
@ -382,8 +397,7 @@ function handleInput(button) {
} }
function draw() { function draw() {
g.setFontAlign(0,0); g.reset().setFontAlign(0,0).clear();
g.clear();
for (let p = 0; p < 2; p++) { for (let p = 0; p < 2; p++) {
if (matchWon(p)) { if (matchWon(p)) {
@ -538,4 +552,4 @@ setupDisplay();
setupInputWatchers(true); setupInputWatchers(true);
setupMatch(); setupMatch();
draw(); draw();
drawInitialMsg(); drawInitialMsg();

View File

@ -26,5 +26,10 @@
"winScore": 11, "winScore": 11,
"enableTwoAhead": true, "enableTwoAhead": true,
"enableMaxScore": false "enableMaxScore": false
},
"Squash": {
"winScore": 9,
"enableTwoAhead": true,
"enableMaxScore": false
} }
} }

View File

@ -1,220 +1,194 @@
(function () { (function (back, inApp, ret) {
return (function (back, inApp, ret) { const isBangle1 = process.env.BOARD === 'BANGLEJS'
const isBangle1 = process.env.BOARD === 'BANGLEJS'
function fillSettingsWithDefaults(settings) { function fillSettingsWithDefaults(settings) {
if (isBangle1) { settings = Object.assign({
if (settings.mirrorScoreButtons == null) { winSets: 2,
settings.mirrorScoreButtons = false; setsPerPage: 5,
} winScore: 21,
if (settings.keepDisplayOn == null) { enableTwoAhead: true,
settings.keepDisplayOn = true; enableMaxScore: true,
} maxScore: 30,
} enableTennisScoring: false,
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) { enableMaxScoreTiebreak: false,
settings.enableMaxScoreTiebreak = false; maxScoreTiebreakWinScore: 6,
} maxScoreTiebreakEnableTwoAhead: true,
if (settings.maxScoreTiebreakWinScore == null) { maxScoreTiebreakEnableMaxScore: false,
settings.maxScoreTiebreakWinScore = 6; maxScoreTiebreakMaxScore: 15,
} }, settings);
if (settings.maxScoreTiebreakEnableTwoAhead == null) {
settings.maxScoreTiebreakEnableTwoAhead = true;
}
if (settings.maxScoreTiebreakEnableMaxScore == null) {
settings.maxScoreTiebreakEnableMaxScore = false;
}
if (settings.maxScoreTiebreakMaxScore == null) {
settings.maxScoreTiebreakMaxScore = 15;
}
return settings; if (isBangle1) {
settings = Object.assign({
mirrorScoreButtons: false,
keepDisplayOn: true,
}, settings);
} }
const fileName = 'score.json'; return settings;
let settings = require('Storage').readJSON(fileName, 1) || {}; }
const offon = ['No', 'Yes'];
let presetsFileName = 'score.presets.json'; const fileName = 'score.json';
let presets = require('Storage').readJSON(presetsFileName); let settings = require('Storage').readJSON(fileName, 1) || {};
let presetNames = Object.keys(presets); const offon = ['No', 'Yes'];
let changed = false; let presetsFileName = 'score.presets.json';
let presets = require('Storage').readJSON(presetsFileName);
let presetNames = Object.keys(presets);
function save(settings) { let changed = false;
require('Storage').writeJSON(fileName, settings);
function save(settings) {
require('Storage').writeJSON(fileName, settings);
}
function setAndSave(key, value, notChanged) {
if (!notChanged) {
changed = true;
} }
settings[key] = value;
function setAndSave(key, value, notChanged) { if (key === 'winScore' && settings.maxScore < value) {
if (!notChanged) { settings.maxScore = value;
changed = true;
}
settings[key] = value;
if (key === 'winScore' && settings.maxScore < value) {
settings.maxScore = value;
}
save(settings);
} }
save(settings);
}
settings = fillSettingsWithDefaults(settings); settings = fillSettingsWithDefaults(settings);
if (ret) { if (ret) {
return settings; return settings;
} }
const presetMenu = function (appMenuBack) { const presetMenu = function (appMenuBack) {
let ret = function (changed) { E.showMenu(appMenu(appMenuBack, changed ? 2 : null)); }; let ret = function (changed) { E.showMenu(appMenu(appMenuBack, changed ? 2 : null)); };
let m = { let m = {
'': {'title': 'Score Presets'}, '': {'title': 'Score Presets'},
'< Back': ret, '< 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;
}; };
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;
const appMenu = function (back, selected) { settings = fillSettingsWithDefaults(presets[presetNames[i]]);
let m = {};
m[''] = {'title': 'Score Settings'}; settings.mirrorScoreButtons = mirrorScoreButtons;
if (selected != null) { settings.keepDisplayOn = keepDisplayOn;
m[''].selected = selected; save(settings);
} ret(true);
m['< Back'] = function () { back(settings, changed, true); };
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'] = { })(i);
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, false); },
'Correct mode': function () { inApp('correct_mode'); back(settings, false, true); },
'Reset match': function () { back(settings, true, true); },
'End current set': function () { inApp('end_set'); back(settings, changed, true); },
'Configuration': function () { E.showMenu(appMenu(function () {
E.showMenu(inAppMenu());
})); },
};
return m;
};
if (inApp != null) {
E.showMenu(inAppMenu());
} else {
E.showMenu(appMenu(back));
} }
}); 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, true); };
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); },
'Correct mode': function () { inApp('correct_mode'); back(settings, false); },
'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));
}
})