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 +4 -1
- package/README.md +1 -0
- package/package.json +2 -2
- package/src/Game.tsx +37 -11
- package/src/__tests__/Game.test.tsx +298 -97
- package/src/components/HashiGrid.tsx +17 -1
- package/src/components/HashiRow.tsx +14 -1
- package/src/components/Messages.tsx +39 -6
- package/src/components/__tests__/HashiRow.test.tsx +28 -3
- package/src/components/__tests__/Messages.test.tsx +61 -38
- package/src/index.tsx +9 -1
- package/src/utils/bridges.ts +238 -14
- package/src/utils/puzzle-encoding.ts +101 -19
- package/src/utils/usePuzzleInput.ts +4 -2
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.
|
|
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
|
-
"
|
|
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 =>
|
|
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
|
}
|