Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions locales/en/apgames.json
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@
"pacru": "This implementation adheres to the 2011 rule change that requires at least one opponent to have at least nine tiles on the board before meetings will trigger.",
"pigs": "Unlike the old Super Duper Games implementation, this one implements the core rule set. Each player enters all five moves, and they are resolved at once.\n\nMovement is resolved before damage is applied.",
"pigs2": "This is the same as the old Super Duper Games. The game starts by you programming three moves, but only one move is resolved and added each turn.\n\nMovement is resolved before damage is applied. Unlike most other AP games, if players resign or timeout, the game does not immediately end. Instead, they're eliminated and their robot becomes inactive, but the other players can play on.",
"pinch": "Pinch is a 2026 design of an original 2019 concept, propose a new perspective to the orthogonal connection genre. At its core is the **pinch capture**: when two friendly stones flank an enemy piece at a right angle, the captured stone is relocated rather than removed. This mechanic can trigger a cascade of further relocations, creating a chain-reaction that expands one local capture into changes over multiple sections of the board.",
"pinch": "Pinch is a 2026 design of an original 2019 concept, proposing a new perspective to the orthogonal connection genre. At its core is the **pinch capture**: when two friendly stones flank an enemy piece at a right angle, the captured stone is relocated rather than removed. This mechanic can trigger a cascade of further relocations, creating a chain-reaction that expands one local capture into changes over multiple sections of the board.",
"pletore": "An intersection is controlled by a player if they 'pinch' it while the opponent does not. You 'pinch' an intersection if you have pieces that can see that intersection from two different axes. Your score is the sum of the number of stones of your color on the board and the number of empty points you control, plus a komi bonus and a button bonus. The button is used when the komi value plus the number of intersections on the board is even, and any player may take a turn off and claim the button if it has yet to be claimed. The game ends when both players pass in succession, but claiming the button does not count toward this. Players cannot pass until the entire board is occupied or controlled.",
"plurality": "Plurality is a finite territorial game. The concept of territory exist disconnected from concepts like groups or liberty. Territories' ownership depend on the most represented color in their perimeters. On their turn, players drop two friendly stones and one adversary stone as a single orthogonally connected group (a tromino). It is forbidden to make a 2x2 shape of stones of any color configuration.",
"queensland": "After the first game completes, the board resets and you play a second game but with blue playing first. Highest combined score wins.",
Expand All @@ -293,7 +293,7 @@
"squirm": "Squirm is one of the first snake/serpent/root games, where groups are defined with at most two friendly connections per stone. But instead of being a territorial or stalemate-oriented game, Squirm is a scoring game, where players need to build the longest possible snake-like group (following the original ruleset), or adding has much points as possible (following either available variant).",
"stawvs": "For clarity, the game pieces are represented as circles, though in real life, caps (small pyramids of distinct player colors) are usually used. The UI will sort and score your trees (trios consisting of one of each pyramid size) for you as in Mega-Volcano, but for reference the point values are: 7 points for each monochrome tree, 5 points for multicolor trees, and 1 point each for loose pyramids. Except in the variant where final pyramids aren't collected, the pyramids under player pieces are added to the players' collections and scored, but greyed-out pyramids remain on the board under the pieces to clarify the final position.",
"stigmergy": "You control a space if more than half of the lines of sight to it end at a piece of your color. Your score is the sum of the number of pieces of your color on the board and the number of empty spaces you control, plus a komi bonus and a button bonus. The button is used when the komi value plus the number of spaces on the board is even, and any player may take a turn off and claim the button if it has yet to be claimed. The game ends when both players pass in succession, but claiming the button does not count toward this. Players cannot pass until the entire board is occupied or controlled.",
"stiletto": "A dagger represents an option for a double move. Stiletto is a 2003 game from the family of Gomoku, where players try to make a five in-a-row. The main difference is that the second player (traditionally White) starts with a dagger. This allows to compensate for Black's strong initial advantage. When players use the dagger, they pass it to the adversary.\n\nIn terms of 'power' available to players, this variant lies between Gomoku and Connect6. The fact that the power to play two stones in not shared, adds tension to the gameplay. The player that has the dagger wants to use it, but he decides to wait for a proper time. It is true that each time he delays using the dagger, there's a toll on the adversary: _the threat is stronger than the execution_, as the old Chess grandmasters said. The game also includes Ko fights, that emerge from the restriction of consecutive dagger uses.\n\n_Stiletto_, from the Italian, is a small, slender dagger with a long, narrow blade designed primarily for thrusting.",
"stiletto": "A dagger represents an option for a double move. Stiletto is a 2003 game from the family of Gomoku, where players try to make a five in-a-row. The main difference is that the second player (traditionally White) starts with a dagger. This allows to compensate for Black's strong initial advantage. When players use the dagger, they pass it to the adversary.\n\nIn terms of 'power' available to players, this variant lies between Gomoku and Connect6. The fact that the power to play two stones in not shared, adds tension to the gameplay.\n\nThe **Open-4** variant makes it illegal to create an open-4 with both dagger stones, which removes part of the dagger's power.\n\n_Stiletto_, from the Italian, is a small, slender dagger with a long, narrow blade designed primarily for thrusting.",
"tablero": "When it's your turn, you will see the dice you have to work with, but once your move is complete, the dice will reroll. Exploration is not helpful because the dice roll is not finalized until after the move is submitted. As you scroll back through the game history, the dice you see are the dice for the *next* turn. The dice used to make the move you're seeing are displayed below the board.\n\nWhile most moves can be unambiguously made by simple clicks, sometimes a button is more helpful. The Place, Take, and Bump buttons to the left of the board are there if you need them.",
"tafl": "The variant names are in the format {ruleset}-{board size}-{initial layout}-{optional: starting player}. For example, 'linnaean-9x9-tcross-w' is the linnaean rules on a 9x9 board with T-cross setup, and the starting player is the defenders. If starting player is not mentioned, then attackers start.",
"taiji": "Moves are done with two clicks. The first tile you place is always the light one, and then the dark one.",
Expand Down Expand Up @@ -2590,6 +2590,10 @@
"stiletto": {
"#board": {
"name": "19x19 board"
},
"open4": {
"description": "Cannot use both stones to form an open-4.",
"name": "Open-4 restriction"
}
},
"stigmergy": {
Expand Down Expand Up @@ -5884,7 +5888,8 @@
"INSTRUCTIONS_INACTIVE_DAGGER": "Place a piece onto an empty space. You have the dagger but it is inactive, since you used it last turn and there is no immediate losing threat.",
"EXCESS": "You may only place two stones if you have an active dagger. Place just one stone.",
"EXCESS_FIRST": "You have placed too many stones. At the start you may only place one stone as the first player.",
"EXTENSION": "It is illegal to use the dagger to extend three friendly stones, in the same line, to make a 5 in-a-row."
"EXTENSION": "It is illegal to use the dagger to extend three friendly stones, in the same line, to make a 5 in-a-row.",
"ILLEGAL_OPEN4": "It is illegal to use the dagger to create an open-4 with both stones."
},
"stigmergy": {
"INITIAL_INSTRUCTIONS": "Place a piece onto an empty space not controlled by the opponent, replace an enemy piece on a space you control, or 'pass' if all spaces are occupied or controlled. The game ends when both players pass in sequence.",
Expand Down
1 change: 1 addition & 0 deletions src/games/domineering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class DomineeringGame extends GameBase {
dateAdded: "2026-04-25",
// i18next.t("apgames:descriptions.domineering")
description: "apgames:descriptions.domineering",
notes: "apgames:notes.domineering",
urls: [
"https://boardgamegeek.com/boardgame/7450/stop-gate",
"https://jpneto.github.io/world_abstract_games/modern_rules/2025_Quelhas.pdf"
Expand Down
1 change: 1 addition & 0 deletions src/games/pinch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class PinchGame extends GameBase {
dateAdded: "2026-04-23",
// i18next.t("apgames:descriptions.pinch")
description: "apgames:descriptions.pinch",
notes: "apgames:notes.pinch",
urls: [
"https://boardgamegeek.com/boardgame/285214/pinch",
],
Expand Down
1 change: 1 addition & 0 deletions src/games/plurality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class PluralityGame extends GameBase {
dateAdded: "2026-02-18",
// i18next.t("apgames:descriptions.plurality")
description: "apgames:descriptions.plurality",
notes: "apgames:notes.plurality",
urls: ["https://boardgamegeek.com/boardgame/462846/plurality"],
people: [
{
Expand Down
2 changes: 1 addition & 1 deletion src/games/sentinel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class SentinelGame extends GameBase {
version: "20260328",
dateAdded: "2026-04-22",
description: "apgames:descriptions.sentinel",
// notes: "apgames:notes.sentinel",
notes: "apgames:notes.sentinel",
urls: [
"https://boardgamegeek.com/thread/3651706/rules-of-sentinel",
],
Expand Down
1 change: 1 addition & 0 deletions src/games/spora.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class SporaGame extends GameBase {
dateAdded: "2026-04-07",
// i18next.t("apgames:descriptions.spora")
description: "apgames:descriptions.spora",
notes: "apgames:notes.spora",
urls: [
"https://boardgamegeek.com/thread/3493284/rules-of-spora"
],
Expand Down
3 changes: 2 additions & 1 deletion src/games/squirm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class SquirmGame extends GameBase {
dateAdded: "2026-04-22",
// i18next.t("apgames:descriptions.squirm")
description: "apgames:descriptions.squirm",
notes: "apgames:notes.squirm",
urls: ["https://jpneto.github.io/world_abstract_games/squirm.htm"],
people: [
{
Expand All @@ -50,7 +51,7 @@ export class SquirmGame extends GameBase {
{ uid: "tromp", group: "ruleset" },
{ uid: "neto", group: "ruleset" },
],
flags: ["pie", "custom-buttons"]
flags: ["pie", "custom-buttons", "automove"]
};

public numplayers = 2;
Expand Down
65 changes: 65 additions & 0 deletions src/games/stiletto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class StilettoGame extends InARowBase {
dateAdded: "2026-03-07",
// i18next.t("apgames:descriptions.Stiletto")
description: "apgames:descriptions.stiletto",
notes: "apgames:notes.stiletto",
urls: [
"https://boardgamegeek.com/boardgame/465550/stiletto",
"https://jpneto.github.io/world_abstract_games/dagger_gomoku.htm",
Expand All @@ -56,6 +57,9 @@ export class StilettoGame extends InARowBase {
},
],
categories: ["goal>arrange", "mechanic>place", "board>shape>rect", "board>connect>rect", "components>simple>2c"],
variants: [
{ uid: "open4", group: "ruleset" },
],
flags: ["no-moves", "custom-colours"],
};

Expand All @@ -81,6 +85,7 @@ export class StilettoGame extends InARowBase {
public variants: string[] = [];
public lastDaggerUse = [0, -1]; // last #turn each player used the dagger
public swapped = false; // abstract attribute of InARowBase
private ruleset: "default" | "open4";

constructor(state?: IStilettoState | string, variants?: string[]) {
super();
Expand Down Expand Up @@ -112,6 +117,7 @@ export class StilettoGame extends InARowBase {
this.stack = [...state.stack];
}
this.load();
this.ruleset = this.getRuleset();
}

public load(idx = -1): StilettoGame {
Expand All @@ -137,6 +143,12 @@ export class StilettoGame extends InARowBase {
return this;
}

private getRuleset(): "default" | "open4" {
if (this.variants.includes("open4")) { return "open4"; }
return "default";
}


private currentTurn(): number {
return this.stack.length;
}
Expand Down Expand Up @@ -212,6 +224,53 @@ export class StilettoGame extends InARowBase {
return false;
}

private empty(xe: number, ye: number): boolean {
if ( this.board.has(this.coords2algebraic(xe, ye)) ) { return false; }
if ( xe < 0 || ye < 0 || xe >= this.boardSize || ye >= this.boardSize ) { return false; }
return true;
}

public checkIllegalOpen4(moves : string[]): boolean {
const [move1, move2] = moves;
const [x1, y1] = this.algebraic2coords(move1);
const [x2, y2] = this.algebraic2coords(move2);

this.board.set(move1, this.currplayer); // temporary add stones at moves1/moves2
this.board.set(move2, this.currplayer);

// search open-4 in all four directions (orthogonal || diagonal)
// we use move1 as a pivot, and try to find move2 in the line
let foundOpen4 = false;
let foundMove2 = false;
for (const [dx, dy] of [[1, 0], [0, 1], [1, 1], [1, -1]]) {
let count = 1; // move1 counted already
let x = x1 + dx;
let y = y1 + dy;
while (this.board.get(this.coords2algebraic(x, y)) === this.currplayer) {
if ( x === x2 && y === y2 ) { foundMove2 = true; }
count++; x += dx; y += dy; // count friendly friends in current direction
}
const end1 = [x, y]; // the next square in that direction

x = x1 - dx; // move backwards now
y = x2 - dy;
while (this.board.get(this.coords2algebraic(x, y)) === this.currplayer) {
if ( x === x2 && y === y2 ) { foundMove2 = true; }
count++; x -= dx; y -= dy; // count friendly friends in opposite direction
}
const end2 = [x, y]; // the next square in that direction

if ( count === 4 && this.empty(end1[0], end1[1]) && this.empty(end2[0], end2[1]) ) {
foundOpen4 = true;
break;
}
}

this.board.delete(move1); // don't forget to remove stones
this.board.delete(move2);
return foundOpen4 && foundMove2;
}

private shuffle(xs: string[]): void {
// Fisher-Yates Shuffle
for (let i = xs.length - 1; i > 0; i--) {
Expand Down Expand Up @@ -413,6 +472,12 @@ export class StilettoGame extends InARowBase {
result.message = i18next.t("apgames:validation.stiletto.EXTENSION");
return result;
}

if (this.ruleset === "open4" && this.checkIllegalOpen4(moves)) {
result.valid = false;
result.message = i18next.t("apgames:validation.stiletto.ILLEGAL_OPEN4");
return result;
}
}

// no more than two placements is possible
Expand Down
13 changes: 12 additions & 1 deletion src/games/xana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class XanaGame extends GameBase {
dateAdded: "2026-04-22",
// i18next.t("apgames:descriptions.xana")
description: "apgames:descriptions.xana",
notes: "apgames:notes.xana",
urls: [
"https://boardgamegeek.com/thread/3482800",
],
Expand Down Expand Up @@ -562,7 +563,17 @@ export class XanaGame extends GameBase {
this.gameover = this.lastmove === "pass" &&
this.stack[this.stack.length - 1].lastmove === "pass";

if (this.gameover) {
// if no shared accessible cells, the game is over, since all areas ownership are decided
if (this.stack.length > 3 && !this.gameover) {
const p1cells: string[] = this.accessibleCells(1);
const p2cells: Set<string> = new Set(this.accessibleCells(2));
const shareCells: string[] = p1cells.filter(c => p2cells.has(c));
if ( shareCells.length == 0 ) {
this.gameover = true;
}
}

if ( this.gameover ) {
this.scores = [this.getPlayerScore(1), this.getPlayerScore(2)];
this.winner = this.scores[0] > this.scores[1] ? [1] : [2];
this.results.push(
Expand Down
Loading