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
26 changes: 23 additions & 3 deletions locales/en/apgames.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@
"spora" : "A territorial game with limited pieces to build stacks that capture by enclosure and/or by sowing.",
"spree": "An impartial n-in-a-row game where players take turns handing a ball to their opponent to place. The goal is to try to make a full line of one of two colours (red and blue by default). There is a third wild neutral colour (green by default) that can play the role of either colour.",
"squaredance": "A game where you move groups of pieces by rotation, trying to force your opponent into a position where they cannot move.",
"squirm" : "Players need to create the largest possible snake-like group.",
"stairs": "Stack higher than your opponent in this game of one-space, one-level jumps where you also have to move your lowest pieces first. To take the lead and win, you must surpass your opponent's tallest stack height or, failing that, their number of stacks at the tallest height.",
"stawvs": "A set collection game for 2-4 players that's similar to Amazons, with old-style Volcano scoring. On a checkerboard randomly filled with individual pyramids, player pieces can move and capture pyramids in a straight line (orthogonally or diagonally), if and only if all intervening spaces and the target space contain pyramids but not pieces.",
"stibro": "Win by forming a loop anywhere on the board, while adhering to the placement restriction that ensures loop-forming is always possible.",
Expand Down Expand Up @@ -1346,11 +1347,11 @@
"name": "37x37 board"
},
"#ruleset": {
"description": "Area counting; situational superko",
"description": "Area scoring; situational superko",
"name": "AGA Rules"
},
"positional": {
"description": "Area counting; positional superko",
"description": "Area scoring; positional superko",
"name": "Positional Superko"
}
},
Expand Down Expand Up @@ -2468,6 +2469,20 @@
"name": "5x5 board"
}
},
"squirm": {
"#board": {
"name": "Hexagonal board (bases 3-5)"
},
"size-4": {
"name": "Hexagonal board (bases 4-6)"
},
"size-5": {
"name": "Hexagonal board (bases 5-7)"
},
"size-6": {
"name": "Hexagonal board (bases 6-8)"
}
},
"stawvs": {
"#setup": {
"description": "In the default setup, the corners of the board are empty.",
Expand Down Expand Up @@ -5743,6 +5758,11 @@
"PARTIAL_DESTINATION": "Select the point where the end stone will end up. You may not cover friendly pieces, and any covered enemy pieces will be captured.",
"PARTIAL_ENDSTONE": "Select the end stone. This is the stone that will move the furthest."
},
"squirm": {
"INITIAL_INSTRUCTIONS": "Drop a friendly stone on an empty cell. Only serpent-like groups are valid. A serpent is a group where each stone has, at most, two friendly neighbors; and there are no three friendly stones in line.",
"INVALID_3ROW": "The stone cannot make a three in-a-row.",
"INVALID_NEIGH": "The stone cannot have more than two friendly neighbors."
},
"stairs": {
"NORMAL_MOVE": "Choose a piece to move.",
"INVALID_MOVE": "You must move a piece to a neighboring stack of the same height. You must move one of your lowest movable pieces.",
Expand Down Expand Up @@ -6178,7 +6198,7 @@
"TOO_MANY_PIECES": "There are too many pieces at {{where}} to add {{num}} more."
},
"xana": {
"INITIAL_INSTRUCTIONS": "Drop pieces from reserve on empty cells or friendly stacks. In alternative, move a stack: click it then click on an empty cell within moving range. Afterwards, and optionally, one or two walls can be placed on any empty accessible cells (closed adversary areas are not accessible).",
"INITIAL_INSTRUCTIONS": "Drop pieces from reserve on empty cells or friendly stacks. Alternatively, move a stack: click it then click on an empty cell within moving range. Afterwards, and optionally, one or two walls can be placed on any empty accessible cells (closed adversary areas are not accessible).",
"DROP_MOVE_INSTRUCTIONS" : "Click again on the stack to place a new stone from the reserve. Otherwise, click N times on an empty cell (within moving range) to move N pieces.",
"ENEMY_PIECE" : "Cannot change an enemy piece.",
"MOVE_NOT_INSIDE_CIRCLE": "Stack cannot be moved outside its circle, ie, its moving range.",
Expand Down
9 changes: 7 additions & 2 deletions src/games/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ import { MinefieldGame, IMinefieldState } from "./minefield";
import { SentinelGame, ISentinelState } from "./sentinel";
import { XanaGame, IXanaState } from "./xana";
import { SporaGame, ISporaState } from "./spora";
import { SquirmGame, ISquirmState } from "./squirm";

export {
APGamesInformation, GameBase, GameBaseSimultaneous, IAPGameState,
Expand Down Expand Up @@ -475,6 +476,7 @@ export {
SentinelGame, ISentinelState,
XanaGame, IXanaState,
SporaGame, ISporaState,
SquirmGame, ISquirmState,
};

const games = new Map<string, typeof AmazonsGame | typeof BlamGame | typeof CannonGame |
Expand Down Expand Up @@ -557,7 +559,7 @@ const games = new Map<string, typeof AmazonsGame | typeof BlamGame | typeof Cann
typeof MagnateGame | typeof ProductGame | typeof OonpiaGame |
typeof GoGame | typeof StilettoGame | typeof BTTGame |
typeof MinefieldGame | typeof SentinelGame | typeof XanaGame |
typeof SporaGame
typeof SporaGame | typeof SquirmGame
>();
// Manually add each game to the following array
[
Expand Down Expand Up @@ -593,7 +595,8 @@ const games = new Map<string, typeof AmazonsGame | typeof BlamGame | typeof Cann
SiegeOfJGame, StairsGame, EmuGame, DeckfishGame, BluestoneGame, SunspotGame, StawvsGame,
LascaGame, EmergoGame, FroggerGame, ArimaaGame, RampartGame, KrypteGame, EnsoGame, RincalaGame,
WaldMeisterGame, WunchunkGame, BambooGame, PluralityGame, CrosshairsGame, MagnateGame,
ProductGame, OonpiaGame, GoGame, StilettoGame, BTTGame, MinefieldGame, SentinelGame, XanaGame, SporaGame,
ProductGame, OonpiaGame, GoGame, StilettoGame, BTTGame, MinefieldGame, SentinelGame,
XanaGame, SporaGame, SquirmGame
].forEach((g) => {
if (games.has(g.gameinfo.uid)) {
throw new Error("Another game with the UID '" + g.gameinfo.uid + "' has already been used. Duplicates are not allowed.");
Expand Down Expand Up @@ -1077,6 +1080,8 @@ export const GameFactory = (game: string, ...args: any[]): GameBase|GameBaseSimu
return new XanaGame(...args);
case "spora":
return new SporaGame(...args);
case "squirm":
return new SquirmGame(...args);
}
return;
}
2 changes: 1 addition & 1 deletion src/games/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ export class ProductGame extends GameBase {

if (this.gameover) {
// tied scores is a P2 win
this.winner = this.getPlayerScore(1) > this.getPlayerScore(2) ? [1] : [2];
this.winner = this.getPlayerScore(1) >= this.getPlayerScore(2) ? [1] : [2];
this.results.push(
{type: "eog"},
{type: "winners", players: [...this.winner]}
Expand Down
32 changes: 27 additions & 5 deletions src/games/spora.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,27 @@ export class SporaGame extends GameBase {
return this.findDead(this.currplayer, cloned).length === 0;
}

// this method is called at the end, when the last player makes a sequence of placements
// in this case, after placing some stones, some captures might become legal,
// so we need to try placing piece by piece, and evaluate their validity
private validSequence(cells: string[]): boolean {
const prevplayer = this.currplayer === 1 ? 2 : 1;
// we'll simulate the process in a cloned board
const cloned = this.cloneBoard();
for (const cell of cells) {
// place the stack (herein, its size is irrelevant)
cloned.set(cell, [this.currplayer, 1]);
// compute all enemy captures
const dead = this.findDead(prevplayer, cloned);
dead.forEach(cell => cloned.delete(cell));
// if there are still friendly captures, the placement is illegal
if (this.findDead(this.currplayer, cloned).length > 0) {
return false;
}
}
return true;
}

// What pieces are orthogonally adjacent to a given area?
public getAdjacentPieces(area: string[], pieces: string[]): string[] {
// convert area strings to numeric coordinates
Expand Down Expand Up @@ -546,11 +567,12 @@ export class SporaGame extends GameBase {
result.message = i18next.t("apgames:validation.spora.MAXIMUM_STACK");
return result;
}
if (! this.validPlacement(cell) ) {
result.valid = false;
result.message = i18next.t("apgames:validation.spora.SELF_CAPTURE");
return result;
}
}
// check if the sequence of placements are able to make legal captures
if (! this.validSequence(cells) ) {
result.valid = false;
result.message = i18next.t("apgames:validation.spora.SELF_CAPTURE");
return result;
}
result.valid = true;
result.complete = this.reserve[this.currplayer - 1] > cells.length ? -1 : 1; // end when all pieces are on board
Expand Down
Loading
Loading