Skip to content

Commit 1b3160e

Browse files
committed
Omny: Add Yex and random setups
1 parent 9bea83f commit 1b3160e

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

locales/en/apgames.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,22 @@
11041104
"name": "Gyre",
11051105
"description": "Stars around the perimeter in in the centre"
11061106
},
1107+
"yex": {
1108+
"name": "Yex",
1109+
"description": "Stars in alternating corners"
1110+
},
1111+
"random-3": {
1112+
"name": "Random placement: 3 stars"
1113+
},
1114+
"random-5": {
1115+
"name": "Random placement: 5 stars"
1116+
},
1117+
"random-7": {
1118+
"name": "Random placement: 7 stars"
1119+
},
1120+
"random-9": {
1121+
"name": "Random placement: 9 stars"
1122+
},
11071123
"free": {
11081124
"name": "Free placement",
11091125
"description": "Intended for players who communicate expectations before the game. Each player will be able to place as many stars as they wish."

src/games/omny.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { GameBase, IAPGameState, IClickResult, IIndividualState, IRenderOpts, IV
44
import { APGamesInformation } from "../schemas/gameinfo";
55
import { APRenderRep, MarkerDots, MarkerGlyph } from "@abstractplay/renderer/src/schemas/schema";
66
import { APMoveResult } from "../schemas/moveresults";
7-
import { reviver, UserFacingError } from "../common";
7+
import { reviver, shuffle, UserFacingError } from "../common";
88
import { connectedComponents } from 'graphology-components';
99
import i18next from "i18next";
1010
import { HexTriGraph } from "../common/graphs";
@@ -24,8 +24,38 @@ interface IMoveState extends IIndividualState {
2424
export interface IOmnyState extends IAPGameState {
2525
winner: playerid[];
2626
stack: Array<IMoveState>;
27+
startStars?: string[];
2728
};
2829

30+
// ensures random stars have a certain minimum distance from each other
31+
const starsValid = (g: HexTriGraph, stars: string[]): boolean => {
32+
for (const star of stars) {
33+
const [sx, sy] = g.algebraic2coords(star);
34+
// immediate neighbours
35+
const n1 = new Set<string>(g.neighbours(star));
36+
// neighbours of neighbours
37+
const n2 = new Set<string>();
38+
[...n1].forEach(cell => g.neighbours(cell).forEach(n => n2.add(n)));
39+
// combined
40+
const alln = new Set<string>([...n1, ...n2]);
41+
// delete starting cell
42+
alln.delete(star);
43+
// delete each cell at straight-line distance 2 away
44+
for (const dir of HexTriGraph.directions) {
45+
const ray = g.ray(sx, sy, dir).map(c => g.coords2algebraic(...c));
46+
if (ray.length >= 2) {
47+
alln.delete(ray[1]);
48+
}
49+
}
50+
// if any of the stars appear in this set, it's invalid
51+
if (stars.filter(s => alln.has(s)).length > 0) {
52+
return false;
53+
}
54+
}
55+
// if we get to this point, it's valid
56+
return true;
57+
}
58+
2959
export class OmnyGame extends GameBase {
3060
public static readonly gameinfo: APGamesInformation = {
3161
name: "Omny",
@@ -50,7 +80,12 @@ export class OmnyGame extends GameBase {
5080
{uid: "size-10", group: "board"},
5181
{uid: "constellation", group: "stars"},
5282
{uid: "gyre", group: "stars"},
83+
{uid: "yex", group: "stars"},
5384
{uid: "free", group: "stars"},
85+
{uid: "random-3", group: "stars"},
86+
{uid: "random-5", group: "stars"},
87+
{uid: "random-7", group: "stars"},
88+
{uid: "random-9", group: "stars"},
5489
{uid: "captures"},
5590
],
5691
categories: ["goal>connect", "mechanic>place", "mechanic>stack", "mechanic>move", "mechanic>coopt", "board>shape>hex", "board>connect>hex", "components>simple>1per"],
@@ -66,6 +101,7 @@ export class OmnyGame extends GameBase {
66101
public results: Array<APMoveResult> = [];
67102
public variants: string[] = [];
68103
private tmpstars = new Set<string>();
104+
public startStars?: string[];
69105

70106
constructor(state?: IOmnyState | string, variants?: string[]) {
71107
super();
@@ -80,10 +116,28 @@ export class OmnyGame extends GameBase {
80116
this.variants = [...state.variants];
81117
this.winner = [...state.winner];
82118
this.stack = [...state.stack];
119+
if (state.startStars !== undefined) {
120+
this.startStars = [...state.startStars];
121+
}
83122
} else {
84123
if ( (variants !== undefined) && (variants.length > 0) ) {
85124
this.variants = [...variants];
86125
}
126+
127+
const found = this.variants.find(v => v.startsWith("random"));
128+
if (found !== undefined) {
129+
const [,numStr] = found.split("-");
130+
const num = parseInt(numStr, 10);
131+
const g = this.graph;
132+
let shuffled = shuffle(g.graph.nodes()) as string[];
133+
let stars = shuffled.slice(0, num);
134+
while (!starsValid(g, stars)) {
135+
shuffled = shuffle(g.graph.nodes()) as string[];
136+
stars = shuffled.slice(0, num);
137+
}
138+
this.startStars = [...stars];
139+
}
140+
87141
const board = new Map<string,playerid[]>();
88142
const fresh: IMoveState = {
89143
_version: OmnyGame.gameinfo.version,
@@ -166,6 +220,16 @@ export class OmnyGame extends GameBase {
166220
[...g.getEdges().values()].forEach(edge => edge.forEach(cell => set.add(cell)));
167221
set.add(g.coords2algebraic(size - 1, size - 1));
168222
}
223+
// alternating corners
224+
else if (this.variants.includes("yex")) {
225+
set.add(g.coords2algebraic(0, 0));
226+
set.add(g.coords2algebraic((size * 2) - 2, size - 1));
227+
set.add(g.coords2algebraic(0, (size * 2) - 2));
228+
}
229+
// random start
230+
else if (this.startStars !== undefined) {
231+
this.startStars.forEach(cell => set.add(cell));
232+
}
169233
// default Sunder (all cells)
170234
else {
171235
g.graph.nodes().forEach(cell => set.add(cell));
@@ -585,6 +649,7 @@ export class OmnyGame extends GameBase {
585649
gameover: this.gameover,
586650
winner: [...this.winner],
587651
stack: [...this.stack],
652+
startStars: this.startStars !== undefined ? [...this.startStars] : undefined,
588653
};
589654
}
590655

0 commit comments

Comments
 (0)