@slugbugblue/trax 0.19.0 → 0.20.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/.dockerignore +11 -0
- package/.gitlab-ci.yml +90 -0
- package/.husky/pre-commit +1 -0
- package/CHANGELOG.md +10 -0
- package/CONTRIBUTING.md +113 -0
- package/Dockerfile +10 -5
- package/LICENSE +1 -1
- package/README.md +25 -7
- package/docs/analyst.md +13 -2
- package/package.json +13 -3
- package/src/analyst.js +52 -29
- package/src/cmds/import-export.js +9 -2
- package/src/cmds/notes.js +3 -0
- package/src/cmds/play-try.js +8 -2
- package/src/cmds/puzzles.js +26 -7
- package/src/cmds/suggest.js +5 -2
- package/src/puzzles.js +285 -16
- package/src/threats.js +1 -1
- package/src/types.d.ts +4 -2
- package/src/utils.js +22 -0
- package/src/version.js +1 -1
- package/benchmark/benchmarks.json +0 -60
- package/benchmark/puzzles.bench.js +0 -190
package/.dockerignore
ADDED
package/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
include:
|
|
2
|
+
- template: Security/SAST.gitlab-ci.yml
|
|
3
|
+
- template: Security/Secret-Detection.gitlab-ci.yml
|
|
4
|
+
|
|
5
|
+
stages:
|
|
6
|
+
- test
|
|
7
|
+
- sast
|
|
8
|
+
- benchmark
|
|
9
|
+
- release
|
|
10
|
+
|
|
11
|
+
sast:
|
|
12
|
+
stage: sast
|
|
13
|
+
|
|
14
|
+
nodejs-scan-sast:
|
|
15
|
+
allow_failure: false
|
|
16
|
+
rules:
|
|
17
|
+
- if: "$SAST_DISABLED"
|
|
18
|
+
when: never
|
|
19
|
+
- if: "$SAST_EXCLUDED_ANALYZERS =~ /nodejs-scan/"
|
|
20
|
+
when: never
|
|
21
|
+
- if: "$CI_COMMIT_BRANCH == 'main'"
|
|
22
|
+
exists:
|
|
23
|
+
- "**/package.json"
|
|
24
|
+
|
|
25
|
+
semgrep-sast:
|
|
26
|
+
allow_failure: false
|
|
27
|
+
rules:
|
|
28
|
+
- if: "$SAST_DISABLED"
|
|
29
|
+
when: never
|
|
30
|
+
- if: "$SAST_EXCLUDED_ANALYZERS =~ /semgrep/"
|
|
31
|
+
when: never
|
|
32
|
+
- if: "$CI_COMMIT_BRANCH == 'main'"
|
|
33
|
+
exists:
|
|
34
|
+
- "**/*.js"
|
|
35
|
+
|
|
36
|
+
secret_detection:
|
|
37
|
+
allow_failure: false
|
|
38
|
+
|
|
39
|
+
code-coverage:
|
|
40
|
+
stage: test
|
|
41
|
+
image: node:lts-alpine
|
|
42
|
+
cache:
|
|
43
|
+
key: lts
|
|
44
|
+
paths:
|
|
45
|
+
- node_modules/
|
|
46
|
+
script:
|
|
47
|
+
- node --version
|
|
48
|
+
- npm install
|
|
49
|
+
- npm test
|
|
50
|
+
coverage: /^All files\s*\|\s*([\d.]+)/
|
|
51
|
+
|
|
52
|
+
node16:
|
|
53
|
+
stage: test
|
|
54
|
+
image: node:16.15.0-alpine
|
|
55
|
+
cache:
|
|
56
|
+
key: node16
|
|
57
|
+
paths:
|
|
58
|
+
- node_modules/
|
|
59
|
+
script:
|
|
60
|
+
- node --version
|
|
61
|
+
- npm install
|
|
62
|
+
- npm test
|
|
63
|
+
|
|
64
|
+
benchmark:
|
|
65
|
+
stage: benchmark
|
|
66
|
+
image: node:lts-alpine
|
|
67
|
+
cache:
|
|
68
|
+
key: lts
|
|
69
|
+
paths:
|
|
70
|
+
- node_modules/
|
|
71
|
+
rules:
|
|
72
|
+
- if: $CI_COMMIT_TAG =~ /^v\d\+\.\d+\.\d+$/
|
|
73
|
+
script:
|
|
74
|
+
- npm run benchmark
|
|
75
|
+
- awk '/^## /{p++}p==1;p==2{exit}' CHANGELOG.md > RELEASE.$CI_COMMIT_TAG.md
|
|
76
|
+
artifacts:
|
|
77
|
+
paths:
|
|
78
|
+
- RELEASE.$CI_COMMIT_TAG.md
|
|
79
|
+
expire_in: 1 day
|
|
80
|
+
|
|
81
|
+
release:
|
|
82
|
+
stage: release
|
|
83
|
+
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
|
84
|
+
rules:
|
|
85
|
+
- if: $CI_COMMIT_TAG =~ /^v\d\+\.\d+\.\d+$/
|
|
86
|
+
script:
|
|
87
|
+
- echo "Releasing $CI_COMMIT_TAG"
|
|
88
|
+
release:
|
|
89
|
+
tag_name: $CI_COMMIT_TAG
|
|
90
|
+
description: ./RELEASE.$CI_COMMIT_TAG.md
|
package/.husky/pre-commit
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @slugbugblue/trax
|
|
2
2
|
|
|
3
|
+
## 0.20.0 - 2023-02-11
|
|
4
|
+
|
|
5
|
+
- Provide timing feedback on the puzzlebot's actions
|
|
6
|
+
- Add license information for puzzle sources
|
|
7
|
+
- Add gitlab CI configuration
|
|
8
|
+
- Drop support for node 14; node v16.15 is our minimum supported version
|
|
9
|
+
- More puzzles from Martin M. S. Pedersen -- bringing the total to 100+
|
|
10
|
+
- Enhance the docker configuration and document it in the README
|
|
11
|
+
- Refine faulty threat detection and add test for once case it was failing
|
|
12
|
+
|
|
3
13
|
## 0.19.0 - 2023-02-06
|
|
4
14
|
|
|
5
15
|
- Added a simple LRU cache to increase the efficiency of the analysis engine
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Contributing to slugbugblue/trax
|
|
2
|
+
|
|
3
|
+
Hi, thanks for considering contributing to this project.
|
|
4
|
+
|
|
5
|
+
If this is your first time working on an open source project, it is recommended
|
|
6
|
+
that you start by reading [How to Contribute to Open Source][howto].
|
|
7
|
+
|
|
8
|
+
## Ways to contribute
|
|
9
|
+
|
|
10
|
+
### Improve documentation
|
|
11
|
+
|
|
12
|
+
One of the simplest ways to get started contributing is to find areas of the
|
|
13
|
+
documentation that are unclear, incorrect, or that have typos or other errors,
|
|
14
|
+
and providing feedback and corrections. You don't have to know how to code or
|
|
15
|
+
even how to use git.
|
|
16
|
+
|
|
17
|
+
### Identify bugs
|
|
18
|
+
|
|
19
|
+
If you are aware of something not working as you think it should, feel free to
|
|
20
|
+
open a new issue to let us know. Browsing through the existing open issues and
|
|
21
|
+
adding comments or making clarifications can also be a great way to get started.
|
|
22
|
+
|
|
23
|
+
### Fixing bugs
|
|
24
|
+
|
|
25
|
+
If you feel you have a solution to an existing bug, you are welcome to code up
|
|
26
|
+
your solution and submit a merge request. It may take some time to get fully up
|
|
27
|
+
to speed with our coding styles and with the structure of the code, so don't
|
|
28
|
+
feel bad if your merge request receives comments rather than being immediately
|
|
29
|
+
accepted. Simply make any necessary changes, and update the merge request, or
|
|
30
|
+
let us know in a subsequent comment if you think we are misunderstanding the
|
|
31
|
+
merge request.
|
|
32
|
+
|
|
33
|
+
## Submitting an issue
|
|
34
|
+
|
|
35
|
+
If you choose to submit an issue, please take the time to ensure that you
|
|
36
|
+
provide as much detail as possible about the issue. A poorly worded or poorly
|
|
37
|
+
researched issue report will unnecessarily delay the issue resolution.
|
|
38
|
+
|
|
39
|
+
Even if you don't have the time or expertise to fully diagnose the problem, a
|
|
40
|
+
good issue report can be very helpful to us as we seek to improve the code.
|
|
41
|
+
|
|
42
|
+
## Submitting a merge request
|
|
43
|
+
|
|
44
|
+
Good merge requests are a fantastic way to contribute to this project. Your
|
|
45
|
+
merge request is going to be accepted more quickly if it is easy to understand.
|
|
46
|
+
Ideally it should be focused on a single item and avoid unrelated commits.
|
|
47
|
+
|
|
48
|
+
Please ask first before embarking on a major change, such as adding a new
|
|
49
|
+
feature or refactoring the code, otherwise you risk spending a significant
|
|
50
|
+
amount of time working on something that will not be merged into the project.
|
|
51
|
+
|
|
52
|
+
If you have never submitted a merge request before, no fear. Here is a [quick
|
|
53
|
+
tutorial][howto-mr] on how to create one.
|
|
54
|
+
|
|
55
|
+
### Merge request steps
|
|
56
|
+
|
|
57
|
+
- Fork the project and clone it to your own workspace.
|
|
58
|
+
- Get the latest changes and update the project dependencies:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
$ git checkout develop
|
|
62
|
+
$ git pull upstream develop
|
|
63
|
+
$ rm -rf node_modules
|
|
64
|
+
$ npm install
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- Create a new branch to contain your changes:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
$ git checkout -b <new-branch-name>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- Make your changes.
|
|
74
|
+
- Test your changes.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
$ npm test
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- Push your changes back to your fork.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
$ git push -u origin <new-branch-name>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
1. Open a merge request in the [slugbugblue/trax][trax-repo] repo.
|
|
87
|
+
|
|
88
|
+
## Working with the code
|
|
89
|
+
|
|
90
|
+
This repository uses [XO][xo] for linting and [Prettier][prettier] for
|
|
91
|
+
formatting. Running `npm test` will produce an error if your changes do not
|
|
92
|
+
conform. You should consider installing the [XO plugin][xo-plugin] for your
|
|
93
|
+
editor to have the linting errors highlighted for you and automatically fixed on
|
|
94
|
+
save. You can also run `npx xo --fix` from the command line to fix most linting
|
|
95
|
+
errors.
|
|
96
|
+
|
|
97
|
+
This repository uses [AVA][ava] for writing and running tests. While we want as
|
|
98
|
+
much code coverage as possible for all code, we have a minimum requirement of
|
|
99
|
+
95% of code coverage for the [analysis engine][analyst] and 100% for the [main
|
|
100
|
+
trax engine][engine]. You should be able to get a quick grasp on the testing
|
|
101
|
+
process by simply examining a few of the test files in the [test
|
|
102
|
+
directory][test].
|
|
103
|
+
|
|
104
|
+
[analyst]: src/analyst.js
|
|
105
|
+
[ava]: https://github.com/avajs/ava
|
|
106
|
+
[engine]: src/engine.js
|
|
107
|
+
[howto]: https://opensource.guide/how-to-contribute/
|
|
108
|
+
[howto-mr]: https://opensource.guide/how-to-contribute/#opening-a-pull-request
|
|
109
|
+
[prettier]: https://prettier.io
|
|
110
|
+
[test]: test/
|
|
111
|
+
[trax-repo]: https://gitlab.com/slugbugblue/trax
|
|
112
|
+
[xo]: https://github.com/sidresorhus/xo
|
|
113
|
+
[xo-plugin]: https://github.com/sidresorhus/xo#editor-plugins
|
package/Dockerfile
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# Use the latest nodejs long-term-support release,
|
|
2
2
|
# on the latest Alpine Linux for its small footprint
|
|
3
3
|
FROM node:lts-alpine
|
|
4
|
-
# Install current source code
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
RUN npm install
|
|
4
|
+
# Install current source code
|
|
5
|
+
WORKDIR /trax
|
|
6
|
+
COPY . .
|
|
7
|
+
RUN npm install
|
|
8
|
+
# Ensure the data files can be persisted
|
|
9
|
+
WORKDIR /mnt/data
|
|
10
|
+
ENV XDG_CONFIG_HOME=/mnt/data
|
|
11
|
+
ENV XDG_DATA_HOME=/mnt/data
|
|
12
|
+
VOLUME /mnt/data
|
|
8
13
|
# Use the trax CLI as the starting point
|
|
9
|
-
ENTRYPOINT ["/
|
|
14
|
+
ENTRYPOINT ["/trax/src/cli.js"]
|
package/LICENSE
CHANGED
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright 2021 Chad Transtrum
|
|
189
|
+
Copyright © 2021-2023 Chad Transtrum
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
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 100 puzzles to help hone your Trax skills
|
|
8
8
|
- Command-line interface for ease of use
|
|
9
9
|
|
|
10
10
|
## Description
|
|
@@ -29,6 +29,9 @@ npm install @slugbugblue/trax
|
|
|
29
29
|
|
|
30
30
|
# Or install globally to use the trax CLI more easily
|
|
31
31
|
npm install -g @slugbugblue/trax
|
|
32
|
+
|
|
33
|
+
# Or create a docker image
|
|
34
|
+
docker build -t trax .
|
|
32
35
|
```
|
|
33
36
|
|
|
34
37
|
## CLI Usage
|
|
@@ -37,6 +40,19 @@ The command-line interface is provided as `trax`, available in your path if
|
|
|
37
40
|
installed globally, or in the `node_modules/.bin` directory and runnable using
|
|
38
41
|
`npx trax` if installed locally.
|
|
39
42
|
|
|
43
|
+
> If you installed by creating a Docker image, use the following command to
|
|
44
|
+
> launch the CLI:
|
|
45
|
+
>
|
|
46
|
+
> - `docker run --name trax -itv $HOME/trax:/mnt/data --rm trax`
|
|
47
|
+
>
|
|
48
|
+
> Since that's a bit much to type, it's recommended to create an alias in one of
|
|
49
|
+
> your startup scripts:
|
|
50
|
+
>
|
|
51
|
+
> - `alias trax="docker run --name trax -itv $HOME/trax:/mnt/data --rm trax"`
|
|
52
|
+
>
|
|
53
|
+
> Then you can launch the docker container simply by typing in `trax`, the same
|
|
54
|
+
> as if it were installed globally.
|
|
55
|
+
|
|
40
56
|
The CLI can be used interactively by running the command with no parameters or
|
|
41
57
|
by passing the `--interactive` or `-i` flag. When run interactively, you can
|
|
42
58
|
avoid worries about the need to escape certain characters from the shell, and it
|
|
@@ -161,6 +177,8 @@ traxgame.com puzzles page][traxpuzzles], and [puzzles curated][puzzlebook] by
|
|
|
161
177
|
## Roadmap
|
|
162
178
|
|
|
163
179
|
- Improve the threats database / analysis engine.
|
|
180
|
+
- Bot should be able to win all puzzles.
|
|
181
|
+
- Analysis should recognize line threats.
|
|
164
182
|
- Add more puzzles.
|
|
165
183
|
|
|
166
184
|
## Support
|
|
@@ -170,8 +188,8 @@ Issues can be opened on the [gitlab project page][repo].
|
|
|
170
188
|
|
|
171
189
|
## Contributing
|
|
172
190
|
|
|
173
|
-
Contributions are welcome. Ideally in the form of pull requests,
|
|
174
|
-
to open an issue with a bug report or a suggestion as well.
|
|
191
|
+
[Contributions are welcome][contributing]. Ideally in the form of pull requests,
|
|
192
|
+
but feel free to open an issue with a bug report or a suggestion as well.
|
|
175
193
|
|
|
176
194
|
## Acknowledgments
|
|
177
195
|
|
|
@@ -188,10 +206,9 @@ always ready to share his insights on the mechanics of the game, as well as
|
|
|
188
206
|
various approaches to encoding its complexities. His undeserved kindness and
|
|
189
207
|
infinite patience in indulging my many questions can never be repaid.
|
|
190
208
|
|
|
191
|
-
Thanks
|
|
192
|
-
puzzles
|
|
193
|
-
|
|
194
|
-
expertise.
|
|
209
|
+
Thanks to [Martin M. S. Pedersen][traxplayer] for contributing ideas, code, and
|
|
210
|
+
puzzles. He's been [curating Trax puzzles][puzzlebook] and [coding solutions for
|
|
211
|
+
Trax][gnutrax] for over two decades, and brings a lot of expertise.
|
|
195
212
|
|
|
196
213
|
## License
|
|
197
214
|
|
|
@@ -218,6 +235,7 @@ The official rules for Trax are found at http://traxgame.com/about_rules.php.
|
|
|
218
235
|
As Trax is a proprietary game, these rules are the intellectual property of
|
|
219
236
|
David Smith and heirs, and are not to be used without permission.
|
|
220
237
|
|
|
238
|
+
[contributing]: CONTRIBUTING.md
|
|
221
239
|
[ctrans]: mailto:chad@transtrum.net
|
|
222
240
|
[dgb]: mailto:donald@traxgame.com
|
|
223
241
|
[docs-engine]: docs/engine.md
|
package/docs/analyst.md
CHANGED
|
@@ -119,11 +119,22 @@ following keys:
|
|
|
119
119
|
- `match`: The portion of the actual edge string that matched the pattern, which
|
|
120
120
|
may or may not be exactly the same as the pattern.
|
|
121
121
|
- `at`: The index of the edge string at which the pattern match began.
|
|
122
|
+
- `value`: The multiplier for this threat. Most often this is `1`, but some
|
|
123
|
+
threats can be re-formed, and this will be `2` in those cases.
|
|
122
124
|
|
|
123
125
|
##### `faulty`
|
|
124
126
|
|
|
125
|
-
|
|
126
|
-
found to be
|
|
127
|
+
An object with two keys, `w` and `b`, each of which is a list of threats that
|
|
128
|
+
were found to be fauly.
|
|
129
|
+
|
|
130
|
+
Example data:
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
{
|
|
134
|
+
w: [{ threat: 'Arw?!pW', match: 'arwpa', at: 1, level: 2, value: 1 }],
|
|
135
|
+
b: [],
|
|
136
|
+
}
|
|
137
|
+
```
|
|
127
138
|
|
|
128
139
|
##### `score`
|
|
129
140
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slugbugblue/trax",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Trax game engine and friends",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"trax",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"./puzzles": "./src/puzzles.js",
|
|
21
21
|
"./threats": "./src/threats.js",
|
|
22
22
|
"./tty": "./src/tty.js",
|
|
23
|
+
"./utils": "./src/utils.js",
|
|
23
24
|
"./version": "./src/version.js"
|
|
24
25
|
},
|
|
25
26
|
"repository": "gitlab:slugbugblue/trax",
|
|
@@ -28,10 +29,12 @@
|
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
30
31
|
"benchmark": "node benchmark/puzzles.bench.js",
|
|
32
|
+
"genversion": "genversion --es6 src/version.js",
|
|
33
|
+
"git-add": "git add src/version.js benchmark/benchmarks.json",
|
|
31
34
|
"test": "xo && c8 ava",
|
|
32
35
|
"prepare": "husky install",
|
|
33
36
|
"preversion": "npm test",
|
|
34
|
-
"version": "
|
|
37
|
+
"version": "npm run genversion ; npm run benchmark ; npm run git-add",
|
|
35
38
|
"postversion": "git push && git push --tags && npm publish"
|
|
36
39
|
},
|
|
37
40
|
"dependencies": {
|
|
@@ -49,7 +52,14 @@
|
|
|
49
52
|
},
|
|
50
53
|
"type": "module",
|
|
51
54
|
"engines": {
|
|
52
|
-
"node": "
|
|
55
|
+
"node": ">=16.15.0"
|
|
56
|
+
},
|
|
57
|
+
"c8": {
|
|
58
|
+
"branches": 95,
|
|
59
|
+
"check-coverage": true,
|
|
60
|
+
"functions": 95,
|
|
61
|
+
"lines": 95,
|
|
62
|
+
"statements": 95
|
|
53
63
|
},
|
|
54
64
|
"prettier": {
|
|
55
65
|
"bracketSpacing": true,
|
package/src/analyst.js
CHANGED
|
@@ -204,7 +204,7 @@ const label = (rawEdge) => {
|
|
|
204
204
|
const length = (array) => array?.length || 0
|
|
205
205
|
|
|
206
206
|
/** Calculate the points for the number of threats at a particular level.
|
|
207
|
-
* @arg {
|
|
207
|
+
* @arg {ColorThreats} from - the threats found for a certain player
|
|
208
208
|
* @arg {number} level - the level to calculate the points for
|
|
209
209
|
* @arg {number} [as] - if included, the level the points should be calculated as
|
|
210
210
|
* @return {number} the number of points for the level
|
|
@@ -217,16 +217,23 @@ const attackScore = (from, level, as) => {
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
/** Shortcut to get the number of attacks.
|
|
220
|
-
* @arg {
|
|
220
|
+
* @arg {ColorThreats} from - the threats found for a certain player
|
|
221
221
|
* @returns {number} the number of attacks (win on next move) present
|
|
222
222
|
*/
|
|
223
223
|
const attackCount = (from) => length(from['1'])
|
|
224
224
|
|
|
225
|
+
/** Shortcut to get the number of threats at a certain level.
|
|
226
|
+
* @arg {ColorThreats} from - the threats found for a player
|
|
227
|
+
* @arg {number} level - the level for the threats to look for
|
|
228
|
+
* @returns {number} the number of threats at the given level
|
|
229
|
+
*/
|
|
230
|
+
const threatCount = (from, level) => length(from[level])
|
|
231
|
+
|
|
225
232
|
/** Shortcut to get the number of passive threats
|
|
226
|
-
* @arg {
|
|
233
|
+
* @arg {ColorThreats} from - the threats found for a player
|
|
227
234
|
* @returns {number} the number of passive threats (win in 2 or more moves)
|
|
228
235
|
*/
|
|
229
|
-
const
|
|
236
|
+
const passiveCount = (from) => {
|
|
230
237
|
let count = 0
|
|
231
238
|
for (const [depth, array] of Object.entries(from)) {
|
|
232
239
|
count += int(depth) > 1 ? length(array) : 0
|
|
@@ -236,23 +243,29 @@ const threatCount = (from) => {
|
|
|
236
243
|
}
|
|
237
244
|
|
|
238
245
|
/** Shortcut to determine if we have an active attack.
|
|
239
|
-
* @arg {
|
|
246
|
+
* @arg {ColorThreats} from - the threats found for a player
|
|
240
247
|
* @returns {boolean}
|
|
241
248
|
*/
|
|
242
249
|
const activeAttack = (from) =>
|
|
243
|
-
attackCount(from) > 1 || (attackCount(from) === 1 &&
|
|
250
|
+
attackCount(from) > 1 || (attackCount(from) === 1 && passiveCount(from) > 0)
|
|
244
251
|
|
|
245
252
|
/** Shortcut to determine if we have a passive attack.
|
|
246
|
-
* @arg {
|
|
253
|
+
* @arg {ColorThreats} from - the threats found for a player
|
|
247
254
|
* @returns {boolean}
|
|
248
255
|
*/
|
|
249
|
-
const passiveAttack = (from) =>
|
|
256
|
+
const passiveAttack = (from) => passiveCount(from) > 1
|
|
257
|
+
|
|
258
|
+
// /** Shortcut to determine if we are under attack.
|
|
259
|
+
// * @arg {ColorThreats} from - the threats found for a player
|
|
260
|
+
// * @returns {boolean}
|
|
261
|
+
// */
|
|
262
|
+
// const underAttack = (from) => attackCount(from) > 0
|
|
250
263
|
|
|
251
|
-
/** Shortcut to determine if we are under
|
|
252
|
-
* @arg {
|
|
264
|
+
/** Shortcut to determine if we are under threat.
|
|
265
|
+
* @arg {ColorThreats} from - the threats found for a player
|
|
253
266
|
* @returns {boolean}
|
|
254
267
|
*/
|
|
255
|
-
const
|
|
268
|
+
const underThreat = (from) => attackCount(from) + passiveCount(from) > 0
|
|
256
269
|
|
|
257
270
|
// Analysis class
|
|
258
271
|
export class Analysis {
|
|
@@ -282,6 +295,10 @@ export class Analysis {
|
|
|
282
295
|
b: (level) => this.threats.b[level]?.length || 0,
|
|
283
296
|
}
|
|
284
297
|
this.#partial = Boolean(partial)
|
|
298
|
+
if (this.#moves.length === 0 || game.over) {
|
|
299
|
+
this.#edge = { b: '', w: '', rawEdge: [] }
|
|
300
|
+
this.#threats = { b: {}, w: {} }
|
|
301
|
+
}
|
|
285
302
|
}
|
|
286
303
|
|
|
287
304
|
get game() {
|
|
@@ -383,7 +400,7 @@ export class Analysis {
|
|
|
383
400
|
const attacking = activeAttack(inactive)
|
|
384
401
|
|
|
385
402
|
// Was the inactive player unable to clear all threats?
|
|
386
|
-
const defending = !attacking &&
|
|
403
|
+
const defending = !attacking && underThreat(active)
|
|
387
404
|
|
|
388
405
|
// Does the inactive player have multiple threats
|
|
389
406
|
const threatening = !attacking && passiveAttack(inactive)
|
|
@@ -559,15 +576,17 @@ export class Analysis {
|
|
|
559
576
|
* @arg {string} color - the player color
|
|
560
577
|
* @returns {Point[]} - locations relevant to the threat
|
|
561
578
|
*/
|
|
562
|
-
|
|
579
|
+
threatLocs(threat, color) {
|
|
563
580
|
const { edge } = this.edge
|
|
564
581
|
const max = edge.length
|
|
565
582
|
let index = threat.at
|
|
583
|
+
let count = 0
|
|
566
584
|
const locs = new Set([index])
|
|
567
|
-
while (threat.match.length
|
|
585
|
+
while (count < threat.match.length) {
|
|
568
586
|
index += 1
|
|
569
587
|
if (index >= max) index -= max
|
|
570
588
|
locs.add(index)
|
|
589
|
+
count += 1
|
|
571
590
|
// Check to see if we have a connectable pair
|
|
572
591
|
if (edge[index]['p' + color]) {
|
|
573
592
|
const line = 'n' + color
|
|
@@ -583,16 +602,15 @@ export class Analysis {
|
|
|
583
602
|
}
|
|
584
603
|
}
|
|
585
604
|
|
|
586
|
-
|
|
587
|
-
return value
|
|
605
|
+
return [...locs].map((i) => new Point(edge[i] || { x: 0, y: 0 }))
|
|
588
606
|
}
|
|
589
607
|
|
|
590
608
|
/** Apply the threats to a color's regex-ready edge.
|
|
591
609
|
* @arg {Color} color - the color to find threats for
|
|
592
|
-
* @returns {
|
|
610
|
+
* @returns {ColorThreats}
|
|
593
611
|
*/
|
|
594
612
|
#applyThreats(color) {
|
|
595
|
-
/** @type {
|
|
613
|
+
/** @type {ColorThreats} */
|
|
596
614
|
const matches = {}
|
|
597
615
|
const { length } = this.edge[color]
|
|
598
616
|
const edge = this.edge[color].repeat(2)
|
|
@@ -616,7 +634,7 @@ export class Analysis {
|
|
|
616
634
|
/** Clean up the list of matches, removing any false positives. */
|
|
617
635
|
#validateThreats() {
|
|
618
636
|
for (const bw of ['w', 'b']) {
|
|
619
|
-
/** @type
|
|
637
|
+
/** @type ColorThreats */
|
|
620
638
|
const matches = this.#threats[bw]
|
|
621
639
|
for (const key of Object.keys(matches)) {
|
|
622
640
|
const level = Number(key)
|
|
@@ -648,10 +666,12 @@ export class Analysis {
|
|
|
648
666
|
* @returns {boolean}
|
|
649
667
|
*/
|
|
650
668
|
#canActivateThreat(threat, color, level) {
|
|
651
|
-
const attacks = this.#threats[color]
|
|
652
|
-
const
|
|
653
|
-
const
|
|
654
|
-
|
|
669
|
+
const attacks = attackCount(this.#threats[color])
|
|
670
|
+
const player = Trax.playerNumber(color)
|
|
671
|
+
const other = Trax.other(color)
|
|
672
|
+
const otherAttacks = attackCount(this.#threats[other])
|
|
673
|
+
const higher = threatCount(this.#threats[color], level - 1)
|
|
674
|
+
for (const loc of this.threatLocs(threat, color)) {
|
|
655
675
|
for (const type of this.game.possibleTiles(loc)) {
|
|
656
676
|
const game = new Trax(this.game.rules, this.game.moves)
|
|
657
677
|
const result = game.dropTile(type, loc)
|
|
@@ -659,20 +679,23 @@ export class Analysis {
|
|
|
659
679
|
|
|
660
680
|
// This might be a bad shortcut, but if we already have an attack here
|
|
661
681
|
// we just assume that this threat is also valid ...
|
|
662
|
-
if (game.over && game.turn ===
|
|
682
|
+
if (game.over && game.turn === player) return true
|
|
683
|
+
|
|
684
|
+
// Otherwise, we cannot evaluate an ended game
|
|
685
|
+
if (game.over) continue
|
|
663
686
|
|
|
664
687
|
const { threats } = analyze(game, true)
|
|
665
688
|
|
|
666
689
|
// If we give our opponent an attack, this is a faulty move
|
|
667
|
-
const faulty = (threats[
|
|
690
|
+
const faulty = attackCount(threats[other]) - otherAttacks
|
|
668
691
|
if (faulty > 0) continue
|
|
669
692
|
|
|
670
693
|
// Activating the threat means we get an attack and a threat
|
|
671
|
-
const newAttacks = (threats[color]
|
|
694
|
+
const newAttacks = attackCount(threats[color]) - attacks
|
|
672
695
|
if (level === 2) {
|
|
673
696
|
if (newAttacks > 1) return true
|
|
674
697
|
} else {
|
|
675
|
-
const newThreats = (threats[color]
|
|
698
|
+
const newThreats = threatCount(threats[color], level - 1) - higher
|
|
676
699
|
if (newAttacks > 0 && newThreats > 0) return true
|
|
677
700
|
}
|
|
678
701
|
}
|
|
@@ -766,7 +789,7 @@ export class Suggestion {
|
|
|
766
789
|
}
|
|
767
790
|
}
|
|
768
791
|
|
|
769
|
-
export const suggest = (game) => {
|
|
792
|
+
export const suggest = (game, partial = false) => {
|
|
770
793
|
const start = Date.now()
|
|
771
794
|
const analyzed = {}
|
|
772
795
|
const { color, moves, rules } = game
|
|
@@ -775,7 +798,7 @@ export const suggest = (game) => {
|
|
|
775
798
|
trax.play(move)
|
|
776
799
|
const unique = trax.normalized
|
|
777
800
|
if (!analyzed[unique]) {
|
|
778
|
-
const analysis = analyze(trax)
|
|
801
|
+
const analysis = analyze(trax, partial)
|
|
779
802
|
analyzed[unique] = { move, score: analysis.scores[color], analysis }
|
|
780
803
|
}
|
|
781
804
|
}
|
|
@@ -248,7 +248,7 @@ const gameNotes = (move, notes) => {
|
|
|
248
248
|
if (moveNotes.length === 0) return ''
|
|
249
249
|
return (
|
|
250
250
|
'; ' +
|
|
251
|
-
moveNotes.map((n) => n.note.
|
|
251
|
+
moveNotes.map((n) => n.note.replace(/\n/g, '\n; ')).join('\n; ') +
|
|
252
252
|
'\n'
|
|
253
253
|
)
|
|
254
254
|
}
|
|
@@ -328,8 +328,15 @@ exportCmd.fn = (CLI, id, filename) => {
|
|
|
328
328
|
const source = sources[puzzle.src]
|
|
329
329
|
content += '; Puzzle ' + game.puzzle
|
|
330
330
|
if (source) {
|
|
331
|
-
content +=
|
|
331
|
+
content += source.copyright
|
|
332
|
+
? ' ©' + source.copyright + ' by '
|
|
333
|
+
: ' provided courtesy of '
|
|
334
|
+
content += source.name
|
|
332
335
|
if (source.url) content += '\n; ' + source.url
|
|
336
|
+
if (source.license) {
|
|
337
|
+
content += '\n; Licensed under ' + source.license
|
|
338
|
+
content += source.licenseUrl ? ' ' + source.licenseUrl : ''
|
|
339
|
+
}
|
|
333
340
|
}
|
|
334
341
|
|
|
335
342
|
content += '\n'
|
package/src/cmds/notes.js
CHANGED
|
@@ -25,6 +25,9 @@ notesCmd.fn = (CLI, id, ...note) => {
|
|
|
25
25
|
if (id.startsWith('#')) id = id.slice(1)
|
|
26
26
|
|
|
27
27
|
const game = CLI.GAMES[id]
|
|
28
|
+
|
|
29
|
+
if (!game) return CLI.error('Game not found.')
|
|
30
|
+
|
|
28
31
|
const notes = game.notes || []
|
|
29
32
|
const move = game.moves.length > 0 ? game.moves.split(' ').length : 0
|
|
30
33
|
|