From 77a91f222527db7e8d27c2eb650ffd4d84f8f17e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 23:02:23 +0000 Subject: [PATCH 1/3] Add "Moves until suicide" sidebar stat to Lielow Fixes the draft's syntax/wiring bugs (mismatched brackets in the 8x8 lookup table, board.forEach key/value mix-up, redundant per-player calls that stringified the whole result array) and wires the stat into sidebarScores(), matching how other games expose per-player sidebar stats. The hand-built lookup tables' actual values were independently re-derived and verified correct. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EhY7aqTm7KNK2BSk2QaGjx --- locales/en/apgames.json | 3 + src/games/lielow.ts | 211 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 213 insertions(+), 1 deletion(-) diff --git a/locales/en/apgames.json b/locales/en/apgames.json index b2d1f07d6..a6c39e075 100644 --- a/locales/en/apgames.json +++ b/locales/en/apgames.json @@ -4863,6 +4863,9 @@ "STEPS": "Maneuverability", "MATERIAL": "Material" }, + "lielow": { + "MOVESUNTILSUICIDE": "Moves until suicide" + }, "majorities": "Scores (─, ⟋, ⟍)", "meg": { "OFFENSE": "Offensive player", diff --git a/src/games/lielow.ts b/src/games/lielow.ts index eb6257627..7246fb344 100644 --- a/src/games/lielow.ts +++ b/src/games/lielow.ts @@ -24,6 +24,196 @@ export interface ILielowState extends IAPGameState { stack: Array; }; +// Number of moves a lone stack at [row][col] can make—growing by one each move, +// choosing direction each time to survive as long as possible—before it has no +// choice left but to bear off. Indexed as [stackHeight - 1][row][col]. Ignores +// interference from other stacks, friendly or enemy. +const stackMovesToSuicideEight: number[][][] = [ + [ // 1-stacks + [8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8], + ], + [ // 2-stacks + [7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7], + ], + [ // 3-stacks + [5, 6, 6, 6, 6, 6, 6, 5], + [6, 6, 6, 6, 6, 6, 6, 6], + [6, 6, 6, 6, 6, 6, 6, 6], + [6, 6, 6, 6, 6, 6, 6, 6], + [6, 6, 6, 6, 6, 6, 6, 6], + [6, 6, 6, 6, 6, 6, 6, 6], + [6, 6, 6, 6, 6, 6, 6, 6], + [5, 6, 6, 6, 6, 6, 6, 5], + ], + [ // 4-stacks + [3, 5, 5, 4, 4, 5, 5, 3], + [5, 5, 5, 5, 5, 5, 5, 5], + [5, 5, 5, 5, 5, 5, 5, 5], + [4, 5, 5, 4, 4, 5, 5, 4], + [4, 5, 5, 4, 4, 5, 5, 4], + [5, 5, 5, 5, 5, 5, 5, 5], + [5, 5, 5, 5, 5, 5, 5, 5], + [3, 5, 5, 4, 4, 5, 5, 3], + ], + [ // 5-stacks + [3, 4, 4, 2, 2, 4, 4, 3], + [4, 4, 4, 4, 4, 4, 4, 4], + [4, 4, 4, 3, 3, 4, 4, 4], + [2, 4, 3, 1, 1, 3, 4, 2], + [2, 4, 3, 1, 1, 3, 4, 2], + [4, 4, 4, 3, 3, 4, 4, 4], + [4, 4, 4, 4, 4, 4, 4, 4], + [3, 4, 4, 2, 2, 4, 4, 3], + ], + [ // 6-stacks + [3, 3, 2, 2, 2, 2, 3, 3], + [3, 3, 3, 3, 3, 3, 3, 3], + [2, 3, 1, 1, 1, 1, 3, 2], + [2, 3, 1, 1, 1, 1, 3, 2], + [2, 3, 1, 1, 1, 1, 3, 2], + [2, 3, 1, 1, 1, 1, 3, 2], + [3, 3, 3, 3, 3, 3, 3, 3], + [3, 3, 2, 2, 2, 2, 3, 3], + ], + [ // 7-stacks + [2, 2, 2, 2, 2, 2, 2, 2], + [2, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 2], + [2, 2, 2, 2, 2, 2, 2, 2], + ], + [ // 8-stacks + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + ] +]; + +// Same as `stackMovesToSuicideEight`, but for the 9x9 board (the `size-9` variant). +const stackMovesToSuicideNine: number[][][] = [ + [ // 1-stacks + [9, 9, 9, 9, 9, 9, 9, 9, 9], + [9, 9, 9, 9, 9, 9, 9, 9, 9], + [9, 9, 9, 9, 9, 9, 9, 9, 9], + [9, 9, 9, 9, 9, 9, 9, 9, 9], + [9, 9, 9, 9, 9, 9, 9, 9, 9], + [9, 9, 9, 9, 9, 9, 9, 9, 9], + [9, 9, 9, 9, 9, 9, 9, 9, 9], + [9, 9, 9, 9, 9, 9, 9, 9, 9], + [9, 9, 9, 9, 9, 9, 9, 9, 9], + ], + [ // 2-stacks + [8, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8], + ], + [ // 3-stacks + [7, 7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7, 7], + [7, 7, 7, 7, 7, 7, 7, 7, 7], + ], + [ // 4-stacks + [3, 5, 6, 6, 4, 6, 6, 5, 3], + [5, 6, 6, 6, 6, 6, 6, 6, 5], + [6, 6, 6, 6, 6, 6, 6, 6, 6], + [6, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 6, 6, 6, 4, 6, 6, 6, 4], + [6, 6, 6, 6, 6, 6, 6, 6, 6], + [6, 6, 6, 6, 6, 6, 6, 6, 6], + [5, 6, 6, 6, 6, 6, 6, 6, 5], + [3, 5, 6, 6, 4, 6, 6, 5, 3], + ], + [ // 5-stacks + [3, 5, 5, 4, 2, 4, 5, 5, 3], + [5, 5, 5, 5, 4, 5, 5, 5, 5], + [5, 5, 5, 5, 5, 5, 5, 5, 5], + [4, 5, 5, 4, 3, 4, 5, 5, 4], + [2, 4, 5, 3, 1, 3, 5, 4, 2], + [4, 5, 5, 4, 3, 4, 5, 5, 4], + [5, 5, 5, 5, 5, 5, 5, 5, 5], + [5, 5, 5, 5, 4, 5, 5, 5, 5], + [3, 5, 5, 4, 2, 4, 5, 5, 3], + ], + [ // 6-stacks + [3, 4, 4, 2, 2, 2, 4, 4, 3], + [4, 4, 4, 4, 4, 4, 4, 4, 4], + [4, 4, 4, 3, 3, 3, 4, 4, 4], + [2, 4, 3, 1, 1, 1, 3, 4, 2], + [2, 4, 3, 1, 1, 1, 3, 4, 2], + [2, 4, 3, 1, 1, 1, 3, 4, 2], + [4, 4, 4, 3, 3, 3, 4, 4, 4], + [4, 4, 4, 4, 4, 4, 4, 4, 4], + [3, 4, 4, 2, 2, 2, 4, 4, 3], + ], + [ // 7-stacks + [3, 3, 2, 2, 2, 2, 2, 3, 3], + [3, 3, 3, 3, 3, 3, 3, 3, 3], + [2, 3, 1, 1, 1, 1, 1, 3, 2], + [2, 3, 1, 1, 1, 1, 1, 3, 2], + [2, 3, 1, 1, 1, 1, 1, 3, 2], + [2, 3, 1, 1, 1, 1, 1, 3, 2], + [2, 3, 1, 1, 1, 1, 1, 3, 2], + [3, 3, 3, 3, 3, 3, 3, 3, 3], + [3, 3, 2, 2, 2, 2, 2, 3, 3], + ], + [ // 8-stacks + [2, 2, 2, 2, 2, 2, 2, 2, 2], + [2, 1, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 1, 2], + [2, 1, 1, 1, 1, 1, 1, 1, 2], + [2, 2, 2, 2, 2, 2, 2, 2, 2], + ], + [ // 9-stacks + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1], + ] +]; + export class LielowGame extends GameBase { public static readonly gameinfo: APGamesInformation = { name: "Lielow", @@ -677,9 +867,28 @@ export class LielowGame extends GameBase { return [...this.board.values()].filter(v => v[0] === player).map(v => v[1]).length; } + // For each player, the sum, over all of their stacks, of the number of moves + // that stack could make (ignoring interference from other stacks) before being + // forced to bear off. Returns [player1Total, player2Total]. + private movesUntilSuicide(): number[] { + const movesUntilSuicide: number[] = [0, 0]; + this.board.forEach((cell, coord) => { + const [x, y] = this.algebraic2coords(coord); + const [player, stackHeight] = cell; + if (this.boardSize === 8) { + movesUntilSuicide[player - 1] += stackMovesToSuicideEight[stackHeight - 1][y][x]; + } else if (this.boardSize === 9) { + movesUntilSuicide[player - 1] += stackMovesToSuicideNine[stackHeight - 1][y][x]; + } + }); + return movesUntilSuicide; + } + public sidebarScores(): IScores[] { + const suicideMoves = this.movesUntilSuicide(); return [ - { name: this.neutralAreaLabel("apgames:status.PIECESREMAINING"), scores: [this.getPlayerPieces(1), this.getPlayerPieces(2)] } + { name: this.neutralAreaLabel("apgames:status.PIECESREMAINING"), scores: [this.getPlayerPieces(1), this.getPlayerPieces(2)] }, + { name: this.neutralAreaLabel("apgames:status.lielow.MOVESUNTILSUICIDE"), scores: [suicideMoves[0], suicideMoves[1]] } ] } From 00e9847e7ee1796eab264aefaddff96795e5bb67 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 23:13:52 +0000 Subject: [PATCH 2/3] Add implementation notes for Lielow's "Moves until suicide" stat Briefly explains what the sidebar stat measures and its caveat that it ignores interference from other stacks, following the existing gameinfo.notes / apgames:notes. convention used by other games. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EhY7aqTm7KNK2BSk2QaGjx --- locales/en/apgames.json | 1 + src/games/lielow.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/locales/en/apgames.json b/locales/en/apgames.json index a6c39e075..b582b45f4 100644 --- a/locales/en/apgames.json +++ b/locales/en/apgames.json @@ -617,6 +617,7 @@ "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.", "knightline": "For convenience, your stack is split approximately in half on your first click. Subsequent clicks on the source or target stack move one piece at a time. When done, click on an empty cell or use the Complete Move button.\n\nThe coordinate system is adapted from the coder's coordinate system for Hive, a game on an unbounded (but still reasonably sized) hex board. The y axis is labeled with Cartesian coordinates (positive and negative integers). The x axis is labeled with letters in alphabetical order, with the origin at (m, 0). If motion on the x-axis exceeds the confines of the alphabet in either direction, the letters are prefixed with another letter (z is followed by aa, ab, ac, etc., and a is preceded by zz, zy, zx, etc.). The game is not expected to reach a width that requires more double coordinates, but for the record, az is followed by ba, za is preceded by yz, and so forth. The board cannot reach a size that would result in ambiguity.", "lasca": "Maneuverability is a measure of how close your pieces are to promoting. Your maximum maneuverability is the board size times the number of stacks you control.\n\nMaterial is calculated by giving you a point for every friendly piece in a stack you control, plus an extra point if you have an officer on top of that stack.", + "lielow": "\"Moves until suicide\" (sidebar) totals, for each player, how many moves each of their stacks could still make — growing by one each move and always choosing the direction that survives longest — before being cornered and forced to bear off. It ignores every other stack on the board, friendly or enemy, so it's a rough measure of how exposed a player's pieces are, not a count of actual legal moves remaining.", "linage": "Linage is a 2016 game designed by Luis Bolaños Mures. The game is based on the following definitions: A **region** is a maximal set of orthogonally contiguous empty points. A **line** is a set of N orthogonally contiguous empty points on the same row or column (default is N=3); and a region is **owned** by Vertical if it contains no horizontal lines, and by Horizontal if it contains no vertical lines. A region is free if it is owned by neither player. Regions must always have at least one line.\n\nOn his turn, the player passes or places a stone in a free region. The game ends when both players pass in succession, and wins the one with highest score (i.e., the number of points in the regions the player owns, plus komi).", "loa": "In the centre of the 9x9 board is a \"black hole.\". Landing on the black hole means the piece is removed from the game. Simultaneous connections are scored as a draw.", "magnate": "The terminology of some Magnate actions has been altered for clarity and brevity. Completely developing a new property is called the \"Buy\" action; purchasing a deed for a new property is called \"Deed\", developing deeds (that is, adding tokens to a deeded property, whether it results in the deed becoming fully developed or not) is called \"Add\". (Selling a card and trading suit tokens 3 for 1 are unchanged.)\n\nIn order to speed up the process of rolling for resources, there are two additional actions:\n* \"Prefer\" is for setting your preference of which suit token to take when a deed pays out on your opponent's roll. If you do not set an explicit preference, the code will choose the rarer token for you based on your non-crown suits and current supply of tokens. The currently preferred token is circled in the UI, but your personal preference is never visible to the other player.\n* \"Choose\" is a mandatory first action for collecting suit tokens when a deed pays out on your own roll. (In all cases where you need to choose a suit token that is not already among your tokens, you still click on the appropriate token pile.)\n\nBecause you can perform several actions during a ply in any order, there is also an \"Undo\" action to back out your most recent action, whether or not it was complete.\n\nNote that only the final resource die result is displayed, but the distribution of expected outcomes is still that of rolling 2d10 and taking the higher value. Taxation happens when the lower of 2d10 comes up 1; a suit die is rolled (or two, in the double taxation variant), and the suits will be displayed underneath the resource result. The roll is logged at the end of a player's turn, and is attributed to the next player (who would have rolled in the physical game). Except for a \"Choose\", no user action is required; resources are added or removed automatically by the server in between turns.\n\nWhen a player is ahead in a district, the Pawn or Excuse for that district is outlined in that player's color. The first tiebreaker score (total property value) is displayed in parentheses after the district score. The second tiebreaker is total number of tokens remaining.", diff --git a/src/games/lielow.ts b/src/games/lielow.ts index 7246fb344..76a780a16 100644 --- a/src/games/lielow.ts +++ b/src/games/lielow.ts @@ -223,6 +223,8 @@ export class LielowGame extends GameBase { dateAdded: "2023-12-24", // i18next.t("apgames:descriptions.lielow") description: "apgames:descriptions.lielow", + // i18next.t("apgames:notes.lielow") + notes: "apgames:notes.lielow", urls: ["https://boardgamegeek.com/boardgame/349408/lielow"], bggid: "349408", people: [ From 01841cddb1749713deb90b03583c2c4f724f7207 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 23:19:44 +0000 Subject: [PATCH 3/3] Tighten Lielow's "Moves until suicide" note wording Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EhY7aqTm7KNK2BSk2QaGjx --- locales/en/apgames.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en/apgames.json b/locales/en/apgames.json index b582b45f4..b42c245a6 100644 --- a/locales/en/apgames.json +++ b/locales/en/apgames.json @@ -617,7 +617,7 @@ "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.", "knightline": "For convenience, your stack is split approximately in half on your first click. Subsequent clicks on the source or target stack move one piece at a time. When done, click on an empty cell or use the Complete Move button.\n\nThe coordinate system is adapted from the coder's coordinate system for Hive, a game on an unbounded (but still reasonably sized) hex board. The y axis is labeled with Cartesian coordinates (positive and negative integers). The x axis is labeled with letters in alphabetical order, with the origin at (m, 0). If motion on the x-axis exceeds the confines of the alphabet in either direction, the letters are prefixed with another letter (z is followed by aa, ab, ac, etc., and a is preceded by zz, zy, zx, etc.). The game is not expected to reach a width that requires more double coordinates, but for the record, az is followed by ba, za is preceded by yz, and so forth. The board cannot reach a size that would result in ambiguity.", "lasca": "Maneuverability is a measure of how close your pieces are to promoting. Your maximum maneuverability is the board size times the number of stacks you control.\n\nMaterial is calculated by giving you a point for every friendly piece in a stack you control, plus an extra point if you have an officer on top of that stack.", - "lielow": "\"Moves until suicide\" (sidebar) totals, for each player, how many moves each of their stacks could still make — growing by one each move and always choosing the direction that survives longest — before being cornered and forced to bear off. It ignores every other stack on the board, friendly or enemy, so it's a rough measure of how exposed a player's pieces are, not a count of actual legal moves remaining.", + "lielow": "\"Moves until suicide\" totals for each player how many moves each of their stacks could still make — growing by one each move and always choosing the direction that survives longest — before being forced to bear off. It ignores every other stack on the board, friendly or enemy, so it's a rough measure of how exposed a player's pieces are, not a count of actual legal moves remaining.", "linage": "Linage is a 2016 game designed by Luis Bolaños Mures. The game is based on the following definitions: A **region** is a maximal set of orthogonally contiguous empty points. A **line** is a set of N orthogonally contiguous empty points on the same row or column (default is N=3); and a region is **owned** by Vertical if it contains no horizontal lines, and by Horizontal if it contains no vertical lines. A region is free if it is owned by neither player. Regions must always have at least one line.\n\nOn his turn, the player passes or places a stone in a free region. The game ends when both players pass in succession, and wins the one with highest score (i.e., the number of points in the regions the player owns, plus komi).", "loa": "In the centre of the 9x9 board is a \"black hole.\". Landing on the black hole means the piece is removed from the game. Simultaneous connections are scored as a draw.", "magnate": "The terminology of some Magnate actions has been altered for clarity and brevity. Completely developing a new property is called the \"Buy\" action; purchasing a deed for a new property is called \"Deed\", developing deeds (that is, adding tokens to a deeded property, whether it results in the deed becoming fully developed or not) is called \"Add\". (Selling a card and trading suit tokens 3 for 1 are unchanged.)\n\nIn order to speed up the process of rolling for resources, there are two additional actions:\n* \"Prefer\" is for setting your preference of which suit token to take when a deed pays out on your opponent's roll. If you do not set an explicit preference, the code will choose the rarer token for you based on your non-crown suits and current supply of tokens. The currently preferred token is circled in the UI, but your personal preference is never visible to the other player.\n* \"Choose\" is a mandatory first action for collecting suit tokens when a deed pays out on your own roll. (In all cases where you need to choose a suit token that is not already among your tokens, you still click on the appropriate token pile.)\n\nBecause you can perform several actions during a ply in any order, there is also an \"Undo\" action to back out your most recent action, whether or not it was complete.\n\nNote that only the final resource die result is displayed, but the distribution of expected outcomes is still that of rolling 2d10 and taking the higher value. Taxation happens when the lower of 2d10 comes up 1; a suit die is rolled (or two, in the double taxation variant), and the suits will be displayed underneath the resource result. The roll is logged at the end of a player's turn, and is attributed to the next player (who would have rolled in the physical game). Except for a \"Choose\", no user action is required; resources are added or removed automatically by the server in between turns.\n\nWhen a player is ahead in a district, the Pawn or Excuse for that district is outlined in that player's color. The first tiebreaker score (total property value) is displayed in parentheses after the district score. The second tiebreaker is total number of tokens remaining.",