shark/tidepool-utils/lib/sim.js

144 lines
3.9 KiB
JavaScript

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
}