UNPKG

jsatomix

Version:

Tools for analysing and playing katomic/atomix type puzzles.

234 lines (214 loc) 5.23 kB
'use strict' /** ## gameObject A `gameObject` is an object representing a playable game. It has a method `move()` which accepts a string representing a list of moves in the `history` format. The moves are applied to the current state of the game to produce a new state. The current state can be interrogated with the following methods. - `endpoint()` returns the endpoint if the game has reached an endpoint or an empty string otherwise. - `toString()` returns a string representing the current state of the arena. An instance of `gameObject` can be created in one of two ways: from a `level` object or from a `solution` object. ```js const jsa = require('jsatomix') const level = jsa.levelsets.get('katomic', '1') const gameFromLevel = jsa.gameObject.from(level) const solution = jsa.solutions.all[0] const gameFromSolution = jsa.gameObject.from(solution) ``` If a solution object is fed to the constructor, then the solutions `levelSet` and `level` properties are used to obtain a `level` object and the contents of the `history` property is fed to the game objects `move()` method. Only those three properties are required so to initialise an object for a particular level do this: ```js const jsa = require('jsatomix') const game = jsa.gameObject.from({ levelSet: 'katomic', level: '1', history: '' }) // ########### // #..#......# // #.3#......# // #.##......# // #.#..#.#### // #....#.2..# // ###.#.....# // #1....#...# // ########### ``` */ const levelsets = require('./levelsets') const chunk = require('chunk') /** * gamePrototype * */ const gamePrototype = { /** * `move` apply a move to the current state of the game. * */ move: function (moves) { moves = Array.from( moves, (v) => v.charCodeAt(0) - 97 ) let pos while (moves.length >= 4) { let [sr, sc, er, ec, ...rest] = moves moves = rest let start = sr * this.width + sc let end = er * this.width + ec do { if (sr < er) { pos = this.down(start) break } if (sr > er) { pos = this.up(start) break } if (sc < ec) { pos = this.right(start) break } if (sc > ec) { pos = this.left(start) break } } while (false) if (!pos || pos !== end) { return 0 // illegal move } } return pos }, /** doMove applies a move to atom * at 'start' using offset provided by * 'dir' */ doMove: function (start, dir) { const arena = this.arena const atom = arena[start] let pos = start if (atom < 1) return 0 while (arena[pos + dir] < 0) { pos += dir } if (pos === start) return 0 arena[start] = arena[pos] arena[pos] = atom return pos }, /** * 'left' moves an atom at 'pos' left * as far as it will go. */ left: function (pos) { return this.doMove(pos, -1) }, /** * 'right' moves an atom at 'pos' right * as far as it will go. */ right: function (pos) { return this.doMove(pos, 1) }, /** * 'up' moves an atom at 'pos' up * as far as it will go. */ up: function (pos) { return this.doMove(pos, -this.width) }, /** * 'down' moves an atom at 'pos' down * as far as it will go. */ down: function (pos) { return this.doMove(pos, this.width) }, endpoint: function () { let ep = this.toString() ep = ep.search(this.reMol) if (ep < 0) return '' return String.fromCodePoint( 97 + Math.floor(ep / this.width), 97 + ep % this.width ) }, /** * `toString()` returns a string representing the current state of the games aren.a. */ toString: function () { let arena = this.arena let hash = '#'.codePointAt(0) let empty = '.'.codePointAt(0) let a = Array.from( arena, (c) => { if (c > 0) return c if (c === 0) return hash return empty } ) a = String.fromCodePoint(...a) a = chunk(a, this.width).join('\n') return a } } function atomSort (atoms) { return atoms.sort((a, b) => { let dif = a[0] - b[0] if (dif !== 0) return dif return a[1] - b[1] }) } function fromLevel (level) { /** gameObject constructor converts a game level into a playable game. */ let atoms = [] let a = level.arena let idx = -1 let aa = Array.from( a.join(''), (c) => { idx += 1 switch (c) { case '#': return 0 case '.': return -1 default: let atom = c.codePointAt(0) atoms.push([atom, idx]) return atom } } ) let mol = level.molecule let pad = '.'.repeat(a.width - mol[0].length) mol = mol.join(pad) let game = { __proto__: gamePrototype } Object.assign(game, { width: a[0].length, height: a.length, arena: aa, reMol: mol, atoms: atomSort(atoms) }) return game } function fromSolution (s) { let {levelSet, level} = s let game = fromLevel(levelsets.get(levelSet, level)) game.move(s.history) return game } function from (o) { if ('history' in o) { return fromSolution(o) } return fromLevel(o) } module.exports = { from }