@haelp/teto
Version:
A typescript-based controllable TETR.IO client.
199 lines (198 loc) • 7.06 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "LegacyGarbageQueue", {
enumerable: true,
get: function() {
return LegacyGarbageQueue;
}
});
const _utils = require("../utils");
const columnWidth = (width, garbageHoleSize)=>{
return Math.max(0, width - (garbageHoleSize - 1));
};
class LegacyGarbageQueue {
options;
queue;
lastTankTime = 0;
lastColumn = null;
rng;
// for opener phase calculations
sent = 0;
constructor(options){
this.options = (0, _utils.deepCopy)(options);
if (!this.options.cap.absolute) this.options.cap.absolute = Number.MAX_SAFE_INTEGER;
this.queue = [];
this.rng = new _utils.RNG(this.options.seed);
}
snapshot() {
return {
seed: this.rng.seed,
lastTankTime: this.lastTankTime,
lastColumn: this.lastColumn,
sent: this.sent,
queue: (0, _utils.deepCopy)(this.queue),
hasChangedColumn: false,
lastReceivedCount: 0
};
}
fromSnapshot(snapshot) {
this.queue = (0, _utils.deepCopy)(snapshot.queue);
this.lastTankTime = snapshot.lastTankTime;
this.lastColumn = snapshot.lastColumn;
this.rng = new _utils.RNG(snapshot.seed);
this.sent = snapshot.sent;
}
rngex() {
return this.rng.nextFloat();
}
get size() {
return this.queue.reduce((a, b)=>a + b.amount, 0);
}
receive(...args) {
this.queue.push(...args.filter((arg)=>arg.amount > 0));
while(this.size > this.options.cap.absolute){
const total = this.size;
if (this.queue.at(-1).amount <= total - this.options.cap.absolute) {
this.queue.pop();
} else {
this.queue.at(-1).amount -= total - this.options.cap.absolute;
}
}
}
confirm(cid, gameid, frame) {
const obj = this.queue.find((g)=>g.cid === cid && g.gameid === gameid);
if (!obj) return false;
obj.frame = frame;
obj.confirmed = true;
return true;
}
cancel(amount, pieceCount, legacy = {}) {
let send = amount, cancel = 0;
let cancelled = [];
if (pieceCount + 1 <= this.options.openerPhase - (legacy.openerPhase ? 1 : 0) && this.size >= this.sent) cancel += amount;
while((send > 0 || cancel > 0) && this.size > 0){
this.queue[0].amount--;
if (cancelled.length === 0 || cancelled[cancelled.length - 1].cid !== this.queue[0].cid) {
cancelled.push({
...this.queue[0],
amount: 1
});
} else {
cancelled[cancelled.length - 1].amount++;
}
if (this.queue[0].amount <= 0) this.queue.shift();
if (send > 0) send--;
else cancel--;
}
this.sent += send;
return [
send,
cancelled
];
}
/**
* This function does NOT take into account messiness on timeout.
* The first garbage hole will be correct,
* but subsequent holes depend on whether or not garbage is cancelled.
*/ predict() {
const rng = this.rng.clone();
const rngex = rng.nextFloat.bind(rng);
let lastColumn = this.lastColumn;
const reroll = ()=>{
lastColumn = this.#__internal_rerollColumn(lastColumn, rngex);
return lastColumn;
};
const result = this.#__internal_tank((0, _utils.deepCopy)(this.queue), ()=>lastColumn, rngex, reroll, -Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, false);
return result.res;
}
get nextColumn() {
const rng = this.rng.clone();
if (this.lastColumn === null) return this.#__internal_rerollColumn(null, rng.nextFloat.bind(rng));
return this.lastColumn;
}
#__internal_rerollColumn(current, rngex) {
let col;
const cols = columnWidth(this.options.boardWidth, this.options.garbage.holeSize);
if (this.options.messiness.nosame && current !== null) {
col = Math.floor(rngex() * (cols - 1));
if (col >= current) col++;
} else {
col = Math.floor(rngex() * cols);
}
return col;
}
#rerollColumn() {
const col = this.#__internal_rerollColumn(this.lastColumn, this.rngex.bind(this));
this.lastColumn = col;
return col;
}
#__internal_tank(queue, lastColumn, rngex, reroll, frame, cap, hard) {
if (queue.length === 0) return {
res: [],
lastColumn,
queue
};
const res = [];
queue = queue.sort((a, b)=>a.frame - b.frame);
if (this.options.messiness.timeout && frame >= this.lastTankTime + this.options.messiness.timeout) {
reroll();
this.lastTankTime = frame;
}
let total = 0;
while(total < cap && queue.length > 0){
const item = (0, _utils.deepCopy)(queue[0]);
// TODO: wtf hacky fix, this is 100% not right idk how to fix this
if (item.frame + this.options.garbage.speed > (hard ? frame : frame - 1)) break; // do not spawn garbage that is still traveling
total += item.amount;
let exausted = false;
if (total > cap) {
const excess = total - cap;
queue[0].amount = excess;
item.amount -= excess;
} else {
queue.shift();
exausted = true;
}
for(let i = 0; i < item.amount; i++){
const r = lastColumn() === null || rngex() < this.options.messiness.within;
res.push({
...item,
id: item.cid,
amount: 1,
column: r ? reroll() : lastColumn()
});
}
if (exausted && rngex() < this.options.messiness.change) {
reroll();
}
}
return {
res,
queue
};
}
tank(frame, cap, hard) {
const { res, queue } = this.#__internal_tank(this.queue, ()=>this.lastColumn, this.rngex.bind(this), this.#rerollColumn.bind(this), frame, cap, hard);
this.queue = queue;
return res.map((v)=>({
...v,
bombs: this.options.bombs
}));
}
round(amount) {
switch(this.options.rounding){
case "down":
return Math.floor(amount);
case "rng":
const floored = Math.floor(amount);
if (floored === amount) return floored;
const decimal = amount - floored;
return floored + (this.rngex() < decimal ? 1 : 0);
default:
throw new Error(`Invalid rounding mode ${this.options.rounding}`);
}
}
}
//# sourceMappingURL=legacy.js.map