Skip to content

Commit 21ae440

Browse files
Byo-Yomi rules implemented
The common Byo-Yomi rules for Japanese and Canadian time-systems are now implemented so that the client's clocks proceed into over-time even in the absence of game-state messages from the KGS server.
1 parent 88b77c9 commit 21ae440

File tree

6 files changed

+230
-138
lines changed

6 files changed

+230
-138
lines changed

models/GameClock.ts

Lines changed: 103 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,131 @@
11
namespace Models {
2+
export interface GameClockState {
3+
running?: boolean;
4+
overtime?: boolean;
5+
expired?: boolean;
6+
7+
time?: number;
8+
periods?: number;
9+
stones?: number;
10+
}
11+
212
export class GameClock {
313
public updated: number;
414
public timeSystem: string;
515

6-
public mainTime: number;
7-
public maxPeriods: number;
16+
public rules: KGS.SGF.RULES;
817

918
public running: boolean;
1019
public overtime: boolean;
1120

1221
public time: number;
1322
public periods: number;
23+
public stones: number;
1424

1525
constructor(perfstamp: number) {
1626
this.updated = perfstamp;
1727
}
1828

19-
public setRules(perfstamp: number, rules: KGS.SGF.RULES) {
20-
this.timeSystem = rules.timeSystem;
21-
this.mainTime = rules.mainTime;
22-
23-
switch (this.timeSystem) {
24-
case KGS.Constants.TimeSystems.Japanese: this.maxPeriods = rules.byoYomiPeriods; break;
25-
case KGS.Constants.TimeSystems.Canadian: this.maxPeriods = rules.byoYomiStones; break;
26-
default: this.maxPeriods = 0; break;
27-
}
28-
}
29-
3029
public mergeClockState(perfstamp: number, clockState: KGS.Downstream.ClockState) {
3130
this.updated = perfstamp;
3231

3332
this.running = (clockState.running)? true : false;
3433

3534
this.time = clockState.time;
3635

37-
let periods: number = 0;
38-
switch (this.timeSystem) {
39-
case KGS.Constants.TimeSystems.Japanese: periods = clockState.periodsLeft; break;
40-
case KGS.Constants.TimeSystems.Canadian: periods = clockState.stonesLeft; break;
36+
switch (this.rules.timeSystem) {
37+
case KGS.Constants.TimeSystems.Japanese:
38+
this.periods = clockState.periodsLeft;
39+
if ((this.periods) && (this.periods > 0)) this.overtime = true;
40+
break;
41+
42+
case KGS.Constants.TimeSystems.Canadian:
43+
this.stones = clockState.stonesLeft;
44+
if ((this.stones) && (this.stones > 0)) this.overtime = true;
45+
break;
46+
}
47+
}
48+
49+
public now(perfstamp?: number): Models.GameClockState {
50+
if (!this.running) {
51+
return {
52+
running: false,
53+
overtime: this.overtime,
54+
time: Math.round(this.time),
55+
periods: this.periods,
56+
stones: this.stones
57+
};
58+
}
59+
else if (this.rules.timeSystem == KGS.Constants.TimeSystems.None) return null;
60+
61+
let expired: boolean = (this.time <= 0);
62+
if (!expired) {
63+
let seconds: number = this.time;
64+
perfstamp = (perfstamp != null)? perfstamp : performance.now();
65+
seconds -= (perfstamp - this.updated) / 1000.0;
66+
seconds = Math.round(seconds);
67+
68+
let japaneseByoYomi: boolean = (this.rules.timeSystem == KGS.Constants.TimeSystems.Japanese);
69+
let canadianByoYomi: boolean = (this.rules.timeSystem == KGS.Constants.TimeSystems.Canadian);
70+
71+
let overtime: boolean = (this.overtime)? true : false;
72+
let periods: number;
73+
if (seconds <= 0) {
74+
if ((japaneseByoYomi) || (canadianByoYomi)) {
75+
let periodLength: number = this.rules.byoYomiTime;
76+
let periodsAvailable: number;
77+
if (japaneseByoYomi) {
78+
periodsAvailable = (!overtime)? this.rules.byoYomiPeriods : (this.periods - 1);
79+
}
80+
else if (canadianByoYomi) {
81+
periodsAvailable = (!overtime)? 1 : 0;
82+
}
83+
84+
periods = - Math.ceil(seconds / periodLength);
85+
overtime = true;
86+
87+
if (periods < periodsAvailable) {
88+
seconds += (periods + 1) * periodLength;
89+
periods = periodsAvailable - periods;
90+
}
91+
else expired = true;
92+
}
93+
else expired = true;
94+
}
95+
else periods = this.periods;
96+
97+
if (!expired) {
98+
if (japaneseByoYomi) {
99+
return {
100+
running: true,
101+
overtime: overtime,
102+
time: seconds,
103+
periods: (overtime)? periods : null
104+
};
105+
}
106+
else if (canadianByoYomi) {
107+
return {
108+
running: true,
109+
overtime: overtime,
110+
time: seconds,
111+
stones: (!overtime)? null : (this.stones)? this.stones : this.rules.byoYomiStones
112+
};
113+
}
114+
else {
115+
return {
116+
running: true,
117+
time: seconds
118+
};
119+
}
120+
}
41121
}
42122

43-
if ((periods) && (periods > 0)) this.overtime = true;
44-
this.periods = periods;
123+
if (this.rules.timeSystem == KGS.Constants.TimeSystems.Canadian) {
124+
return { expired: true, stones: (this.stones) ? this.stones : this.rules.byoYomiStones };
125+
}
126+
else {
127+
return { expired: true };
128+
}
45129
}
46130
}
47131
}

models/GameState.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ namespace Models {
2929
}
3030

3131
private sgfSetRules(perfstamp: number, rules: KGS.SGF.RULES) {
32-
this.clockWhite.setRules(perfstamp, rules);
33-
this.clockBlack.setRules(perfstamp, rules);
32+
this.clockWhite.rules = rules;
33+
this.clockBlack.rules = rules;
3434
}
3535

3636
private sgfAffectsPosition(propName: string): boolean {

views/go-board/GoBoard.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
}
2121
}
2222

23+
.go-board > div {
24+
min-width: 0;
25+
min-height: 0;
26+
overflow: hidden;
27+
}
2328
.go-board > div > div {
2429
@extend .block-absolute;
2530
left: auto;

views/go-clock/GoClock.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@
1515
float: right;
1616
margin-right: 7px;
1717
}
18+
19+
.go-clock.expired .lcd-clock .lcd-clock-separator svg, .go-clock.expired svg .segment-on {
20+
fill: $brand-warning;
21+
}

views/go-clock/GoClock.ts

Lines changed: 93 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@ namespace Views {
22
export class GoClock implements Views.View<HTMLDivElement> {
33
private _div: HTMLDivElement;
44
private _clock: Views.LCDClock;
5-
private _periodNumber: Views.LCDDisplay;
6-
private _periodCounter: Views.LCDCounter;
5+
private _overtimeNumber: Views.LCDDisplay;
6+
private _overtimeCounter: Views.LCDCounter;
77

8-
private _maxPeriods: number;
8+
private _data: Models.GameClock;
9+
private _rules: KGS.SGF.RULES;
10+
private _expired: boolean;
11+
private _hidden: boolean;
12+
13+
private _resolution: number = 500;
14+
private _timeoutHandler: Function;
15+
private _timeoutHandle: number;
916

1017
constructor() {
1118
this._div = document.createElement('div');
@@ -14,11 +21,11 @@ namespace Views {
1421
this._clock = new Views.LCDClock();
1522
this._clock.attach(this._div);
1623

17-
this._periodNumber = new Views.LCDDisplay(2, 0, false, true);
18-
this._periodNumber.attach(this._div);
24+
this._overtimeNumber = new Views.LCDDisplay(2, 0, false, true);
25+
this._overtimeNumber.attach(this._div);
1926

20-
this._periodCounter = new Views.LCDCounter(25);
21-
this._periodCounter.attach(this._div);
27+
this._overtimeCounter = new Views.LCDCounter(25);
28+
this._overtimeCounter.attach(this._div);
2229
}
2330

2431
public attach(parent: HTMLElement): void {
@@ -27,55 +34,107 @@ namespace Views {
2734

2835
public activate(): void {
2936
this._clock.activate();
30-
this._periodNumber.activate();
31-
this._periodCounter.activate();
37+
this._overtimeNumber.activate();
38+
this._overtimeCounter.activate();
39+
40+
if (this._timeoutHandle == null) {
41+
this.restoreTimeout();
42+
}
3243
}
3344
public deactivate(): void {
34-
this._periodCounter.deactivate();
35-
this._periodNumber.deactivate();
45+
this.clearTimeout();
46+
47+
this._overtimeCounter.deactivate();
48+
this._overtimeNumber.deactivate();
3649
this._clock.deactivate();
3750
}
3851

39-
public hide() {
40-
this._div.classList.add('hidden');
52+
private restoreTimeout() {
53+
if (this._timeoutHandler != null) {
54+
this._timeoutHandle = window.setTimeout(this._timeoutHandler, this._resolution);
55+
}
4156
}
42-
public show() {
43-
this._div.classList.remove('hidden');
57+
58+
private clearTimeout() {
59+
if (this._timeoutHandle != null) {
60+
window.clearTimeout(this._timeoutHandle);
61+
this._timeoutHandle = null;
62+
}
4463
}
4564

46-
public update(clock: Models.GameClock) {
47-
if ((clock) && (clock.timeSystem) && (clock.timeSystem != KGS.Constants.TimeSystems.None)) {
48-
if (this._maxPeriods != clock.maxPeriods) {
49-
this._maxPeriods = clock.maxPeriods;
65+
public update(data: Models.GameClock) {
66+
if ((data) && (data.rules) && (data.rules.timeSystem) && (data.rules.timeSystem != KGS.Constants.TimeSystems.None)) {
67+
// Register a Timeout of the clock is running
68+
let clockState = data.now();
69+
if (clockState.running) {
70+
if (this._data != data) {
71+
this._data = data;
72+
this._timeoutHandler = this.update.bind(this, data);
73+
}
74+
75+
this.restoreTimeout();
76+
}
77+
78+
if (this._rules != data.rules) {
79+
// Show or hide the Overtime Counters
80+
let maxOvertimes: number;
81+
switch (data.rules.timeSystem) {
82+
case KGS.Constants.TimeSystems.Japanese: maxOvertimes = data.rules.byoYomiPeriods; break;
83+
case KGS.Constants.TimeSystems.Canadian: maxOvertimes = data.rules.byoYomiStones; break;
84+
default: maxOvertimes = 0;
85+
}
5086

51-
if ((this._maxPeriods) && (this._maxPeriods > 0) && (this._maxPeriods <= 30)) {
52-
this._periodCounter.setMaximum(this._maxPeriods, (this._maxPeriods <= 15)? 1 : 2);
53-
this._periodCounter.show();
87+
if ((maxOvertimes) && (maxOvertimes > 0) && (maxOvertimes <= 30)) {
88+
this._overtimeCounter.setMaximum(maxOvertimes, (maxOvertimes <= 15)? 1 : 2);
89+
this._overtimeCounter.show();
5490
}
5591
else {
56-
this._periodCounter.hide();
92+
this._overtimeCounter.hide();
5793
}
5894

59-
if ((this._maxPeriods) && (this._maxPeriods > 0)) {
60-
this._periodNumber.show();
95+
if ((maxOvertimes) && (maxOvertimes)) {
96+
this._overtimeNumber.show();
6197
}
6298
else {
63-
this._periodNumber.hide();
99+
this._overtimeNumber.hide();
64100
}
65101
}
66102

67-
let periods = (clock.overtime)? clock.periods : null;
68-
this._periodCounter.value = periods;
69-
this._periodNumber.value = periods;
103+
if (!clockState.expired) {
104+
let overtimeValue: number = (clockState.overtime)? (clockState.periods || clockState.stones) : null;
105+
this._overtimeCounter.value = overtimeValue;
106+
this._overtimeNumber.value = overtimeValue;
70107

71-
if (clock.running) this._clock.start(clock.time, clock.updated);
72-
else this._clock.stop(clock.time);
108+
this._clock.update(clockState.time, clockState.running);
73109

74-
this.show();
110+
if (this._expired) {
111+
this._div.classList.remove('expired');
112+
this._expired = false;
113+
}
114+
}
115+
else {
116+
let overtimeValue: number = (clockState.stones)? clockState.stones : 0;
117+
this._overtimeCounter.value = overtimeValue;
118+
this._overtimeNumber.value = overtimeValue;
119+
this._clock.update(0, false);
120+
if (!this._expired) {
121+
this._div.classList.add('expired');
122+
this._expired = true;
123+
}
124+
}
125+
126+
// Show the clock if it was previously hidden
127+
if (this._hidden) {
128+
this._div.classList.remove('hidden');
129+
this._hidden = false;
130+
}
75131
}
76132
else {
77-
this._clock.stop();
78-
this.hide();
133+
// Clear Timeout and Hide the clock
134+
this.clearTimeout();
135+
if (!this._hidden) this._div.classList.add('hidden');
136+
this._data = null;
137+
this._hidden = true;
79138
}
80139
}
81140
}

0 commit comments

Comments
 (0)