@slugbugblue/trax 0.3.1 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slugbugblue/trax",
3
- "version": "0.3.1",
3
+ "version": "0.7.0",
4
4
  "description": "Trax game engine and friends",
5
5
  "keywords": [
6
6
  "trax",
@@ -14,16 +14,40 @@
14
14
  "exports": "./engine.js",
15
15
  "repository": "gitlab:slugbugblue/trax",
16
16
  "scripts": {
17
- "test": "tape tests.js",
17
+ "test": "xo && c8 ava",
18
18
  "preversion": "npm test",
19
19
  "postversion": "git push && git push --tags"
20
20
  },
21
+ "dependencies": {
22
+ "env-paths": "^3.0.0",
23
+ "make-dir": "^3.1.0",
24
+ "yaml": "^2.0.0-11"
25
+ },
21
26
  "devDependencies": {
22
- "eslint": "8.x",
23
- "tape": "5.x"
27
+ "ava": "^4.1.0",
28
+ "c8": "^7.11.0",
29
+ "prettier": "^2.6.1",
30
+ "xo": "^0.48.0"
24
31
  },
25
32
  "type": "module",
26
33
  "engines": {
27
34
  "node": "^14.14.0 || >=16.0.0"
35
+ },
36
+ "prettier": {
37
+ "bracketSpacing": true,
38
+ "proseWrap": "always",
39
+ "semi": false,
40
+ "singleQuote": true,
41
+ "trailingComma": "all",
42
+ "useTabs": false
43
+ },
44
+ "xo": {
45
+ "prettier": true,
46
+ "rules": {
47
+ "curly": [
48
+ "error",
49
+ "multi-line"
50
+ ]
51
+ }
28
52
  }
29
53
  }
package/point.js ADDED
@@ -0,0 +1,116 @@
1
+ /* Copyright 2022 Chad Transtrum
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+ const is = (v, s) => (typeof v).slice(0, 3) === s // Sigh
17
+
18
+ // Are you tired of doing x,y movements in multiple statements? I am.
19
+
20
+ // Point: an immutable two-dimensional location (x,y)
21
+ // with functions for moving around quickly to neighboring points
22
+ export const Point = class {
23
+ #x = 0
24
+ #y = 0
25
+
26
+ constructor(x, y) {
27
+ if (is(x, 'obj') && is(x.x, 'num') && is(x.y, 'num')) {
28
+ // You can call this with new Point(point) or new Point({x,y})
29
+ this.#x = x.x
30
+ this.#y = x.y
31
+ } else {
32
+ // Or with new Point(x, y)
33
+ if (!is(x, 'num')) throw new SyntaxError('x must be a number')
34
+ if (!is(y, 'num')) throw new SyntaxError('y must be a number')
35
+ this.#x = x
36
+ this.#y = y
37
+ }
38
+ }
39
+
40
+ get x() {
41
+ return this.#x
42
+ }
43
+
44
+ set x(_) {
45
+ throw new ReferenceError('Point is immutable')
46
+ }
47
+
48
+ get y() {
49
+ return this.#y
50
+ }
51
+
52
+ set y(_) {
53
+ throw new ReferenceError('Point is immutable')
54
+ }
55
+
56
+ get up() {
57
+ return new Point(this.#x, this.#y - 1)
58
+ }
59
+
60
+ get down() {
61
+ return new Point(this.#x, this.#y + 1)
62
+ }
63
+
64
+ get left() {
65
+ return new Point(this.#x - 1, this.#y)
66
+ }
67
+
68
+ get right() {
69
+ return new Point(this.#x + 1, this.#y)
70
+ }
71
+
72
+ get around() {
73
+ return [this.up, this.down, this.left, this.right]
74
+ }
75
+
76
+ dir(direction) {
77
+ const error = 'direction must be one of: up, down, left, right, u, d, l, r'
78
+ if (!is(direction, 'str')) throw new SyntaxError(error)
79
+ if (direction[0] === 'u') return this.up
80
+ if (direction[0] === 'd') return this.down
81
+ if (direction[0] === 'l') return this.left
82
+ if (direction[0] === 'r') return this.right
83
+ throw new SyntaxError(error)
84
+ }
85
+
86
+ distX(from) {
87
+ return Math.abs(this.#x - from.x)
88
+ }
89
+
90
+ distY(from) {
91
+ return Math.abs(this.#y - from.y)
92
+ }
93
+
94
+ distance(from) {
95
+ return Math.hypot(this.distX(from), this.distY(from))
96
+ }
97
+
98
+ eq(that) {
99
+ return this.#x === that.x && this.#y === that.y
100
+ }
101
+
102
+ // Play nicely with the rest of the world
103
+
104
+ toString() {
105
+ return 'Point(' + this.#x + ',' + this.#y + ')'
106
+ }
107
+
108
+ toJSON() {
109
+ return { x: this.#x, y: this.#y }
110
+ }
111
+
112
+ [Symbol.for('nodejs.util.inspect.custom')](_, options) {
113
+ const [y, s, n] = [options.stylize, 'special', 'number']
114
+ return `${y('Point', s)}(${y(this.#x, n)},${y(this.#y, n)})`
115
+ }
116
+ }
package/tty.js CHANGED
@@ -1,4 +1,4 @@
1
- /* Copyright 2021 Chad Transtrum
1
+ /* Copyright 2021-2022 Chad Transtrum
2
2
  *
3
3
  * Licensed under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License.
@@ -15,13 +15,15 @@
15
15
 
16
16
  // Ever wanted to draw Trax boards on the console? I have.
17
17
 
18
- import Trax from './engine.js'
18
+ import process from 'node:process'
19
19
 
20
- const esc = '\x1b[' // start of console color escape code
21
- const off = esc + 'm' // color reset code
20
+ import { Trax } from './engine.js'
21
+
22
+ const esc = '\u001B[' // Start of console color escape code
23
+ const off = esc + 'm' // Color reset code
22
24
 
23
25
  // display a message with optional color
24
- const out = (message, fgcolor, bgcolor) => {
26
+ export const outty = (message, fgcolor, bgcolor) => {
25
27
  let ansi = fgcolor || bgcolor ? esc : ''
26
28
  if (fgcolor) ansi += '38;5;' + fgcolor
27
29
  if (bgcolor) ansi += '48;5;' + bgcolor
@@ -29,10 +31,11 @@ const out = (message, fgcolor, bgcolor) => {
29
31
  console.log(ansi + message + off)
30
32
  }
31
33
 
32
- let set = 'trax' // which color set should we draw with?
34
+ let set = 'trax' // Which color set should we draw with?
33
35
 
34
36
  // a little magic to display the game in progress
35
- const color_sets = { // Colors for terminal sequence: <Esc>[38;5;<color_num>m
37
+ const colorSets = {
38
+ // Colors for terminal sequence: <Esc>[38;5;<color_num>m
36
39
  // b, B, w, W: color of line for each player, capital for a winning line
37
40
  // e: empty (ie, board) color, t: tile color
38
41
  trax: { b: 238, B: 57, e: 19, t: 74, w: 255, W: 184 },
@@ -41,149 +44,179 @@ const color_sets = { // Colors for terminal sequence: <Esc>[38;5;<color_num>m
41
44
  tentative: { b: 235, B: 18, e: 19, t: 244, w: 252, W: 217 },
42
45
  }
43
46
 
44
- const ln = 'm\u2584' // lower half block -- with the leading m to terminate ansi color escape
45
- const up = 'm\u2588' // full block
46
- const lt = 'm\u2599' // lower half and left half block
47
- const rt = 'm\u259f' // lower half and right half block
48
- const fg = n => '38;5;' + String(set[n]) // foreground color_num
49
- const bg = n => '48;5;' + String(set[n]) // background color_num
50
- const sp = () => esc + bg('e') + 'm ' // lower space
47
+ const ln = 'm\u2584' // Lower half block -- with the leading m to terminate ansi color escape
48
+ const up = 'm\u2588' // Full block
49
+ const lt = 'm\u2599' // Lower half and left half block
50
+ const rt = 'm\u259F' // Lower half and right half block
51
+ const fg = (n) => '38;5;' + String(set[n]) // Foreground color_num
52
+ const bg = (n) => '48;5;' + String(set[n]) // Background color_num
53
+ const sp = () => esc + bg('e') + 'm ' // Lower space
51
54
  // tiles are displayed on two lines; the top half of the first line is empty
52
55
  // and the second half is composed of three characters, up color surrounded by tile color
53
- const uptty = clr => esc + fg('t') + ';' + bg('e') + ln
54
- + esc + fg(clr) + ln + esc + fg('t') + ln + ' '
55
- // the top half of the second line has both horizontal lines with a center transition
56
+ const uptty = (clr) => {
57
+ const left = esc + fg('t') + ';' + bg('e') + ln
58
+ const center = esc + fg(clr) + ln
59
+ const right = esc + fg('t') + ln + ' '
60
+ return left + center + right
61
+ }
62
+
63
+ // The top half of the second line has both horizontal lines with a center transition
56
64
  // while the bottom half is the tile background color surrounding the down color
57
- const horiztty = clr => esc + fg('t') + ';' + bg(clr) + ln
58
- const dntty = (clr, oppclr, char) => esc + fg(clr) + (char === up ? '' : ';' + bg(oppclr)) + char
59
- // list of icon_char => tile line colors for easy reference in order up, right, down, left
60
- const tiles = { a: 'bbww', b: 'bwbw', c: 'bwwb', d: 'wbbw', e: 'wbwb', f: 'wwbb' }
65
+ const horiztty = (clr) => esc + fg('t') + ';' + bg(clr) + ln
66
+ const dntty = (clr, oppclr, char) =>
67
+ esc + fg(clr) + (char === up ? '' : ';' + bg(oppclr)) + char
68
+ // List of icon_char => tile line colors for easy reference in order up, right, down, left
69
+ const tiles = {
70
+ a: 'bbww',
71
+ b: 'bwbw',
72
+ c: 'bwwb',
73
+ d: 'wbbw',
74
+ e: 'wbwb',
75
+ f: 'wwbb',
76
+ }
61
77
 
62
- // return the ANSI codes to display a 3x1.5 block tile, one line at a time
63
- const tile_tty = (tile, top) => { // top: true for first line, false for second
78
+ // Return the ANSI codes to display a 3x1.5 block tile, one line at a time
79
+ const tileTty = (tile, top) => {
80
+ // Top: true for first line, false for second
64
81
  // quick access to tile color and its opposite in upper case
65
- let colors = { b: 'b', w: 'w', B: 'w', W: 'b' }
66
- let save = set
67
- let tentative = tile !== tile.toLowerCase()
68
- if (tentative) set = color_sets.tentative
82
+ const colors = { b: 'b', w: 'w', B: 'w', W: 'b' }
83
+ const save = set
84
+ const tentative = tile !== tile.toLowerCase()
85
+ if (tentative) set = colorSets.tentative
69
86
  tile = tile.toLowerCase()
70
- if (!(tile in tiles)) { // winning paths are represented by adding 6 to the tile char code
71
- tile = String.fromCharCode(tile.charCodeAt(0) - 6)
87
+ if (!(tile in tiles)) {
88
+ // Winning paths are represented by adding 6 to the tile char code
89
+ tile = String.fromCodePoint(tile.codePointAt(0) - 6)
72
90
  if (tile in tiles) {
73
91
  colors.w = 'W'
74
92
  colors.B = 'W'
75
- } else { // and another six for a black win
76
- tile = String.fromCharCode(tile.charCodeAt(0) - 6)
93
+ } else {
94
+ // And another six for a black win
95
+ tile = String.fromCodePoint(tile.codePointAt(0) - 6)
77
96
  colors.b = 'B'
78
97
  colors.W = 'B'
79
98
  }
80
99
  }
81
- let val = false
100
+
101
+ let value = false
82
102
  if (tile in tiles) {
83
103
  tile = tiles[tile]
84
104
  if (top) {
85
- val = uptty(colors[tile[0]])
105
+ value = uptty(colors[tile[0]])
86
106
  } else {
87
- let center_char = tile[1] === tile[2] ? rt : tile[2] === tile[3] ? lt : up
88
- let centertty = dntty(colors[tile[2]], colors[tile[2].toUpperCase()], center_char)
89
- val = horiztty(colors[tile[3]]) + centertty + horiztty(colors[tile[1]]) + sp()
107
+ const centerChar =
108
+ tile[1] === tile[2] ? rt : tile[2] === tile[3] ? lt : up
109
+ const centertty = dntty(
110
+ colors[tile[2]],
111
+ colors[tile[2].toUpperCase()],
112
+ centerChar,
113
+ )
114
+ value =
115
+ horiztty(colors[tile[3]]) + centertty + horiztty(colors[tile[1]]) + sp()
90
116
  }
91
117
  }
118
+
92
119
  set = save
93
- return val || (top ? '??? ' : '\'\'\' ')
120
+ return value || (top ? '??? ' : "''' ")
94
121
  }
95
122
 
96
- const merge_tentative = () => {
97
- color_sets.tentative.e = set.e // copy empty color to tentative set
98
- color_sets.tentative.W = set.W
99
- color_sets.tentative.B = set.B
123
+ const mergeTentative = () => {
124
+ colorSets.tentative.e = set.e // Copy empty color to tentative set
125
+ colorSets.tentative.W = set.W
126
+ colorSets.tentative.B = set.B
100
127
  }
101
128
 
102
- const print_icon = (icon, set_name) => {
103
- set = color_sets[set_name] || color_sets.trax
104
- merge_tentative(set)
129
+ const printIcon = (icon, setName) => {
130
+ set = colorSets[setName] || colorSets.trax
131
+ mergeTentative(set)
105
132
  const on = esc + '38;5;' + String(set.t) + ';48;5;' + String(set.e) + 'm'
106
133
  let o1 = ' '
107
134
  let o2 = '1 '
108
135
  let row = 1
109
136
  let width = 1
110
- let info = []
111
- let line_width = 0
112
- while (icon.length) {
113
- let c = icon[0]
137
+ const info = []
138
+ let lineWidth = 0
139
+ while (icon.length > 0) {
140
+ const c = icon[0]
114
141
  icon = icon.slice(1)
115
142
  if (c === ':') {
116
- info.push({o: o1, w: line_width})
117
- info.push({o: o2, w: line_width})
143
+ info.push({ o: o1, w: lineWidth }, { o: o2, w: lineWidth })
118
144
  row++
119
- width = Math.max(width, line_width)
120
- line_width = 0
145
+ width = Math.max(width, lineWidth)
146
+ lineWidth = 0
121
147
  o1 = ' '
122
148
  o2 = String(row).padEnd(3)
123
149
  } else if (c >= '0' && c <= '9') {
124
- let empty = Number(c) + 1
150
+ const empty = Number(c) + 1
125
151
  o1 += ' '.repeat(empty * 4)
126
152
  o2 += ' '.repeat(empty * 4)
127
- line_width += empty
153
+ lineWidth += empty
128
154
  } else {
129
- o1 += tile_tty(c, 'top')
130
- o2 += tile_tty(c)
131
- line_width++
155
+ o1 += tileTty(c, 'top')
156
+ o2 += tileTty(c)
157
+ lineWidth++
132
158
  }
133
159
  }
134
- if (o1.length) {
135
- info.push({o: o1, w: line_width})
136
- info.push({o: o2, w: line_width})
137
- width = Math.max(width, line_width)
160
+
161
+ if (o1.length > 0) {
162
+ info.push({ o: o1, w: lineWidth }, { o: o2, w: lineWidth })
163
+ width = Math.max(width, lineWidth)
138
164
  }
139
- let columns = process.stdout.columns || 0
140
- let rows = process.stdout.rows || 0
165
+
166
+ const columns = process.stdout.columns || 0
167
+ const rows = process.stdout.rows || 0
141
168
  if (width * 4 < columns - 4 && info.length < rows - 2) {
142
- let col_out = ' '
169
+ let colOut = ' '
143
170
  for (let col = 1; col <= width; col++) {
144
- col_out += ' ' + Trax.column(col).padEnd(3)
171
+ colOut += ' ' + Trax.encodeCol(col).padEnd(3)
172
+ }
173
+
174
+ console.info(on + colOut.padEnd((width + 1) * 4) + off)
175
+ for (const line of info) {
176
+ console.info(on + line.o + ''.padEnd((width - line.w) * 4) + ' ' + off)
145
177
  }
146
- console.info(on + col_out.padEnd((width + 1) * 4) + off)
147
- info.forEach(line => console.info(on + line.o + ''.padEnd((width - line.w) * 4) + ' ' + off))
178
+
148
179
  console.info(on + ''.padEnd((width + 1) * 4) + off)
149
180
  } else {
150
181
  console.warn('Console is too small to display this game')
151
182
  }
152
183
  }
153
184
 
154
- // flexible way to get a Trax game object
185
+ // Flexible way to get a Trax game object
155
186
  // game = existing Trax instance, or a string indicating the variant name
156
187
  // moves = optional list or string of moves to be made on the game
157
188
  // returns a Trax instance
158
- const trax_game = (game='trax', moves) => {
189
+ export const traxGame = (game = 'trax', moves = '') => {
159
190
  let trax
160
191
  if (game instanceof Trax) {
161
192
  trax = game
162
193
  if (moves) {
163
194
  if (typeof moves === 'string') moves = moves.split(' ')
164
- moves.forEach(move => trax.drop_tile(move))
195
+ for (const move of moves) trax.dropTile(move)
165
196
  }
166
197
  } else {
167
198
  trax = new Trax(game, moves, 'cli')
168
199
  }
200
+
169
201
  return trax
170
202
  }
171
203
 
172
- const display_game = (game='trax', moves='', tentative) => {
173
- let trax = trax_game(game, moves)
174
- let icon = trax.icon
204
+ export const displayGame = (game = 'trax', moves = '', tentative = false) => {
205
+ const trax = traxGame(game, moves)
206
+ let { icon } = trax
175
207
  if (tentative) {
176
- let drops = trax.drop_tile(tentative)
208
+ const drops = trax.dropTile(tentative)
177
209
  if (drops.valid) {
178
- icon = trax.tentative_icon(drops)
210
+ icon = trax.dropsIcon(drops)
179
211
  } else {
180
212
  tentative = false
181
213
  }
182
214
  }
215
+
183
216
  console.info(trax.notation)
184
- print_icon(icon, trax.rules)
217
+ printIcon(icon, trax.rules)
185
218
  if (tentative) console.info('--- tentative position ---')
186
- let name = {1: 'white', 2: 'black'}[trax.turn]
219
+ const name = { 1: 'white', 2: 'black' }[trax.turn]
187
220
  if (trax.over) {
188
221
  if (trax.turn) {
189
222
  console.info(name + ' wins')
@@ -191,12 +224,6 @@ const display_game = (game='trax', moves='', tentative) => {
191
224
  console.info('game end' + (tentative ? 's' : 'ed') + ' in a draw')
192
225
  }
193
226
  } else if (trax.turn) {
194
- console.info((tentative ? 'leaving ' : '') + name +' to play')
227
+ console.info((tentative ? 'leaving ' : '') + name + ' to play')
195
228
  }
196
229
  }
197
-
198
- export default {
199
- display: display_game,
200
- game: trax_game,
201
- out,
202
- }
package/.eslintrc.yaml DELETED
@@ -1,68 +0,0 @@
1
- root: true
2
-
3
- extends:
4
- - eslint:recommended
5
-
6
- parserOptions:
7
- sourceType: module
8
-
9
- env:
10
- node: true
11
- es2021: true
12
-
13
- globals:
14
- crypto: readonly
15
-
16
- rules:
17
- curly:
18
- - error
19
- - multi-line
20
- - consistent
21
- dot-location:
22
- - error
23
- - property
24
- dot-notation: warn
25
- eqeqeq:
26
- - warn
27
- - smart
28
- guard-for-in: warn
29
- indent:
30
- - warn
31
- - 2
32
- - MemberExpression: 0
33
- SwitchCase: 1
34
- linebreak-style:
35
- - error
36
- - unix
37
- max-len:
38
- - warn
39
- - code: 99
40
- tabWidth: 2
41
- no-alert: error
42
- no-caller: error
43
- no-div-regex: warn
44
- no-else-return:
45
- - error
46
- - allowElseIf: false
47
- no-eval: error
48
- no-extra-parens: warn
49
- no-invalid-this: error
50
- no-new: error
51
- no-unused-vars:
52
- - warn
53
- - vars: all
54
- args: all
55
- caughtErrors: all
56
- no-with: error
57
- quotes:
58
- - warn
59
- - single
60
- - avoidEscape: true
61
- allowTemplateLiterals: true
62
- semi:
63
- - error
64
- - never
65
- - beforeStatementContinuationChars: always
66
- wrap-iife:
67
- - error
68
- - inside
package/jsconfig.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "es2020",
4
- "target": "es2020"
5
- },
6
- "checkJs": true,
7
- "exclude": ["node_modules"]
8
- }
package/tests.js DELETED
@@ -1,11 +0,0 @@
1
- import test from 'tape'
2
- import Trax from './engine.js'
3
-
4
- test('create trax game', t => {
5
- let trax = new Trax('trax', '@0/', 'tape')
6
- t.equal(trax.id, 'tape', 'id is assigned')
7
- t.equal(trax.count, 1, '1 tile is played')
8
- t.equal(trax.turn, 2, 'player 2 is next')
9
- t.equal(trax.color, 'b', 'player 2 is black')
10
- t.end()
11
- })