@mideind/netskrafl-react 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +8 -0
- package/.eslintrc.json +13 -0
- package/README.md +63 -0
- package/dist/cjs/index.css +6837 -0
- package/dist/cjs/index.js +3046 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.css +6837 -0
- package/dist/esm/index.js +3046 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/package.json +63 -0
- package/rollup.config.js +60 -0
- package/src/components/index.ts +2 -0
- package/src/components/netskrafl/Netskrafl.stories.tsx +66 -0
- package/src/components/netskrafl/Netskrafl.tsx +135 -0
- package/src/components/netskrafl/Netskrafl.types.ts +7 -0
- package/src/components/netskrafl/index.ts +2 -0
- package/src/css/fonts.css +4 -0
- package/src/css/glyphs.css +224 -0
- package/src/css/skrafl-explo.css +6616 -0
- package/src/fonts/glyphicons-regular.eot +0 -0
- package/src/fonts/glyphicons-regular.ttf +0 -0
- package/src/fonts/glyphicons-regular.woff +0 -0
- package/src/index.ts +2 -0
- package/src/messages/messages.json +1576 -0
- package/src/mithril/actions.ts +319 -0
- package/src/mithril/bag.ts +65 -0
- package/src/mithril/bestdisplay.ts +74 -0
- package/src/mithril/blankdialog.ts +94 -0
- package/src/mithril/board.ts +336 -0
- package/src/mithril/buttons.ts +303 -0
- package/src/mithril/challengedialog.ts +186 -0
- package/src/mithril/channel.ts +162 -0
- package/src/mithril/chat.ts +228 -0
- package/src/mithril/components.ts +496 -0
- package/src/mithril/dragdrop.ts +219 -0
- package/src/mithril/elopage.ts +180 -0
- package/src/mithril/friend.ts +227 -0
- package/src/mithril/game.ts +1378 -0
- package/src/mithril/gameview.ts +111 -0
- package/src/mithril/globalstate.ts +33 -0
- package/src/mithril/i18n.ts +186 -0
- package/src/mithril/localstorage.ts +133 -0
- package/src/mithril/login.ts +122 -0
- package/src/mithril/logo.ts +270 -0
- package/src/mithril/main.ts +737 -0
- package/src/mithril/mithril.ts +29 -0
- package/src/mithril/model.ts +817 -0
- package/src/mithril/movelistitem.ts +226 -0
- package/src/mithril/page.ts +852 -0
- package/src/mithril/playername.ts +91 -0
- package/src/mithril/promodialog.ts +82 -0
- package/src/mithril/recentlist.ts +148 -0
- package/src/mithril/request.ts +52 -0
- package/src/mithril/review.ts +634 -0
- package/src/mithril/rightcolumn.ts +398 -0
- package/src/mithril/searchbutton.ts +118 -0
- package/src/mithril/statsdisplay.ts +109 -0
- package/src/mithril/tabs.ts +169 -0
- package/src/mithril/tile.ts +145 -0
- package/src/mithril/twoletter.ts +76 -0
- package/src/mithril/types.ts +379 -0
- package/src/mithril/userinfodialog.ts +171 -0
- package/src/mithril/util.ts +304 -0
- package/src/mithril/wait.ts +246 -0
- package/src/mithril/wordcheck.ts +102 -0
- package/tsconfig.json +28 -0
- package/vite.config.ts +12 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Review.ts
|
|
4
|
+
|
|
5
|
+
Single page UI for Explo using the Mithril library
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2025 Miðeind ehf.
|
|
8
|
+
Author: Vilhjálmur Þorsteinsson
|
|
9
|
+
|
|
10
|
+
The Creative Commons Attribution-NonCommercial 4.0
|
|
11
|
+
International Public License (CC-BY-NC 4.0) applies to this software.
|
|
12
|
+
For further information, see https://github.com/mideind/Netskrafl
|
|
13
|
+
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { ts } from "./i18n";
|
|
17
|
+
import { IView, Move, MoveInfo } from "./types";
|
|
18
|
+
import { glyph, coord, toVector, nbsp, buttonOver, buttonOut } from "./util";
|
|
19
|
+
import {
|
|
20
|
+
m, Vnode, VnodeChildren, VnodeAttrs,
|
|
21
|
+
ComponentFunc,
|
|
22
|
+
} from "./mithril";
|
|
23
|
+
import { ExploLogoOnly } from "./logo";
|
|
24
|
+
import { PlayerName } from "./playername";
|
|
25
|
+
import { makeButton } from "./buttons";
|
|
26
|
+
import { Info, LeftLogo } from "./components";
|
|
27
|
+
import { Board, Rack } from "./board";
|
|
28
|
+
|
|
29
|
+
export const vwReview = (view: IView): VnodeChildren => {
|
|
30
|
+
// A review of a finished game
|
|
31
|
+
|
|
32
|
+
const model = view.model;
|
|
33
|
+
if (!model.game) return undefined;
|
|
34
|
+
const game = model.game;
|
|
35
|
+
let moveIndex = model.reviewMove ?? 0;
|
|
36
|
+
let bestMoves = model.bestMoves || [];
|
|
37
|
+
|
|
38
|
+
function vwRightColumn(): Vnode {
|
|
39
|
+
// A container for the right-side header and area components
|
|
40
|
+
|
|
41
|
+
function vwRightHeading(): Vnode {
|
|
42
|
+
// The right-side heading on the game screen
|
|
43
|
+
|
|
44
|
+
const fairplay = game.fairplay;
|
|
45
|
+
const player = game.player;
|
|
46
|
+
let sc0 = "";
|
|
47
|
+
let sc1 = "";
|
|
48
|
+
if (moveIndex) {
|
|
49
|
+
let s0 = 0;
|
|
50
|
+
let s1 = 0;
|
|
51
|
+
for (let i = 0; i < moveIndex; i++) {
|
|
52
|
+
// Add up the scores until and including this move
|
|
53
|
+
let m = game.moves[i];
|
|
54
|
+
if (i % 2 === 0)
|
|
55
|
+
s0 += m[1][2];
|
|
56
|
+
else
|
|
57
|
+
s1 += m[1][2];
|
|
58
|
+
}
|
|
59
|
+
sc0 = s0.toString();
|
|
60
|
+
sc1 = s1.toString();
|
|
61
|
+
}
|
|
62
|
+
return m(".heading",
|
|
63
|
+
[
|
|
64
|
+
m(".logowrapper",
|
|
65
|
+
m(".header-logo",
|
|
66
|
+
m(m.route.Link,
|
|
67
|
+
{
|
|
68
|
+
href: "/page",
|
|
69
|
+
class: "backlink"
|
|
70
|
+
},
|
|
71
|
+
m(ExploLogoOnly)
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
),
|
|
75
|
+
m(".playerwrapper", [
|
|
76
|
+
m(".leftplayer" + (player === 1 ? ".autoplayercolor" : ".humancolor"), [
|
|
77
|
+
m(".player", m(PlayerName, { view, side: "left" })),
|
|
78
|
+
m(".scorewrapper", m(".scoreleft", sc0)),
|
|
79
|
+
]),
|
|
80
|
+
m(".rightplayer" + (player === 1 ? ".humancolor" : ".autoplayercolor"), [
|
|
81
|
+
m(".player", m(PlayerName, { view, side: "right" })),
|
|
82
|
+
m(".scorewrapper", m(".scoreright", sc1)),
|
|
83
|
+
]),
|
|
84
|
+
m(".fairplay",
|
|
85
|
+
{ style: { visibility: fairplay ? "visible" : "hidden" } },
|
|
86
|
+
m("span.fairplay-btn.large", { title: ts("Skraflað án hjálpartækja") }))
|
|
87
|
+
])
|
|
88
|
+
]
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function vwRightArea(): Vnode {
|
|
93
|
+
// A container for the list of best possible moves
|
|
94
|
+
return m(".right-area", vwBestMoves(view, moveIndex, bestMoves));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return m(".rightcol", [vwRightHeading(), vwRightArea()]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let r: VnodeChildren = [];
|
|
101
|
+
if (game) {
|
|
102
|
+
// Create a list of major elements that we're showing
|
|
103
|
+
r.push(vwRightColumn());
|
|
104
|
+
r.push(m(BoardReview, { view, moveIndex }));
|
|
105
|
+
if (model.reviewMove !== null && moveIndex === 0) {
|
|
106
|
+
// Only show the stats overlay if moveIndex is 0
|
|
107
|
+
const n = vwStatsReview(view);
|
|
108
|
+
n && r.push(n);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return m("div", // Removing this div messes up Mithril
|
|
112
|
+
[
|
|
113
|
+
m(".game-container", r),
|
|
114
|
+
m(LeftLogo), // Button to go back to main screen
|
|
115
|
+
m(Info) // Help button
|
|
116
|
+
]
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const vwBestMoves = (view: IView, moveIndex: number, bestMoves: Move[]): VnodeChildren => {
|
|
121
|
+
// List of best moves, in a game review
|
|
122
|
+
|
|
123
|
+
const model = view.model;
|
|
124
|
+
const game = model.game;
|
|
125
|
+
|
|
126
|
+
function bestHeader(co: string, tiles: string, score: number): Vnode {
|
|
127
|
+
// Generate the header of the best move list
|
|
128
|
+
let wrdclass = "wordmove";
|
|
129
|
+
let dispText: string | any[];
|
|
130
|
+
if (co.length > 0) {
|
|
131
|
+
// Regular move
|
|
132
|
+
dispText = [
|
|
133
|
+
m("i", tiles.split("?").join("")),
|
|
134
|
+
" (" + co + ")"
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
/* Not a regular tile move */
|
|
139
|
+
wrdclass = "othermove";
|
|
140
|
+
if (tiles == "PASS")
|
|
141
|
+
/* Pass move */
|
|
142
|
+
dispText = ts("Pass");
|
|
143
|
+
else
|
|
144
|
+
if (tiles.indexOf("EXCH") === 0) {
|
|
145
|
+
/* Exchange move - we don't show the actual tiles exchanged, only their count */
|
|
146
|
+
let numtiles = tiles.slice(5).length
|
|
147
|
+
const letters = ts(numtiles == 1 ? "letter" : "letters");
|
|
148
|
+
dispText = ts("exchanged", { numtiles: numtiles.toString(), letters: letters });
|
|
149
|
+
}
|
|
150
|
+
else
|
|
151
|
+
if (tiles == "RSGN")
|
|
152
|
+
/* Resigned from game */
|
|
153
|
+
dispText = ts("Gaf viðureign");
|
|
154
|
+
else
|
|
155
|
+
if (tiles == "CHALL")
|
|
156
|
+
/* Challenge issued */
|
|
157
|
+
dispText = ts("Véfengdi lögn");
|
|
158
|
+
else
|
|
159
|
+
if (tiles == "RESP") {
|
|
160
|
+
/* Challenge response */
|
|
161
|
+
if (score < 0)
|
|
162
|
+
dispText = ts("Óleyfileg lögn");
|
|
163
|
+
else
|
|
164
|
+
dispText = ts("Röng véfenging");
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
if (tiles == "TIME") {
|
|
168
|
+
/* Score adjustment for time */
|
|
169
|
+
dispText = ts("Umframtími");
|
|
170
|
+
}
|
|
171
|
+
else
|
|
172
|
+
if (tiles == "OVER") {
|
|
173
|
+
/* Game over */
|
|
174
|
+
dispText = ts("Viðureign lokið");
|
|
175
|
+
wrdclass = "gameover";
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// The rack leave at the end of the game (which is always in lowercase
|
|
179
|
+
// and thus cannot be confused with the above abbreviations)
|
|
180
|
+
wrdclass = "othermove";
|
|
181
|
+
if (tiles == "--")
|
|
182
|
+
dispText = ts("Stafaleif: (engin)");
|
|
183
|
+
else
|
|
184
|
+
dispText = [ts("Stafaleif: "), m("i.upper", tiles)];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return m(".reviewhdr",
|
|
188
|
+
[
|
|
189
|
+
m("span.movenumber", "#" + moveIndex),
|
|
190
|
+
m("span", { class: wrdclass }, dispText)
|
|
191
|
+
]
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function bestMoveList(): VnodeChildren {
|
|
196
|
+
let r: VnodeChildren = [];
|
|
197
|
+
// Use a 1-based index into the move list
|
|
198
|
+
// (We show the review summary if move==0)
|
|
199
|
+
if (!game || !moveIndex || moveIndex > game.moves.length)
|
|
200
|
+
return r;
|
|
201
|
+
// Prepend a header that describes the move being reviewed
|
|
202
|
+
const m = game.moves[moveIndex - 1];
|
|
203
|
+
const [co, tiles, score] = m[1];
|
|
204
|
+
r.push(bestHeader(co, tiles, score));
|
|
205
|
+
const mlist = bestMoves;
|
|
206
|
+
for (let i = 0; i < mlist.length; i++) {
|
|
207
|
+
const [player, [co, tiles, score]] = mlist[i];
|
|
208
|
+
const n = vwBestMove(view, moveIndex, i, mlist[i],
|
|
209
|
+
{
|
|
210
|
+
key: i.toString(),
|
|
211
|
+
player: player, co: co, tiles: tiles,
|
|
212
|
+
score: score, leftTotal: 0, rightTotal: 0
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
n && r.push(n);
|
|
216
|
+
}
|
|
217
|
+
return r;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return m(".movelist-container", [m(".movelist.bestmoves", bestMoveList())]);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const vwBestMove = (
|
|
224
|
+
view: IView,
|
|
225
|
+
moveIndex: number,
|
|
226
|
+
bestMoveIndex: number,
|
|
227
|
+
move: Move,
|
|
228
|
+
info: MoveInfo,
|
|
229
|
+
): VnodeChildren => {
|
|
230
|
+
// Displays a move in a list of best available moves
|
|
231
|
+
|
|
232
|
+
const model = view.model;
|
|
233
|
+
if (!model.game) return undefined;
|
|
234
|
+
const game = model.game;
|
|
235
|
+
const { player, co, tiles, score } = info;
|
|
236
|
+
|
|
237
|
+
function highlightMove(co: string, tiles: string, playerColor: 0 | 1, show: boolean) {
|
|
238
|
+
/* Highlight a move's tiles when hovering over it in the best move list */
|
|
239
|
+
const vec = toVector(co);
|
|
240
|
+
let col = vec.col;
|
|
241
|
+
let row = vec.row;
|
|
242
|
+
let nextBlank = false;
|
|
243
|
+
// If we're highlighting a move, show all moves leading up to it on the board
|
|
244
|
+
if (show) {
|
|
245
|
+
model.highlightedMove = bestMoveIndex;
|
|
246
|
+
game.placeTiles(moveIndex - 1, true); // No highlight
|
|
247
|
+
}
|
|
248
|
+
for (let tile of tiles) {
|
|
249
|
+
if (tile === "?") {
|
|
250
|
+
nextBlank = true;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const sq = coord(row, col);
|
|
254
|
+
if (sq === null) continue; // Should not happen
|
|
255
|
+
const letter = tile;
|
|
256
|
+
if (nextBlank)
|
|
257
|
+
tile = '?';
|
|
258
|
+
const tscore = game.tilescore(tile);
|
|
259
|
+
if (show) {
|
|
260
|
+
if (!(sq in game.tiles)) {
|
|
261
|
+
// Showing a tile that was not already on the board
|
|
262
|
+
game.tiles[sq] = {
|
|
263
|
+
player,
|
|
264
|
+
tile,
|
|
265
|
+
letter,
|
|
266
|
+
score: tscore,
|
|
267
|
+
draggable: false,
|
|
268
|
+
freshtile: false,
|
|
269
|
+
index: 0,
|
|
270
|
+
xchg: false,
|
|
271
|
+
review: true, // Mark as a 'review tile'
|
|
272
|
+
highlight: playerColor
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Highlighting a tile that was already on the board
|
|
277
|
+
game.tiles[sq].highlight = playerColor;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
col += vec.dx;
|
|
281
|
+
row += vec.dy;
|
|
282
|
+
nextBlank = false;
|
|
283
|
+
}
|
|
284
|
+
if (!show) {
|
|
285
|
+
model.highlightedMove = null;
|
|
286
|
+
model.reviewMove !== null && game.placeTiles(model.reviewMove);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Add a single move to the move list
|
|
291
|
+
// Normal tile move
|
|
292
|
+
const coParens = "(" + co + ")";
|
|
293
|
+
// Note: String.replace() will not work here since there may be two question marks in the string
|
|
294
|
+
const word = tiles.split("?").join(""); /* TBD: Display wildcard characters differently? */
|
|
295
|
+
// Normal game move
|
|
296
|
+
let title = "Smelltu til að fletta upp";
|
|
297
|
+
let playerColor: 0 | 1 = 0;
|
|
298
|
+
const lcp = game.player;
|
|
299
|
+
let cls: string;
|
|
300
|
+
if (player === lcp || (lcp === null && player === 0))
|
|
301
|
+
cls = "humangrad" + (player === 0 ? "_left" : "_right"); /* Local player */
|
|
302
|
+
else {
|
|
303
|
+
cls = "autoplayergrad" + (player === 0 ? "_left" : "_right"); /* Remote player */
|
|
304
|
+
playerColor = 1;
|
|
305
|
+
}
|
|
306
|
+
const attribs: VnodeAttrs = { title };
|
|
307
|
+
// Word lookup, if Icelandic game
|
|
308
|
+
if (game.locale === "is_IS")
|
|
309
|
+
attribs.onclick = () => { window.open('https://malid.is/leit/' + word, 'malid'); };
|
|
310
|
+
// Highlight the move on the board while hovering over it
|
|
311
|
+
attribs.onmouseover = () => {
|
|
312
|
+
move[2] = true; // highlighted
|
|
313
|
+
highlightMove(co, tiles, playerColor, true);
|
|
314
|
+
};
|
|
315
|
+
attribs.onmouseout = () => {
|
|
316
|
+
move[2] = false; // highlighted
|
|
317
|
+
highlightMove(co, tiles, playerColor, false);
|
|
318
|
+
};
|
|
319
|
+
if (player === 0) {
|
|
320
|
+
// Move by left side player
|
|
321
|
+
return m(".move.leftmove." + cls, attribs,
|
|
322
|
+
[
|
|
323
|
+
m("span.score" + (move[2] ? ".highlight" : ""), score),
|
|
324
|
+
m("span.wordmove", [m("i", word), nbsp(), coParens])
|
|
325
|
+
]
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
// Move by right side player
|
|
330
|
+
return m(".move.rightmove." + cls, attribs,
|
|
331
|
+
[
|
|
332
|
+
m("span.wordmove", [coParens, nbsp(), m("i", word)]),
|
|
333
|
+
m("span.score" + (move[2] ? ".highlight" : ""), score)
|
|
334
|
+
]
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const vwScoreReview = (view: IView, moveIndex: number): VnodeChildren => {
|
|
340
|
+
// Shows the score of the current move within a game review screen
|
|
341
|
+
const game = view.model.game;
|
|
342
|
+
if (!game) return undefined;
|
|
343
|
+
const mv = moveIndex ? game.moves[moveIndex - 1] : undefined;
|
|
344
|
+
if (mv === undefined)
|
|
345
|
+
return undefined;
|
|
346
|
+
const [_, [coord, tiles, score]] = mv;
|
|
347
|
+
if (score === undefined || (coord === "" && tiles === "OVER"))
|
|
348
|
+
// No score available, or this is a "game over" sentinel move: don't display
|
|
349
|
+
return undefined;
|
|
350
|
+
let sc = [".score"];
|
|
351
|
+
if (moveIndex > 0) {
|
|
352
|
+
if (moveIndex % 2 === (game.player ?? 0))
|
|
353
|
+
// Opponent's move
|
|
354
|
+
sc.push("opponent");
|
|
355
|
+
else
|
|
356
|
+
// Local player's move
|
|
357
|
+
sc.push("localplayer");
|
|
358
|
+
}
|
|
359
|
+
return m(sc.join("."), score.toString());
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const vwScoreDiff = (view: IView, moveIndex: number): VnodeChildren => {
|
|
363
|
+
// Shows the score of the current move within a game review screen
|
|
364
|
+
const model = view.model;
|
|
365
|
+
const game = model.game;
|
|
366
|
+
if (!game) return undefined;
|
|
367
|
+
let sc = [".scorediff"];
|
|
368
|
+
const mv = moveIndex ? game.moves[moveIndex - 1] : undefined;
|
|
369
|
+
let score = mv ? mv[1][2] : undefined;
|
|
370
|
+
let diff = "";
|
|
371
|
+
if (score === undefined || model.bestMoves === null || model.highlightedMove === null) {
|
|
372
|
+
// Unable to display score difference
|
|
373
|
+
} else {
|
|
374
|
+
const bestScore = model.bestMoves[model.highlightedMove][1][2];
|
|
375
|
+
diff = (score - bestScore).toString();
|
|
376
|
+
if (diff[0] != "-" && diff[0] != "0")
|
|
377
|
+
diff = "+" + diff;
|
|
378
|
+
if (score >= bestScore)
|
|
379
|
+
sc.push("posdiff");
|
|
380
|
+
}
|
|
381
|
+
return m(sc.join("."), { style: { visibility: "visible" } }, diff);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const vwStatsReview = (view: IView): VnodeChildren => {
|
|
385
|
+
// Shows the game statistics overlay
|
|
386
|
+
if (view.model.game === null) return undefined;
|
|
387
|
+
const game = view.model.game;
|
|
388
|
+
if (game.stats === null)
|
|
389
|
+
// No stats yet loaded: do it now
|
|
390
|
+
game.loadStats();
|
|
391
|
+
|
|
392
|
+
function fmt(p: string, digits?: number, value?: string | number): string {
|
|
393
|
+
let txt = value;
|
|
394
|
+
if (txt === undefined && game.stats)
|
|
395
|
+
txt = game.stats[p];
|
|
396
|
+
if (txt === undefined)
|
|
397
|
+
return "";
|
|
398
|
+
if (typeof txt == "number") {
|
|
399
|
+
if (digits !== undefined && digits > 0)
|
|
400
|
+
txt = txt.toFixed(digits).replace(".", ","); // Convert decimal point to comma
|
|
401
|
+
else
|
|
402
|
+
txt = txt.toString();
|
|
403
|
+
}
|
|
404
|
+
return txt;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let leftPlayerColor: string, rightPlayerColor: string;
|
|
408
|
+
|
|
409
|
+
if (game.player === 1) {
|
|
410
|
+
rightPlayerColor = "humancolor";
|
|
411
|
+
leftPlayerColor = "autoplayercolor";
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
leftPlayerColor = "humancolor";
|
|
415
|
+
rightPlayerColor = "autoplayercolor";
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return m(
|
|
419
|
+
".gamestats", { style: { visibility: "visible" } },
|
|
420
|
+
[
|
|
421
|
+
m("div", { style: { position: "relative", width: "100%" } },
|
|
422
|
+
[
|
|
423
|
+
m(".player", { class: leftPlayerColor, style: { width: "50%" } },
|
|
424
|
+
m(".robot-btn.left",
|
|
425
|
+
game.autoplayer[0] ?
|
|
426
|
+
[glyph("cog"), nbsp(), game.nickname[0]]
|
|
427
|
+
:
|
|
428
|
+
game.nickname[0]
|
|
429
|
+
)
|
|
430
|
+
),
|
|
431
|
+
m(".player", { class: rightPlayerColor, style: { width: "50%", "text-align": "right" } },
|
|
432
|
+
m(".robot-btn.right",
|
|
433
|
+
game.autoplayer[1] ?
|
|
434
|
+
[glyph("cog"), nbsp(), game.nickname[1]]
|
|
435
|
+
:
|
|
436
|
+
game.nickname[1]
|
|
437
|
+
)
|
|
438
|
+
)
|
|
439
|
+
]
|
|
440
|
+
),
|
|
441
|
+
m("div", { id: "gamestarted" },
|
|
442
|
+
[
|
|
443
|
+
m("p",
|
|
444
|
+
[
|
|
445
|
+
"Viðureignin hófst ",
|
|
446
|
+
m("span", fmt("gamestart")), m("br"),
|
|
447
|
+
"og henni lauk ",
|
|
448
|
+
m("span", fmt("gameend"))
|
|
449
|
+
]
|
|
450
|
+
),
|
|
451
|
+
game.manual ? m("p", "Leikið var í keppnisham") : ""
|
|
452
|
+
]
|
|
453
|
+
),
|
|
454
|
+
m(".statscol", { style: { clear: "left" } },
|
|
455
|
+
[
|
|
456
|
+
m("p",
|
|
457
|
+
["Fjöldi leikja: ", m("span", fmt("moves0"))]
|
|
458
|
+
),
|
|
459
|
+
m("p",
|
|
460
|
+
[
|
|
461
|
+
"Fjöldi bingóa: ", m("span", fmt("bingoes0")),
|
|
462
|
+
" (bónus ",
|
|
463
|
+
m(
|
|
464
|
+
"span",
|
|
465
|
+
fmt("bingopoints0", 0, !game.stats ? 0 : game.stats.bingoes0 * 50)
|
|
466
|
+
),
|
|
467
|
+
" stig)"
|
|
468
|
+
]
|
|
469
|
+
),
|
|
470
|
+
m("p",
|
|
471
|
+
[
|
|
472
|
+
"Stafir lagðir niður: ", m("span", fmt("tiles0")),
|
|
473
|
+
" (þar af ", m("span", fmt("blanks0")), " auðir)"
|
|
474
|
+
]
|
|
475
|
+
),
|
|
476
|
+
m("p", ["Meðalstig stafa (án auðra): ", m("span", fmt("average0", 2))]),
|
|
477
|
+
m("p", ["Samanlögð stafastig: ", m("span", fmt("letterscore0"))]),
|
|
478
|
+
m("p", ["Margföldun stafastiga: ", m("span", fmt("multiple0", 2))]),
|
|
479
|
+
m("p", ["Stig án stafaleifar í lok: ", m("span", fmt("cleantotal0"))]),
|
|
480
|
+
m("p", ["Meðalstig hvers leiks: ", m("span", fmt("avgmove0", 2))]),
|
|
481
|
+
game.manual ? m("p", ["Rangar véfengingar andstæðings x 10: ", m("span", fmt("wrongchall0"))]) : "",
|
|
482
|
+
m("p", ["Stafaleif og frádráttur í lok: ", m("span", fmt("remaining0"))]),
|
|
483
|
+
m("p", ["Umframtími: ", m("span", fmt("overtime0"))]),
|
|
484
|
+
m("p",
|
|
485
|
+
[
|
|
486
|
+
"Stig: ",
|
|
487
|
+
m(
|
|
488
|
+
"span",
|
|
489
|
+
fmt("total0", 0, !game.stats ? 0 : game.stats.scores[0])
|
|
490
|
+
),
|
|
491
|
+
" (", m("span", fmt("ratio0", 1)), "%)"
|
|
492
|
+
]
|
|
493
|
+
)
|
|
494
|
+
]
|
|
495
|
+
),
|
|
496
|
+
m(".statscol",
|
|
497
|
+
[
|
|
498
|
+
m("p",
|
|
499
|
+
["Fjöldi leikja: ", m("span", fmt("moves1"))]
|
|
500
|
+
),
|
|
501
|
+
m("p",
|
|
502
|
+
[
|
|
503
|
+
"Fjöldi bingóa: ", m("span", fmt("bingoes1")),
|
|
504
|
+
" (bónus ",
|
|
505
|
+
m(
|
|
506
|
+
"span",
|
|
507
|
+
fmt("bingopoints0", 0, !game.stats ? 0 : game.stats.bingoes1 * 50)
|
|
508
|
+
),
|
|
509
|
+
" stig)"
|
|
510
|
+
]
|
|
511
|
+
),
|
|
512
|
+
m("p",
|
|
513
|
+
[
|
|
514
|
+
"Stafir lagðir niður: ", m("span", fmt("tiles1")),
|
|
515
|
+
" (þar af ", m("span", fmt("blanks1")), " auðir)"
|
|
516
|
+
]
|
|
517
|
+
),
|
|
518
|
+
m("p", ["Meðalstig stafa (án auðra): ", m("span", fmt("average1", 2))]),
|
|
519
|
+
m("p", ["Samanlögð stafastig: ", m("span", fmt("letterscore1"))]),
|
|
520
|
+
m("p", ["Margföldun stafastiga: ", m("span", fmt("multiple1", 2))]),
|
|
521
|
+
m("p", ["Stig án stafaleifar í lok: ", m("span", fmt("cleantotal1"))]),
|
|
522
|
+
m("p", ["Meðalstig hvers leiks: ", m("span", fmt("avgmove1", 2))]),
|
|
523
|
+
game.manual ? m("p", ["Rangar véfengingar andstæðings x 10: ", m("span", fmt("wrongchall1"))]) : "",
|
|
524
|
+
m("p", ["Stafaleif og frádráttur í lok: ", m("span", fmt("remaining1"))]),
|
|
525
|
+
m("p", ["Umframtími: ", m("span", fmt("overtime1"))]),
|
|
526
|
+
m("p",
|
|
527
|
+
[
|
|
528
|
+
"Stig: ",
|
|
529
|
+
m(
|
|
530
|
+
"span",
|
|
531
|
+
fmt("total1", 0, !game.stats ? 0 : game.stats.scores[1])
|
|
532
|
+
),
|
|
533
|
+
" (", m("span", fmt("ratio1", 1)), "%)"
|
|
534
|
+
]
|
|
535
|
+
)
|
|
536
|
+
]
|
|
537
|
+
),
|
|
538
|
+
m(".closebtn",
|
|
539
|
+
{
|
|
540
|
+
id: "review-close",
|
|
541
|
+
onclick: (ev: Event) => {
|
|
542
|
+
// Navigate to move #1
|
|
543
|
+
setTimeout(() => {
|
|
544
|
+
m.route.set("/review/" + game.uuid, { move: 1 });
|
|
545
|
+
});
|
|
546
|
+
ev.preventDefault();
|
|
547
|
+
},
|
|
548
|
+
onmouseover: buttonOver,
|
|
549
|
+
onmouseout: buttonOut
|
|
550
|
+
},
|
|
551
|
+
[glyph("play"), " Rekja"]
|
|
552
|
+
)
|
|
553
|
+
]
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const vwButtonsReview = (view: IView, moveIndex: number): VnodeChildren => {
|
|
558
|
+
// The navigation buttons below the board on the review screen
|
|
559
|
+
const model = view.model;
|
|
560
|
+
const game = model.game;
|
|
561
|
+
const numMoves = game?.moves.length ?? 0;
|
|
562
|
+
const gameUuid = game?.uuid ?? "";
|
|
563
|
+
let r: VnodeChildren = [];
|
|
564
|
+
if (!gameUuid) return r;
|
|
565
|
+
r.push(
|
|
566
|
+
makeButton(
|
|
567
|
+
"navbtn", !moveIndex, // Disabled if at moveIndex 0 (initial review dialog)
|
|
568
|
+
() => {
|
|
569
|
+
// Navigate to previous moveIndex
|
|
570
|
+
model.loadBestMoves(moveIndex ? moveIndex - 1 : 0);
|
|
571
|
+
},
|
|
572
|
+
"Sjá fyrri leik",
|
|
573
|
+
m("span",
|
|
574
|
+
{ id: "nav-prev-visible" },
|
|
575
|
+
[glyph("chevron-left"), " Fyrri"]
|
|
576
|
+
),
|
|
577
|
+
"navprev"
|
|
578
|
+
)
|
|
579
|
+
);
|
|
580
|
+
r.push(
|
|
581
|
+
makeButton(
|
|
582
|
+
"navbtn", (!moveIndex) || (moveIndex >= numMoves),
|
|
583
|
+
() => {
|
|
584
|
+
// Navigate to next moveIndex
|
|
585
|
+
model.loadBestMoves(moveIndex + 1);
|
|
586
|
+
},
|
|
587
|
+
"Sjá næsta leik",
|
|
588
|
+
m("span",
|
|
589
|
+
{ id: "nav-next-visible" },
|
|
590
|
+
["Næsti ", glyph("chevron-right")]
|
|
591
|
+
),
|
|
592
|
+
"navnext"
|
|
593
|
+
)
|
|
594
|
+
);
|
|
595
|
+
// Show the score difference between an actual moveIndex and
|
|
596
|
+
// a particular moveIndex on the best moveIndex list
|
|
597
|
+
if (model.highlightedMove !== null) {
|
|
598
|
+
const n = vwScoreDiff(view, moveIndex);
|
|
599
|
+
if (n !== undefined)
|
|
600
|
+
r.push(n);
|
|
601
|
+
}
|
|
602
|
+
const n = vwScoreReview(view, moveIndex);
|
|
603
|
+
if (n !== undefined)
|
|
604
|
+
r.push(n);
|
|
605
|
+
return r;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
interface IBoardReviewAttributes {
|
|
609
|
+
view: IView;
|
|
610
|
+
moveIndex: number;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export const BoardReview: ComponentFunc<IBoardReviewAttributes> = (initialVnode) => {
|
|
614
|
+
// The board area within a game review screen
|
|
615
|
+
const view = initialVnode.attrs.view;
|
|
616
|
+
const model = view.model;
|
|
617
|
+
return {
|
|
618
|
+
view: (vnode) => {
|
|
619
|
+
const game = model.game;
|
|
620
|
+
let r: VnodeChildren = [];
|
|
621
|
+
if (game) {
|
|
622
|
+
r = [
|
|
623
|
+
m(Board, { view, review: true }),
|
|
624
|
+
m(Rack, { view, review: true }),
|
|
625
|
+
];
|
|
626
|
+
const moveIndex = vnode.attrs.moveIndex;
|
|
627
|
+
if (moveIndex !== null)
|
|
628
|
+
// Don't show navigation buttons if currently at overview (move==null)
|
|
629
|
+
r = r.concat(vwButtonsReview(view, moveIndex));
|
|
630
|
+
}
|
|
631
|
+
return m(".board-area", r);
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
}
|