@slugbugblue/trax 0.17.0 → 0.19.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.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ npm test
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @slugbugblue/trax
2
2
 
3
+ ## 0.19.0 - 2023-02-06
4
+
5
+ - Added a simple LRU cache to increase the efficiency of the analysis engine
6
+
7
+ ## 0.18.0 - 2023-02-05
8
+
9
+ - Analysis improvements:
10
+
11
+ - Add `count.b(level)` and `count.w(level)` methods to an analysis to quickly
12
+ count the number of threats at a given level
13
+ - Remove faulty threats from an analysis
14
+ - Add additional threats for more puzzle solving mojo
15
+
16
+ - Development environment enhancements:
17
+
18
+ - Added an initial benchmarking framework with puzzle benchmarks so far
19
+ - Added husky pre-commit hook to ensure npm test is run before a commit
20
+
21
+ - CLI improvements:
22
+
23
+ - Puzzle information is now stored as notes, to make it searchable
24
+
25
+ - More puzzles:
26
+
27
+ - Martin M. S. Pedersen contributed eleven new puzzles
28
+
3
29
  ## 0.17.0 - 2022-12-29
4
30
 
5
31
  - The `import` CLI command now loads comments from a .trx file
@@ -26,9 +52,9 @@
26
52
  - Adjust puzzle category levels for harder puzzles
27
53
  - Add multiple hints when the puzzle can be solved multiple ways
28
54
 
29
- - Analysis engine improvements:
55
+ - Analysis improvements:
30
56
 
31
- - Analysis engine suggestions now "solve" all tough puzzles
57
+ - Analysis suggestions now "solve" all tough puzzles
32
58
  - Reworked threats to be more precise
33
59
  - Reworked scoring to add more nuance
34
60
 
@@ -39,9 +65,9 @@
39
65
  - `trax puzzles ls`: see all the puzzles
40
66
  - `trax puzzle new`: start a new puzzle
41
67
 
42
- - Analysis engine tests now include correctly suggesting the first move for
43
- solving all tutorial and easy puzzles. As the threats database improves, we
44
- will add more puzzles to the tests.
68
+ - Analysis tests now include correctly suggesting the first move for solving all
69
+ tutorial and easy puzzles. As the threats database improves, we will add more
70
+ puzzles to the tests.
45
71
 
46
72
  ## 0.13.0 - 2022-11-25
47
73
 
@@ -134,7 +160,7 @@ variables from snake_case to camelCase.
134
160
  - `to`: the normalized position code of the board after the move
135
161
  - `via`: the move notation to go from `from` to `to`
136
162
 
137
- ### 0.3.1 - 2022-01-07
163
+ ## 0.3.1 - 2022-01-07
138
164
 
139
165
  - Make a faster implementation of `trax.possible_locations()`
140
166
  - Display updates for tentative moves
package/Dockerfile ADDED
@@ -0,0 +1,9 @@
1
+ # Use the latest nodejs long-term-support release,
2
+ # on the latest Alpine Linux for its small footprint
3
+ FROM node:lts-alpine
4
+ # Install current source code globally
5
+ COPY . /src
6
+ WORKDIR /src
7
+ RUN npm install -g @slugbugblue/trax
8
+ # Use the trax CLI as the starting point
9
+ ENTRYPOINT ["/usr/local/bin/trax"]
package/README.md CHANGED
@@ -4,7 +4,7 @@ This project aims to provide tools for working with Trax games in Javascript.
4
4
 
5
5
  - Trax game-playing engine
6
6
  - Position analysis engine
7
- - Over 50 puzzles to help hone your Trax skills
7
+ - Over 60 puzzles to help hone your Trax skills
8
8
  - Command-line interface for ease of use
9
9
 
10
10
  ## Description
@@ -153,9 +153,10 @@ threats.
153
153
 
154
154
  ### Puzzles database
155
155
 
156
- The puzzles database is provided in `puzzles.js`, and so far just contains
157
- puzzles that I've been using to test the analysis engine, along with the list of
158
- publicly available puzzles at [the traxgame.com puzzles page][traxpuzzles].
156
+ The puzzles database is provided in `puzzles.js`, and contains puzzles that I've
157
+ been using to test the analysis engine, publicly available puzzles from [the
158
+ traxgame.com puzzles page][traxpuzzles], and [puzzles curated][puzzlebook] by
159
+ [Martin M. S. Pedersen][traxplayer].
159
160
 
160
161
  ## Roadmap
161
162
 
@@ -187,9 +188,14 @@ always ready to share his insights on the mechanics of the game, as well as
187
188
  various approaches to encoding its complexities. His undeserved kindness and
188
189
  infinite patience in indulging my many questions can never be repaid.
189
190
 
191
+ Thanks for [Martin M. S. Pedersen][traxplayer] for contributing ideas, code, and
192
+ puzzles to this effort. He's been [curating Trax puzzles][puzzlebook] and
193
+ [coding solutions for Trax][gnutrax] for over two decades, and brings a lot of
194
+ expertise.
195
+
190
196
  ## License
191
197
 
192
- Copyright 2019-2022 Chad Transtrum
198
+ Copyright 2019-2023 Chad Transtrum
193
199
 
194
200
  Licensed under the Apache License, Version 2.0 (the "License"); you may not use
195
201
  the files in this project except in compliance with the License. You may obtain
@@ -217,12 +223,15 @@ David Smith and heirs, and are not to be used without permission.
217
223
  [docs-engine]: docs/engine.md
218
224
  [docs-analyst]: docs/analyst.md
219
225
  [env-path]: https://www.npmjs.com/package/env-paths
226
+ [gnutrax]: https://gnutrax.com
220
227
  [goldtoken]: https://goldtoken.com/
228
+ [puzzlebook]: https://gnutrax.com/book
221
229
  [repo]: https://gitlab.com/slugbugblue/trax
222
230
  [sbb]: https://slugbugblue.com/
223
231
  [traxbook]: http://traxgame.com/shop_book.php
224
232
  [traxgame]: http://traxgame.com/
225
233
  [traxhistory]: http://www.traxgame.com/about_history.php
234
+ [traxplayer]: mailto:traxplayer@gmail.com
226
235
  [traxpuzzles]: http://www.traxgame.com/games_puzzles.php
227
236
  [traxrules]: http://www.traxgame.com/about_rules.php
228
237
  [xdg]:
@@ -0,0 +1,60 @@
1
+ {
2
+ "0.17.0": {
3
+ "puzzles.firstmove.tutorial": {
4
+ "count": 16,
5
+ "ms": 2007
6
+ },
7
+ "puzzles.firstmove.beginner": {
8
+ "count": 16,
9
+ "ms": 2664
10
+ },
11
+ "puzzles.firstmove.tough": {
12
+ "count": 1,
13
+ "ms": 76
14
+ },
15
+ "puzzles.firstmove.total": {
16
+ "count": 33,
17
+ "ms": 4838
18
+ }
19
+ },
20
+ "0.18.0": {
21
+ "puzzles.firstmove.tutorial": {
22
+ "count": 16,
23
+ "ms": 10164
24
+ },
25
+ "puzzles.firstmove.beginner": {
26
+ "count": 15,
27
+ "ms": 17864
28
+ },
29
+ "puzzles.firstmove.tough": {
30
+ "count": 26,
31
+ "ms": 30349
32
+ },
33
+ "puzzles.firstmove.advanced": {
34
+ "count": 1,
35
+ "ms": 427
36
+ },
37
+ "puzzles.firstmove.total": {
38
+ "count": 58,
39
+ "ms": 59031
40
+ }
41
+ },
42
+ "0.19.0": {
43
+ "puzzles.firstmove.tutorial": {
44
+ "count": 16,
45
+ "ms": 9753
46
+ },
47
+ "puzzles.firstmove.beginner": {
48
+ "count": 15,
49
+ "ms": 17376
50
+ },
51
+ "puzzles.firstmove.tough": {
52
+ "count": 25,
53
+ "ms": 26380
54
+ },
55
+ "puzzles.firstmove.total": {
56
+ "count": 56,
57
+ "ms": 55167
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+
3
+ /** Let's try to solve the puzzles
4
+ * @copyright 2023
5
+ * @author Chad Transtrum <chad@transtrum.net>
6
+ * @license Apache-2.0
7
+ */
8
+
9
+ import fs from 'node:fs'
10
+ import process from 'node:process'
11
+ import { fileURLToPath } from 'node:url'
12
+
13
+ import { Trax } from '@slugbugblue/trax'
14
+ import { suggest } from '@slugbugblue/trax/analyst'
15
+ import { puzzles } from '@slugbugblue/trax/puzzles'
16
+ import { version } from '@slugbugblue/trax/version'
17
+
18
+ import * as tty from '@slugbugblue/trax/tty'
19
+
20
+ let STUMPED = false
21
+ const FINAL = {}
22
+
23
+ const record = (name, data) => {
24
+ FINAL['puzzles.firstmove.' + name] = data
25
+ }
26
+
27
+ const timeString = (ms) => {
28
+ if (ms < 1000) {
29
+ return (ms === Math.floor(ms) ? String(ms) : ms.toFixed(2)) + ' ms'
30
+ }
31
+
32
+ ms /= 1000
33
+ if (ms < 60) return ms.toFixed(3) + ' sec'
34
+
35
+ const min = Math.floor(ms / 60)
36
+ ms -= min * 60
37
+ return String(min) + ' min ' + ms.toFixed(3) + ' sec'
38
+ }
39
+
40
+ const time = (ms) => timeString(Date.now() - ms)
41
+
42
+ const out = (messages) => {
43
+ tty.outty(messages.join(' '))
44
+ }
45
+
46
+ const section = (title) => {
47
+ out([tty.color('===', 250), tty.color(title, 39), tty.color('===', 250)])
48
+ }
49
+
50
+ const success = (puzzle, ms) => {
51
+ out([tty.color('✓', 35), tty.color(puzzle, 250), tty.color(ms, 240)])
52
+ }
53
+
54
+ const puzzled = (puzzle, move) => {
55
+ tty.display(new Trax(puzzle.game, puzzle.notation))
56
+ tty.outty('Puzzled by ' + puzzle.id, 205)
57
+ out([
58
+ tty.color(' played', 250),
59
+ tty.color(move, 127),
60
+ tty.color('instead of', 250),
61
+ tty.color(puzzle.hint || puzzle.hints.join(' '), 39),
62
+ ])
63
+ STUMPED = true
64
+ }
65
+
66
+ const finalize = (name, ms, count) => {
67
+ out([
68
+ tty.color('Finished', 195),
69
+ tty.color(String(count) + ' ' + name + ' puzzles', 123),
70
+ tty.color('in ' + time(ms), 195),
71
+ ])
72
+ }
73
+
74
+ const solvePuzzleLevel = (name, level) => {
75
+ if (STUMPED) return 0
76
+ section(name + ' puzzles')
77
+ name = name.toLowerCase()
78
+ let count = 0
79
+ const begin = Date.now()
80
+ for (const puzzle of puzzles) {
81
+ if (puzzle.level !== level) continue
82
+ const start = Date.now()
83
+ const game = new Trax(puzzle.game, puzzle.notation)
84
+ const { move } = suggest(game).pick
85
+ const solved = puzzle.hint
86
+ ? move === puzzle.hint
87
+ : puzzle.hints
88
+ ? puzzle.hints.includes(move)
89
+ : false
90
+ if (solved) {
91
+ success(puzzle.id, time(start))
92
+ count++
93
+ record(name, { count, ms: Date.now() - begin })
94
+ } else {
95
+ puzzled(puzzle, move)
96
+ process.exitCode = level + 1
97
+ return count
98
+ }
99
+ }
100
+
101
+ finalize(name, begin, count)
102
+ return count
103
+ }
104
+
105
+ // Gather the benchmark data
106
+ let total = 0
107
+ const timer = Date.now()
108
+ total += solvePuzzleLevel('Tutorial', 0)
109
+ total += solvePuzzleLevel('Beginner', 1)
110
+ total += solvePuzzleLevel('Tough', 2)
111
+ total += solvePuzzleLevel('Advanced', 3)
112
+ total += solvePuzzleLevel('Genius', 4)
113
+
114
+ record('total', { count: total, ms: Date.now() - timer })
115
+ tty.outty(
116
+ 'Benchmark: ' + total + ' puzzles in ' + time(timer),
117
+ STUMPED ? 205 : 35,
118
+ )
119
+
120
+ tty.outty()
121
+
122
+ // Load the previous benchmarks
123
+ const FILE = new URL('benchmarks.json', import.meta.url)
124
+ let CONTENTS
125
+ try {
126
+ CONTENTS = fs.readFileSync(FILE, 'utf8')
127
+ } catch {
128
+ CONTENTS = '{}'
129
+ }
130
+
131
+ const BENCHMARK = JSON.parse(CONTENTS)
132
+
133
+ const PREV = Object.values(BENCHMARK).pop() || {}
134
+
135
+ // Report the benchmark data
136
+ const size = [0, 0, 0, 0, 0]
137
+ const REPORT = []
138
+ for (const name of Object.keys(FINAL)) {
139
+ const report = [
140
+ name,
141
+ String(FINAL[name].count) + ' puzzles',
142
+ 'in ' + timeString(FINAL[name].ms),
143
+ 'avg ' + timeString(FINAL[name].ms / FINAL[name].count),
144
+ ]
145
+ if (PREV[name]) {
146
+ const diff =
147
+ FINAL[name].ms / FINAL[name].count - PREV[name].ms / PREV[name].count
148
+ if (diff <= 0) {
149
+ report.push('-' + timeString(Math.abs(diff)), '35')
150
+ } else {
151
+ report.push('+' + timeString(diff), '208')
152
+ }
153
+ } else {
154
+ report.push('new', '250')
155
+ }
156
+
157
+ size[0] = Math.max(size[0], report[0].length)
158
+ size[1] = Math.max(size[1], report[1].length)
159
+ size[2] = Math.max(size[2], report[2].length)
160
+ size[3] = Math.max(size[3], report[3].length)
161
+ size[4] = Math.max(size[4], report[4].length)
162
+ REPORT.push(report)
163
+ }
164
+
165
+ for (const line of REPORT) {
166
+ out([
167
+ tty.color(line[0].padEnd(size[0]), 39),
168
+ tty.color(line[1].padStart(size[1]), 123),
169
+ tty.color(line[2].padStart(size[2]), 195),
170
+ tty.color(line[3].padEnd(size[3]), 245),
171
+ tty.color(line[4].padStart(size[4]), Number(line[5])),
172
+ ])
173
+ }
174
+
175
+ tty.outty()
176
+
177
+ // Save the benchmark data to a file
178
+ if (BENCHMARK[version]) {
179
+ tty.outty('Not overwriting existing benchmark data')
180
+ } else {
181
+ BENCHMARK[version] = FINAL
182
+ try {
183
+ fs.writeFileSync(FILE, JSON.stringify(BENCHMARK, null, 2), 'utf8')
184
+ const filename = fileURLToPath(FILE)
185
+ tty.outty('Saved benchmark data to ' + tty.color(filename, 74))
186
+ } catch (error) {
187
+ tty.outty('Error saving benchmark data', 205)
188
+ tty.outty(String(error.code) + ' ' + error.message, 127)
189
+ }
190
+ }
package/docs/analyst.md CHANGED
@@ -79,6 +79,7 @@ themselves. The following keys are present in each edge space representation:
79
79
  connectable pair of lines with this color.
80
80
  - `zb` and `zw`: The character used in the black or white string representation
81
81
  of this space.
82
+ - `idx`: The index of this edge space in the edge array.
82
83
 
83
84
  For the edge string, the following characters are used as a hopefully useful
84
85
  shorthand summary of the tiles surrounding the space. To make regex pattern
@@ -90,7 +91,7 @@ so that it appears as though all black tiles are white tiles.
90
91
  - `l`: both a black and a white line enter this space.
91
92
  - `a`: a white line enters this space, and the next white line in the edge is
92
93
  the other end of this line.
93
- - 'c': a white line enters this space, and the next white line in the edge forms
94
+ - `c`: a white line enters this space, and the next white line in the edge forms
94
95
  a connectable pair with this line.
95
96
  - `m`: both a black and a white line enter this space, and the next white line
96
97
  in the edge is the other end of the white line.
@@ -119,6 +120,11 @@ following keys:
119
120
  may or may not be exactly the same as the pattern.
120
121
  - `at`: The index of the edge string at which the pattern match began.
121
122
 
123
+ ##### `faulty`
124
+
125
+ Contains the same information as `threats` but for removed threats that were
126
+ found to be faulty.
127
+
122
128
  ##### `score`
123
129
 
124
130
  Computes a rough "score" of the current position using the list of threats. A
@@ -135,6 +141,14 @@ because the scores can change depending on who has the initiative.
135
141
 
136
142
  #### methods
137
143
 
144
+ ##### `count.w(level)`
145
+
146
+ Returns the number of threats for white at the given level.
147
+
148
+ ##### `count.b(level)`
149
+
150
+ Returns the number of threats for black at the given level.
151
+
138
152
  ##### `inCave(loc)`
139
153
 
140
154
  For any given location, returns true if that location is found within a cave.
@@ -188,7 +202,7 @@ suggestion.
188
202
 
189
203
  ## License
190
204
 
191
- Copyright 2019-2022 Chad Transtrum
205
+ Copyright 2022-2023 Chad Transtrum
192
206
 
193
207
  Licensed under the Apache License, Version 2.0 (the "License"); you may not use
194
208
  the files in this project except in compliance with the License. You may obtain
package/docs/engine.md CHANGED
@@ -42,7 +42,7 @@ class properties and methods:
42
42
  - `Trax.point(x, y)`: quick access to the [Point class][point-docs], for
43
43
  two-dimensional locations used internally by the Trax engine and which are
44
44
  required for some of the lower-level engine functions. Operates as a
45
- constructor (without `new`) and returns a Point instance.
45
+ constructor (without `new`) and returns a [Point instance][point-docs].
46
46
 
47
47
  - `Trax.colorOf(playerNumber)`: Player 1 is white and Player 2 is black. So this
48
48
  is a quick function to turn `1` into `'w'` and `2` into `'b'`. Quick and
@@ -414,7 +414,7 @@ tie.
414
414
 
415
415
  ## License
416
416
 
417
- Copyright 2019-2022 Chad Transtrum
417
+ Copyright 2019-2023 Chad Transtrum
418
418
 
419
419
  Licensed under the Apache License, Version 2.0 (the "License"); you may not use
420
420
  the files in this project except in compliance with the License. You may obtain
package/docs/lru.md ADDED
@@ -0,0 +1,151 @@
1
+ # lru.js API documentation
2
+
3
+ This module provides a simple Least Recently Used cache.
4
+
5
+ Internally, this uses a Map, which give us insertion-ordering for free. The
6
+ first item in the list is the oldest. The last item is the most recently set or
7
+ accessed. Most of the base functionality of the Map are replicated.
8
+
9
+ Since I don't need it to be super fancy, there isn't a lot of extra
10
+ functionality here.
11
+
12
+ ## Example usage
13
+
14
+ ```javascript
15
+ import { LRU } from '@slugbugblue/trax/lru.js'
16
+
17
+ const cache = new LRU(100)
18
+
19
+ cache.set('a', 1)
20
+ cache.set('b', 2)
21
+
22
+ // Get an item from the cache
23
+ console.log(cache.get('a')) // 1
24
+
25
+ // Accessing a key makes it the most recent:
26
+ console.log(cache.keys()) // [Map Iterator] { 'b', 'a' }
27
+ console.log(cache.hits) // 1
28
+
29
+ console.log(cache.get('z')) // undefined
30
+ console.log(cache.misses) // 1
31
+
32
+ console.log(cache.size) // 2
33
+ console.log(cache.capacity) // 100
34
+ ```
35
+
36
+ ## API
37
+
38
+ ### LRU class
39
+
40
+ An LRU cache is a way to remember a limited set of items, with the oldest
41
+ expiring when the capacity is reached.
42
+
43
+ #### constructor
44
+
45
+ ##### `new LRU(capacity)`
46
+
47
+ Create the cache by specifying the maximum number of items it should hold.
48
+
49
+ #### computed properties
50
+
51
+ ##### `capacity`
52
+
53
+ The maximum number of items the cache can hold. This cannot be changed after the
54
+ cache creation.
55
+
56
+ ##### `size`
57
+
58
+ The number of items currently stored in the cache.
59
+
60
+ ##### `hits`
61
+
62
+ The number of times a `get` call has found an item in the cache.
63
+
64
+ ##### `misses`
65
+
66
+ The number of times a `get` call has not found an item in the cache.
67
+
68
+ ##### `expired`
69
+
70
+ The number of times an item has been removed from the cache.
71
+
72
+ #### methods
73
+
74
+ ##### `get(key)`
75
+
76
+ Retrieve an item from the cache. If the item is found in the cache, `hits` will
77
+ be updated, the item will be marked as the most-recently-used item, and the
78
+ value will be returned. If the item is not present in the cache, `misses` will
79
+ be updated, and `undefined` will be returned.
80
+
81
+ Note that if you had stored `undefined` as the value in the cache, the return of
82
+ `undefined` doesn't necessarily represent a cache miss.
83
+
84
+ ##### `peek(key)`
85
+
86
+ Retrieve an item from the cache, if present, but without updating any of the
87
+ LRU-specific information about the retrieval attempt. The item's age will not be
88
+ updated. If the item is not present in the cache, `undefined` will be returned.
89
+
90
+ ##### `set(key, value)`
91
+
92
+ Store an item in the cache. If the item is already present in the cache, it will
93
+ be replaced and marked as the most-recently-used item.
94
+
95
+ ##### `delete(key)`
96
+
97
+ Remove an item from the cache.
98
+
99
+ ##### `clear()`
100
+
101
+ Clears all items and statistics from the cache. The maximum capacity will not
102
+ change.
103
+
104
+ ##### `entries()`
105
+
106
+ Returns an iterator that operates over all the \[key, value\] pairs stored in
107
+ the cache. The entries will be in order from oldest to newest.
108
+
109
+ ##### `has(key)`
110
+
111
+ Returns true if a key is found in the cache, false otherwise.
112
+
113
+ ##### `keys()`
114
+
115
+ Returns an iterator that operates over all the keys stored in the cache. The
116
+ keys will be returned oldest first, newest last.
117
+
118
+ ##### `values()`
119
+
120
+ Returns an iterator that operates over all the values stored in the cache. The
121
+ values will be returned oldest first, newest last.
122
+
123
+ #### Plays well with others
124
+
125
+ The LRU class also includes functionality to provide useful default results when
126
+ used in other contexts.
127
+
128
+ ##### `toString()`
129
+
130
+ Returns a string representation of the cache in the format `LRU(75 of 1000)`.
131
+ This allows simple logging calls with `console.log(cache)`.
132
+
133
+ ##### `util.inspect.custom()`
134
+
135
+ When running in the node command line interpreter, an LRU cache instance will be
136
+ pretty-printed using the magic of `util.inspect`.
137
+
138
+ ## License
139
+
140
+ Copyright 2023 Chad Transtrum
141
+
142
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
143
+ the files in this project except in compliance with the License. You may obtain
144
+ a copy of the License at
145
+
146
+ http://www.apache.org/licenses/LICENSE-2.0
147
+
148
+ Unless required by applicable law or agreed to in writing, software distributed
149
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
150
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
151
+ specific language governing permissions and limitations under the License.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slugbugblue/trax",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Trax game engine and friends",
5
5
  "keywords": [
6
6
  "trax",
@@ -14,8 +14,9 @@
14
14
  "types": "./src/types.d.ts",
15
15
  "exports": {
16
16
  ".": "./src/engine.js",
17
- "./point": "./src/point.js",
18
17
  "./analyst": "./src/analyst.js",
18
+ "./lru": "./src/lru.js",
19
+ "./point": "./src/point.js",
19
20
  "./puzzles": "./src/puzzles.js",
20
21
  "./threats": "./src/threats.js",
21
22
  "./tty": "./src/tty.js",
@@ -26,9 +27,11 @@
26
27
  "trax": "./src/cli.js"
27
28
  },
28
29
  "scripts": {
30
+ "benchmark": "node benchmark/puzzles.bench.js",
29
31
  "test": "xo && c8 ava",
32
+ "prepare": "husky install",
30
33
  "preversion": "npm test",
31
- "version": "genversion --es6 src/version.js && git add src/version.js",
34
+ "version": "genversion --es6 src/version.js && npm run benchmark || git add src/version.js benchmark/benchmarks.json",
32
35
  "postversion": "git push && git push --tags && npm publish"
33
36
  },
34
37
  "dependencies": {
@@ -40,6 +43,7 @@
40
43
  "ava": "^5.1.0",
41
44
  "c8": "^7.11.0",
42
45
  "genversion": "^3.0.2",
46
+ "husky": "^8.0.3",
43
47
  "prettier": "^2.6.1",
44
48
  "xo": "0.*"
45
49
  },