Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ad69c6ff2 | ||
|
|
a34d6058a1 |
@@ -96,7 +96,8 @@ normally restarted — use pause and reset for those.
|
|||||||
|
|
||||||
Set **Match** in the settings to the number of points you're playing to — 1, 3,
|
Set **Match** in the settings to the number of points you're playing to — 1, 3,
|
||||||
5 and up to 29. The time you set is *per point*, so a 5-point match at 2:00 a
|
5 and up to 29. The time you set is *per point*, so a 5-point match at 2:00 a
|
||||||
point puts 10:00 on each clock, and the settings sheet shows you that sum.
|
point puts 10:00 on each clock, and the settings sheet shows you that sum — one
|
||||||
|
sum, or one for each player if you've set their times apart.
|
||||||
|
|
||||||
Each half reads the state from that player's own side of the board:
|
Each half reads the state from that player's own side of the board:
|
||||||
|
|
||||||
@@ -136,17 +137,33 @@ score down to fit.
|
|||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
Under **Delay** are three presets — **2m / 12s**, **3m / 12s** and **3m / 15s**.
|
Under **Delay** are three presets — **2m / 12s**, **3m / 12s** and **3m / 15s**.
|
||||||
Tapping one jumps both timers there and lights it up. Change the time or delay by
|
Tapping one puts both the time and the delay there and lights it up. Change
|
||||||
hand and the highlight goes out; land back on a preset's numbers and it comes
|
either by hand and the highlight goes out; land back on a preset's numbers and it
|
||||||
back on.
|
comes back on.
|
||||||
|
|
||||||
|
**Different time per player** — the switch under the Time steppers — gives each
|
||||||
|
player their own time *and* their own delay. Turn it on and a second row appears
|
||||||
|
under both, labelled **You** for the bottom half of the phone and **Them** for
|
||||||
|
the far end, the same way round as the score sheet. Them starts as a copy of You;
|
||||||
|
change either from there. Turn it back off and both players go back to whatever
|
||||||
|
You says, so You is always the row that survives — set the shared time first and
|
||||||
|
split it afterwards, not the other way round.
|
||||||
|
|
||||||
|
The presets are gone while that switch is on. A preset is one pair of numbers and
|
||||||
|
there are two players to give it to, so there is nothing sensible for it to mean.
|
||||||
|
A handicap match is the point of the switch: give the stronger player less time,
|
||||||
|
or take one player's delay down to zero and leave the other's alone.
|
||||||
|
|
||||||
| Setting | Range | Default |
|
| Setting | Range | Default |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Match | 1 – 29 points, odd | 1 |
|
| Match | 1 – 29 points, odd | 1 |
|
||||||
| Time | 0:15 – 99:00 per point | 3:00 |
|
| Time | 0:15 – 99:00 per point, per player | 3:00 |
|
||||||
| Delay | 0 – 60 s | 12 s |
|
| Delay | 0 – 60 s, per player | 12 s |
|
||||||
|
| Different time per player | on / off | off |
|
||||||
| Colour | Sage, Brass, Slate, Teal, Plum | Sage |
|
| Colour | Sage, Brass, Slate, Teal, Plum | Sage |
|
||||||
| Sound | on / off | on |
|
|
||||||
|
Sound isn't in the sheet — it's the speaker button in the bar, reachable at any
|
||||||
|
time, mid-match included.
|
||||||
|
|
||||||
Brass is the colour in the chess.com app, if you want the exact original look.
|
Brass is the colour in the chess.com app, if you want the exact original look.
|
||||||
Changing match, time or delay resets the clocks — the score is left alone, so you
|
Changing match, time or delay resets the clocks — the score is left alone, so you
|
||||||
@@ -157,7 +174,7 @@ survive closing the tab, force-quitting the installed app and rebooting the
|
|||||||
phone — the app also asks for persistent storage so they aren't evicted when the
|
phone — the app also asks for persistent storage so they aren't evicted when the
|
||||||
device runs low on space (Chrome honours that request; Safari ignores it).
|
device runs low on space (Chrome honours that request; Safari ignores it).
|
||||||
Reopening restores the score, both clocks, the move counts, whose turn it is and
|
Reopening restores the score, both clocks, the move counts, whose turn it is and
|
||||||
how much delay was left. The one thing it can't restore is time passing while
|
how much delay was left — each player's own delay, if you've set them apart. The one thing it can't restore is time passing while
|
||||||
the app was closed, so it comes back paused rather than charging anyone for the
|
the app was closed, so it comes back paused rather than charging anyone for the
|
||||||
gap: a crash shouldn't decide a game.
|
gap: a crash shouldn't decide a game.
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "nl.hansdezwart.bgclock"
|
applicationId = "nl.hansdezwart.bgclock"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 6
|
versionCode = 7
|
||||||
versionName = "1.5"
|
versionName = "1.6"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Rationalised the settings page and added the option to set a different time and delay per player.
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 117 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 174 KiB |
+200
-61
@@ -369,8 +369,33 @@
|
|||||||
border-top:1px solid var(--sheet-line);
|
border-top:1px solid var(--sheet-line);
|
||||||
}
|
}
|
||||||
.row:first-of-type{border-top:0}
|
.row:first-of-type{border-top:0}
|
||||||
|
.row[hidden]{display:none} /* .row's own display would otherwise win */
|
||||||
.row h2{font-size:15px;font-weight:600;letter-spacing:.01em}
|
.row h2{font-size:15px;font-weight:600;letter-spacing:.01em}
|
||||||
.row p{font-size:12.5px;color:var(--sheet-dim);margin-top:3px;line-height:1.35}
|
.row p{font-size:12.5px;color:var(--sheet-dim);margin-top:3px;line-height:1.35}
|
||||||
|
/* the Time copy puts its two sums on a line of their own when the players are
|
||||||
|
set apart; everything else here has no newline in it and is unaffected */
|
||||||
|
#p-time{white-space:pre-line}
|
||||||
|
|
||||||
|
/* Time and Delay each hold one stepper, or two when the players are set
|
||||||
|
separately. The second row is added rather than the first relabelled, so
|
||||||
|
the row a player already knows keeps its place and its numbers. */
|
||||||
|
.sides{display:flex;flex-direction:column;gap:10px}
|
||||||
|
.side{display:flex;align-items:center;justify-content:flex-end;gap:10px}
|
||||||
|
.side[hidden]{display:none} /* .side's own display would otherwise win */
|
||||||
|
/* pushed left against the stepper's right edge, so both rows' steppers line
|
||||||
|
up whatever the two labels measure */
|
||||||
|
.who{font-size:15px;font-weight:600;color:var(--sheet-dim);margin-right:auto}
|
||||||
|
/* nobody to tell apart while there is one row, and the label would only be
|
||||||
|
one more word to read */
|
||||||
|
.sheet:not(.split) .who{display:none}
|
||||||
|
/* a labelled stepper pair can't share a line with the heading at any width;
|
||||||
|
scoped to .split so the shared sheet keeps exactly the layout it had */
|
||||||
|
.sheet.split .row--wide,.sheet.split .row--delay{flex-direction:column;align-items:stretch}
|
||||||
|
|
||||||
|
/* the switch sits above the two rows it governs, so it reads as the thing
|
||||||
|
that decides their shape rather than as one more Time control */
|
||||||
|
.onoff-ctl{display:flex;align-items:center;gap:10px}
|
||||||
|
.onoff-ctl span{font-size:13px;color:var(--sheet-dim)}
|
||||||
|
|
||||||
/* a minutes+seconds stepper is 302px wide, so it can't share a line with a
|
/* a minutes+seconds stepper is 302px wide, so it can't share a line with a
|
||||||
label on any phone — stack it under one instead of overflowing the sheet */
|
label on any phone — stack it under one instead of overflowing the sheet */
|
||||||
@@ -576,34 +601,75 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<h2>Different time per player</h2>
|
||||||
|
<p>Sets the delay separately too.</p>
|
||||||
|
</div>
|
||||||
|
<div class="onoff-ctl">
|
||||||
|
<span>Off</span>
|
||||||
|
<button class="toggle" id="t-split" role="switch" aria-checked="false" aria-label="Different time per player"></button>
|
||||||
|
<span>On</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row row--wide">
|
<div class="row row--wide">
|
||||||
<div>
|
<div>
|
||||||
<h2>Time</h2>
|
<h2>Time</h2>
|
||||||
<p id="p-time">Reserve time per point.</p>
|
<p id="p-time">Reserve time per point.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sides">
|
||||||
|
<div class="side">
|
||||||
|
<span class="who">You</span>
|
||||||
<div class="stepper">
|
<div class="stepper">
|
||||||
<button class="step" data-time-adj="-60">–</button>
|
<button class="step" data-time-adj="-60" data-who="1">–</button>
|
||||||
<span class="val"><span id="v-min">3</span><span class="unit">min</span></span>
|
<span class="val"><span data-v="min" data-who="1">3</span><span class="unit">min</span></span>
|
||||||
<button class="step" data-time-adj="60">+</button>
|
<button class="step" data-time-adj="60" data-who="1">+</button>
|
||||||
<button class="step" data-time-adj="-15" style="margin-left:8px">–</button>
|
<button class="step" data-time-adj="-15" data-who="1" style="margin-left:8px">–</button>
|
||||||
<span class="val"><span id="v-sec">00</span><span class="unit">sec</span></span>
|
<span class="val"><span data-v="sec" data-who="1">00</span><span class="unit">sec</span></span>
|
||||||
<button class="step" data-time-adj="15">+</button>
|
<button class="step" data-time-adj="15" data-who="1">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="side" data-them-row hidden>
|
||||||
|
<span class="who">Them</span>
|
||||||
|
<div class="stepper">
|
||||||
|
<button class="step" data-time-adj="-60" data-who="0">–</button>
|
||||||
|
<span class="val"><span data-v="min" data-who="0">3</span><span class="unit">min</span></span>
|
||||||
|
<button class="step" data-time-adj="60" data-who="0">+</button>
|
||||||
|
<button class="step" data-time-adj="-15" data-who="0" style="margin-left:8px">–</button>
|
||||||
|
<span class="val"><span data-v="sec" data-who="0">00</span><span class="unit">sec</span></span>
|
||||||
|
<button class="step" data-time-adj="15" data-who="0">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row row--delay">
|
||||||
<div>
|
<div>
|
||||||
<h2>Delay</h2>
|
<h2>Delay</h2>
|
||||||
<p>Free seconds each turn. Never carries over.</p>
|
<p>Free seconds each turn. Never carries over.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sides">
|
||||||
|
<div class="side">
|
||||||
|
<span class="who">You</span>
|
||||||
<div class="stepper">
|
<div class="stepper">
|
||||||
<button class="step" data-delay-adj="-1">–</button>
|
<button class="step" data-delay-adj="-1" data-who="1">–</button>
|
||||||
<span class="val"><span id="v-delay">15</span><span class="unit">sec</span></span>
|
<span class="val"><span data-v="delay" data-who="1">15</span><span class="unit">sec</span></span>
|
||||||
<button class="step" data-delay-adj="1">+</button>
|
<button class="step" data-delay-adj="1" data-who="1">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="side" data-them-row hidden>
|
||||||
|
<span class="who">Them</span>
|
||||||
|
<div class="stepper">
|
||||||
|
<button class="step" data-delay-adj="-1" data-who="0">–</button>
|
||||||
|
<span class="val"><span data-v="delay" data-who="0">15</span><span class="unit">sec</span></span>
|
||||||
|
<button class="step" data-delay-adj="1" data-who="0">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row row--presets">
|
<div class="row row--presets" id="row-presets">
|
||||||
<div>
|
<div>
|
||||||
<h2>Preset time limits</h2>
|
<h2>Preset time limits</h2>
|
||||||
<p>Quickly pick a setting.</p>
|
<p>Quickly pick a setting.</p>
|
||||||
@@ -623,16 +689,7 @@
|
|||||||
<div class="swatches" id="swatches" role="radiogroup" aria-label="Colour"></div>
|
<div class="swatches" id="swatches" role="radiogroup" aria-label="Colour"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<h2>Sound</h2>
|
|
||||||
<p>Turn change, delay expiry, time out.</p>
|
|
||||||
</div>
|
|
||||||
<button class="toggle" id="t-sound" role="switch" aria-checked="true" aria-label="Sound"></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="done" id="done">Done</button>
|
<button class="done" id="done">Done</button>
|
||||||
<p class="hint">Changing match, time or delay resets the clocks. The score is left alone.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sheet" id="scoresheet" role="dialog" aria-modal="true" aria-label="Score">
|
<div class="sheet" id="scoresheet" role="dialog" aria-modal="true" aria-label="Score">
|
||||||
@@ -691,8 +748,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="done" id="score-done">Done</button>
|
<button class="done" id="score-done">Done</button>
|
||||||
<p class="hint">Changing the score starts a new game: moves back to zero, turn back to
|
<p class="hint" id="p-score-hint"></p>
|
||||||
the first tap. The clocks keep their times.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -722,24 +778,46 @@
|
|||||||
{ id:"plum", accent:"#8A7396", ink:"#FFFFFF", mute:.77 }
|
{ id:"plum", accent:"#8A7396", ink:"#FFFFFF", mute:.77 }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/* The limits the steppers enforce, kept here because the load path needs them
|
||||||
|
too: bg.base.them and bg.delay.them can arrive from a half-upgraded install,
|
||||||
|
and a corrupt reserve makes a clock that can never flag. */
|
||||||
|
var LIMIT = { base: [15000, 99 * 60000], delay: [0, 60000] };
|
||||||
|
function clampTimer(which, ms){
|
||||||
|
var l = LIMIT[which];
|
||||||
|
return isFinite(ms) ? Math.min(l[1], Math.max(l[0], ms)) : l[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* base and delay are per side, indexed like st.reserve and the panels: 1 is
|
||||||
|
the bottom half ("you"), 0 the far end ("them"), the same way round as the
|
||||||
|
score sheet. They are arrays whether or not the setting is split — split is
|
||||||
|
a fact about the sheet, not about the clock, so nothing downstream of here
|
||||||
|
has to ask. Shared is simply the state where the two entries are equal.
|
||||||
|
|
||||||
|
bg.base and bg.delay keep meaning what they always did, which is side 1, so
|
||||||
|
an older build reading this storage still finds its own settings, and an
|
||||||
|
install that predates the split lands symmetric. */
|
||||||
|
var split = store.get("bg.split", "0") === "1";
|
||||||
|
var yBase = clampTimer("base", store.num("bg.base", 180000));
|
||||||
|
var yDelay = clampTimer("delay", store.num("bg.delay", 12000));
|
||||||
var cfg = {
|
var cfg = {
|
||||||
base: store.num("bg.base", 180000), // reserve per point
|
base: [split ? clampTimer("base", store.num("bg.base.them", yBase)) : yBase, yBase],
|
||||||
delay: store.num("bg.delay", 12000),
|
delay: [split ? clampTimer("delay", store.num("bg.delay.them", yDelay)) : yDelay, yDelay],
|
||||||
|
split: split,
|
||||||
points: store.num("bg.points", 1), // match length, odd, 1..29
|
points: store.num("bg.points", 1), // match length, odd, 1..29
|
||||||
theme: store.get("bg.theme", "sage"),
|
theme: store.get("bg.theme", "sage"),
|
||||||
sound: store.get("bg.sound", "1") === "1"
|
sound: store.get("bg.sound", "1") === "1"
|
||||||
};
|
};
|
||||||
if(!(cfg.points >= 1 && cfg.points <= 29 && cfg.points % 2 === 1)) cfg.points = 1;
|
if(!(cfg.points >= 1 && cfg.points <= 29 && cfg.points % 2 === 1)) cfg.points = 1;
|
||||||
|
|
||||||
// a match to N points is worth N times the per-point reserve
|
// a match to N points is worth N times that side's per-point reserve
|
||||||
function matchTime(){ return cfg.base * cfg.points; }
|
function matchTime(i){ return cfg.base[i] * cfg.points; }
|
||||||
|
|
||||||
/* ============ state ============ */
|
/* ============ state ============ */
|
||||||
var IDLE = 0, RUN = 1, PAUSE = 2, FLAG = 3;
|
var IDLE = 0, RUN = 1, PAUSE = 2, FLAG = 3;
|
||||||
var st = {
|
var st = {
|
||||||
phase: IDLE,
|
phase: IDLE,
|
||||||
active: -1,
|
active: -1,
|
||||||
reserve: [matchTime(), matchTime()], // reserve banked at the start of the current turn
|
reserve: [matchTime(0), matchTime(1)], // reserve banked at the start of the current turn
|
||||||
score: [0, 0], // match points, by side
|
score: [0, 0], // match points, by side
|
||||||
moves: [0, 0],
|
moves: [0, 0],
|
||||||
turnAt: 0, // performance.now() when the turn began
|
turnAt: 0, // performance.now() when the turn began
|
||||||
@@ -861,7 +939,7 @@
|
|||||||
}
|
}
|
||||||
// how much of this turn has already come off the reserve (0 while the delay runs)
|
// how much of this turn has already come off the reserve (0 while the delay runs)
|
||||||
function consumed(i){
|
function consumed(i){
|
||||||
return isLive(i) ? Math.max(0, elapsed() - cfg.delay) : 0;
|
return isLive(i) ? Math.max(0, elapsed() - cfg.delay[i]) : 0;
|
||||||
}
|
}
|
||||||
// the number the panel is showing, which is what the score sheet edits
|
// the number the panel is showing, which is what the score sheet edits
|
||||||
function shownTime(i){
|
function shownTime(i){
|
||||||
@@ -877,7 +955,7 @@
|
|||||||
return st.phase === IDLE && st.active === -1 &&
|
return st.phase === IDLE && st.active === -1 &&
|
||||||
st.moves[0] === 0 && st.moves[1] === 0 &&
|
st.moves[0] === 0 && st.moves[1] === 0 &&
|
||||||
st.score[0] === 0 && st.score[1] === 0 &&
|
st.score[0] === 0 && st.score[1] === 0 &&
|
||||||
st.reserve[0] === matchTime() && st.reserve[1] === matchTime();
|
st.reserve[0] === matchTime(0) && st.reserve[1] === matchTime(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- write only on change ----
|
/* ---- write only on change ----
|
||||||
@@ -909,10 +987,10 @@
|
|||||||
var main = st.reserve[i], dLeft = 0;
|
var main = st.reserve[i], dLeft = 0;
|
||||||
|
|
||||||
if(live){
|
if(live){
|
||||||
if(e < cfg.delay){
|
if(e < cfg.delay[i]){
|
||||||
dLeft = cfg.delay - e;
|
dLeft = cfg.delay[i] - e;
|
||||||
}else{
|
}else{
|
||||||
main = st.reserve[i] - (e - cfg.delay);
|
main = st.reserve[i] - (e - cfg.delay[i]);
|
||||||
if(!st.ticked && st.phase === RUN){ st.ticked = true; sndTick(); }
|
if(!st.ticked && st.phase === RUN){ st.ticked = true; sndTick(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -931,11 +1009,11 @@
|
|||||||
setText(el[i].them, st.score[1 - i]);
|
setText(el[i].them, st.score[1 - i]);
|
||||||
setText(el[i].target, cfg.points);
|
setText(el[i].target, cfg.points);
|
||||||
|
|
||||||
var showDelay = live && cfg.delay > 0 && dLeft > 0;
|
var showDelay = live && cfg.delay[i] > 0 && dLeft > 0;
|
||||||
el[i].delay.classList.toggle("on", showDelay); // no-op when already right
|
el[i].delay.classList.toggle("on", showDelay); // no-op when already right
|
||||||
if(showDelay){
|
if(showDelay){
|
||||||
// the bar really does move every frame, but transform is compositor-only
|
// the bar really does move every frame, but transform is compositor-only
|
||||||
setStyle(el[i].fill, "transform", "scaleX(" + (dLeft / cfg.delay).toFixed(4) + ")");
|
setStyle(el[i].fill, "transform", "scaleX(" + (dLeft / cfg.delay[i]).toFixed(4) + ")");
|
||||||
setText(el[i].dnum, Math.ceil(dLeft / 1000));
|
setText(el[i].dnum, Math.ceil(dLeft / 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -996,7 +1074,7 @@
|
|||||||
// the same instant charges the outgoing turn and starts the incoming one,
|
// the same instant charges the outgoing turn and starts the incoming one,
|
||||||
// so nothing falls between the two and goes to neither player
|
// so nothing falls between the two and goes to neither player
|
||||||
var e = now - st.turnAt;
|
var e = now - st.turnAt;
|
||||||
st.reserve[side] = Math.max(0, st.reserve[side] - Math.max(0, e - cfg.delay));
|
st.reserve[side] = Math.max(0, st.reserve[side] - Math.max(0, e - cfg.delay[side]));
|
||||||
st.moves[side]++;
|
st.moves[side]++;
|
||||||
st.active = 1 - side;
|
st.active = 1 - side;
|
||||||
st.turnAt = now;
|
st.turnAt = now;
|
||||||
@@ -1052,7 +1130,7 @@
|
|||||||
// clocks back to full for the match length; the score is not this function's business
|
// clocks back to full for the match length; the score is not this function's business
|
||||||
function resetClocks(){
|
function resetClocks(){
|
||||||
cancelHold();
|
cancelHold();
|
||||||
st.reserve = [matchTime(), matchTime()];
|
st.reserve = [matchTime(0), matchTime(1)];
|
||||||
newGame();
|
newGame();
|
||||||
saveGame();
|
saveGame();
|
||||||
}
|
}
|
||||||
@@ -1106,7 +1184,9 @@
|
|||||||
st.active = g.active;
|
st.active = g.active;
|
||||||
st.phase = g.phase === RUN ? PAUSE : g.phase;
|
st.phase = g.phase === RUN ? PAUSE : g.phase;
|
||||||
st.held = st.phase === PAUSE ? g.held : 0;
|
st.held = st.phase === PAUSE ? g.held : 0;
|
||||||
st.ticked = st.held > cfg.delay; // don't re-tick a delay that already expired
|
// the delay that matters is the one belonging to whoever's turn it was;
|
||||||
|
// active is legitimately -1 here, which has no delay to compare against
|
||||||
|
st.ticked = st.active >= 0 && st.held > cfg.delay[st.active];
|
||||||
if(st.phase === PAUSE && st.active < 0) st.phase = IDLE; // can't be mid-turn with no turn
|
if(st.phase === PAUSE && st.active < 0) st.phase = IDLE; // can't be mid-turn with no turn
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1237,43 +1317,103 @@
|
|||||||
return Math.floor(s / 60) + ":" + ("0" + (s % 60)).slice(-2);
|
return Math.floor(s / 60) + ":" + ("0" + (s % 60)).slice(-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the settings readouts, by side — the score sheet already owns the obvious
|
||||||
|
// v-you-min / v-them-min ids, and a side index is what data- is for here
|
||||||
|
function readout(name, who){
|
||||||
|
return document.querySelector('[data-v="' + name + '"][data-who="' + who + '"]');
|
||||||
|
}
|
||||||
|
var vals = [0, 1].map(function(who){
|
||||||
|
return { min: readout("min", who), sec: readout("sec", who), delay: readout("delay", who) };
|
||||||
|
});
|
||||||
|
|
||||||
|
// "5 × 3:00 = 15:00" for one side
|
||||||
|
function sum(i){
|
||||||
|
return cfg.points + " × " + clock(cfg.base[i]) + " = " + clock(matchTime(i));
|
||||||
|
}
|
||||||
|
|
||||||
function paintSettings(){
|
function paintSettings(){
|
||||||
var s = Math.round(cfg.base / 1000);
|
for(var i = 0; i < 2; i++){
|
||||||
document.getElementById("v-min").textContent = Math.floor(s / 60);
|
var s = Math.round(cfg.base[i] / 1000);
|
||||||
document.getElementById("v-sec").textContent = ("0" + (s % 60)).slice(-2);
|
vals[i].min.textContent = Math.floor(s / 60);
|
||||||
document.getElementById("v-delay").textContent = Math.round(cfg.delay / 1000);
|
vals[i].sec.textContent = ("0" + (s % 60)).slice(-2);
|
||||||
|
vals[i].delay.textContent = Math.round(cfg.delay[i] / 1000);
|
||||||
|
}
|
||||||
document.getElementById("v-points").textContent = cfg.points;
|
document.getElementById("v-points").textContent = cfg.points;
|
||||||
document.getElementById("p-time").textContent = cfg.points === 1
|
/* At a single point the match total is the per-point time, so there is no
|
||||||
? "Reserve time per point."
|
sum to introduce and the colon would dangle. Split, the two sums are put
|
||||||
: "Reserve time per point. " + cfg.points + " × " + clock(cfg.base) +
|
on their own line — together they are too long to sit after the label
|
||||||
" = " + clock(matchTime()) + " each.";
|
without breaking mid-sum, which reads as one number split in half.
|
||||||
|
The newline needs white-space:pre-line on #p-time. */
|
||||||
|
document.getElementById("p-time").textContent =
|
||||||
|
cfg.points === 1 ? "Reserve time per point."
|
||||||
|
: cfg.split ? "Reserve time per point:\n" + sum(1) + " you, " + sum(0) + " them."
|
||||||
|
: "Reserve time per point: " + sum(1) + " each.";
|
||||||
|
paintSplit();
|
||||||
paintMatchChrome();
|
paintMatchChrome();
|
||||||
document.getElementById("t-sound").setAttribute("aria-checked", cfg.sound ? "true" : "false");
|
|
||||||
btnSound.setAttribute("aria-pressed", cfg.sound ? "true" : "false");
|
btnSound.setAttribute("aria-pressed", cfg.sound ? "true" : "false");
|
||||||
showIcon(btnSound, "on", cfg.sound);
|
showIcon(btnSound, "on", cfg.sound);
|
||||||
showIcon(btnSound, "off", !cfg.sound);
|
showIcon(btnSound, "off", !cfg.sound);
|
||||||
paintPresets();
|
paintPresets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The presets can only mean one thing for both players, so they go while the
|
||||||
|
two sides can disagree. Everything else here is the sheet rearranging
|
||||||
|
itself; the clock never asks whether the setting is split. */
|
||||||
|
function paintSplit(){
|
||||||
|
sheet.classList.toggle("split", cfg.split);
|
||||||
|
document.getElementById("t-split").setAttribute("aria-checked", cfg.split ? "true" : "false");
|
||||||
|
[].forEach.call(document.querySelectorAll("[data-them-row]"), function(n){
|
||||||
|
if(cfg.split) n.removeAttribute("hidden"); else n.setAttribute("hidden", "");
|
||||||
|
});
|
||||||
|
document.getElementById("row-presets").hidden = cfg.split;
|
||||||
|
}
|
||||||
|
|
||||||
/* highlight is derived from cfg, so it clears when you step away from a
|
/* highlight is derived from cfg, so it clears when you step away from a
|
||||||
preset and comes back by itself when you step onto one */
|
preset and comes back by itself when you step onto one. Side 1 is enough:
|
||||||
|
the row is only on show while both sides hold the same numbers. */
|
||||||
function paintPresets(){
|
function paintPresets(){
|
||||||
[].forEach.call(document.querySelectorAll(".preset"), function(b){
|
[].forEach.call(document.querySelectorAll(".preset"), function(b){
|
||||||
var on = (+b.dataset.base === cfg.base && +b.dataset.presetDelay === cfg.delay);
|
var on = (+b.dataset.base === cfg.base[1] && +b.dataset.presetDelay === cfg.delay[1]);
|
||||||
b.setAttribute("aria-pressed", on ? "true" : "false");
|
b.setAttribute("aria-pressed", on ? "true" : "false");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* the one writer for both timers. Shared mode is kept as an equality rather
|
||||||
|
than a special case: write one side, write the other. */
|
||||||
|
function setTimer(which, who, ms){
|
||||||
|
cfg[which][who] = clampTimer(which, ms);
|
||||||
|
if(!cfg.split) cfg[which][1 - who] = cfg[which][who];
|
||||||
|
saveTimers();
|
||||||
|
paintSettings(); resetClocks();
|
||||||
|
}
|
||||||
|
function saveTimers(){
|
||||||
|
store.set("bg.base", String(cfg.base[1]));
|
||||||
|
store.set("bg.delay", String(cfg.delay[1]));
|
||||||
|
store.set("bg.base.them", String(cfg.base[0]));
|
||||||
|
store.set("bg.delay.them", String(cfg.delay[0]));
|
||||||
|
}
|
||||||
|
|
||||||
[].forEach.call(document.querySelectorAll(".preset"), function(b){
|
[].forEach.call(document.querySelectorAll(".preset"), function(b){
|
||||||
b.addEventListener("click", function(){
|
b.addEventListener("click", function(){
|
||||||
cfg.base = parseInt(b.dataset.base, 10);
|
// only reachable while shared, so both sides take the preset
|
||||||
cfg.delay = parseInt(b.dataset.presetDelay, 10);
|
var base = parseInt(b.dataset.base, 10), d = parseInt(b.dataset.presetDelay, 10);
|
||||||
store.set("bg.base", String(cfg.base));
|
cfg.base = [base, base];
|
||||||
store.set("bg.delay", String(cfg.delay));
|
cfg.delay = [d, d];
|
||||||
|
saveTimers();
|
||||||
paintSettings(); resetClocks();
|
paintSettings(); resetClocks();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("t-split").addEventListener("click", function(){
|
||||||
|
cfg.split = !cfg.split;
|
||||||
|
// the same line both ways: Them copies You going in, You wins coming out
|
||||||
|
cfg.base[0] = cfg.base[1];
|
||||||
|
cfg.delay[0] = cfg.delay[1];
|
||||||
|
store.set("bg.split", cfg.split ? "1" : "0");
|
||||||
|
saveTimers();
|
||||||
|
paintSettings(); resetClocks();
|
||||||
|
});
|
||||||
|
|
||||||
[].forEach.call(document.querySelectorAll("[data-points-adj]"), function(b){
|
[].forEach.call(document.querySelectorAll("[data-points-adj]"), function(b){
|
||||||
b.addEventListener("click", function(){
|
b.addEventListener("click", function(){
|
||||||
// stepping by 2 from 1 keeps it odd without any parity juggling
|
// stepping by 2 from 1 keeps it odd without any parity juggling
|
||||||
@@ -1287,18 +1427,14 @@
|
|||||||
|
|
||||||
[].forEach.call(document.querySelectorAll("[data-time-adj]"), function(b){
|
[].forEach.call(document.querySelectorAll("[data-time-adj]"), function(b){
|
||||||
b.addEventListener("click", function(){
|
b.addEventListener("click", function(){
|
||||||
var next = cfg.base + parseInt(b.dataset.timeAdj, 10) * 1000;
|
var who = parseInt(b.dataset.who, 10);
|
||||||
cfg.base = Math.min(99 * 60000, Math.max(15000, next));
|
setTimer("base", who, cfg.base[who] + parseInt(b.dataset.timeAdj, 10) * 1000);
|
||||||
store.set("bg.base", String(cfg.base));
|
|
||||||
paintSettings(); resetClocks();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
[].forEach.call(document.querySelectorAll("[data-delay-adj]"), function(b){
|
[].forEach.call(document.querySelectorAll("[data-delay-adj]"), function(b){
|
||||||
b.addEventListener("click", function(){
|
b.addEventListener("click", function(){
|
||||||
var next = cfg.delay + parseInt(b.dataset.delayAdj, 10) * 1000;
|
var who = parseInt(b.dataset.who, 10);
|
||||||
cfg.delay = Math.min(60000, Math.max(0, next));
|
setTimer("delay", who, cfg.delay[who] + parseInt(b.dataset.delayAdj, 10) * 1000);
|
||||||
store.set("bg.delay", String(cfg.delay));
|
|
||||||
paintSettings(); resetClocks();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1308,7 +1444,6 @@
|
|||||||
paintSettings();
|
paintSettings();
|
||||||
if(on){ audio(); sndTick(); }
|
if(on){ audio(); sndTick(); }
|
||||||
}
|
}
|
||||||
document.getElementById("t-sound").addEventListener("click", function(){ setSound(!cfg.sound); });
|
|
||||||
btnSound.addEventListener("click", function(){ setSound(!cfg.sound); });
|
btnSound.addEventListener("click", function(){ setSound(!cfg.sound); });
|
||||||
|
|
||||||
/* ============ sheets ============ */
|
/* ============ sheets ============ */
|
||||||
@@ -1359,6 +1494,10 @@
|
|||||||
document.getElementById("v-" + p[1] + "-min").textContent = Math.floor(s / 60);
|
document.getElementById("v-" + p[1] + "-min").textContent = Math.floor(s / 60);
|
||||||
document.getElementById("v-" + p[1] + "-sec").textContent = ("0" + (s % 60)).slice(-2);
|
document.getElementById("v-" + p[1] + "-sec").textContent = ("0" + (s % 60)).slice(-2);
|
||||||
});
|
});
|
||||||
|
// the sheet only opens on a match of 3 points or more, so there is always a
|
||||||
|
// target worth naming
|
||||||
|
document.getElementById("p-score-hint").textContent =
|
||||||
|
"Changing the score will start a new game within this match to " + cfg.points + ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
function openScore(){
|
function openScore(){
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
/* Cache-first: once installed the clock never touches the network again. */
|
/* Cache-first: once installed the clock never touches the network again. */
|
||||||
const CACHE = "bgclock-v21";
|
const CACHE = "bgclock-v22";
|
||||||
const FILES = [
|
const FILES = [
|
||||||
"./",
|
"./",
|
||||||
"./index.html",
|
"./index.html",
|
||||||
|
|||||||
Reference in New Issue
Block a user