-
Notifications
You must be signed in to change notification settings - Fork 112
/
HanabiUI.ts
251 lines (213 loc) · 7.1 KB
/
HanabiUI.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/*
The object that comprises the entire game UI
It is re-created every time when going into a new game
(and destroyed when going to the lobby)
*/
// Imports
import Konva from 'konva';
import * as action from './action';
import { LABEL_COLOR, STACK_BASE_RANK } from '../../constants';
import drawCards from './drawCards';
import drawUI from './drawUI';
import globals, { Globals } from './globals';
import HanabiCard from './HanabiCard';
import LayoutChild from './LayoutChild';
import Loader from './Loader';
import * as keyboard from './keyboard';
import * as stats from './stats';
import * as timer from './timer';
import * as ui from './ui';
export default class HanabiUI {
globals: Globals;
constructor(lobby: any, game: any) {
// Since the "HanabiUI" object is being reinstantiated,
// we need to explicitly reinitialize all globals varaibles
// (or else they will retain their old values)
globals.reset();
// Expose the globals to functions in the "game" directory
this.globals = globals;
// Store references to the parent objects for later use
globals.lobby = lobby; // This is the "Globals" object in the root of the "src" directory
// We name it "lobby" here to distinguish it from the UI globals;
// after more refactoring, we will eventually merge these objects to make it less confusing
globals.game = game; // This is the "gameExports" from the "/src/game/main.ts" file
// We should also combine this with the UI object in the future
// Initialize the stage and show the loading screen
initStage();
showLoadingScreen();
}
/*
The following methods are called from various parent functions
*/
updateChatLabel() { // eslint-disable-line class-methods-use-this
let text = '💬';
if (globals.lobby.chatUnread > 0) {
text += ` (${globals.lobby.chatUnread})`;
}
globals.elements.chatButton!.text(text);
globals.layers.get('UI')!.batchDraw();
}
destroy() { // eslint-disable-line class-methods-use-this
keyboard.destroy();
timer.stop();
globals.stage!.destroy();
// window.removeEventListener('resize', resizeCanvas, false);
}
reshowClueUIAfterWarning() { // eslint-disable-line class-methods-use-this
action.handle();
}
}
// Initialize and size the stage depending on the window size
const initStage = () => {
globals.stage = new Konva.Stage({
container: 'game',
});
const ratio = 16 / 9;
let ww = window.innerWidth;
let wh = window.innerHeight;
if (ww < 640) {
ww = 640;
}
if (wh < 360) {
wh = 360;
}
let cw;
let ch;
if (ww < wh * ratio) {
cw = ww;
ch = ww / ratio;
} else {
ch = wh;
cw = wh * ratio;
}
cw = Math.floor(cw);
ch = Math.floor(ch);
if (cw > 0.98 * ww) {
cw = ww;
}
if (ch > 0.98 * wh) {
ch = wh;
}
globals.stage.width(cw);
globals.stage.height(ch);
};
const showLoadingScreen = () => {
const winW = globals.stage!.width();
const winH = globals.stage!.height();
const loadingLayer = new Konva.Layer();
const loadingLabel = new Konva.Text({
fill: LABEL_COLOR,
stroke: '#747278',
strokeWidth: 1,
text: 'Loading...',
align: 'center',
x: 0,
y: 0.7 * winH,
width: winW,
height: 0.05 * winH,
fontFamily: 'Arial',
fontStyle: 'bold',
fontSize: 0.05 * winH,
});
loadingLayer.add(loadingLabel);
const progresslabel = new Konva.Text({
fill: LABEL_COLOR,
stroke: '#747278',
strokeWidth: 1,
text: '0 / 0',
align: 'center',
x: 0,
y: 0.8 * winH,
width: winW,
height: 0.05 * winH,
fontFamily: 'Arial',
fontStyle: 'bold',
fontSize: 0.05 * winH,
});
loadingLayer.add(progresslabel);
globals.stage!.add(loadingLayer);
const loadingProgressCallback = (done: number, total: number) => {
progresslabel.text(`${done}/${total}`);
loadingLayer.batchDraw();
};
globals.ImageLoader = new Loader(loadingProgressCallback, loadingFinishedCallback);
};
const loadingFinishedCallback = () => {
// Build images for every card
// (with respect to the variant that we are playing
// and whether or not we have the colorblind UI feature enabled)
globals.cardImages = drawCards(globals.variant, globals.lobby.settings.get('showColorblindUI'));
// Construct a list of all of the cards in the deck
initCardsMap();
// Build all of the reusuable card objects
initCards();
// Draw the user interface
drawUI();
// Keyboard hotkeys can only be initialized once the clue buttons are drawn
keyboard.init();
// If the game is paused, darken the background
ui.setPause();
// Tell the server that we are finished loading
globals.lobby.conn.send('ready');
};
const initCardsMap = () => {
for (const suit of globals.variant.suits) {
if (globals.variant.name.startsWith('Up or Down')) {
// 6 is an unknown rank, so we use 7 to represent a "START" card
const key = `${suit.name}7`;
globals.cardsMap.set(key, 1);
}
for (let rank = 1; rank <= 5; rank++) {
// In a normal suit of Hanabi,
// there are three 1's, two 2's, two 3's, two 4's, and one five
let amountToAdd = 2;
if (rank === 1) {
amountToAdd = 3;
if (globals.variant.name.startsWith('Up or Down')) {
amountToAdd = 1;
}
} else if (rank === 5) {
amountToAdd = 1;
}
if (suit.oneOfEach) {
amountToAdd = 1;
}
const key = `${suit.name}${rank}`;
globals.cardsMap.set(key, amountToAdd);
}
}
};
const initCards = () => {
globals.deckSize = stats.getTotalCardsInTheDeck(globals.variant);
for (let order = 0; order < globals.deckSize; order++) {
// Create the "learned" card object
// (this must be done before creating the HanabiCard object)
globals.learnedCards.push({
suit: null,
rank: null,
revealed: false,
});
// Create the notes for this card
// (this must be done before creating the HanabiCard object)
globals.ourNotes.push('');
globals.allNotes.push([]);
// Create the HanabiCard object
const card = new HanabiCard({
order,
});
globals.deck.push(card);
// Create the LayoutChild that will be the parent of the card
const child = new LayoutChild();
child.addCard(card);
}
// Also create objects for the stack bases
for (const suit of globals.variant.suits) {
globals.learnedCards.push({
suit,
rank: STACK_BASE_RANK,
revealed: true,
});
globals.ourNotes.push('');
globals.allNotes.push([]);
}
};