@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.
- package/.husky/pre-commit +4 -0
- package/CHANGELOG.md +32 -6
- package/Dockerfile +9 -0
- package/README.md +14 -5
- package/benchmark/benchmarks.json +60 -0
- package/benchmark/puzzles.bench.js +190 -0
- package/docs/analyst.md +16 -2
- package/docs/engine.md +2 -2
- package/docs/lru.md +151 -0
- package/package.json +7 -3
- package/src/analyst.js +139 -8
- package/src/cli.js +10 -1
- package/src/cmds/analyze.js +24 -4
- package/src/cmds/import-export.js +2 -7
- package/src/cmds/view.js +5 -2
- package/src/engine.js +8 -1
- package/src/lru.js +153 -0
- package/src/point.js +1 -1
- package/src/puzzles.js +135 -9
- package/src/threats.js +25 -4
- package/src/types.d.ts +15 -1
- package/src/version.js +1 -1
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
|
|
55
|
+
- Analysis improvements:
|
|
30
56
|
|
|
31
|
-
- Analysis
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
157
|
-
|
|
158
|
-
|
|
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-
|
|
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
|
-
-
|
|
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
|
|
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-
|
|
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.
|
|
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
|
},
|