Compare commits
No commits in common. "6f258bf13d97cc9086e3156d296c682a7eea1240" and "5bcff37e0740dd8c57abac1cdbfad2f9e5597b96" have entirely different histories.
6f258bf13d
...
5bcff37e07
|
|
@ -24,8 +24,7 @@
|
||||||
<li><a href="/gradient-bands/">Gradient Bands</a></li>
|
<li><a href="/gradient-bands/">Gradient Bands</a></li>
|
||||||
<li><a href="/2lcc/">Two-letter Country Codes</a></li>
|
<li><a href="/2lcc/">Two-letter Country Codes</a></li>
|
||||||
<li><a href="/timestamps/">Timestamp Converter</a></li>
|
<li><a href="/timestamps/">Timestamp Converter</a></li>
|
||||||
<li><a href="/scorecard-2p/">Generic Scorecard - 2 Players</a></li>
|
<li><a href="/scorecard/">Generic Scorecard</a></li>
|
||||||
<li><a href="/scorecard-4p/">Generic Scorecard - 4 Players</a></li>
|
|
||||||
<li><a href="/mahjong/">Mahjong Scorecard</a></li>
|
<li><a href="/mahjong/">Mahjong Scorecard</a></li>
|
||||||
<li><a href="/finska/">Finska Scorecard</a></li>
|
<li><a href="/finska/">Finska Scorecard</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Score Card - Tools</title>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
|
||||||
>
|
|
||||||
<link rel="stylesheet" href="./style.css">
|
|
||||||
<script src="./scripts/main.js" type="module"></script>
|
|
||||||
</head>
|
|
||||||
<body class="container">
|
|
||||||
<header>
|
|
||||||
<hgroup>
|
|
||||||
<h1>Scorecard</h1>
|
|
||||||
<p>4 Players</p>
|
|
||||||
</hgroup>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
|
|
||||||
<div data-controller="finska-scorecard">
|
|
||||||
<table class="score-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">Player A</td>
|
|
||||||
<td colspan="2">Player B</td>
|
|
||||||
<td colspan="2">Player C</td>
|
|
||||||
<td colspan="2">Player D</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody data-finska-scorecard-target="scoreTable">
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<form>
|
|
||||||
<input class="score-input" type="number"
|
|
||||||
data-finska-scorecard-target="score1Input" data-action="keydown->finska-scorecard#score1KeyDown">
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td>
|
|
||||||
<form>
|
|
||||||
<input class="score-input" type="number"
|
|
||||||
data-finska-scorecard-target="score2Input" data-action="keydown->finska-scorecard#score2KeyDown">
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td>
|
|
||||||
<form>
|
|
||||||
<input class="score-input" type="number"
|
|
||||||
data-finska-scorecard-target="score3Input" data-action="keydown->finska-scorecard#score3KeyDown">
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td>
|
|
||||||
<form>
|
|
||||||
<input class="score-input" type="number"
|
|
||||||
data-finska-scorecard-target="score4Input" data-action="keydown->finska-scorecard#score4KeyDown">
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="action-bar">
|
|
||||||
<button data-action="finska-scorecard#undoLast">
|
|
||||||
Undo
|
|
||||||
</button>
|
|
||||||
<button data-action="finska-scorecard#resetAll">
|
|
||||||
Reset
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
import { Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js";
|
|
||||||
import { Scorecard, getStoreDAO } from "./models.js";
|
|
||||||
|
|
||||||
const storeDao = getStoreDAO();
|
|
||||||
|
|
||||||
export class FinskaScorecardController extends Controller {
|
|
||||||
static targets = ["score1Input", "score2Input", "score3Input", "score4Input", "scoreTable"];
|
|
||||||
|
|
||||||
connect() {
|
|
||||||
this._undoStack = [];
|
|
||||||
this._scorecard = storeDao.loadOrCreate();
|
|
||||||
this.updateTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTable() {
|
|
||||||
let tableBody = this.scoreTableTarget;
|
|
||||||
let tableRows = tableBody.querySelectorAll("tr");
|
|
||||||
let pairs = this._scorecard.pairs();
|
|
||||||
|
|
||||||
for (let pairIndex = 0; pairIndex < pairs.length; pairIndex++) {
|
|
||||||
let tableRow;
|
|
||||||
if (pairIndex >= tableRows.length) {
|
|
||||||
tableRow = this._appendRow();
|
|
||||||
} else {
|
|
||||||
tableRow = tableRows[pairIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
this._updateRow(tableRow, pairs[pairIndex])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any extra rows
|
|
||||||
for (let i = pairs.length; i < tableRows.length; i++) {
|
|
||||||
tableBody.removeChild(tableRows[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(JSON.stringify(this._scorecard.toJson()));
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateRow(tableRow, pair) {
|
|
||||||
let tds = tableRow.querySelectorAll("td");
|
|
||||||
|
|
||||||
this._updateCell(pair.p1, tds[0], tds[1]);
|
|
||||||
this._updateCell(pair.p2, tds[2], tds[3]);
|
|
||||||
this._updateCell(pair.p3, tds[4], tds[5]);
|
|
||||||
this._updateCell(pair.p4, tds[6], tds[7]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateCell(score, scoreCell, totalCell) {
|
|
||||||
scoreCell.classList.value = "";
|
|
||||||
totalCell.classList.value = "";
|
|
||||||
|
|
||||||
if (score != null) {
|
|
||||||
scoreCell.textContent = score.score;
|
|
||||||
totalCell.textContent = score.total;
|
|
||||||
} else {
|
|
||||||
scoreCell.textContent = "";
|
|
||||||
totalCell.textContent = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_appendRow() {
|
|
||||||
let newRow = document.createElement("tr");
|
|
||||||
newRow.classList.add("score-entry");
|
|
||||||
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
newRow.appendChild(document.createElement("td"));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scoreTableTarget.appendChild(newRow);
|
|
||||||
return newRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
addScore1() {
|
|
||||||
this._addScore(this.score1InputTarget, this.score2InputTarget,
|
|
||||||
this._scorecard.addPlayer1Score.bind(this._scorecard),
|
|
||||||
this._scorecard.removeLastPlayer1Score.bind(this._scorecard));
|
|
||||||
}
|
|
||||||
|
|
||||||
addScore2() {
|
|
||||||
this._addScore(this.score2InputTarget, this.score3InputTarget,
|
|
||||||
this._scorecard.addPlayer2Score.bind(this._scorecard),
|
|
||||||
this._scorecard.removeLastPlayer2Score.bind(this._scorecard));
|
|
||||||
}
|
|
||||||
|
|
||||||
addScore3() {
|
|
||||||
this._addScore(this.score3InputTarget, this.score4InputTarget,
|
|
||||||
this._scorecard.addPlayer3Score.bind(this._scorecard),
|
|
||||||
this._scorecard.removeLastPlayer3Score.bind(this._scorecard));
|
|
||||||
}
|
|
||||||
|
|
||||||
addScore4() {
|
|
||||||
this._addScore(this.score4InputTarget, this.score1InputTarget,
|
|
||||||
this._scorecard.addPlayer4Score.bind(this._scorecard),
|
|
||||||
this._scorecard.removeLastPlayer4Score.bind(this._scorecard));
|
|
||||||
}
|
|
||||||
|
|
||||||
_addScore(inputElem, focusToInputElem, addScoreFn, queueUndoFn) {
|
|
||||||
let score = parseInt(inputElem.value);
|
|
||||||
if (isNaN(score)) {
|
|
||||||
score = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
addScoreFn(score);
|
|
||||||
this._undoStack.push(queueUndoFn);
|
|
||||||
|
|
||||||
inputElem.value = "";
|
|
||||||
|
|
||||||
storeDao.save(this._scorecard);
|
|
||||||
this.updateTable();
|
|
||||||
|
|
||||||
focusToInputElem.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
score1KeyDown(e) {
|
|
||||||
this._handleKeyDown(e, this.addScore1.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
score2KeyDown(e) {
|
|
||||||
this._handleKeyDown(e, this.addScore2.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
score3KeyDown(e) {
|
|
||||||
this._handleKeyDown(e, this.addScore3.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
score4KeyDown(e) {
|
|
||||||
this._handleKeyDown(e, this.addScore4.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleKeyDown(e, addScoreFn) {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
addScoreFn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
undoLast() {
|
|
||||||
if (this._undoStack.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(this._undoStack.pop())();
|
|
||||||
|
|
||||||
storeDao.save(this._scorecard);
|
|
||||||
this.updateTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
resetAll() {
|
|
||||||
if (!confirm("Really reset?")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._scorecard.reset();
|
|
||||||
storeDao.clear();
|
|
||||||
this._undoStack = [];
|
|
||||||
this.updateTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import { Application } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js";
|
|
||||||
import { FinskaScorecardController } from "./controllers.js"
|
|
||||||
|
|
||||||
window.Stimulus = Application.start();
|
|
||||||
|
|
||||||
window.Stimulus.register('finska-scorecard', FinskaScorecardController);
|
|
||||||
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
export class ScoreEntry {
|
|
||||||
constructor(score, total) {
|
|
||||||
this.score = score;
|
|
||||||
this.total = total;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Scorecard {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this._player1Scores = [];
|
|
||||||
this._player2Scores = [];
|
|
||||||
this._player3Scores = [];
|
|
||||||
this._player4Scores = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
addPlayer1Score(newScore) {
|
|
||||||
this._addScore(this._player1Scores, newScore);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeLastPlayer1Score() {
|
|
||||||
this._player1Scores.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
addPlayer2Score(newScore) {
|
|
||||||
this._addScore(this._player2Scores, newScore);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeLastPlayer2Score() {
|
|
||||||
this._player2Scores.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
addPlayer3Score(newScore) {
|
|
||||||
this._addScore(this._player3Scores, newScore);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeLastPlayer3Score() {
|
|
||||||
this._player3Scores.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
addPlayer4Score(newScore) {
|
|
||||||
this._addScore(this._player4Scores, newScore);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeLastPlayer4Score() {
|
|
||||||
this._player4Scores.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
_addScore(playerScores, newScore) {
|
|
||||||
let lastEntry;
|
|
||||||
if (playerScores.length === 0) {
|
|
||||||
lastEntry = new ScoreEntry(0, 0, 0, false, false);
|
|
||||||
} else {
|
|
||||||
lastEntry = playerScores[playerScores.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
let newEntry = this._newEntryFromPrevious(newScore, lastEntry);
|
|
||||||
|
|
||||||
playerScores.push(newEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
length() {
|
|
||||||
return Math.max(this._player1Scores.length, this._player2Scores.length,
|
|
||||||
this._player3Scores.length, this._player4Scores.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
pairs() {
|
|
||||||
let pairs = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < this.length(); i++) {
|
|
||||||
pairs.push({
|
|
||||||
p1: (i < this._player1Scores.length ? this._player1Scores[i] : null),
|
|
||||||
p2: (i < this._player2Scores.length ? this._player2Scores[i] : null),
|
|
||||||
p3: (i < this._player3Scores.length ? this._player3Scores[i] : null),
|
|
||||||
p4: (i < this._player4Scores.length ? this._player4Scores[i] : null),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return pairs;
|
|
||||||
}
|
|
||||||
|
|
||||||
_newEntryFromPrevious(score, previousScoreEntry) {
|
|
||||||
if (previousScoreEntry === null) {
|
|
||||||
return new ScoreEntry(score, score, (score === 0 ? 1 : 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
let newTotal = previousScoreEntry.total + score;
|
|
||||||
return new ScoreEntry(score, newTotal);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJson() {
|
|
||||||
return {
|
|
||||||
"version": 1,
|
|
||||||
"p1": { "scores": this._player1Scores.map(p => p.score) },
|
|
||||||
"p2": { "scores": this._player2Scores.map(p => p.score) },
|
|
||||||
"p3": { "scores": this._player3Scores.map(p => p.score) },
|
|
||||||
"p4": { "scores": this._player4Scores.map(p => p.score) },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(o) {
|
|
||||||
let scorecard = new Scorecard();
|
|
||||||
o["p1"]["scores"].forEach(x => scorecard.addPlayer1Score(x));
|
|
||||||
o["p2"]["scores"].forEach(x => scorecard.addPlayer2Score(x));
|
|
||||||
if (o["p3"]) o["p3"]["scores"].forEach(x => scorecard.addPlayer3Score(x));
|
|
||||||
if (o["p4"]) o["p4"]["scores"].forEach(x => scorecard.addPlayer4Score(x));
|
|
||||||
return scorecard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StoreDAO {
|
|
||||||
|
|
||||||
constructor(localStorage) {
|
|
||||||
this._localStorage = localStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
save(scoreCard) {
|
|
||||||
this._localStorage.setItem('generic-scorecard-4p', JSON.stringify(scoreCard.toJson()));
|
|
||||||
}
|
|
||||||
|
|
||||||
loadOrCreate() {
|
|
||||||
try {
|
|
||||||
console.log("Loading scorecard");
|
|
||||||
let scoreCardJson = this._localStorage.getItem('generic-scorecard-4p');
|
|
||||||
if (scoreCardJson !== null) {
|
|
||||||
return Scorecard.fromJson(JSON.parse(scoreCardJson));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Could not restore game: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Scorecard();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this._localStorage.removeItem('generic-scorecard-4p');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DummyStoreDAO {
|
|
||||||
save(scoreCard) { }
|
|
||||||
loadOrCreate() {
|
|
||||||
return new Scorecard();
|
|
||||||
}
|
|
||||||
clear() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStoreDAO() {
|
|
||||||
if (!!window.localStorage) {
|
|
||||||
return new StoreDAO(window.localStorage);
|
|
||||||
} else {
|
|
||||||
return new DummyStoreDAO();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
tfoot input {
|
|
||||||
border-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-foul {
|
|
||||||
color: #861D13;
|
|
||||||
background-color: #F8DCD6;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-overflow {
|
|
||||||
color: #5B4200;
|
|
||||||
background-color: #FCEFD9;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-win {
|
|
||||||
color: #394D00;
|
|
||||||
background-color: #DEFC85;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
@ -12,13 +12,7 @@
|
||||||
<script src="./scripts/main.js" type="module"></script>
|
<script src="./scripts/main.js" type="module"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="container">
|
<body class="container">
|
||||||
<header>
|
<h1>Scorecard</h1>
|
||||||
<hgroup>
|
|
||||||
<h1>Scorecard</h1>
|
|
||||||
<p>2 Players</p>
|
|
||||||
</hgroup>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
|
|
||||||
<div data-controller="finska-scorecard">
|
<div data-controller="finska-scorecard">
|
||||||
<table class="score-table">
|
<table class="score-table">
|
||||||
|
|
@ -94,13 +94,13 @@ class StoreDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
save(scoreCard) {
|
save(scoreCard) {
|
||||||
this._localStorage.setItem('generic-scorecard-2p', JSON.stringify(scoreCard.toJson()));
|
this._localStorage.setItem('generic-scorecard', JSON.stringify(scoreCard.toJson()));
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOrCreate() {
|
loadOrCreate() {
|
||||||
try {
|
try {
|
||||||
console.log("Loading scorecard");
|
console.log("Loading scorecard");
|
||||||
let scoreCardJson = this._localStorage.getItem('generic-scorecard-2p');
|
let scoreCardJson = this._localStorage.getItem('generic-scorecard');
|
||||||
if (scoreCardJson !== null) {
|
if (scoreCardJson !== null) {
|
||||||
return Scorecard.fromJson(JSON.parse(scoreCardJson));
|
return Scorecard.fromJson(JSON.parse(scoreCardJson));
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +112,7 @@ class StoreDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this._localStorage.removeItem('generic-scorecard-2p');
|
this._localStorage.removeItem('generic-scorecard');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loading…
Reference in a new issue