From: Carl Worth Date: Tue, 7 Jul 2020 05:58:12 +0000 (-0700) Subject: Add support for detecting glyph shapes X-Git-Url: https://git.cworth.org/git?p=lmno.games;a=commitdiff_plain;h=7274cc0cc286ead5e767155101658074353afb14;ds=inline Add support for detecting glyph shapes And color the background of each cell that is part fo a glyph in a player-specific color. Note: I'm not at all convinced that this is a good way to style things, but the actual functionality is here now, and I'll be happy to improve the specific styling in the future. --- diff --git a/scribe/scribe.css b/scribe/scribe.css index aba532e..3bac47b 100644 --- a/scribe/scribe.css +++ b/scribe/scribe.css @@ -1,3 +1,9 @@ +:root { + /* Colors for the two players. */ + --o-color: #c2dfff; + --plus-color: #fdd7e4; +} + /* We want our board to be the largest square that can * fit. Unfortunately, it requires a bit of CSS magic to make that * happen. We can set a width easily enough, but what we can't easily @@ -51,12 +57,34 @@ border-radius: 6px; border: 3px solid #999; + + position:relative; } .mini-grid.active { border: 3px solid var(--accent-color-bright); } +.mini-grid.o-wins .square, .mini-grid.plus-wins .square { + opacity: 0.6; +} + +.mini-grid .winner { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + + display: flex; + justify-content: center; + align-items: center; + line-height: 0; + font-size: calc(min(35vw, .35 * var(--page-max-width))); + font-weight: bold; + opacity: 0.20; +} + .square { display: flex; justify-content: center; @@ -76,6 +104,14 @@ cursor: default; } +.square.glyph-o { + background: var(--o-color); +} + +.square.glyph-plus { + background: var(--plus-color); +} + .square.open:hover { background-color: var(--accent-color-bright); } diff --git a/scribe/scribe.jsx b/scribe/scribe.jsx index b736f78..d8f6afc 100644 --- a/scribe/scribe.jsx +++ b/scribe/scribe.jsx @@ -542,15 +542,176 @@ class Game extends React.Component { }); } - find_connected(mini_grid_index, position) { - /* TODO: Implement actual finding of connected squares here. */ - let connected = Array(9).fill(false); + find_connected_recursive(recursion_state, position) { + + if (position < 0 || position >= 9) + return; + + if (recursion_state.visited[position]) + return; + + recursion_state.visited[position] = true; + + if (recursion_state.mini_grid[position].symbol !== recursion_state.target) + return; + + recursion_state.connected[position] = true; + + /* Left */ + if (position % 3 !== 0) + this.find_connected_recursive(recursion_state, position - 1); + /* Right */ + if (position % 3 !== 2) + this.find_connected_recursive(recursion_state, position + 1); + /* Up */ + this.find_connected_recursive(recursion_state, position - 3); + /* Down */ + this.find_connected_recursive(recursion_state, position + 3); + } + + /* Find all cells within a mini-grid that are 4-way connected to the + * given cell. */ + find_connected(mini_grid, position) { + const connected = Array(9).fill(false); + + /* If the given cell is empty then there is nothing connected. */ + if (mini_grid[position] === null) + return connected; + + const cell = mini_grid[position].symbol; + + let recursion_state = { + mini_grid: mini_grid, + connected: connected, + visited: Array(9).fill(false), + target: cell, + }; + this.find_connected_recursive(recursion_state, position); return connected; } - detect_glyph(connected) { - /* TODO: Implement actual glyph detection here. */ + /* Determine whether a connected group of cells is a glyph. + * + * Here, 'connected' is a length-9 array of Booleans, true + * for the connected cells in a mini-grid. + */ + is_glyph(connected) { + + /* Now that we have a set of connected cells, let's collect some + * stats on them, (width, height, number of cells, configuration + * of corner cells, etc.). + */ + let min_row = 2; + let min_col = 2; + let max_row = 0; + let max_col = 0; + let count = 0; + + for (let i = 0; i < 9; i++) { + const row = Math.floor(i/3); + const col = i % 3; + + if (! connected[i]) + continue; + + count++; + + min_row = Math.min(row, min_row); + min_col = Math.min(col, min_col); + max_row = Math.max(row, max_row); + max_col = Math.max(col, max_col); + } + + const width = max_col - min_col + 1; + const height = max_row - min_row + 1; + + /* Corners, (top-left, top-right, bottom-left, and bottom-right) */ + const tl = connected[3 * min_row + min_col]; + const tr = connected[3 * min_row + max_col]; + const bl = connected[3 * max_row + min_col]; + const br = connected[3 * max_row + max_col]; + + const count_true = (acc, val) => acc + (val ? 1 : 0); + const corners_count = [tl, tr, bl, br].reduce(count_true, 0); + const top_corners_count = [tl, tr].reduce(count_true, 0); + const bottom_corners_count = [bl, br].reduce(count_true, 0); + const left_corners_count = [tl, bl].reduce(count_true, 0); + const right_corners_count = [tr, br].reduce(count_true, 0); + + let two_corners_in_a_line = false; + if (top_corners_count === 2 || + bottom_corners_count === 2 || + left_corners_count === 2 || + right_corners_count === 2) + { + two_corners_in_a_line = true; + } + + let zero_corners_in_a_line = false; + if (top_corners_count === 0 || + bottom_corners_count === 0 || + left_corners_count === 0 || + right_corners_count === 0) + { + zero_corners_in_a_line = true; + } + + /* Now we have the information we need to determine glyphs. */ + switch (count) { + case 1: + /* Single */ + return true; + case 2: + /* Double */ + return true; + case 3: + /* Line */ + return (width === 3 || height === 3); + case 4: + /* Pipe, Squat-T, and 4-block, but not Tetris S */ + return two_corners_in_a_line; + case 5: + if (width !== 3 || height !== 3 || ! connected[4]) + { + /* Pentomino P and U are not glyphs (not 3x3) */ + /* Pentomino V is not a glyph (center not connected) */ + return false; + } + else if (corners_count === 0 || two_corners_in_a_line) + { + /* Pentomino X is glyph Cross (no corners) */ + /* Pentomino T is glyph T (has a row or column with 2 corners) */ + return true; + } else { + /* The corner counting above excludes pentomino F, W, and Z + * which are not glyphs. */ + return false; + } + break; + case 6: + /* 6-Block has width or height of 2. */ + /* Bomber, Chair, and J have 3 corners occupied. */ + if (width === 2 || height === 2 || corners_count === 3) + return true; + return false; + case 7: + /* Earring and U have no center square occupied */ + /* H has 4 corners occupied */ + /* House has a row or column with 0 corners occupied */ + if ((! connected[4]) || corners_count === 4 || zero_corners_in_a_line) + return true; + return false; + case 8: + /* Ottoman or O */ + if (corners_count === 4) + return true; + return false; + case 9: + return true; + } + + /* Should be unreachable */ return false; } @@ -573,8 +734,8 @@ class Game extends React.Component { /* With the symbol added to the squares, we need to see if this * newly-placed move forms a glyph or not. */ - const connected = this.find_connected(mini_grid_index, position); - const is_glyph = this.detect_glyph(connected); + const connected = this.find_connected(new_squares[mini_grid_index], position); + const is_glyph = this.is_glyph(connected); for (let i = 0; i < 9; i++) { if (connected[i])