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
7 changes: 5 additions & 2 deletions locales/en/apgames.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@
"frogger": "As in other Decktet games at Abstract Play, the deck is displayed at the bottom of the board and includes both cards in the deck and unknown cards in other players' hands. After the first hand, all cards are drawn from the open draw pool, so hands gradually become open. The discards pile is also displayed.\n\nDue to how randomization works at Abstract Play, forced passes are needed for a player to refill the draw pool in the middle of his turn. Passes are handled automatically by the server, but a there's also a draw pool variant that avoids forced passing if desired.\n\nThe Crocodiles variant is by Jorge Arroyo, the translator of the English rules. The Advanced rules and other minor variants are by P. D. Magnus; they appear in The Decktet Book, where the game is called Xing.",
"garden": "To make it very clear what happened on a previous turn, each move is displayed over four separate boards. The first board shows the game after the piece was first placed. The second board shows the state after adjacent pieces were flipped. The third board shows any harvests. The fourth board is the final game state and is where you make your moves.\n\nIn our implementation, black is always the \"tome\" or tie-breaker colour. The last player to harvest black will have a `0.1` after their score.",
"gyges": "The goal squares are adjacent to all the cells in the back row. The renderer cannot currently handle \"floating\" cells.",
"halma": "[Halma](https://en.wikipedia.org/wiki/Halma) was one of the first commercial successes for an abstract game. The game was designed by [George Howard Monks](https://en.wikipedia.org/wiki/George_Howard_Monks) in 1883/4. It is said that Halma was inspired by an older British game called *Hoppity*. However, this game has no documentation or surviving boards, turning this lineal statement into a historical mystery. Halma was later adapted (c.1892/3) into an even bigger success: [Chinese Checkers](https://boardgamegeek.com/boardgame/2386/chinese-checkers) (which could easily be played by three or six players).",
"halma": "To prevent the [drawish nature](https://boardgamegeek.com/thread/3706389/unspoiling-halma-redux) of the game, the following rules apply: (a) a player wins if the opposite home-base is complete with at least one friendly stone (David Parlett's criteria); (b) Any piece in the player's home-base must make progress towards the enemy camp whenever this is possible by jumping over an enemy piece. (Zillions rule, to remove drawish strategies); (c) No stone can return to his home-base.\n\n[Halma](https://en.wikipedia.org/wiki/Halma) was one of the first commercial successes for an abstract game. The game was designed by [George Howard Monks](https://en.wikipedia.org/wiki/George_Howard_Monks) in 1883/4. It is said that Halma was inspired by an older British game called *Hoppity*. However, this game has no documentation or surviving boards, turning this lineal statement into a historical mystery. Halma was later adapted (c.1892/3) into an even bigger success: [Chinese Checkers](https://boardgamegeek.com/boardgame/2386/chinese-checkers) (which could easily be played by three or six players).\n\nSuper Halma is a more dynamic, uncredited variant, presented in the 1992's book **New Rules for Classic Games** by Wayne Schmittberger. This variant proposes long jumps, where the number of empty cells before and after the jumped stone must be equal. The base's move restrictions still apply in this implementation of Super Halma.",
"homeworlds": "The win condition is what's called \"Sinister Homeworlds.\" You only win by defeating the opponent to your left. If someone else does that, the game continues, but your left-hand opponent now shifts clockwise. For example, in a four-player game, if I'm South, then I win if I eliminate West. But if the North player ends up eliminating West, the game continues, but now my left-hand opponent is North.",
"jacynth": "More information on the Decktet system can be found on the [official Decktet website](https://www.decktet.com). Cards in players' hands are hidden from observers and opponents.",
"konane": "Several competing opening protocols exist, but the most common ruleset is the Naihe Ruleset, used by tournaments at the Bishop Museum in Hawaii and described in the BGG reference. This is what is implemented here.",
Expand Down Expand Up @@ -1451,6 +1451,9 @@
"halma": {
"#board": {
"name": "16x16 board"
},
"superhalma": {
"name": "Super Halma (allows long-jumps)"
}
},
"havannah": {
Expand Down Expand Up @@ -4983,7 +4986,7 @@
"PARTIAL": "Select an empty cell to place a second stone. It must be adjacent to the same number of groups as the first stone."
},
"halma": {
"INITIAL_INSTRUCTIONS": "Move to an adjacent empty cell, or multiple short-jump a friendly piece.",
"INITIAL_INSTRUCTIONS": "Move to an adjacent empty cell, or multiple short-jump a friendly piece. Players cannot re-enter their home-base, and cannot leave the enemy home-base once inside. Forwards jumps over enemy stones inside the player's home-base takes precedence.",
"NONEXISTENT": "Trying to interact with a friendly piece that doesn't exist at {{where}}.",
"ILLEGAL_MOVE": "No piece can return to his home-base, nor pieces inside the opponent's home-base can leave again.",
"FORCED_MOVES": "Pieces still at home-base are forced to forward-jump over enemy neighbors: pick {{forced}}.",
Expand Down
77 changes: 73 additions & 4 deletions src/games/halma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,29 @@ export class HalmaGame extends GameBase {
description: "apgames:descriptions.halma",
// i18next.t("apgames:notes.halma")
notes: "apgames:notes.halma",
urls: ["https://en.wikipedia.org/wiki/Halma"],
urls: [
"https://en.wikipedia.org/wiki/Halma",
"https://boardgamegeek.com/boardgame/38950/halma",
"https://www.abstractgames.org/uploads/1/1/6/4/116462923/abstract_games_issue_15.pdf#page=11",
"https://blackandwhite.develz.org/games/SuperHalma.pdf",
],
people: [
{
type: "designer",
name: "George Howard Monks",
urls: ["https://en.wikipedia.org/wiki/George_Howard_Monks"],
},
{
type: "coder",
name: "João Pedro Neto",
urls: ["https://boardgamegeek.com/boardgamedesigner/3829/joao-pedro-neto"],
apid: "9228bccd-a1bd-452b-b94f-d05380e6638f",
},
],
variants: [
{ uid: "#board", },
{ uid: "superhalma", group: "ruleset" },
],
categories: ["goal>evacuate", "other>traditional", "mechanic>move", "board>shape>rect", "components>simple>1per", "other>2+players"],
flags: ["no-moves", "experimental"]
};
Expand All @@ -51,6 +65,7 @@ export class HalmaGame extends GameBase {
public stack!: Array<IMoveState>;
public results: Array<APMoveResult> = [];
private dots: string[] = [];
private ruleset: "default" | "superhalma";

constructor(state: IHalmaState | string, variants?: string[]) {
super();
Expand Down Expand Up @@ -92,6 +107,7 @@ export class HalmaGame extends GameBase {
this.stack = [...state.stack];
}
this.load();
this.ruleset = this.getRuleset();
}

public load(idx = -1): HalmaGame {
Expand All @@ -118,6 +134,11 @@ export class HalmaGame extends GameBase {
return new SquareGraph(this.boardsize, this.boardsize);
}

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

private homeBase(player?: playerid): string[] {
if (player === undefined) { player = this.currplayer; }
return player === 1 ?
Expand Down Expand Up @@ -146,6 +167,14 @@ export class HalmaGame extends GameBase {
}

private jumpNeighbors(cell: string): string[] {
if (this.ruleset === "superhalma") {
return this.jumpNeighborsSuperHalma(cell);
} else {
return this.jumpNeighborsHalma(cell);
}
}

private jumpNeighborsHalma(cell: string): string[] {
const res: string[] = [];
const g = this.graph;
const [x, y] = g.algebraic2coords(cell);
Expand All @@ -163,6 +192,31 @@ export class HalmaGame extends GameBase {
return res;
}

private jumpNeighborsSuperHalma(cell: string): string[] {
const res: string[] = [];
const g = this.graph;
const [x, y] = g.algebraic2coords(cell);

for (const dir of allDirections) {
const ray = g.ray(x, y, dir).map(c => g.coords2algebraic(...c));
if (ray.length >= 2) {
for (let delta=0; delta<Math.floor(ray.length/2); delta++) { // consider all possible long-jumps
const start = cell;
const pivot = ray[delta];
const end = ray[2*delta+1];
if (! this.respectBases(start, end) ) { continue; } // base movements must be respected
if (! this.board.has(pivot) ) { continue; } // the pivot cell must be occupied
if ( this.board.has(end) ) { continue; } // the final cell must be empty
// all cell in-between must be empty
if (! ray.slice(0, delta).every(c => !this.board.has(c)) ) { continue; }
if (! ray.slice(delta+1, 2*delta+1).every(c => !this.board.has(c)) ) { continue; }
res.push(end);
}
}
}
return res;
}

// Any piece in a player's home-base must make progress towards the enemy camp whenever
// this is possible by jumping over an enemy piece (Zillions' rule quoting Sid Sackson)
// This method returns all mandatory jumps (in any)
Expand All @@ -185,9 +239,23 @@ export class HalmaGame extends GameBase {
for (const dir of dirs) {
const ray = g.ray(x, y, dir).map(c => g.coords2algebraic(...c));
if (ray.length >= 2) {
if (this.board.has(ray[0]) && this.board.get(ray[0]) === prevplayer && !this.board.has(ray[1])) {
res.push(`${cell}-${ray[1]}`);
if (this.ruleset === "superhalma") {
for (let delta=0; delta<Math.floor(ray.length/2); delta++) { // consider all possible long-jumps
const pivot = ray[delta]; // the pivot cell must be occupied by an enemy stone
const end = ray[2*delta+1]; // the final cell must be empty
if ( !this.board.has(pivot) || this.board.get(pivot) !== prevplayer) { continue; }
if ( this.board.has(end) ) { continue; }
// all cell in-between must be empty
if (! ray.slice(0, delta).every(c => !this.board.has(c)) ) { continue; }
if (! ray.slice(delta+1, 2*delta+1).every(c => !this.board.has(c)) ) { continue; }
res.push(`${cell}-${end}`);
}
} else { // in the original ruleset, just check the adjacent cells
if (this.board.has(ray[0]) && this.board.get(ray[0]) === prevplayer && !this.board.has(ray[1])) {
res.push(`${cell}-${ray[1]}`);
}
}

}
}
}
Expand Down Expand Up @@ -340,7 +408,8 @@ export class HalmaGame extends GameBase {
const mandatory = this.mandatoryMoves();
if ( mandatory.length > 0 ) { // mandatory moves take precedence!
if ( this.hasPrefix(mandatory, m) ) {
this.dots.push(...mandatory.map(move => move.split('-')[1]) );
this.dots.push(...mandatory.filter(move => move.startsWith(m))
.map(move => move.split('-')[1]) );
}
if (! mandatory.some(move => m.startsWith(move)) ) {
return this;
Expand Down
Loading