From 6953c680f7a1cdc96b145977fc7ea01479c77082 Mon Sep 17 00:00:00 2001 From: th555 Date: Tue, 20 Oct 2026 15:17:31 +0200 Subject: [PATCH 1/8] bricka board generation --- package-lock.json | 26 +-- src/boards/bricka.ts | 229 ++++++++++++++++++++++++++ src/boards/index.ts | 1 + src/renderers/_base.ts | 5 +- src/renderers/stackingOffset.ts | 5 +- src/schemas/schema.d.ts | 1 + src/schemas/schema.json | 1 + test/fixtures/playground-samples.json | 5 + 8 files changed, 259 insertions(+), 14 deletions(-) create mode 100644 src/boards/bricka.ts diff --git a/package-lock.json b/package-lock.json index 24d7b7e..c96b9bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -862,6 +862,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -878,6 +879,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=6.0.0" } @@ -889,6 +891,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=6.0.0" } @@ -900,6 +903,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -911,7 +915,8 @@ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -920,6 +925,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -2052,7 +2058,6 @@ "integrity": "sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.32.0", "@typescript-eslint/types": "8.32.0", @@ -2229,7 +2234,6 @@ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2629,7 +2633,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.11.23", "caniuse-lite": "^1.0.30001810", @@ -2685,7 +2688,8 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/builtin-modules": { "version": "5.0.0", @@ -3987,7 +3991,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6338,7 +6341,6 @@ "integrity": "sha512-ek8NRg/OPvS9ISOJNWNAz5vZcpYacWNFDWNJjj5OXsc6YuKacfey6wF04cXz/tOJIVrZ2nGSkHpAY5qKtF6ISg==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "d": "^1.0.2", "duration": "^0.2.2", @@ -8317,6 +8319,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -8329,6 +8332,7 @@ "dev": true, "license": "BSD-3-Clause", "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8733,6 +8737,7 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -8752,7 +8757,8 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/through": { "version": "2.3.8", @@ -8826,7 +8832,6 @@ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8959,7 +8964,6 @@ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -9101,7 +9105,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9414,7 +9417,6 @@ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/src/boards/bricka.ts b/src/boards/bricka.ts new file mode 100644 index 0000000..ea4fdf8 --- /dev/null +++ b/src/boards/bricka.ts @@ -0,0 +1,229 @@ +import { IPolyPolygon, rectOfRects } from "../grids/index.js"; +import { RendererBase } from "../renderers/_base.js"; +import { Graph, SquareOrthGraph } from "../graphs/index.js"; +import { BoardReturn, createGridlineLayers } from "./index.js"; + +export const bricka = (ctx: RendererBase): BoardReturn => { + if ( (ctx.json === undefined) || (ctx.rootSvg === undefined) || (ctx.json.board === null) ) { + throw new Error("Object in an invalid state!"); + } + + // Hard-code size to the only official board size + const width: number = 13; + const height: number = 13; + const cellsize = ctx.cellsize; + + let baseStroke = 1; + let baseColour = ctx.options.colourContext.strokes; + let baseOpacity = 1; + if ( ("strokeWeight" in ctx.json.board) && (ctx.json.board.strokeWeight !== undefined) ) { + baseStroke = ctx.json.board.strokeWeight; + } + if ( ("strokeColour" in ctx.json.board) && (ctx.json.board.strokeColour !== undefined) ) { + baseColour = ctx.resolveColour(ctx.json.board.strokeColour) as string; + } + if ( ("strokeOpacity" in ctx.json.board) && (ctx.json.board.strokeOpacity !== undefined) ) { + baseOpacity = ctx.json.board.strokeOpacity; + } + + // Get a grid of points + const grid = rectOfRects({gridHeight: height, gridWidth: width, cellSize: cellsize}); + + // start generating the SVG + const board = ctx.rootSvg.group().id("board"); + const layers = createGridlineLayers(board); + const gridlines = layers.root; + ctx.markBoard({svgGroup: gridlines, preGridLines: true, grid}); + + // Add board labels + let labelColour = ctx.options.colourContext.labels; + if ( ("labelColour" in ctx.json.board) && (ctx.json.board.labelColour !== undefined) ) { + labelColour = ctx.resolveColour(ctx.json.board.labelColour) as string; + } + let labelOpacity = 1; + if ( ("labelOpacity" in ctx.json.board) && (ctx.json.board.labelOpacity !== undefined) ) { + labelOpacity = ctx.json.board.labelOpacity; + } + + const labels = board.group().id("labels"); + // Columns (letters) + let columnLabels = ctx.getLabels(undefined, width); + for (let col = 0; col < width; col++) { + const pointTop = {x: grid[0][col].x, y: grid[0][col].y - (cellsize) }; + const pointBottom = {x: grid[height - 1][col].x, y: grid[height - 1][col].y + (cellsize) }; + ctx.applyCoordinateLabelStyle(labels.text(columnLabels[col]), labelColour, labelOpacity).center(pointTop.x, pointTop.y); + ctx.applyCoordinateLabelStyle(labels.text(columnLabels[col]), labelColour, labelOpacity).center(pointBottom.x, pointBottom.y); + } + + // Rows (numbers) + let rowLabels: string[] = []; + for (let row = 0; row < height; row++) { + rowLabels.push((height - row).toString()); + } + for (let row = 0; row < height; row++) { + const pointL = {x: grid[row][0].x - (cellsize), y: grid[row][0].y}; + const pointR = {x: grid[row][width - 1].x + (cellsize), y: grid[row][width - 1].y}; + ctx.applyCoordinateLabelStyle(labels.text(rowLabels[row]), labelColour, labelOpacity).center(pointL.x, pointL.y); + ctx.applyCoordinateLabelStyle(labels.text(rowLabels[row]), labelColour, labelOpacity).center(pointR.x, pointR.y); + } + + + // Start from a square graph, drop all nodes + // we don't need, and define the needed diagonal edges + const graph: Graph = new SquareOrthGraph(width, height); + + function dropIfHas(x: number, y: number) { + if(graph.hasNode([x, y])) { + graph.dropNode([x, y]); + } + } + + function dropSymm(x: number, y: number) { + /* Drop nodes with 4-way symmetry */ + dropIfHas(x, y); + dropIfHas(width - x - 1, y); + dropIfHas(x, height - y - 1); + dropIfHas(width - x - 1, height - y - 1); + } + + function addEdge(x1: number, y1: number, x2: number, y2: number) { + const nodeA = [x1, y1].join(","); + const nodeB = [x2, y2].join(","); + if (!graph.graph.hasEdge(nodeA, nodeB)) { + graph.graph.addEdge(nodeA, nodeB); + } + } + + function addEdgeSymm(x1: number, y1: number, x2: number, y2: number) { + /* Add edges with 4-way symmetry */ + addEdge(x1, y1, x2, y2); + addEdge(width - x1 - 1, y1, width - x2 - 1, y2); + addEdge(x1, height - y1 - 1, x2, height - y2 - 1); + addEdge(width - x1 - 1, height - y1 - 1, width - x2 - 1, height - y2 - 1); + } + + // Remove the corners + const cornerDrops = [5, 4, 2, 1, 1, 1]; + cornerDrops.forEach((maxY, x) => { + for (let y = 0; y < maxY; y++) { + dropSymm(x, y); + } + }); + + // Remove centers of the polygons + const polyDrops = [ + [1, 6], + [2, 5], + [3, 3], + [3, 6], + [4, 2], + [4, 4], + [5, 3], + [5, 6], + [6, 1], + [6, 5] + ] + polyDrops.forEach(([x, y]) => { + dropSymm(x, y); + }); + + // Remove nodes on the midpoint of long horizontal connections + const midDrops = [ + [0, 6], + [2, 3], + [4, 6], + [6, 3] + ] + midDrops.forEach(([x, y]) => { + dropSymm(x, y); + addEdgeSymm(x, y - 1, x, y + 1); + }); + + // Vertical: + const vDrops = [ + [4, 1] + ] + vDrops.forEach(([x, y]) => { + dropSymm(x, y); + addEdgeSymm(x - 1, y, x + 1, y); + }); + + // Add diagonals + const diags = [ + [2, 6, 1, 5], + [2, 6, 3, 5], + [6, 6, 5, 5], + [4, 3, 3, 2], + [4, 3, 3, 4], + [4, 3, 5, 2], + [4, 3, 5, 4], + [6, 0, 5, 1] + ] + diags.forEach(([x1, y1, x2, y2]) => { + addEdgeSymm(x1, y1, x2, y2); + }); + + // Draw the edges based on the graph + graph.graph.forEachEdge( + (_edge, _attributes, source, target) => { + const [cx1, cy1]: number[] = source.split(",").map(s => parseInt(s, 10)); + const [cx2, cy2]: number[] = target.split(",").map(s => parseInt(s, 10)); + const x1: number = grid[cx1][cy1].x; + const y1: number = grid[cx1][cy1].y; + const x2: number = grid[cx2][cy2].x; + const y2: number = grid[cx2][cy2].y; + layers.strokes.line(x1, y1, x2, y2).stroke({width: baseStroke, color: baseColour, opacity: baseOpacity, linecap: "round", linejoin: "round"}).attr({ 'pointer-events': 'none' }); + } + ); + + // Draw dots for each cell + graph.graph.forEachNode( + (node) => { + const [x, y]: number[] = node.split(",").map(s => parseInt(s, 10)); + const pt = grid[x][y]; + layers.strokes.circle(baseStroke * 7.5) + .attr({ 'pointer-events': 'none' }) + .fill(baseColour) + .opacity(baseOpacity) + .stroke({width: 0}) + .center(pt.x, pt.y); + } + ); + + if (ctx.options.boardClick !== undefined) { + // moving to click catchers across the board to make arbitrary rotation easier + const tiles = board.group().id("tiles"); + const tile = ctx.rootSvg.defs().rect(ctx.cellsize, ctx.cellsize).fill(ctx.options.colourContext.background).opacity(0).id("_clickCatcher"); + + graph.graph.forEachNode( + (node) => { + const [row, col]: number[] = node.split(",").map(s => parseInt(s, 10)); + const {x, y} = grid[row][col]; + const t = tiles.use(tile).dmove(x - (cellsize / 2), y - (cellsize / 2)); + t.click(() => ctx.options.boardClick!(row, col, "")); + } + ); + } + + ctx.markBoard({svgGroup: gridlines, preGridLines: false, grid}); + + // derive boardFill + const delta = cellsize / 1.5; + const xs = grid.flat().map(pt => pt.x); + const ys = grid.flat().map(pt => pt.y); + const minx = Math.min(...xs); + const miny = Math.min(...ys); + const maxx = Math.max(...xs); + const maxy = Math.max(...ys); + const boardFill: IPolyPolygon = { + type: "poly", + points: [ + {x: minx - (delta), y: miny - (delta)}, + {x: maxx + (delta), y: miny - (delta)}, + {x: maxx + (delta), y: maxy + (delta)}, + {x: minx - (delta), y: maxy + (delta)}, + ] + }; + + return {grid, boardFill}; +} diff --git a/src/boards/index.ts b/src/boards/index.ts index 37d38c5..e74d21f 100644 --- a/src/boards/index.ts +++ b/src/boards/index.ts @@ -19,6 +19,7 @@ export { hexSlanted } from "./hexSlanted.js"; export { fracturedFlat } from "./fracturedFlat.js"; export { moon } from "./moon.js"; export { onyx } from "./onyx.js"; +export { bricka } from "./bricka.js"; export { pentagonal } from "./pentagonal.js"; export { bentTri } from "./bentTri.js"; export { star } from "./star.js"; diff --git a/src/renderers/_base.ts b/src/renderers/_base.ts index c7ba82f..604593e 100644 --- a/src/renderers/_base.ts +++ b/src/renderers/_base.ts @@ -63,7 +63,7 @@ import { } from "../common/buttonBar.js"; import { attachMarkerPulse } from "../common/markerPulse.js"; import { CompassDirection, edges2corners, getBoardFill, BoardReturn } from "../boards/index.js"; -import { cairoCatalan, cairoCollinear, cobweb, conhex, conicalHex, dvgc, eleven, fracturedFlat, hexOfCir, hexOfHex, hexOfTri, hexOfTriF, hexSlanted, moon, onyx, pentagonal, bentTri, star, pyramidHex, rectOfHex, rectOfTri, snubSquare, snubSquareCells, sowing, squares, squaresDiamonds, squaresStacked, stackingTriangles, vertex, wheel } from "../boards/index.js"; +import { cairoCatalan, cairoCollinear, cobweb, conhex, conicalHex, dvgc, eleven, fracturedFlat, hexOfCir, hexOfHex, hexOfTri, hexOfTriF, hexSlanted, moon, onyx, bricka, pentagonal, bentTri, star, pyramidHex, rectOfHex, rectOfTri, snubSquare, snubSquareCells, sowing, squares, squaresDiamonds, squaresStacked, stackingTriangles, vertex, wheel } from "../boards/index.js"; import { glyphForLayoutRotation, glyphKeepsUpright, @@ -3577,6 +3577,9 @@ export abstract class RendererBase { case "onyx": ({ grid: gridPoints, boardFill } = onyx(this)); break; + case "bricka": + ({ grid: gridPoints, boardFill } = bricka(this)); + break; case "snubsquare-cells": ({ grid: gridPoints, polys, boardFill } = snubSquareCells(this)); break; diff --git a/src/renderers/stackingOffset.ts b/src/renderers/stackingOffset.ts index 7a047eb..33a3381 100644 --- a/src/renderers/stackingOffset.ts +++ b/src/renderers/stackingOffset.ts @@ -3,7 +3,7 @@ import { GridPoints, IPoint, Poly } from "../grids/_base.js"; import { APRenderRep } from "../schemas/schema.js"; import { IRendererOptionsIn, RendererBase } from "./_base.js"; import { centroid, projectPoint, rotatePoint, usePieceAt } from "../common/plotting.js"; -import { cairoCatalan, cairoCollinear, cobweb, conhex, conicalHex, fracturedFlat, hexOfCir, hexOfHex, hexOfTri, hexOfTriF, hexSlanted, moon, onyx, pentagonal, bentTri, star, pyramidHex, rectOfHex, rectOfTri, snubSquare, snubSquareCells, sowingRound, squares, squaresDiamonds, vertex, wheel } from "../boards/index.js"; +import { cairoCatalan, cairoCollinear, cobweb, conhex, conicalHex, fracturedFlat, hexOfCir, hexOfHex, hexOfTri, hexOfTriF, hexSlanted, moon, onyx, bricka, pentagonal, bentTri, star, pyramidHex, rectOfHex, rectOfTri, snubSquare, snubSquareCells, sowingRound, squares, squaresDiamonds, vertex, wheel } from "../boards/index.js"; /** * The `stacking-offset` renderer creates stacks of pieces by offsetting them slightly to give a 3D look. @@ -61,6 +61,9 @@ export class StackingOffsetRenderer extends RendererBase { case "onyx": ({ grid: gridPoints, boardFill } = onyx(this)); break; + case "bricka": + ({ grid: gridPoints, boardFill } = bricka(this)); + break; case "snubsquare-cells": ({ grid: gridPoints, polys, boardFill } = snubSquareCells(this)); break; diff --git a/src/schemas/schema.d.ts b/src/schemas/schema.d.ts index 33bf153..41c2dad 100644 --- a/src/schemas/schema.d.ts +++ b/src/schemas/schema.d.ts @@ -283,6 +283,7 @@ export type BoardStyles = | "snubsquare" | "snubsquare-cells" | "onyx" + | "bricka" | "pentagonal" | "pentagonal-bluestone" | "bent-tri" diff --git a/src/schemas/schema.json b/src/schemas/schema.json index f9516ca..b0d2324 100644 --- a/src/schemas/schema.json +++ b/src/schemas/schema.json @@ -394,6 +394,7 @@ "snubsquare", "snubsquare-cells", "onyx", + "bricka", "pentagonal", "pentagonal-bluestone", "bent-tri", diff --git a/test/fixtures/playground-samples.json b/test/fixtures/playground-samples.json index 9fbfbf0..cb778d9 100644 --- a/test/fixtures/playground-samples.json +++ b/test/fixtures/playground-samples.json @@ -1,4 +1,9 @@ { + "board-bricka": { + "name": "Boards: Bricka", + "description": "Board for the game Bricka, made up of pentagonal cells, played on intersections with both 5- and 6-connectivity, and the possibility to mark cells around an intersection.", + "render": "{\"board\":{\"style\":\"bricka\",\"width\":12,\"height\":12},\"legend\":{\"A\":{\"name\":\"piece\",\"scale\":0.75,\"paint\":{\"fill\":1}},\"B\":{\"name\":\"piece\",\"scale\":0.75,\"paint\":{\"fill\":2}}},\"pieces\":\"A\"}" + }, "pieces-simple": { "name": "Glyphs: Simple", "description": "Simple glyphs on a square board", From 62b5dadf0f077b4a8a67454ca16fa5b4f3c3c488 Mon Sep 17 00:00:00 2001 From: th555 Date: Wed, 21 Oct 2026 14:52:23 +0200 Subject: [PATCH 2/8] render board edge markers --- src/boards/bricka.ts | 50 ++++++++++++++++++--------- src/renderers/_base.ts | 36 +++++++++++++++++++ test/fixtures/playground-samples.json | 2 +- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/src/boards/bricka.ts b/src/boards/bricka.ts index ea4fdf8..4dc0e7a 100644 --- a/src/boards/bricka.ts +++ b/src/boards/bricka.ts @@ -1,4 +1,5 @@ import { IPolyPolygon, rectOfRects } from "../grids/index.js"; +import { IPoint} from "../grids/_base.js"; import { RendererBase } from "../renderers/_base.js"; import { Graph, SquareOrthGraph } from "../graphs/index.js"; import { BoardReturn, createGridlineLayers } from "./index.js"; @@ -204,26 +205,41 @@ export const bricka = (ctx: RendererBase): BoardReturn => { } ); } - - ctx.markBoard({svgGroup: gridlines, preGridLines: false, grid}); - - // derive boardFill - const delta = cellsize / 1.5; - const xs = grid.flat().map(pt => pt.x); - const ys = grid.flat().map(pt => pt.y); - const minx = Math.min(...xs); - const miny = Math.min(...ys); - const maxx = Math.max(...xs); - const maxy = Math.max(...ys); + + // derive the board edge polygon, this will be used for boardFill and in the + // renderer _base.ts for the edge markings. Going clockwise starting from the + // leftmost point. + + const leftpt = [6, 0] + const NW = [ + [5, 1], + [3, 1], + [3, 2], + [2, 2], + [2, 4], + [1, 4], + [1, 5], + [0, 5] + ] + let NE = [...NW] + NE.reverse(); + NE = NE.map(([x, y]) => ([x, height - y - 1])); + const rightpt = [6, 12] + let SE = [...NE] + SE.reverse(); + SE = SE.map(([x, y]) => ([width - x - 1, y])); + let SW = [...NW] + SW.reverse(); + SW = SW.map(([x, y]) => ([width - x - 1, y])); + const boardEdge = [leftpt].concat(NW, NE, [rightpt], SE, SW); + const boardEdgePoints: IPoint[] = boardEdge.map(([x, y]) => (grid[x][y])); + const boardFill: IPolyPolygon = { type: "poly", - points: [ - {x: minx - (delta), y: miny - (delta)}, - {x: maxx + (delta), y: miny - (delta)}, - {x: maxx + (delta), y: maxy + (delta)}, - {x: minx - (delta), y: maxy + (delta)}, - ] + points: boardEdgePoints }; + ctx.markBoard({svgGroup: gridlines, preGridLines: false, grid, boardFill: boardFill}); + return {grid, boardFill}; } diff --git a/src/renderers/_base.ts b/src/renderers/_base.ts index 604593e..687e86c 100644 --- a/src/renderers/_base.ts +++ b/src/renderers/_base.ts @@ -278,6 +278,7 @@ export interface IMarkBoardOptions { hexWidth?: number; hexHeight?: number; polys?: Poly[][]; + boardFill?: Poly; cairoStart?: "H"|"V"; } @@ -2200,6 +2201,7 @@ export abstract class RendererBase { if (opts.polys !== undefined) { polys = opts.polys; } + let boardFill: Poly|undefined = opts.boardFill; if ( (this.json === undefined) || (this.rootSvg === undefined) ) { throw new Error("Object in an invalid state!"); @@ -3280,6 +3282,40 @@ export abstract class RendererBase { pts.push(...realgrid.map(row => row[row.length - 1])) } targetGroup.polyline(pts.map(pt => `${pt.x},${pt.y}`).join(" ")).addClass(`aprender-marker-${x2uid(cloned)}`).stroke({width: baseStroke * 3, color: colour, opacity, linecap: "round", linejoin: "round"}).fill("none"); + } else if (style === "bricka" && boardFill !== undefined) { + let pts: IPoint[] = []; + const edge: IPoint[] = (boardFill as IPolyPolygon).points; + switch (marker.edge) { + case "NW": + pts = edge.slice(1, 9); + break; + case "NE": + pts = edge.slice(9, 17); + break; + case "SE": + pts = edge.slice(18, 26); + break; + case "SW": + pts = edge.slice(26, 34); + break; + case "W": + pts = [edge[0]]; + break; + case "E": + pts = [edge[17]]; + break; + } + targetGroup.polyline(pts.map(pt => `${pt.x},${pt.y}`).join(" ")).addClass(`aprender-marker-${x2uid(cloned)}`).stroke({width: baseStroke * 3, color: colour, opacity, linecap: "round", linejoin: "round"}).fill("none"); + pts.forEach( + (pt) => { + targetGroup.circle(baseStroke * 10) + .attr({ 'pointer-events': 'none' }) + .fill(colour) + .opacity(opacity) + .stroke({width: 0}) + .center(pt.x, pt.y); + } + ); } } else if (marker.type === "fence") { let colour = this.options.colourContext.strokes; diff --git a/test/fixtures/playground-samples.json b/test/fixtures/playground-samples.json index cb778d9..1555da8 100644 --- a/test/fixtures/playground-samples.json +++ b/test/fixtures/playground-samples.json @@ -2,7 +2,7 @@ "board-bricka": { "name": "Boards: Bricka", "description": "Board for the game Bricka, made up of pentagonal cells, played on intersections with both 5- and 6-connectivity, and the possibility to mark cells around an intersection.", - "render": "{\"board\":{\"style\":\"bricka\",\"width\":12,\"height\":12},\"legend\":{\"A\":{\"name\":\"piece\",\"scale\":0.75,\"paint\":{\"fill\":1}},\"B\":{\"name\":\"piece\",\"scale\":0.75,\"paint\":{\"fill\":2}}},\"pieces\":\"A\"}" + "render": "{\"board\":{\"style\":\"bricka\",\"width\":12,\"height\":12,\"markers\":[{\"type\":\"edge\",\"edge\":\"NW\",\"colour\":3},{\"type\":\"edge\",\"edge\":\"SE\",\"colour\":3},{\"type\":\"edge\",\"edge\":\"NE\",\"colour\":5},{\"type\":\"edge\",\"edge\":\"SW\",\"colour\":5},{\"type\":\"edge\",\"edge\":\"W\",\"colour\":6},{\"type\":\"edge\",\"edge\":\"E\",\"colour\":6}]},\"legend\":{\"A\":{\"name\":\"piece\",\"scale\":0.75,\"paint\":{\"fill\":1}},\"B\":{\"name\":\"piece\",\"scale\":0.75,\"paint\":{\"fill\":2}}},\"pieces\":\"A\"}" }, "pieces-simple": { "name": "Glyphs: Simple", From 2555d486e81ac82462ef76ed3fe972db03155bca Mon Sep 17 00:00:00 2001 From: th555 Date: Wed, 21 Oct 2026 15:53:02 +0200 Subject: [PATCH 3/8] bricka polygon markers --- src/boards/bricka.ts | 122 ++++++++++++++++++-------- test/fixtures/playground-samples.json | 10 +-- 2 files changed, 90 insertions(+), 42 deletions(-) diff --git a/src/boards/bricka.ts b/src/boards/bricka.ts index 4dc0e7a..a843ed3 100644 --- a/src/boards/bricka.ts +++ b/src/boards/bricka.ts @@ -34,7 +34,6 @@ export const bricka = (ctx: RendererBase): BoardReturn => { const board = ctx.rootSvg.group().id("board"); const layers = createGridlineLayers(board); const gridlines = layers.root; - ctx.markBoard({svgGroup: gridlines, preGridLines: true, grid}); // Add board labels let labelColour = ctx.options.colourContext.labels; @@ -164,6 +163,89 @@ export const bricka = (ctx: RendererBase): BoardReturn => { addEdgeSymm(x1, y1, x2, y2); }); + // derive the board edge polygon, this will be used for boardFill and in the + // renderer _base.ts for the edge markings. Going clockwise starting from the + // leftmost point. + + const leftpt = [6, 0] + const NW = [ + [5, 1], + [3, 1], + [3, 2], + [2, 2], + [2, 4], + [1, 4], + [1, 5], + [0, 5] + ] + let NE = [...NW] + NE.reverse(); + NE = NE.map(([x, y]) => ([x, height - y - 1])); + const rightpt = [6, 12] + let SE = [...NE] + SE.reverse(); + SE = SE.map(([x, y]) => ([width - x - 1, y])); + let SW = [...NW] + SW.reverse(); + SW = SW.map(([x, y]) => ([width - x - 1, y])); + const boardEdge = [leftpt].concat(NW, NE, [rightpt], SE, SW); + const boardEdgePoints: IPoint[] = boardEdge.map(([x, y]) => (grid[x][y])); + + const boardFill: IPolyPolygon = { + type: "poly", + points: boardEdgePoints + }; + + // Polygons are indexed by their center coordinate, there will be empty spots + // in the array. + const polys: IPolyPolygon[][] = []; + for (let i = 0; i < 13; i++) { + polys[i] = []; + } + const polyCenters = [ + [1, 6], + [2, 5], + [3, 3], + [3, 6], + [4, 2], + [4, 4], + [5, 3], + [5, 6], + [6, 1], + [6, 5] + ] + polyCenters.push(...polyCenters.map(([x, y]) => ([width - x - 1, y]))); + polyCenters.push(...polyCenters.map(([x, y]) => ([x, height - y - 1]))); + const offsets = [ + [-1, -1], + [-1, 0], + [-1, 1], + [0, 1], + [1, 1], + [1, 0], + [1, -1], + [0, -1] + ] + + polyCenters.forEach(([x, y]) => { + if(!(y in polys[x])) { + const pts: IPoint[] = []; + offsets.forEach(([dx, dy]) => { + if(graph.hasNode([x + dx, y + dy])) { + pts.push(grid[x + dx][y + dy]); + } + }); + polys[x][y] = { + type: "poly", + points: pts + }; + } + }); + + /* Everything generated, now do the actual drawing */ + ctx.markBoard({svgGroup: gridlines, preGridLines: true, grid, polys: polys, boardFill: boardFill}); + + // Draw the edges based on the graph graph.graph.forEachEdge( (_edge, _attributes, source, target) => { @@ -192,7 +274,6 @@ export const bricka = (ctx: RendererBase): BoardReturn => { ); if (ctx.options.boardClick !== undefined) { - // moving to click catchers across the board to make arbitrary rotation easier const tiles = board.group().id("tiles"); const tile = ctx.rootSvg.defs().rect(ctx.cellsize, ctx.cellsize).fill(ctx.options.colourContext.background).opacity(0).id("_clickCatcher"); @@ -205,41 +286,8 @@ export const bricka = (ctx: RendererBase): BoardReturn => { } ); } - - // derive the board edge polygon, this will be used for boardFill and in the - // renderer _base.ts for the edge markings. Going clockwise starting from the - // leftmost point. - - const leftpt = [6, 0] - const NW = [ - [5, 1], - [3, 1], - [3, 2], - [2, 2], - [2, 4], - [1, 4], - [1, 5], - [0, 5] - ] - let NE = [...NW] - NE.reverse(); - NE = NE.map(([x, y]) => ([x, height - y - 1])); - const rightpt = [6, 12] - let SE = [...NE] - SE.reverse(); - SE = SE.map(([x, y]) => ([width - x - 1, y])); - let SW = [...NW] - SW.reverse(); - SW = SW.map(([x, y]) => ([width - x - 1, y])); - const boardEdge = [leftpt].concat(NW, NE, [rightpt], SE, SW); - const boardEdgePoints: IPoint[] = boardEdge.map(([x, y]) => (grid[x][y])); - - const boardFill: IPolyPolygon = { - type: "poly", - points: boardEdgePoints - }; - ctx.markBoard({svgGroup: gridlines, preGridLines: false, grid, boardFill: boardFill}); + ctx.markBoard({svgGroup: gridlines, preGridLines: false, grid, polys: polys, boardFill: boardFill}); - return {grid, boardFill}; + return {grid, polys, boardFill}; } diff --git a/test/fixtures/playground-samples.json b/test/fixtures/playground-samples.json index 1555da8..8ead52f 100644 --- a/test/fixtures/playground-samples.json +++ b/test/fixtures/playground-samples.json @@ -1,9 +1,4 @@ { - "board-bricka": { - "name": "Boards: Bricka", - "description": "Board for the game Bricka, made up of pentagonal cells, played on intersections with both 5- and 6-connectivity, and the possibility to mark cells around an intersection.", - "render": "{\"board\":{\"style\":\"bricka\",\"width\":12,\"height\":12,\"markers\":[{\"type\":\"edge\",\"edge\":\"NW\",\"colour\":3},{\"type\":\"edge\",\"edge\":\"SE\",\"colour\":3},{\"type\":\"edge\",\"edge\":\"NE\",\"colour\":5},{\"type\":\"edge\",\"edge\":\"SW\",\"colour\":5},{\"type\":\"edge\",\"edge\":\"W\",\"colour\":6},{\"type\":\"edge\",\"edge\":\"E\",\"colour\":6}]},\"legend\":{\"A\":{\"name\":\"piece\",\"scale\":0.75,\"paint\":{\"fill\":1}},\"B\":{\"name\":\"piece\",\"scale\":0.75,\"paint\":{\"fill\":2}}},\"pieces\":\"A\"}" - }, "pieces-simple": { "name": "Glyphs: Simple", "description": "Simple glyphs on a square board", @@ -99,6 +94,11 @@ "description": "A version of a snubsquare board where you place pieces on the spaces instead of the vertices. The height and width you provide relate to the number of regular vertices. Because at many sizes the number of spaces varies from row to row, the labelling mirrors the `hex-of-*` model.", "render": "{\"board\":{\"style\":\"snubsquare-cells\",\"width\":8,\"height\":8,\"markers\":[{\"type\":\"flood\",\"colour\":2,\"points\":[{\"row\":2,\"col\":3},{\"row\":2,\"col\":2},{\"row\":2,\"col\":4}]},{\"type\":\"edge\",\"edge\":\"N\",\"colour\":1},{\"type\":\"edge\",\"edge\":\"S\",\"colour\":1},{\"type\":\"edge\",\"edge\":\"E\",\"colour\":2},{\"type\":\"edge\",\"edge\":\"W\",\"colour\":2}]},\"legend\":{\"A\":{\"name\":\"piece\",\"scale\":0.5,\"paint\":{\"fill\":1}},\"B\":{\"name\":\"piece\",\"scale\":0.5,\"paint\":{\"fill\":2}}},\"pieces\":\"ABA\"}" }, + "board-bricka": { + "name": "Boards: Bricka", + "description": "Board for the game Bricka, made up of pentagonal cells, played on intersections with both 5- and 6-connectivity, and the possibility to mark cells around an intersection.", + "render": "{\"board\":{\"style\":\"bricka\",\"width\":12,\"height\":12,\"markers\":[{\"type\":\"edge\",\"edge\":\"NW\",\"colour\":4},{\"type\":\"edge\",\"edge\":\"SE\",\"colour\":4},{\"type\":\"edge\",\"edge\":\"NE\",\"colour\":5},{\"type\":\"edge\",\"edge\":\"SW\",\"colour\":5},{\"type\":\"edge\",\"edge\":\"W\",\"colour\":6},{\"type\":\"edge\",\"edge\":\"E\",\"colour\":6},{\"type\":\"flood\",\"points\":[{\"row\":2,\"col\":5},{\"row\":3,\"col\":6},{\"row\":4,\"col\":4}],\"colour\":1},{\"type\":\"flood\",\"points\":[{\"row\":6,\"col\":7},{\"row\":5,\"col\":9},{\"row\":7,\"col\":9}],\"colour\":2}]},\"legend\":{\"A\":{\"name\":\"piece\",\"paint\":{\"fill\":1}},\"B\":{\"name\":\"piece\",\"paint\":{\"fill\":2}},\"C\":{\"name\":\"piece\",\"paint\":{\"fill\":3}}},\"pieces\":\"\\n\\n\\n,,,,,A\\n,,,,,C\\n\\n,,,,,,,,B\"}" + }, "board-pentagonal": { "name": "Boards: Pentagonal (Bluestone)", "description": "A slightly modified pentagonal board for the game Bluestone. But the raw pentagonal board is also available.", From afe55ff30151932dd031fa3ed0328b73bb42d62b Mon Sep 17 00:00:00 2001 From: th555 Date: Wed, 21 Oct 2026 18:08:43 +0200 Subject: [PATCH 4/8] Bricka: pass board edges in a more reasonable way --- src/boards/bricka.ts | 28 ++++++++++++----------- src/renderers/_base.ts | 51 +++++++++++++----------------------------- 2 files changed, 31 insertions(+), 48 deletions(-) diff --git a/src/boards/bricka.ts b/src/boards/bricka.ts index a843ed3..ff88abc 100644 --- a/src/boards/bricka.ts +++ b/src/boards/bricka.ts @@ -2,7 +2,7 @@ import { IPolyPolygon, rectOfRects } from "../grids/index.js"; import { IPoint} from "../grids/_base.js"; import { RendererBase } from "../renderers/_base.js"; import { Graph, SquareOrthGraph } from "../graphs/index.js"; -import { BoardReturn, createGridlineLayers } from "./index.js"; +import { BoardReturn, CompassDirection, createGridlineLayers } from "./index.js"; export const bricka = (ctx: RendererBase): BoardReturn => { if ( (ctx.json === undefined) || (ctx.rootSvg === undefined) || (ctx.json.board === null) ) { @@ -47,7 +47,7 @@ export const bricka = (ctx: RendererBase): BoardReturn => { const labels = board.group().id("labels"); // Columns (letters) - let columnLabels = ctx.getLabels(undefined, width); + const columnLabels = ctx.getLabels(undefined, width); for (let col = 0; col < width; col++) { const pointTop = {x: grid[0][col].x, y: grid[0][col].y - (cellsize) }; const pointBottom = {x: grid[height - 1][col].x, y: grid[height - 1][col].y + (cellsize) }; @@ -56,7 +56,7 @@ export const bricka = (ctx: RendererBase): BoardReturn => { } // Rows (numbers) - let rowLabels: string[] = []; + const rowLabels: string[] = []; for (let row = 0; row < height; row++) { rowLabels.push((height - row).toString()); } @@ -188,13 +188,15 @@ export const bricka = (ctx: RendererBase): BoardReturn => { let SW = [...NW] SW.reverse(); SW = SW.map(([x, y]) => ([width - x - 1, y])); - const boardEdge = [leftpt].concat(NW, NE, [rightpt], SE, SW); - const boardEdgePoints: IPoint[] = boardEdge.map(([x, y]) => (grid[x][y])); - - const boardFill: IPolyPolygon = { - type: "poly", - points: boardEdgePoints - }; + + const boardEdges: Map = new Map([ + ["W", [leftpt]], + ["NW", NW], + ["NE", NE], + ["E", [rightpt]], + ["SE", SE], + ["SW", SW] + ].map(([k, pts]) => ([(k as CompassDirection), {type: "poly", points: (pts as number[][]).map(([x, y]) => (grid[x][y]))}]))) // Polygons are indexed by their center coordinate, there will be empty spots // in the array. @@ -243,7 +245,7 @@ export const bricka = (ctx: RendererBase): BoardReturn => { }); /* Everything generated, now do the actual drawing */ - ctx.markBoard({svgGroup: gridlines, preGridLines: true, grid, polys: polys, boardFill: boardFill}); + ctx.markBoard({svgGroup: gridlines, preGridLines: true, grid, polys: polys, boardEdges: boardEdges}); // Draw the edges based on the graph @@ -287,7 +289,7 @@ export const bricka = (ctx: RendererBase): BoardReturn => { ); } - ctx.markBoard({svgGroup: gridlines, preGridLines: false, grid, polys: polys, boardFill: boardFill}); + ctx.markBoard({svgGroup: gridlines, preGridLines: false, grid, polys: polys, boardEdges: boardEdges}); - return {grid, polys, boardFill}; + return {grid, polys}; } diff --git a/src/renderers/_base.ts b/src/renderers/_base.ts index 27c8c2e..e1e26a8 100644 --- a/src/renderers/_base.ts +++ b/src/renderers/_base.ts @@ -281,7 +281,7 @@ export interface IMarkBoardOptions { hexWidth?: number; hexHeight?: number; polys?: Poly[][]; - boardFill?: Poly; + boardEdges?: Map; cairoStart?: "H"|"V"; } @@ -2261,7 +2261,7 @@ export abstract class RendererBase { if (opts.polys !== undefined) { polys = opts.polys; } - let boardFill: Poly|undefined = opts.boardFill; + const boardEdges: Map | undefined = opts.boardEdges; if ( (this.json === undefined) || (this.rootSvg === undefined) ) { throw new Error("Object in an invalid state!"); @@ -3342,40 +3342,21 @@ export abstract class RendererBase { pts.push(...realgrid.map(row => row[row.length - 1])) } targetGroup.polyline(pts.map(pt => `${pt.x},${pt.y}`).join(" ")).addClass(`aprender-marker-${x2uid(cloned)}`).stroke({width: baseStroke * 3, color: colour, opacity, linecap: "round", linejoin: "round"}).fill("none"); - } else if (style === "bricka" && boardFill !== undefined) { - let pts: IPoint[] = []; - const edge: IPoint[] = (boardFill as IPolyPolygon).points; - switch (marker.edge) { - case "NW": - pts = edge.slice(1, 9); - break; - case "NE": - pts = edge.slice(9, 17); - break; - case "SE": - pts = edge.slice(18, 26); - break; - case "SW": - pts = edge.slice(26, 34); - break; - case "W": - pts = [edge[0]]; - break; - case "E": - pts = [edge[17]]; - break; + } else if (style === "bricka" && boardEdges !== undefined) { + if (boardEdges.has(marker.edge)) { + const pts: IPoint[] = boardEdges.get(marker.edge)!.points; + targetGroup.polyline(pts.map(pt => `${pt.x},${pt.y}`).join(" ")).addClass(`aprender-marker-${x2uid(cloned)}`).stroke({width: baseStroke * 3, color: colour, opacity, linecap: "round", linejoin: "round"}).fill("none"); + pts.forEach( + (pt) => { + targetGroup.circle(baseStroke * 10) + .attr({ 'pointer-events': 'none' }) + .fill(colour) + .opacity(opacity) + .stroke({width: 0}) + .center(pt.x, pt.y); + } + ); } - targetGroup.polyline(pts.map(pt => `${pt.x},${pt.y}`).join(" ")).addClass(`aprender-marker-${x2uid(cloned)}`).stroke({width: baseStroke * 3, color: colour, opacity, linecap: "round", linejoin: "round"}).fill("none"); - pts.forEach( - (pt) => { - targetGroup.circle(baseStroke * 10) - .attr({ 'pointer-events': 'none' }) - .fill(colour) - .opacity(opacity) - .stroke({width: 0}) - .center(pt.x, pt.y); - } - ); } } else if (marker.type === "fence") { let colour = this.options.colourContext.strokes; From 341353e46e38a1c44230ab5a3825baf8ef78699b Mon Sep 17 00:00:00 2001 From: th555 Date: Wed, 21 Oct 2026 18:19:29 +0200 Subject: [PATCH 5/8] destructure polys --- src/renderers/_base.ts | 2 +- src/renderers/stackingOffset.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderers/_base.ts b/src/renderers/_base.ts index e1e26a8..3aaa504 100644 --- a/src/renderers/_base.ts +++ b/src/renderers/_base.ts @@ -3655,7 +3655,7 @@ export abstract class RendererBase { ({ grid: gridPoints, boardFill } = onyx(this)); break; case "bricka": - ({ grid: gridPoints, boardFill } = bricka(this)); + ({ grid: gridPoints, polys, boardFill } = bricka(this)); break; case "snubsquare-cells": ({ grid: gridPoints, polys, boardFill } = snubSquareCells(this)); diff --git a/src/renderers/stackingOffset.ts b/src/renderers/stackingOffset.ts index b73233b..cb66609 100644 --- a/src/renderers/stackingOffset.ts +++ b/src/renderers/stackingOffset.ts @@ -62,7 +62,7 @@ export class StackingOffsetRenderer extends RendererBase { ({ grid: gridPoints, boardFill } = onyx(this)); break; case "bricka": - ({ grid: gridPoints, boardFill } = bricka(this)); + ({ grid: gridPoints, polys, boardFill } = bricka(this)); break; case "snubsquare-cells": ({ grid: gridPoints, polys, boardFill } = snubSquareCells(this)); From 772681a1d9a17eaebf437d9fb34d98905c232932 Mon Sep 17 00:00:00 2001 From: th555 Date: Wed, 21 Oct 2026 19:02:01 +0200 Subject: [PATCH 6/8] render boardfill --- src/boards/bricka.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/boards/bricka.ts b/src/boards/bricka.ts index ff88abc..168d039 100644 --- a/src/boards/bricka.ts +++ b/src/boards/bricka.ts @@ -2,7 +2,7 @@ import { IPolyPolygon, rectOfRects } from "../grids/index.js"; import { IPoint} from "../grids/_base.js"; import { RendererBase } from "../renderers/_base.js"; import { Graph, SquareOrthGraph } from "../graphs/index.js"; -import { BoardReturn, CompassDirection, createGridlineLayers } from "./index.js"; +import { BoardReturn, CompassDirection, createGridlineLayers, getBoardFill } from "./index.js"; export const bricka = (ctx: RendererBase): BoardReturn => { if ( (ctx.json === undefined) || (ctx.rootSvg === undefined) || (ctx.json.board === null) ) { @@ -245,6 +245,11 @@ export const bricka = (ctx: RendererBase): BoardReturn => { }); /* Everything generated, now do the actual drawing */ + + // Do boardfill before the markers + const [hexFill, hexOpacity] = getBoardFill(ctx, "white"); + layers.fill.polygon([...boardEdges.values()].map((poly) => (poly.points)).flat().map((ipt) => ([ipt.x, ipt.y])).flat()).fill(hexFill!).opacity(hexOpacity); + ctx.markBoard({svgGroup: gridlines, preGridLines: true, grid, polys: polys, boardEdges: boardEdges}); From d34d754669475a2043c15b02fd0c519278e0f754 Mon Sep 17 00:00:00 2001 From: th555 Date: Wed, 21 Oct 2026 19:10:38 +0200 Subject: [PATCH 7/8] remove board size from renderer sample --- test/fixtures/playground-samples.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/playground-samples.json b/test/fixtures/playground-samples.json index c4cd54f..bcb228a 100644 --- a/test/fixtures/playground-samples.json +++ b/test/fixtures/playground-samples.json @@ -102,7 +102,7 @@ "board-bricka": { "name": "Boards: Bricka", "description": "Board for the game Bricka, made up of pentagonal cells, played on intersections with both 5- and 6-connectivity, and the possibility to mark cells around an intersection.", - "render": "{\"board\":{\"style\":\"bricka\",\"width\":12,\"height\":12,\"markers\":[{\"type\":\"edge\",\"edge\":\"NW\",\"colour\":4},{\"type\":\"edge\",\"edge\":\"SE\",\"colour\":4},{\"type\":\"edge\",\"edge\":\"NE\",\"colour\":5},{\"type\":\"edge\",\"edge\":\"SW\",\"colour\":5},{\"type\":\"edge\",\"edge\":\"W\",\"colour\":6},{\"type\":\"edge\",\"edge\":\"E\",\"colour\":6},{\"type\":\"flood\",\"points\":[{\"row\":2,\"col\":5},{\"row\":3,\"col\":6},{\"row\":4,\"col\":4}],\"colour\":1},{\"type\":\"flood\",\"points\":[{\"row\":6,\"col\":7},{\"row\":5,\"col\":9},{\"row\":7,\"col\":9}],\"colour\":2}]},\"legend\":{\"A\":{\"name\":\"piece\",\"paint\":{\"fill\":1}},\"B\":{\"name\":\"piece\",\"paint\":{\"fill\":2}},\"C\":{\"name\":\"piece\",\"paint\":{\"fill\":3}}},\"pieces\":\"\\n\\n\\n,,,,,A\\n,,,,,C\\n\\n,,,,,,,,B\"}" + "render": "{\"board\":{\"style\":\"bricka\",\"markers\":[{\"type\":\"edge\",\"edge\":\"NW\",\"colour\":4},{\"type\":\"edge\",\"edge\":\"SE\",\"colour\":4},{\"type\":\"edge\",\"edge\":\"NE\",\"colour\":5},{\"type\":\"edge\",\"edge\":\"SW\",\"colour\":5},{\"type\":\"edge\",\"edge\":\"W\",\"colour\":6},{\"type\":\"edge\",\"edge\":\"E\",\"colour\":6},{\"type\":\"flood\",\"points\":[{\"row\":2,\"col\":5},{\"row\":3,\"col\":6},{\"row\":4,\"col\":4}],\"colour\":1},{\"type\":\"flood\",\"points\":[{\"row\":6,\"col\":7},{\"row\":5,\"col\":9},{\"row\":7,\"col\":9}],\"colour\":2}]},\"legend\":{\"A\":{\"name\":\"piece\",\"paint\":{\"fill\":1}},\"B\":{\"name\":\"piece\",\"paint\":{\"fill\":2}},\"C\":{\"name\":\"piece\",\"paint\":{\"fill\":3}}},\"pieces\":\"\\n\\n\\n,,,,,A\\n,,,,,C\\n\\n,,,,,,,,B\"}" }, "board-pentagonal": { "name": "Boards: Pentagonal (Bluestone)", From aaea057a6037a66c46b60f0493946e99985a9507 Mon Sep 17 00:00:00 2001 From: th555 Date: Wed, 21 Oct 2026 19:40:42 +0200 Subject: [PATCH 8/8] handle label formatting options --- src/boards/bricka.ts | 68 +++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/src/boards/bricka.ts b/src/boards/bricka.ts index 168d039..cd3c0ee 100644 --- a/src/boards/bricka.ts +++ b/src/boards/bricka.ts @@ -3,6 +3,13 @@ import { IPoint} from "../grids/_base.js"; import { RendererBase } from "../renderers/_base.js"; import { Graph, SquareOrthGraph } from "../graphs/index.js"; import { BoardReturn, CompassDirection, createGridlineLayers, getBoardFill } from "./index.js"; +import { + hiddenRectLabelSides, + resolveRectEdgeLabelStrings, + shouldDrawRectLabelSide, + type RectEdgeLabelsOverride, +} from "./rectEdgeLabels.js"; +import { BoardBasic } from "../schemas/schema.js"; export const bricka = (ctx: RendererBase): BoardReturn => { if ( (ctx.json === undefined) || (ctx.rootSvg === undefined) || (ctx.json.board === null) ) { @@ -45,26 +52,49 @@ export const bricka = (ctx: RendererBase): BoardReturn => { labelOpacity = ctx.json.board.labelOpacity; } - const labels = board.group().id("labels"); - // Columns (letters) - const columnLabels = ctx.getLabels(undefined, width); - for (let col = 0; col < width; col++) { - const pointTop = {x: grid[0][col].x, y: grid[0][col].y - (cellsize) }; - const pointBottom = {x: grid[height - 1][col].x, y: grid[height - 1][col].y + (cellsize) }; - ctx.applyCoordinateLabelStyle(labels.text(columnLabels[col]), labelColour, labelOpacity).center(pointTop.x, pointTop.y); - ctx.applyCoordinateLabelStyle(labels.text(columnLabels[col]), labelColour, labelOpacity).center(pointBottom.x, pointBottom.y); - } + if ( (! ctx.json.options) || (! ctx.json.options.includes("hide-labels") ) ) { + const hiddenSides = hiddenRectLabelSides(ctx.json.options); + const labels = board.group().id("labels"); + let customLabels: string[]|undefined; + if ( ("columnLabels" in ctx.json.board) && (ctx.json.board.columnLabels !== undefined) ) { + customLabels = ctx.json.board.columnLabels; + } + let edgeLabels: RectEdgeLabelsOverride | undefined; + if ( ("edgeLabels" in ctx.json.board) && (ctx.json.board.edgeLabels !== undefined) ) { + edgeLabels = ctx.json.board.edgeLabels as RectEdgeLabelsOverride; + } + const edge = resolveRectEdgeLabelStrings(ctx, { + width, + height, + columnLabels: customLabels, + rowLabels: (ctx.json.board as BoardBasic).rowLabels, + edgeLabels, + options: ctx.json.options, + }); - // Rows (numbers) - const rowLabels: string[] = []; - for (let row = 0; row < height; row++) { - rowLabels.push((height - row).toString()); - } - for (let row = 0; row < height; row++) { - const pointL = {x: grid[row][0].x - (cellsize), y: grid[row][0].y}; - const pointR = {x: grid[row][width - 1].x + (cellsize), y: grid[row][width - 1].y}; - ctx.applyCoordinateLabelStyle(labels.text(rowLabels[row]), labelColour, labelOpacity).center(pointL.x, pointL.y); - ctx.applyCoordinateLabelStyle(labels.text(rowLabels[row]), labelColour, labelOpacity).center(pointR.x, pointR.y); + // Columns (letters) + for (let col = 0; col < width; col++) { + const pointTop = {x: grid[0][col].x, y: grid[0][col].y - (cellsize)}; + const pointBottom = {x: grid[height - 1][col].x, y: grid[height - 1][col].y + (cellsize)}; + if (shouldDrawRectLabelSide(hiddenSides, "north")) { + ctx.applyCoordinateLabelStyle(labels.text(edge.north[col]), labelColour, labelOpacity).center(pointTop.x, pointTop.y); + } + if (shouldDrawRectLabelSide(hiddenSides, "south")) { + ctx.applyCoordinateLabelStyle(labels.text(edge.south[col]), labelColour, labelOpacity).center(pointBottom.x, pointBottom.y); + } + } + + // Rows (numbers) + for (let row = 0; row < height; row++) { + const pointL = {x: grid[row][0].x - (cellsize), y: grid[row][0].y}; + const pointR = {x: grid[row][width - 1].x + (cellsize), y: grid[row][width - 1].y}; + if (shouldDrawRectLabelSide(hiddenSides, "west")) { + ctx.applyCoordinateLabelStyle(labels.text(edge.west[row]), labelColour, labelOpacity).center(pointL.x, pointL.y); + } + if (shouldDrawRectLabelSide(hiddenSides, "east")) { + ctx.applyCoordinateLabelStyle(labels.text(edge.east[row]), labelColour, labelOpacity).center(pointR.x, pointR.y); + } + } }