Skip to content

Commit 64b7e5d

Browse files
committed
Yavalath: 3 player ready for live testing
1 parent 4f77904 commit 64b7e5d

File tree

2 files changed

+88
-20
lines changed

2 files changed

+88
-20
lines changed

locales/en/apgames.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4589,6 +4589,7 @@
45894589
"TOO_MANY_PIECES": "There are too many pieces at {{where}} to add {{num}} more."
45904590
},
45914591
"yavalath": {
4592+
"BAD_PASS": "You may only pass if you have been eliminated.",
45924593
"INITIAL_INSTRUCTIONS": "Click an empty cell to place a piece."
45934594
},
45944595
"zola": {

src/games/yavalath.ts

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import { HexTriGraph, reviver, UserFacingError } from "../common";
66
import type { HexDir } from "../common/graphs/hextri";
77
import i18next from "i18next";
88

9-
export type playerid = 1|2;
9+
export type playerid = 1|2|3;
1010

1111
export interface IMoveState extends IIndividualState {
1212
currplayer: playerid;
1313
board: Map<string, playerid>;
1414
lastmove?: string;
15+
eliminated?: playerid;
1516
};
1617

1718
export interface IYavalathState extends IAPGameState {
@@ -23,7 +24,7 @@ export class YavalathGame extends GameBase {
2324
public static readonly gameinfo: APGamesInformation = {
2425
name: "Yavalath",
2526
uid: "yavalath",
26-
playercounts: [2],
27+
playercounts: [2,3],
2728
version: "20250112",
2829
dateAdded: "2025-01-12",
2930
// i18next.t("apgames:descriptions.yavalath")
@@ -48,10 +49,12 @@ export class YavalathGame extends GameBase {
4849
public variants: string[] = [];
4950
public stack!: Array<IMoveState>;
5051
public results: Array<APMoveResult> = [];
52+
public eliminated?: playerid;
5153

52-
constructor(state?: IYavalathState | string) {
54+
constructor(state: IYavalathState | string | number) {
5355
super();
54-
if (state === undefined) {
56+
if (typeof state === "number") {
57+
this.numplayers = state;
5558
const board = new Map<string, playerid>();
5659
const fresh: IMoveState = {
5760
_version: YavalathGame.gameinfo.version,
@@ -68,6 +71,7 @@ export class YavalathGame extends GameBase {
6871
if (state.game !== YavalathGame.gameinfo.uid) {
6972
throw new Error(`The Yavalath engine cannot process a game of '${state.game}'.`);
7073
}
74+
this.numplayers = state.numplayers;
7175
this.gameover = state.gameover;
7276
this.winner = [...state.winner];
7377
this.variants = state.variants;
@@ -88,12 +92,17 @@ export class YavalathGame extends GameBase {
8892
this.currplayer = state.currplayer;
8993
this.board = new Map(state.board);
9094
this.lastmove = state.lastmove;
95+
this.eliminated = state.eliminated;
9196
return this;
9297
}
9398

9499
public moves(): string[] {
95100
if (this.gameover) { return []; }
96101

102+
if (this.eliminated !== undefined && this.eliminated === this.currplayer) {
103+
return ["pass"];
104+
}
105+
97106
const g = new HexTriGraph(5, 9);
98107
const moves = g.graph.nodes().filter(n => !this.board.has(n));
99108
return moves.sort((a,b) => a.localeCompare(b));
@@ -139,6 +148,20 @@ export class YavalathGame extends GameBase {
139148
return result;
140149
}
141150

151+
// pass only valid if you're eliminated
152+
if (m === "pass") {
153+
if (this.eliminated !== undefined && this.eliminated === this.currplayer) {
154+
result.valid = true;
155+
result.complete = 1;
156+
result.message = i18next.t("apgames:validation._general.VALID_MOVE");
157+
return result;
158+
} else {
159+
result.valid = false;
160+
result.message = i18next.t("apgames:validation.yavalath.BAD_PASS", {where: m});
161+
return result;
162+
}
163+
}
164+
142165
// cell must exist
143166
if (!g.graph.nodes().includes(m)) {
144167
result.valid = false;
@@ -177,15 +200,26 @@ export class YavalathGame extends GameBase {
177200
}
178201

179202
this.results = [];
180-
this.board.set(m, this.currplayer);
181-
this.results.push({type: "place", where: m});
203+
204+
if (m === "pass") {
205+
this.results.push({type: "pass"});
206+
} else {
207+
this.board.set(m, this.currplayer);
208+
this.results.push({type: "place", where: m});
209+
}
182210

183211
// update currplayer
184212
this.lastmove = m;
185213
let newplayer = (this.currplayer as number) + 1;
186214
if (newplayer > this.numplayers) {
187215
newplayer = 1;
188216
}
217+
while (this.eliminated !== undefined && this.eliminated === newplayer) {
218+
newplayer = (newplayer as number) + 1;
219+
if (newplayer > this.numplayers) {
220+
newplayer = 1;
221+
}
222+
}
189223
this.currplayer = newplayer as playerid;
190224

191225
this.checkEOG();
@@ -227,25 +261,52 @@ export class YavalathGame extends GameBase {
227261
}
228262

229263
protected checkEOG(): YavalathGame {
230-
const prevPlayer = this.currplayer === 1 ? 2 : 1;
231-
const hasFour = this.checkLines(4, prevPlayer);
232-
const hasThree = this.checkLines(3, prevPlayer);
233-
const g = new HexTriGraph(5, 9);
264+
let prevPlayer: playerid;
265+
if (this.numplayers === 2) {
266+
prevPlayer = this.currplayer === 1 ? 2 : 1;
267+
} else if (this.eliminated === undefined) {
268+
prevPlayer = this.currplayer === 1 ? 3 : this.currplayer === 3 ? 2 : 1;
269+
} else {
270+
const remaining = ([1,2,3] as playerid[]).filter(p => p !== this.eliminated);
271+
prevPlayer = this.currplayer === remaining[0] ? remaining[1] : remaining[0];
272+
}
234273

235-
// if previous player has four, they win
274+
// regardless of number of players, four in a row is a win
275+
const hasFour = this.checkLines(4, prevPlayer);
236276
if (hasFour) {
237277
this.gameover = true;
238278
this.winner = [prevPlayer];
239279
}
240-
// if previous player has three, they lose
241-
else if (hasThree) {
242-
this.gameover = true;
243-
this.winner = [this.currplayer];
280+
281+
if (!this.gameover) {
282+
// if they have a three, then results vary
283+
const hasThree = this.checkLines(3, prevPlayer);
284+
if (hasThree) {
285+
if (this.numplayers === 2) {
286+
this.gameover = true;
287+
this.winner = [this.currplayer];
288+
} else if (this.eliminated === undefined) {
289+
this.eliminated = prevPlayer;
290+
} else {
291+
this.gameover = true;
292+
this.winner = ([1,2,3] as playerid[]).filter(p => p !== this.eliminated && p !== prevPlayer);
293+
}
294+
}
244295
}
245-
// if board full, draw
246-
else if (g.graph.nodes().filter(n => !this.board.has(n)).length === 0) {
247-
this.gameover = true;
248-
this.winner = [1, 2];
296+
297+
// regardless of number of players, a full board is a draw
298+
if (!this.gameover) {
299+
const g = new HexTriGraph(5, 9);
300+
if (g.graph.nodes().filter(n => !this.board.has(n)).length === 0) {
301+
this.gameover = true;
302+
if (this.numplayers === 2) {
303+
this.winner = [1, 2];
304+
} else if (this.eliminated === undefined) {
305+
this.winner = [1, 2, 3];
306+
} else {
307+
this.winner = ([1,2,3] as playerid[]).filter(p => p !== this.eliminated);
308+
}
309+
}
249310
}
250311

251312
if (this.gameover) {
@@ -276,6 +337,7 @@ export class YavalathGame extends GameBase {
276337
currplayer: this.currplayer,
277338
lastmove: this.lastmove,
278339
board: new Map(this.board),
340+
eliminated: this.eliminated,
279341
};
280342
}
281343

@@ -292,7 +354,8 @@ export class YavalathGame extends GameBase {
292354
if (!this.board.has(cell)) {
293355
pieces.push("-");
294356
} else {
295-
pieces.push(this.board.get(cell)! === 1 ? "A" : "B");
357+
const contents = this.board.get(cell)!;
358+
pieces.push(contents === 1 ? "A" : contents === 2 ? "B" : "C");
296359
}
297360
}
298361
pstr += pieces.join("");
@@ -313,6 +376,10 @@ export class YavalathGame extends GameBase {
313376
B: {
314377
name: "piece",
315378
colour: 2
379+
},
380+
C: {
381+
name: "piece",
382+
colour: 3
316383
}
317384
},
318385
pieces: pstr

0 commit comments

Comments
 (0)