import { Field, Mino } from 'tetris-fumen' import { SplitMix64 } from './prng.js' export class Simul { constructor(seed, config) { let rng1 = new SplitMix64(seed); let rng2 = rng1.clone(); this.queue = new Queue(rng1, config.rules.previews); this.cheese = new Cheese(rng2, config.goal, config.rules.min, config.rules.max); this.matrix = this.cheese.generate(0, false); this._nextField = Field.create(this.matrix); } play(mino) { let page = { field: this._nextField, comment: this.queue.quiz(), operation: mino, }; this._nextField = undefined; this.queue.remove(mino.type); let field = Field.create(this.matrix); field.fill(mino); let matrix1 = field.str(FLATTEN); field.clearLine(); let matrix2 = field.str(FLATTEN); let comboing = matrix1 !== matrix2; let level = getGarbageLevel(matrix2); let garbage = this.cheese.generate(level, comboing); this.matrix = matrix2 + garbage; if (garbage !== '') { this._nextField = Field.create(this.matrix); } return page; } } const FLATTEN = { reduced: true, separator: '', garbage: false }; export class Queue { constructor(rng, previews) { // see `static tidepool::sim::PIECES` this._bag = 'IJLOSTZ'.split(''); this._pos = 0; this._rng = rng; this.hold = null; this.next = []; while (this.next.length < previews) { this.next.push(this._pop()); } } _pop() { // see `static tidepool::sim::Bag::pop()` let i = this._pos; this._pos += 1; this._pos %= this._bag.length; let j = this._rng.genRange(i, this._bag.length); let tmp = this._bag[j]; this._bag[j] = this._bag[i]; this._bag[i] = tmp; return tmp; } quiz() { return `#Q=[${this.hold||''}](${this.next[0]})${this.next.slice(1).join('')}`; } remove(ty) { let top = this.next.shift(); this.next.push(this._pop()); if (this.hold === ty || top !== ty) { if (!this.hold) { this.next.shift(); this.next.push(this._pop()); } this.hold = top; } } } export class Cheese { constructor(rng, total, min, max) { this._col = rng.genRange(0, 10); this._rng = rng; this.linesLeft = total; this._level = 0; this._remaining = total; this._min = min; this._max = max; } _nextColumn() { // see `static tidepool::sim::Cheese::next()` this._col += this._rng.genRange(1, 10); this._col %= 10; return this._col; } generate(level, comboing = false) { let target = comboing ? this._min : this._max; let count = Math.min(this._remaining, Math.max(0, target - level)); this._remaining -= count; let result = []; for (let i = 0; i < count; i++) { let col = this._nextColumn(); for (let x = 0; x < 10; x++) { result.push((x === col) ? '_' : 'X'); } } return result.join(''); } } function getGarbageLevel(matrix) { let level = 0; for (let i = 0; i < matrix.length; i++) { let y = Math.floor(i / 10); let cell = matrix[matrix.length - i - 1]; if (cell === 'X') { level = y + 1; } } return level; } if (false) { // see `tidepool::sim::test::test_deterministic()` let sim = new Simul("0x1234567800ABCDEF", { goal: 100, rules: { min: 0, max: 9, previews: 7 }, }); console.log(sim.queue.next.join(',')); // J,L,T,Z,S,O,I console.log(sim.matrix); // XXX_XXXXXXXXXXX_XXXX_XXXXXXXXXXXX_XXXXXXX_XXXXXXXX_XXXXXXXXXXXXXXXXXX_XXXX_XXXXX_XXXXXXXXX }