bridges-cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -12,7 +12,10 @@ This is a CLI renderer for Hashiwokakero (Bridges) puzzles. It renders a grid of
12
12
 
13
13
  ## Testing
14
14
 
15
- Tests use Vitest with `@testing-library/react` patterns via `ink-testing-library`.
15
+ - Tests use Vitest with `@testing-library/react` patterns via `ink-testing-library`.
16
+ - The output is a grid drawn with ascii, and tests often do exact matches. When nodes are
17
+ highlighted, they have ANSI codes to appear as bold/dim or red/green. The tests will put
18
+ these codes directly in the test expectation around the nodes.
16
19
 
17
20
  ## Code Style
18
21
 
package/README.md CHANGED
@@ -22,6 +22,7 @@ bun start
22
22
 
23
23
  ### CLI Options
24
24
  - `-p, --puzzle <identifier>` - Render a puzzle via shorthand encoding (see `samplePuzzles.ts`)
25
+ - `--enable-solutions` - Enable the show solution toggle in the game (disabled by default)
25
26
 
26
27
  ### Run tests and linter
27
28
  ``` bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bridges-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Bridges CLI puzzle game",
5
5
  "bin": {
6
6
  "bridges": "src/index.tsx"
@@ -15,7 +15,7 @@
15
15
  "keywords": [
16
16
  "game",
17
17
  "puzzle",
18
- "hashi",
18
+ "hashiwokakero",
19
19
  "bridges",
20
20
  "cli"
21
21
  ],
package/src/Game.tsx CHANGED
@@ -2,12 +2,14 @@ import { useCallback, useMemo, useState } from 'react'
2
2
 
3
3
  import HashiGrid from './components/HashiGrid.tsx'
4
4
  import type { HashiNodeData, PlacedBridge } from './types.ts'
5
+ import { areAllNodesFilled, isConnected } from './utils/bridges.ts'
5
6
  import { type PuzzleData, parsePuzzle } from './utils/puzzle-encoding.ts'
6
7
  import usePuzzleInput from './utils/usePuzzleInput.ts'
7
8
 
8
9
  type GameProps = {
9
10
  puzzles: PuzzleData[]
10
11
  hasCustomPuzzle: boolean
12
+ enableSolutions: boolean
11
13
  }
12
14
 
13
15
  // Compares two bridges for equality, treating bridges in either direction as equivalent.
@@ -100,32 +102,36 @@ function mergeBridges(originalRows: HashiNodeData[][], bridges: PlacedBridge[]):
100
102
  return rows
101
103
  }
102
104
 
103
- export default function Game({ puzzles, hasCustomPuzzle }: GameProps) {
105
+ export default function Game({ puzzles, hasCustomPuzzle, enableSolutions }: GameProps) {
104
106
  const [puzzleIndex, setPuzzleIndex] = useState(0)
105
107
  const [showSolution, setShowSolution] = useState(false)
106
108
  const [userBridges, setUserBridges] = useState<PlacedBridge[]>([])
109
+ const [solutionReached, setSolutionReached] = useState(false)
110
+ const [gridNotConnected, setGridNotConnected] = useState(false)
107
111
 
108
112
  const handlePrev = useCallback(() => {
109
113
  setPuzzleIndex(i => i - 1)
110
114
  setShowSolution(false)
115
+ setSolutionReached(false)
116
+ setGridNotConnected(false)
111
117
  }, [])
112
118
  const handleNext = useCallback(() => {
113
119
  setPuzzleIndex(i => i + 1)
114
120
  setShowSolution(false)
121
+ setSolutionReached(false)
122
+ setGridNotConnected(false)
115
123
  }, [])
116
124
  const handleToggleSolution = useCallback(() => {
117
- setShowSolution(s => !s)
125
+ setShowSolution(s => {
126
+ if (!s) {
127
+ setUserBridges([])
128
+ setSolutionReached(false)
129
+ setGridNotConnected(false)
130
+ }
131
+ return !s
132
+ })
118
133
  }, [])
119
134
 
120
- const handleBridgePlaced = useCallback(
121
- (bridge: PlacedBridge) => {
122
- const result = toggleBridge(userBridges, bridge)
123
- setUserBridges(result.bridges)
124
- return result.erased
125
- },
126
- [userBridges]
127
- )
128
-
129
135
  const puzzle = puzzles[puzzleIndex]
130
136
  if (!puzzle) throw new Error('HashiGrid: no puzzle found')
131
137
 
@@ -137,6 +143,22 @@ export default function Game({ puzzles, hasCustomPuzzle }: GameProps) {
137
143
 
138
144
  const rows = useMemo(() => mergeBridges(originalRows, userBridges), [originalRows, userBridges])
139
145
 
146
+ const handleBridgePlaced = useCallback(
147
+ (bridge: PlacedBridge) => {
148
+ const result = toggleBridge(userBridges, bridge)
149
+ setUserBridges(result.bridges)
150
+
151
+ const mergedRows = mergeBridges(originalRows, result.bridges)
152
+ const allFilled = areAllNodesFilled(mergedRows)
153
+ const connected = isConnected(mergedRows)
154
+ setSolutionReached(allFilled && connected)
155
+ setGridNotConnected(allFilled && !connected)
156
+
157
+ return result.erased
158
+ },
159
+ [userBridges, originalRows]
160
+ )
161
+
140
162
  // Compute min and max numbers in the puzzle
141
163
  const { minNumber, maxNumber } = useMemo(() => {
142
164
  let min = 9
@@ -159,6 +181,7 @@ export default function Game({ puzzles, hasCustomPuzzle }: GameProps) {
159
181
  puzzlesLength: puzzles.length,
160
182
  rows: rows,
161
183
  showSolution,
184
+ enableSolutions,
162
185
  onPrev: handlePrev,
163
186
  onNext: handleNext,
164
187
  onToggleSolution: handleToggleSolution,
@@ -176,9 +199,12 @@ export default function Game({ puzzles, hasCustomPuzzle }: GameProps) {
176
199
  isCustomPuzzle={hasCustomPuzzle && puzzleIndex === 0}
177
200
  hasSolution={!!puzzle.solution}
178
201
  showSolution={showSolution}
202
+ enableSolutions={enableSolutions}
179
203
  selectionState={selectionState}
180
204
  minNumber={minNumber}
181
205
  maxNumber={maxNumber}
206
+ solutionReached={solutionReached}
207
+ gridNotConnected={gridNotConnected}
182
208
  />
183
209
  )
184
210
  }