144 lines
3.9 KiB
JavaScript
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
|
|
}
|