From 154652e51d9d7e46e4566a3b8c70b2d2e3cc8122 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 10 Oct 2021 22:08:20 +0200 Subject: [PATCH] create new app 'score' --- apps.json | 13 ++ apps/score/ChangeLog | 1 + apps/score/score.app-icon.js | 1 + apps/score/score.app.js | 288 +++++++++++++++++++++++++++++++++++ apps/score/score.app.png | Bin 0 -> 897 bytes apps/score/score.settings.js | 81 ++++++++++ 6 files changed, 384 insertions(+) create mode 100644 apps/score/ChangeLog create mode 100644 apps/score/score.app-icon.js create mode 100644 apps/score/score.app.js create mode 100644 apps/score/score.app.png create mode 100644 apps/score/score.settings.js diff --git a/apps.json b/apps.json index 6b0703601..314504276 100644 --- a/apps.json +++ b/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} + ] } ] diff --git a/apps/score/ChangeLog b/apps/score/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/score/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/score/score.app-icon.js b/apps/score/score.app-icon.js new file mode 100644 index 000000000..b1d4631ba --- /dev/null +++ b/apps/score/score.app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AE2IxAKSCigv/F/4vS44ABB4IECAAoKECgM7AAIJBAgQAFBQguJF6HHEhAvKGAwvy4wPB4wuGBQwdCmgJBmguGBQwvJ0ulF5AKFEgeCwQvIBQqPJ4wuHBQ4lEFw4KHF5IAQFJAALF+vNACYv/F/4v053P64vPxPXAAOJF6vP6wbCF52zCQQAB2YvTDIgvOLoWzMJQvOL6JeCss7spgIF5nPMQgvNCAQEBr4FEd6YvVAowv/F/4v4d9WzCANlndlAgOzF82JFQWJGgWJF8xgDAAReGF8RhDLo4vRABQiHABgv/F/4v/F4owTCgIuZAH4A/AH4A/ADgA==")) diff --git a/apps/score/score.app.js b/apps/score/score.app.js new file mode 100644 index 000000000..7bfadb1ce --- /dev/null +++ b/apps/score/score.app.js @@ -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(); diff --git a/apps/score/score.app.png b/apps/score/score.app.png new file mode 100644 index 0000000000000000000000000000000000000000..c1e7e22157fc295811c8b0291dd736d7d1878e5a GIT binary patch literal 897 zcmV-{1AhF8P)-q;>a)qdEv4t%nBoLyDDR~j<9&`&l zbnlXgNO%l7dGZn+>{ierq}uY3iGi7fhrmBe8YsJ!ZRKF5hsneX&? z@7wQvzu)iun+1IM@Zsag;`zG|(=XDKQ*LtI)|9FZ5{3?d@yx}|bz4*5QaZvo_5oOy zko+<=h%Gt*3K0Na?esrO?pyOcToDvOX{aMREff?CXj)klm;(SLM)Dl%$jTK2(7J#y z^yO~k;SR%E0h9SdgF=K%2d(B02Tg-mP_#jFpEUrJd6qArQo9mtxf&DA=4r8L3>7!4 zYT9w>u4LHm0lKe`mM57SG_L|kWOZVz^-?kMX^$|#^ zgzW@QxI(xVp)>ZTop4XqJs`B!xIRIGx}T#ZQ~TIgzz@J{RJ{l6KYO`()yg>E zR8B~V0^f2P0AOxi)5qJI_;Sl1z{-<@_}do^q4IMkUt;=KZ=qx?`uZT>gU_>LRIK37 zyk{j&XUF~?@GJmY;fORBMb-9>SAu5&{M}yfVbd;b3>hJ8$b27Co&~V*tDNH60KwUY zvW;Z3VQ29-UkMyNCEP$dLvf|GeAYj>7DPHj(c5O6{co2kmyTxvj4!IVaZW^gOhmF- zuw?)98pWGszQ>SfOGslt|AdUOc?D|)22}&r3JhcO3i>Bx-v1JuRY0A%?Br*Au%!Z{ zvkFFM6~`L>J>WG?TmUO>iZkH*f6~-p34j27y9tMxh%nLz=B}ndSf3kOK79D_;ST-* XivUhVT==`h00000NkvXXu0mjflmwo` literal 0 HcmV?d00001 diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js new file mode 100644 index 000000000..dde77a225 --- /dev/null +++ b/apps/score/score.settings.js @@ -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) + +})