Skip to content

Commit 64fce2d

Browse files
Game scoring user experience complete
The user-experience at the end of a game is now complete. Stones and groups can be marked as dead or alive and the user can indicated that they are satisfied with the result, leading to a scored culmination.
1 parent 6f5a05d commit 64fce2d

File tree

22 files changed

+292
-226
lines changed

22 files changed

+292
-226
lines changed

controllers/Application.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ namespace Controllers {
2828
}
2929

3030
public reinitialise() {
31+
this.layout.main.clear();
32+
this.layout.sidebar.clear();
3133
this._channelController.reinitialise();
32-
this.layout.clearMain();
33-
this.layout.clearSidebar();
3434
}
3535
}
3636
}

controllers/ChannelController.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,28 @@ namespace Controllers {
1616

1717
constructor(parent: Application) {
1818
super(parent);
19-
20-
this._joinedChannelIds = [];
21-
this._channelControllers = {};
22-
this._homeController = new Controllers.HomeController(this);
23-
this._activeChannel = null;
24-
this._operationChannelId = null;
25-
26-
this._channelList = this.application.layout.channelList;
27-
this._channelList.selectionCallback = (channelId: number) => this.activateChannel(channelId);
28-
this._channelList.closeCallback = (channelId: number) => this.unjoinChannel(channelId);
19+
this.reinitialise();
2920
}
3021

3122
public reinitialise() {
23+
this.detachChildren();
24+
3225
this._joinedChannelIds = [];
26+
this._channelControllers = {};
3327
this._activeChannel = null;
3428
this._operationChannelId = null;
3529

36-
this.detachChildren();
37-
38-
this._channelControllers = {};
3930
this._homeController = new Controllers.HomeController(this);
31+
32+
if (this._channelList != null) {
33+
this._channelList.selectionCallback = null;
34+
this._channelList.closeCallback = null;
35+
}
36+
37+
this._channelList = new Views.ChannelList();
38+
this._channelList.selectionCallback = this._onChannelListSelection;
39+
this._channelList.closeCallback = this._onChannelListClose;
40+
this.application.layout.sidebar.show(this._channelList);
4041
}
4142

4243
protected digest(digest: KGS.DataDigest) {
@@ -84,10 +85,10 @@ namespace Controllers {
8485
}
8586

8687
if (activateChannelId != null) {
87-
this.activateChannel(activateChannelId);
88+
this._onChannelListSelection(activateChannelId);
8889
}
8990
else if (this._activeChannel == null) {
90-
this.activateChannel((this._joinedChannelIds.length > 0)? this._joinedChannelIds[0] : Controllers.HomeController.channelId);
91+
this._onChannelListSelection((this._joinedChannelIds.length > 0)? this._joinedChannelIds[0] : Controllers.HomeController.channelId);
9192
}
9293
else {
9394
this.updateChannelList();
@@ -122,7 +123,7 @@ namespace Controllers {
122123
this._channelList.update(this.database.channels, this._joinedChannelIds, (this._activeChannel == this._homeController)? Controllers.HomeController.channelId : (<Controllers.ChannelBase>this._activeChannel).channelId);
123124
}
124125

125-
private activateChannel(channelId: number) {
126+
private _onChannelListSelection = (channelId: number) => {
126127
let activate = (channelId == Controllers.HomeController.channelId)? this._homeController : this._channelControllers[channelId];
127128

128129
if (this._activeChannel != activate) {
@@ -146,7 +147,7 @@ namespace Controllers {
146147

147148
public joinChannel(channelId: number) {
148149
if (this.channelOperationInProgress) throw 'Channel operation in progress';
149-
else if (this._joinedChannelIds.indexOf(channelId) >= 0) this.activateChannel(channelId);
150+
else if (this._joinedChannelIds.indexOf(channelId) >= 0) this._onChannelListSelection(channelId);
150151
else {
151152
this._operationChannelId = channelId;
152153

@@ -157,7 +158,7 @@ namespace Controllers {
157158
}
158159
}
159160

160-
public unjoinChannel(channelId: number) {
161+
private _onChannelListClose = (channelId: number) => {
161162
if (this.channelOperationInProgress) throw 'Channel operation in progress';
162163
else if (this._joinedChannelIds.indexOf(channelId) >= 0) {
163164
this._operationChannelId = channelId;

controllers/LayoutController.ts

Lines changed: 70 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,92 @@ namespace Controllers {
77
Lightbox
88
}
99

10+
export class LayoutZoneController {
11+
private _container: HTMLElement;
12+
private _views: { wrapper: HTMLDivElement, view: Views.View<any> }[];
13+
14+
constructor(container: HTMLElement) {
15+
this._container = container;
16+
this._views = [];
17+
}
18+
19+
public show(view: Views.View<any>): HTMLDivElement {
20+
let wrapper = document.createElement('div');
21+
this._container.appendChild(wrapper);
22+
23+
view.attach(wrapper);
24+
25+
this._views.push({ wrapper: wrapper, view: view });
26+
27+
view.activate();
28+
29+
return wrapper;
30+
}
31+
32+
public hide(view: Views.View<any>): boolean {
33+
for (let j = (this._views.length - 1); j >= 0; --j) {
34+
if (this._views[j].view == view) {
35+
view.deactivate();
36+
this._container.removeChild(this._views[j].wrapper);
37+
this._views.splice(j, 1);
38+
return true;
39+
}
40+
}
41+
42+
return false;
43+
}
44+
45+
public clear() {
46+
if (this._views) {
47+
for (let j = 0; j < this._views.length; ++j) {
48+
this._views[j].view.deactivate();
49+
}
50+
51+
this._views.length = 0;
52+
}
53+
54+
$(this._container).children().detach();
55+
}
56+
}
57+
1058
export class LayoutController extends ControllerBase<Application> {
1159
private _lightbox: Views.LightboxContainer;
12-
private _mainContainer: HTMLDivElement;
13-
private _mainViews: Views.MainContainer[] = [];
14-
private _sidebar: HTMLDivElement;
15-
private _sidebarChannelList: Views.ChannelList;
16-
private _sidebarContainer: HTMLDivElement;
17-
private _sidebarView: Views.View<any>;
60+
61+
public main: LayoutZoneController;
62+
public sidebar: LayoutZoneController;
1863

1964
constructor(parent: Application) {
2065
super(parent);
2166
$layout = this;
2267

23-
this._mainContainer = document.querySelector('#main') as HTMLDivElement;
68+
let mainContainer = document.querySelector('#main') as HTMLDivElement;
69+
this.main = new LayoutZoneController(mainContainer);
70+
this.main.clear();
2471

25-
this._sidebar = document.querySelector('#sidebar') as HTMLDivElement;
26-
this._sidebarChannelList = new Views.ChannelList();
27-
this._sidebarChannelList.attach(this._sidebar);
28-
this._sidebarChannelList.activate();
29-
this._sidebarContainer = document.createElement('div');
30-
this._sidebar.appendChild(this._sidebarContainer);
31-
32-
this.clearMain();
33-
this.clearSidebar();
72+
let sidebarContainer = document.querySelector('#sidebar') as HTMLDivElement;
73+
this.sidebar = new LayoutZoneController(sidebarContainer);
74+
this.sidebar.clear();
3475
}
3576

3677
public showView(view: Views.View<any>, zone: LayoutZone) {
3778
switch (zone) {
38-
case LayoutZone.Main: this.showMain(view); break;
39-
case LayoutZone.Sidebar: this.showSidebar(view); break;
79+
case LayoutZone.Main: this.main.show(view); break;
80+
case LayoutZone.Sidebar: this.sidebar.show(view); break;
4081
case LayoutZone.Lightbox: this.showLightbox(view); break;
4182
}
4283
}
4384

85+
public hideView(view: Views.View<any>, zone?: LayoutZone) {
86+
if (zone != null) {
87+
switch (zone) {
88+
case LayoutZone.Main: this.main.hide(view); break;
89+
case LayoutZone.Sidebar: this.sidebar.hide(view); break;
90+
case LayoutZone.Lightbox: this.hideLightbox(); break;
91+
}
92+
}
93+
else throw "Not Implemented";
94+
}
95+
4496
public showLightbox(view: Views.View<any>, suppressTransitions?: boolean) {
4597
this.hideLightbox();
4698
this._lightbox = Views.LightboxContainer.showLightbox(view, suppressTransitions);
@@ -52,46 +104,6 @@ namespace Controllers {
52104
this._lightbox = null;
53105
}
54106
}
55-
56-
public showMain(view: Views.View<any>) {
57-
let idx: number = this._mainViews.length;
58-
this._mainViews.push(new Views.MainContainer(view));
59-
this._mainViews[idx].attach(this._mainContainer);
60-
this._mainViews[idx].activate();
61-
}
62-
63-
public clearMain() {
64-
if (this._mainViews) {
65-
for (let j = 0; j < this._mainViews.length; ++j) {
66-
this._mainViews[j].deactivate();
67-
}
68-
69-
this._mainViews.length = 0;
70-
}
71-
72-
$(this._mainContainer).children().detach();
73-
}
74-
75-
public get channelList(): Views.ChannelList {
76-
return this._sidebarChannelList;
77-
}
78-
79-
public showSidebar(view: Views.View<any>) {
80-
this.clearSidebar();
81-
82-
this._sidebarView = view;
83-
this._sidebarView.attach(this._sidebarContainer);
84-
this._sidebarView.activate();
85-
}
86-
87-
public clearSidebar() {
88-
if (this._sidebarView) {
89-
this._sidebarView.deactivate();
90-
this._sidebarView = null;
91-
}
92-
93-
$(this._sidebarContainer).children().detach();
94-
}
95107
}
96108
}
97109

controllers/ViewControllerBase.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@ namespace Controllers {
4040
public deactivate(): boolean {
4141
if (!this._activated) return false;
4242

43-
this.application.layout.clearMain();
44-
this.application.layout.clearSidebar();
43+
if (this._views) {
44+
for (let j = (this._views.length - 1); j >= 0; --j) {
45+
this.application.layout.hideView(this._views[j].view, this._views[j].zone);
46+
}
47+
}
4548

4649
this._activated = false;
4750
return true;

controllers/channels/GameChannel.ts

Lines changed: 68 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -71,59 +71,94 @@ namespace Controllers {
7171
home = temp;
7272
}
7373

74+
let homeButtons: { text: string, callback: Function, dangerous?: boolean}[];
7475
if (this._userColour != null) {
75-
let passCallback: Function = ((gameChannel.phase == Models.GamePhase.Active) && (gameChannel.hasAction(Models.GameActions.Move)))? this._passCallback : null;
76-
let resignCallback: Function = (gameChannel.phase != Models.GamePhase.Concluded)? this._resignCallback : null;
77-
this._board.playerHome.update(home.colour, home.clock, home.prisoners, home.komi, home.user, true, passCallback, resignCallback);
78-
}
79-
else {
80-
this._board.playerHome.update(home.colour, home.clock, home.prisoners, home.komi, home.user);
76+
homeButtons = [];
77+
78+
if (gameChannel.phase != Models.GamePhase.Concluded) {
79+
if (gameChannel.phase == Models.GamePhase.Active) {
80+
if (gameChannel.hasAction(Models.GameActions.Score)) {
81+
homeButtons.push({ text: "done", callback: this._doneScoringCallback });
82+
}
83+
else {
84+
homeButtons.push({ text: "pass", callback: ((gameChannel.hasAction(Models.GameActions.Move))? this._passCallback : null) });
85+
}
86+
}
87+
88+
homeButtons.push({ text: "resign", dangerous: true, callback: this._resignCallback });
89+
}
8190
}
8291

92+
this._board.playerHome.update(home.colour, home.clock, home.prisoners, home.komi, home.user, homeButtons);
8393
this._board.playerAway.update(away.colour, away.clock, away.prisoners, away.komi, away.user);
8494
}
8595
}
8696

8797
private tryPlay(x: number, y: number): boolean {
8898
let gameChannel = this.channel as Models.GameChannel;
89-
if (!gameChannel.hasAction(Models.GameActions.Move)) return false;
90-
9199
let gameState = this.database.games[this.channelId];
92100
if ((!gameState) || (!gameState.tree)) return false;
93101

94-
if ((x != null) && (y != null)) {
95-
let r = gameState.tree.tryPlay(x, y);
96-
switch (r as Models.GameMoveError) {
97-
case Models.GameMoveError.Success:
98-
gameChannel.disableAction(Models.GameActions.Move);
99-
this.client.post(<KGS.Upstream.GAME_MOVE>{
100-
type: KGS.Upstream._GAME_MOVE,
101-
channelId: this.channelId,
102-
x: x,
103-
y: y
104-
});
105-
return true;
106-
107-
case Models.GameMoveError.InvalidLocation: console.log("given coordinates are not on board"); return false;
108-
case Models.GameMoveError.StonePresent: console.log("on given coordinates already is a stone"); return false;
109-
case Models.GameMoveError.Suicide: console.log("suicide (currently they are forbbiden)"); return false;
110-
case Models.GameMoveError.Ko: console.log("ko ko ko"); return false;
111-
default: console.log("unknown outcome"); return false;
102+
if (gameChannel.hasAction(Models.GameActions.Move)) {
103+
if ((x != null) && (y != null)) {
104+
let r = gameState.tree.tryPlay(x, y);
105+
switch (r as Models.GameMoveError) {
106+
case Models.GameMoveError.Success:
107+
gameChannel.disableAction(Models.GameActions.Move);
108+
this.client.post(<KGS.Upstream.GAME_MOVE>{
109+
type: KGS.Upstream._GAME_MOVE,
110+
channelId: this.channelId,
111+
loc: { x: x, y: y }
112+
});
113+
return true;
114+
115+
case Models.GameMoveError.InvalidLocation: console.log("given coordinates are not on board"); return false;
116+
case Models.GameMoveError.StonePresent: console.log("on given coordinates already is a stone"); return false;
117+
case Models.GameMoveError.Suicide: console.log("suicide (currently they are forbbiden)"); return false;
118+
case Models.GameMoveError.Ko: console.log("ko ko ko"); return false;
119+
default: console.log("unknown outcome"); return false;
120+
}
121+
}
122+
else {
123+
gameChannel.disableAction(Models.GameActions.Move);
124+
this.client.post(<KGS.Upstream.GAME_MOVE>{
125+
type: KGS.Upstream._GAME_MOVE,
126+
channelId: this.channelId,
127+
loc: "PASS"
128+
});
129+
return true;
112130
}
113131
}
114-
else {
115-
gameChannel.disableAction(Models.GameActions.Move);
116-
this.client.post(<KGS.Upstream.GAME_MOVE>{
117-
type: KGS.Upstream._GAME_MOVE,
118-
channelId: this.channelId,
119-
});
120-
return true;
132+
else if (gameChannel.hasAction(Models.GameActions.Score)) {
133+
let positionValue = gameState.tree.position.get(x, y);
134+
if ((positionValue != null) && ((positionValue & (Models.GameMarks.BlackStone | Models.GameMarks.WhiteStone)) != 0)) {
135+
this.client.post(<KGS.Upstream.GAME_MARK_LIFE>{
136+
type: KGS.Upstream._GAME_MARK_LIFE,
137+
channelId: this.channelId,
138+
x: x,
139+
y: y,
140+
alive: ((positionValue & Models.GameMarks.Dead) == Models.GameMarks.Dead)
141+
});
142+
return true;
143+
}
121144
}
145+
146+
return false;
122147
}
123148

124149
private _passCallback = () => {
125150
this.tryPlay(undefined, undefined);
126151
}
152+
private _doneScoringCallback = () => {
153+
let gameChannel = this.channel as Models.GameChannel;
154+
if ((gameChannel.hasAction(Models.GameActions.Score)) && (gameChannel.doneScoringId != null)) {
155+
this.client.post(<KGS.Upstream.GAME_SCORING_DONE>{
156+
type: KGS.Upstream._GAME_SCORING_DONE,
157+
channelId: this.channelId,
158+
doneId: gameChannel.doneScoringId
159+
});
160+
}
161+
}
127162
private _resignCallback = () => {
128163
if (this._userColour != null) {
129164
let gameChannel = this.channel as Models.GameChannel;

0 commit comments

Comments
 (0)