@slugbugblue/trax 0.5.0 → 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/docs/point.md ADDED
@@ -0,0 +1,134 @@
1
+ # point.js API documentation
2
+
3
+ This module provides a class structure to work with `x`, `y` locations as a
4
+ single point.
5
+
6
+ Since I don't need it to be super fancy, there isn't a lot of extra
7
+ functionality here.
8
+
9
+ ## Example usage
10
+
11
+ ```javascript
12
+ import { Point } from '@slugbugblue/trax/point.js'
13
+
14
+ const zero = new Point(0, 0)
15
+
16
+ const up = zero.up
17
+ console.log('up:', up.x, up.y) // up: 0 -1
18
+
19
+ const rt = zero.right
20
+ console.log('rt:', rt) // rt: Point(1,0)
21
+
22
+ const up2 = zero.dir('up')
23
+ console.log('up === up2 ?', up.eq(up2)) // up === up2 ? true
24
+
25
+ // distances
26
+ console.log(zero.distX(up), zero.distX(rt)) // 0 1
27
+ console.log(up.distX(rt), up.distY(rt)) // 0 0
28
+ console.log(zero.distance(up), zero.distance(rt)) // 1 1
29
+ console.log(up.distance(rt)) // 1.4142135623730951
30
+ ```
31
+
32
+ ## API
33
+
34
+ ### Point class
35
+
36
+ A Point instance is a representation of an immutable point in two dimensional
37
+ space, represented by an `x` and a `y` component. Since this implementation was
38
+ created for game boards, we assume that all points are integers, though that is
39
+ not a requirement, and the math will work equally well on non-integers.
40
+
41
+ #### constructor
42
+
43
+ ##### `new Point(fromPoint)`
44
+
45
+ ##### `new Point(x, y)`
46
+
47
+ You can create a point from either a point-like object (ie, any object with both
48
+ an `x` and a `y` key with numeric values), or from individual `x` and `y`
49
+ numbers. Once a point is created, it cannot be changed; however, you can use its
50
+ functions to return other points relative to it.
51
+
52
+ ##### `x` and `y` immutable properties
53
+
54
+ You can return the individual `x` or `y` property of a point by accessing it
55
+ directly. Attempts to change these properties will fail.
56
+
57
+ ##### `up`, `down`, `left`, `right` calculated properties
58
+
59
+ Since points are immutable, to move a point, you need to create a new point.
60
+ These calculated properties provide quick access to cardinal integer neighboring
61
+ points.
62
+
63
+ ##### `around` calculated property
64
+
65
+ Or, get an array of all four cardinal points around the existing point.
66
+
67
+ ##### `dir(direction)`
68
+
69
+ Pass in a string named direction (ie, one of `up`, `down`, `left`, or `right`)
70
+ to get a point one integer away from the existing point in the direction
71
+ indicated. This does the exact same thing as the calculated properties with
72
+ these names, but it can be done using a variable instead.
73
+
74
+ ##### `distX(from)`, `distY(from)`
75
+
76
+ Returns the difference between the `x` or `y` properties of two points. This
77
+ will always be represented by zero or a positive number.
78
+
79
+ ##### `distance(from)`
80
+
81
+ Returns the distance between two points. This will never be a negative number.
82
+
83
+ ##### `eq(to)`
84
+
85
+ Returns `true` if the given point or point-like object is at the same location
86
+ as the existing point.
87
+
88
+ #### Plays well with others
89
+
90
+ The Point class also includes functionality to provide useful default results
91
+ when used in other contexts.
92
+
93
+ ##### `toString()`
94
+
95
+ Returns a string representation of the point in the format `Point(x,y)`. This
96
+ allows simple logging calls with `console.log(point)`.
97
+
98
+ ##### `toJSON()`
99
+
100
+ JSON cannot encode special classes, but this class can be stored as a bare
101
+ object with `x` and `y` keys when converted to JSON. Note, though, that when
102
+ parsing the resultant JSON string, all Point functionality is not restored by
103
+ default. For example:
104
+
105
+ ```javascript
106
+ const zero = new Point(0, 0)
107
+ const json = JSON.stringify(zero)
108
+ const jsonzero = JSON.parse(json)
109
+
110
+ console.log(zero.eq(jsonzero)) // true
111
+ jsonzero.eq(zero) // TypeError: jsonzero.eq is not a function
112
+ const reconstituted = new Point(jsonzero)
113
+ console.log(reconstituted.eq(zero)) // true
114
+ ```
115
+
116
+ ##### `util.inspect.custom()`
117
+
118
+ When running in the node command line interpreter, a point will be
119
+ pretty-printed using the magic of `util.inspect`.
120
+
121
+ ## License
122
+
123
+ Copyright 2019-2022 Chad Transtrum
124
+
125
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
126
+ the files in this project except in compliance with the License. You may obtain
127
+ a copy of the License at
128
+
129
+ http://www.apache.org/licenses/LICENSE-2.0
130
+
131
+ Unless required by applicable law or agreed to in writing, software distributed
132
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
133
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
134
+ specific language governing permissions and limitations under the License.
package/engine.js CHANGED
@@ -15,12 +15,15 @@
15
15
 
16
16
  // Trax internals. Woot.
17
17
 
18
- // tile type names are determined by listing the color of line at each edge, clockwise
18
+ // Tile type names are determined by listing the color of line at each edge, clockwise
19
19
  // and then sorted alphabetically and given a single letter name, so 'bbww' becomes 'a'
20
20
  // which gives us six different tile names: a-f
21
21
 
22
- // fun trax helper constants
22
+ import { Point } from './point.js'
23
23
 
24
+ // Fun trax helper constants
25
+
26
+ const zero = new Point(0, 0)
24
27
  const notationRegex = /^ *([@a-z]+) *(\d+) *([/\\+]) *$/i
25
28
 
26
29
  const slash = { a: '\\', b: '+', c: '/', d: '/', e: '+', f: '\\' }
@@ -51,18 +54,14 @@ const rightColor = (t) =>
51
54
  'ade'.includes(t) ? 'b' : 'bcf'.includes(t) ? 'w' : false
52
55
  const leftColor = (t) =>
53
56
  'cef'.includes(t) ? 'b' : 'abd'.includes(t) ? 'w' : false
54
- const squaresAround = (x, y) => [
55
- { x: x - 1, y },
56
- { x: x + 1, y },
57
- { x, y: y - 1 },
58
- { x, y: y + 1 },
59
- ]
60
57
 
61
58
  const addBlank = (code) => {
62
59
  // Encode missing tiles as digits 0-9, for 1-10 missing
63
60
  const lastNumber = code.codePointAt(code.length - 1) // This is NaN if code is empty, which is ok
64
- if (lastNumber > 47 && lastNumber < 57)
61
+ if (lastNumber > 47 && lastNumber < 57) {
65
62
  return code.slice(0, -1) + String.fromCodePoint(lastNumber + 1)
63
+ }
64
+
66
65
  return code + '0'
67
66
  }
68
67
 
@@ -76,6 +75,7 @@ export const Trax = class {
76
75
  static variants = new Set(['trax', 'traxloop', 'trax8']) // Known variants
77
76
 
78
77
  // Static class methods
78
+ static point = (x, y) => new Point(x, y)
79
79
  static colorOf = (playerNumber) => ({ 1: 'w', 2: 'b' }[playerNumber])
80
80
  static playerNumber = (color) => ({ b: 2, w: 1 }[color])
81
81
  static other = (color) => ({ b: 'w', w: 'b' }[color])
@@ -121,13 +121,15 @@ export const Trax = class {
121
121
  this.invalid = false
122
122
  // Moves might be a notation string
123
123
  if (moves) {
124
- if (typeof moves === 'string') moves = moves.split(' ')
124
+ if (typeof moves === 'string') moves = moves.split(/\s+/)
125
125
  for (let move of moves) {
126
126
  if (typeof move === 'string') {
127
127
  move = { move: this.move, notation: move }
128
128
  }
129
129
 
130
- this.playMove(move.move, move.notation)
130
+ if (notationRegex.test(move.notation)) {
131
+ this.playMove(move.move, move.notation)
132
+ }
131
133
  }
132
134
  }
133
135
  }
@@ -162,6 +164,10 @@ export const Trax = class {
162
164
  this.tiles = JSON.parse(saved.tiles)
163
165
  this.path = JSON.parse(saved.path)
164
166
  this.invalid = saved.invalid
167
+ // Reconstitute the locations
168
+ for (const tile of Object.values(this.tiles)) {
169
+ tile.loc = new Point(tile.loc)
170
+ }
165
171
  }
166
172
 
167
173
  get count() {
@@ -184,39 +190,42 @@ export const Trax = class {
184
190
  return this.over ? this.turn : false // Or 1 or 2 for a win, or 0 for a tie
185
191
  }
186
192
 
187
- tileId(x, y) {
188
- return this.id + '-' + String(x) + 'x' + String(y)
193
+ tileId(loc) {
194
+ return this.id + '-' + String(loc.x) + 'x' + String(loc.y)
189
195
  }
190
196
 
191
- addTile(type, x, y) {
197
+ addTile(type, loc) {
192
198
  // Note: no validity checking is done here
193
- const id = this.tileId(x, y)
194
- const tile = { type, x, y, id, move: this.move, seq: this.count }
199
+ const id = this.tileId(loc)
200
+ const tile = { type, loc, id, move: this.move, seq: this.count }
195
201
  if (type === 'x') {
196
202
  this.invalid = true // Invalid tile is being played
197
203
  } else {
198
204
  this.tiles[id] = tile
199
- this.left = Math.min(this.left, x)
200
- this.right = Math.max(this.right, x)
201
- this.top = Math.min(this.top, y)
202
- this.bottom = Math.max(this.bottom, y)
205
+ this.left = Math.min(this.left, loc.x)
206
+ this.right = Math.max(this.right, loc.x)
207
+ this.top = Math.min(this.top, loc.y)
208
+ this.bottom = Math.max(this.bottom, loc.y)
203
209
  }
204
210
 
205
211
  return tile
206
212
  }
207
213
 
208
- tileAt(x, y) {
209
- return (this.tiles[this.tileId(x, y)] || {}).type
214
+ tileAt(loc) {
215
+ return (this.tiles[this.tileId(loc)] || {}).type
210
216
  }
211
217
 
212
- validTile(type, x, y) {
213
- if (this.count === 0 && x === 0 && y === 0)
218
+ validTile(type, loc) {
219
+ if (this.count === 0 && loc.x === 0 && loc.y === 0) {
214
220
  return type === 'd' || type === 'e'
215
- const up = downColor(this.tileAt(x, y - 1))
216
- const down = upColor(this.tileAt(x, y + 1))
217
- const left = rightColor(this.tileAt(x - 1, y))
218
- const right = leftColor(this.tileAt(x + 1, y))
221
+ }
222
+
223
+ const up = downColor(this.tileAt(loc.up))
224
+ const down = upColor(this.tileAt(loc.down))
225
+ const left = rightColor(this.tileAt(loc.left))
226
+ const right = leftColor(this.tileAt(loc.right))
219
227
  return (
228
+ (up || down || left || right) &&
220
229
  (!up || up === upColor(type)) &&
221
230
  (!down || down === downColor(type)) &&
222
231
  (!left || left === leftColor(type)) &&
@@ -224,11 +233,11 @@ export const Trax = class {
224
233
  )
225
234
  }
226
235
 
227
- possibleTiles(x, y, s = false) {
236
+ possibleTiles(loc, s = false) {
228
237
  // S: slash (/ or \ or +)
229
238
  const possibles = []
230
239
  for (const t of ['a', 'b', 'c', 'd', 'e', 'f']) {
231
- if ((!s || s === slash[t]) && this.validTile(t, x, y)) possibles.push(t)
240
+ if ((!s || s === slash[t]) && this.validTile(t, loc)) possibles.push(t)
232
241
  }
233
242
 
234
243
  return possibles
@@ -242,41 +251,42 @@ export const Trax = class {
242
251
  return this.right - this.left + 1
243
252
  }
244
253
 
245
- validLocation(x, y) {
254
+ validLocation(loc) {
246
255
  if (this.over) return false
247
- if (this.tileAt(x, y)) return false
248
- if (this.count === 0 && x === 0 && y === 0) return true
256
+ if (this.tileAt(loc)) return false
257
+ if (this.count === 0 && loc.eq(zero)) return true
249
258
  if (this.rules === 'trax8') {
250
- if (this.width > 7 && (x < this.left || x > this.right)) return false
251
- if (this.height > 7 && (y < this.top || y > this.bottom)) return false
259
+ if (this.width > 7 && (loc.x < this.left || loc.x > this.right)) {
260
+ return false
261
+ }
262
+
263
+ if (this.height > 7 && (loc.y < this.top || loc.y > this.bottom)) {
264
+ return false
265
+ }
252
266
  }
253
267
 
254
- return Boolean(
255
- this.tileAt(x - 1, y) ||
256
- this.tileAt(x + 1, y) ||
257
- this.tileAt(x, y - 1) ||
258
- this.tileAt(x, y + 1),
259
- )
268
+ for (const location of loc.around) {
269
+ if (this.tileAt(location)) return true
270
+ }
271
+
272
+ return false
260
273
  }
261
274
 
262
275
  possibleLocations() {
263
- const locs = { '0x0': { x: 0, y: 0 } } // If no tiles are placed, we can play a starting tile
264
- const maybe = (x, y) => {
276
+ const locs = { [this.tileId(zero)]: zero } // If no tiles are placed, we can play a starting tile
277
+ const maybe = (loc) => {
265
278
  // Add a loc if a tile isn't already there
266
- const loc = x + 'x' + y
267
- locs[loc] = locs[loc] || { x, y }
279
+ const id = this.tileId(loc)
280
+ locs[id] = locs[id] || loc
268
281
  }
269
282
 
270
283
  for (const tile of Object.values(this.tiles)) {
271
- locs[tile.x + 'x' + tile.y] = 'tile' // Add an existing tile
272
- maybe(tile.x - 1, tile.y) // And four maybe tiles around it
273
- maybe(tile.x + 1, tile.y) // Which will either be replaced by real tiles
274
- maybe(tile.x, tile.y - 1) // Or will be potentially valid locations
275
- maybe(tile.x, tile.y + 1)
284
+ locs[tile.id] = 'tile' // Add an existing tile
285
+ tile.loc.around.map((loc) => maybe(loc))
276
286
  }
277
287
 
278
288
  // Get rid of all the real tiles and return all the maybes
279
- return Object.values(locs).filter((l) => l !== 'tile')
289
+ return Object.values(locs).filter((loc) => loc !== 'tile')
280
290
  }
281
291
 
282
292
  possibleMoves() {
@@ -285,8 +295,8 @@ export const Trax = class {
285
295
  const possibles = []
286
296
  if (this.over) return possibles // Shortcut if the game is over
287
297
  for (const loc of this.possibleLocations()) {
288
- for (const tile of this.possibleTiles(loc.x, loc.y)) {
289
- const drop = this.dropTile(tile, loc.x, loc.y, 'tentative')
298
+ for (const tile of this.possibleTiles(loc)) {
299
+ const drop = this.dropTile(tile, loc, 'tentative')
290
300
  if (drop.valid) possibles.push(drop.notation)
291
301
  }
292
302
  }
@@ -294,22 +304,22 @@ export const Trax = class {
294
304
  return possibles
295
305
  }
296
306
 
297
- forcedMoves(x, y) {
298
- // We just played at x, y, so return all forced moves
307
+ forcedMoves(loc) {
308
+ // We just played at loc, so return all forced moves
299
309
  const forced = []
300
310
  const invalids = {}
301
- let check = squaresAround(x, y)
311
+ let check = loc.around
302
312
  while (check.length > 0) {
303
313
  const pos = check.shift()
304
- if (this.validLocation(pos.x, pos.y)) {
305
- const possibles = this.possibleTiles(pos.x, pos.y)
314
+ if (this.validLocation(pos)) {
315
+ const possibles = this.possibleTiles(pos)
306
316
  if (possibles.length === 1) {
307
317
  // Only 1 possible move
308
- forced.push(this.addTile(possibles[0], pos.x, pos.y))
309
- check = [...check, ...squaresAround(pos.x, pos.y)]
318
+ forced.push(this.addTile(possibles[0], pos))
319
+ check = [...check, ...pos.around]
310
320
  } else if (possibles.length === 0) {
311
321
  // No possible moves at all
312
- const invalid = this.addTile('x', pos.x, pos.y)
322
+ const invalid = this.addTile('x', pos)
313
323
  if (!invalids[invalid.id]) forced.push(invalid)
314
324
  invalids[invalid.id] = true
315
325
  }
@@ -319,62 +329,72 @@ export const Trax = class {
319
329
  return forced
320
330
  }
321
331
 
322
- notate(type, x, y) {
332
+ notate(type, loc) {
323
333
  // Notation depends on the position of the board BEFORE the move
324
- return (
325
- Trax.encodeCol(x - this.left + 1) + String(y - this.top + 1) + slash[type]
326
- )
334
+ let notation = Trax.encodeCol(loc.x - this.left + 1)
335
+ notation += String(loc.y - this.top + 1)
336
+ notation += slash[type]
337
+ return notation
327
338
  }
328
339
 
329
340
  decodeNotation(notation) {
330
- // Return {type, x, y} from a notation
331
- const r = { type: 'x', x: 0, y: 0 } // Default to an invalid move
341
+ // Return {type, loc} from a notation
342
+ const bad = { type: 'x', loc: zero }
332
343
  const match = notation.match(notationRegex)
333
- if (match === null) return r
334
- r.x = this.left - 1 + Trax.decodeCol(match[1])
335
- r.y = this.top - 1 + Number(match[2])
336
- const possible = this.possibleTiles(r.x, r.y, match[3])
337
- if (possible) r.type = possible[0]
338
- return r
344
+ if (match === null) return bad
345
+ const loc = new Point(
346
+ this.left - 1 + Trax.decodeCol(match[1]),
347
+ this.top - 1 + Number(match[2]),
348
+ )
349
+ const possible = this.possibleTiles(loc, match[3])
350
+ if (possible.length > 0) return { type: possible[0], loc }
351
+ return bad
339
352
  }
340
353
 
341
- follow(color, x, y, from) {
354
+ follow(color, loc, from) {
342
355
  const path = []
343
- let id = this.tileId(x, y)
344
- let tile = this.tileAt(x, y)
356
+ let id = this.tileId(loc)
357
+ let tile = this.tileAt(loc)
345
358
  while (tile && path[0] !== id) {
346
359
  if (from !== 'top' && upColor(tile) === color) {
347
360
  from = 'bottom'
348
- y--
361
+ loc = loc.up
349
362
  } else if (from !== 'bottom' && downColor(tile) === color) {
350
363
  from = 'top'
351
- y++
364
+ loc = loc.down
352
365
  } else if (from !== 'left' && leftColor(tile) === color) {
353
366
  from = 'right'
354
- x--
367
+ loc = loc.left
355
368
  } else if (from !== 'right' && rightColor(tile) === color) {
356
369
  from = 'left'
357
- x++
370
+ loc = loc.right
358
371
  }
359
372
 
360
373
  path.push(id)
361
- id = this.tileId(x, y)
362
- tile = this.tileAt(x, y)
374
+ id = this.tileId(loc)
375
+ tile = this.tileAt(loc)
363
376
  }
364
377
 
365
- return { x, y, path }
378
+ return { loc, path }
366
379
  }
367
380
 
368
- findEnds(color, x, y) {
381
+ findEnds(color, loc) {
369
382
  const ends = []
370
- const tile = this.tileAt(x, y)
371
- if (upColor(tile) === color) ends.push(this.follow(color, x, y, 'top'))
372
- if (downColor(tile) === color) ends.push(this.follow(color, x, y, 'bottom'))
373
- if (leftColor(tile) === color) ends.push(this.follow(color, x, y, 'left'))
374
- if (rightColor(tile) === color) ends.push(this.follow(color, x, y, 'right'))
383
+ const tile = this.tileAt(loc)
384
+ if (upColor(tile) === color) ends.push(this.follow(color, loc, 'top'))
385
+ if (downColor(tile) === color) ends.push(this.follow(color, loc, 'bottom'))
386
+ if (leftColor(tile) === color) ends.push(this.follow(color, loc, 'left'))
387
+ if (rightColor(tile) === color) ends.push(this.follow(color, loc, 'right'))
375
388
  return ends
376
389
  }
377
390
 
391
+ lineWin(locA, locB) {
392
+ if (this.rules === 'traxloop') return false
393
+ if (this.width > 7 && locA.distX(locB) > this.width) return true
394
+ if (this.height > 7 && locA.distY(locB) > this.height) return true
395
+ return false
396
+ }
397
+
378
398
  checkWin(tiles) {
379
399
  let winner = false
380
400
  const colors = [this.color, Trax.other(this.color)] // Check win for current player first
@@ -382,15 +402,11 @@ export const Trax = class {
382
402
  if (winner) continue // Skip the second player if the first has won
383
403
  for (const tile of tiles) {
384
404
  if (winner) continue // Skip the remaining tiles once we find a win
385
- const ends = this.findEnds(color, tile.x, tile.y)
386
- if (ends[0].x === ends[1].x && ends[0].y === ends[1].y) {
405
+ const ends = this.findEnds(color, tile.loc)
406
+ if (ends[0].loc.eq(ends[1].loc)) {
387
407
  winner = color // Loop win
388
408
  this.path = ends[0].path // Both paths are essentially the same
389
- } else if (
390
- this.rules !== 'traxloop' && // Check for line win
391
- ((this.width > 7 && Math.abs(ends[0].x - ends[1].x) > this.width) ||
392
- (this.height > 7 && Math.abs(ends[0].y - ends[1].y) > this.height))
393
- ) {
409
+ } else if (this.lineWin(ends[0].loc, ends[1].loc)) {
394
410
  winner = color // Line win, paths need to be combined
395
411
  this.path = [...ends[0].path.reverse(), ...ends[1].path.slice(1)]
396
412
  }
@@ -417,9 +433,9 @@ export const Trax = class {
417
433
  return this.dropTile(notation)
418
434
  }
419
435
 
420
- playMove(moveNumber, notation) {
436
+ playMove(currentMoveNumber, nextMoveNotation) {
421
437
  // Generic API call for dropTile
422
- if (moveNumber === this.move) return this.dropTile(notation)
438
+ if (currentMoveNumber === this.move) return this.dropTile(nextMoveNotation)
423
439
  return { valid: false }
424
440
  }
425
441
 
@@ -441,7 +457,7 @@ export const Trax = class {
441
457
  .filter((n) => !n.endsWith('.'))
442
458
  }
443
459
 
444
- dropTile(type, x, y, tentative = false) {
460
+ dropTile(type, loc, tentative = false) {
445
461
  // Pass in tentative to not save the move
446
462
  let dropped = []
447
463
  let notation = ''
@@ -461,16 +477,18 @@ export const Trax = class {
461
477
  } else {
462
478
  if (type.search(notationRegex) === 0) {
463
479
  // We were passed a notation
464
- ;({ type, x, y } = this.decodeNotation(type))
480
+ const decoded = this.decodeNotation(type)
481
+ type = decoded.type
482
+ loc = decoded.loc
465
483
  }
466
484
 
467
- if (this.validLocation(x, y) && this.validTile(type, x, y)) {
485
+ if (this.validLocation(loc) && this.validTile(type, loc)) {
468
486
  valid = true
469
487
  const saved = this.save() // Just in case we need to roll back
470
- notation = this.notate(type, x, y) // Determine notation BEFORE we play the move
488
+ notation = this.notate(type, loc) // Determine notation BEFORE we play the move
471
489
  this.updateNotation(notation)
472
- dropped.push(this.addTile(type, x, y)) // Play the tile
473
- dropped = [...dropped, ...this.forcedMoves(x, y)] // Play all forced moves
490
+ dropped.push(this.addTile(type, loc)) // Play the tile
491
+ dropped = [...dropped, ...this.forcedMoves(loc)] // Play all forced moves
474
492
  if (this.invalid || tentative) {
475
493
  if (this.invalid) valid = false
476
494
  this.restore(saved) // Abort! abort!
@@ -490,6 +508,8 @@ export const Trax = class {
490
508
  if (match === null) return []
491
509
  const x = Trax.decodeCol(match[1])
492
510
  const y = Number(match[2])
511
+ const X = this.height - x + 1
512
+ const Y = this.width - y + 1
493
513
  const s = match[3]
494
514
  const slashes = s === '+' ? [s] : ['/', '\\']
495
515
  const moves = new Set()
@@ -497,16 +517,8 @@ export const Trax = class {
497
517
  for (const slash of slashes) {
498
518
  moves.add(Trax.encodeCol(x) + String(y) + slash)
499
519
  moves.add(Trax.encodeCol(y) + String(x) + slash)
500
- moves.add(
501
- Trax.encodeCol(this.height - x + 1) +
502
- String(this.width - y + 1) +
503
- slash,
504
- )
505
- moves.add(
506
- Trax.encodeCol(this.width - y + 1) +
507
- String(this.height - x + 1) +
508
- slash,
509
- )
520
+ moves.add(Trax.encodeCol(X) + String(Y) + slash)
521
+ moves.add(Trax.encodeCol(Y) + String(X) + slash)
510
522
  }
511
523
 
512
524
  return [...moves]
@@ -547,7 +559,7 @@ export const Trax = class {
547
559
  return this.positionCode()
548
560
  }
549
561
 
550
- tentativeIcon(drops) {
562
+ dropsIcon(drops) {
551
563
  return this.positionCode(undefined, undefined, undefined, drops)
552
564
  }
553
565
 
@@ -562,19 +574,22 @@ export const Trax = class {
562
574
  let x = startX
563
575
  let y = startY
564
576
  do {
565
- let tile = this.tileAt(x, y) // Normal tile for normal orientation
577
+ const loc = new Point(x, y)
578
+ let tile = this.tileAt(loc) // Normal tile for normal orientation
566
579
  if (tile) {
567
580
  tile = applySymmetry(tile, rightToLeft, bottomToTop, rotate)
568
- if (this.path.includes(this.tileId(x, y))) {
581
+ if (this.path.includes(this.tileId(loc))) {
569
582
  tile = String.fromCodePoint(tile.codePointAt(0) + this.turn * 6)
570
583
  }
571
584
 
572
585
  if (
573
586
  drops &&
574
587
  drops.dropped &&
575
- drops.dropped.some((t) => t.x === x && t.y === y)
576
- )
588
+ drops.dropped.some((t) => loc.eq(t.loc))
589
+ ) {
577
590
  tile = tile.toUpperCase()
591
+ }
592
+
578
593
  line += tile
579
594
  } else {
580
595
  line = addBlank(line)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slugbugblue/trax",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Trax game engine and friends",
5
5
  "keywords": [
6
6
  "trax",
@@ -18,11 +18,16 @@
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
- "ava": "^4.0.1",
27
+ "ava": "^4.1.0",
23
28
  "c8": "^7.11.0",
24
- "prettier": "^2.5.1",
25
- "xo": "^0.47.0"
29
+ "prettier": "^2.6.1",
30
+ "xo": "^0.48.0"
26
31
  },
27
32
  "type": "module",
28
33
  "engines": {
@@ -37,6 +42,12 @@
37
42
  "useTabs": false
38
43
  },
39
44
  "xo": {
40
- "prettier": true
45
+ "prettier": true,
46
+ "rules": {
47
+ "curly": [
48
+ "error",
49
+ "multi-line"
50
+ ]
51
+ }
41
52
  }
42
53
  }