/**
* Build command files for TexasSolver
*/
import { POST_FLOP_STREETS, DEFAULT_SOLVER_CONFIG } from '../utils/constants.js';
export class CommandBuilder {
constructor() {
this.commands = [];
}
/**
* Set pot size
* @param {number} amount
* @returns {CommandBuilder}
*/
setPot(amount) {
this.commands.push(`set_pot ${amount}`);
return this;
}
/**
* Set effective stack size
* @param {number} amount
* @returns {CommandBuilder}
*/
setEffectiveStack(amount) {
this.commands.push(`set_effective_stack ${amount}`);
return this;
}
/**
* Set board cards
* @param {string} cards - "Qs,8d,7h" or empty string for preflop
* @returns {CommandBuilder}
*/
setBoard(cards) {
if (cards && cards.trim()) {
this.commands.push(`set_board ${cards}`);
}
return this;
}
/**
* Set player range
* @param {string} position - "ip" or "oop"
* @param {string} range - "AA-22,AK-AJ" or path to range file
* @returns {CommandBuilder}
*/
setRange(position, range) {
if (!position || !range) {
throw new Error('Position and range are required');
}
this.commands.push(`set_range_${position} ${range}`);
return this;
}
/**
* Set bet sizes for a position, street, and action
* @param {string} position - "ip" or "oop"
* @param {string} street - "flop", "turn", or "river"
* @param {string} action - "bet", "raise", "donk", or "allin"
* @param {Array<number>|string} sizes - Array of sizes or "allin"
* @returns {CommandBuilder}
*/
setBetSizes(position, street, action, sizes) {
if (!position || !street || !action) {
throw new Error('Position, street, and action are required');
}
const cmdBase = `set_bet_sizes ${position},${street},${action}`;
if (action === 'allin' || sizes === 'allin') {
this.commands.push(cmdBase);
} else if (Array.isArray(sizes) && sizes.length > 0) {
const sizeStr = sizes.join(',');
this.commands.push(`${cmdBase},${sizeStr}`);
} else if (sizes) {
this.commands.push(`${cmdBase},${sizes}`);
}
return this;
}
/**
* Build the game tree
* @returns {CommandBuilder}
*/
buildTree() {
this.commands.push('build_tree');
return this;
}
/**
* Set solver configuration
* @param {Object} config
* @returns {CommandBuilder}
*/
setSolverConfig(config = {}) {
const {
thread_num = DEFAULT_SOLVER_CONFIG.thread_num,
accuracy = DEFAULT_SOLVER_CONFIG.accuracy,
max_iteration = DEFAULT_SOLVER_CONFIG.max_iteration,
print_interval = DEFAULT_SOLVER_CONFIG.print_interval,
use_isomorphism = DEFAULT_SOLVER_CONFIG.use_isomorphism,
allin_threshold = DEFAULT_SOLVER_CONFIG.allin_threshold
} = config;
this.commands.push(`set_thread_num ${thread_num}`);
this.commands.push(`set_accuracy ${accuracy}`);
this.commands.push(`set_max_iteration ${max_iteration}`);
this.commands.push(`set_print_interval ${print_interval}`);
this.commands.push(`set_use_isomorphism ${use_isomorphism ? 1 : 0}`);
this.commands.push(`set_allin_threshold ${allin_threshold}`);
return this;
}
/**
* Start solving
* @returns {CommandBuilder}
*/
startSolve() {
this.commands.push('start_solve');
return this;
}
/**
* Set which rounds to dump
* @param {number} rounds - 0=flop, 1=turn, 2=river
* @returns {CommandBuilder}
*/
setDumpRounds(rounds) {
if (![0, 1, 2].includes(rounds)) {
throw new Error('dump_rounds must be 0 (flop), 1 (turn), or 2 (river)');
}
this.commands.push(`set_dump_rounds ${rounds}`);
return this;
}
/**
* Set output filename
* @param {string} outputPath - Path to output file
* @returns {CommandBuilder}
*/
dumpResult(outputPath) {
if (!outputPath) {
throw new Error('Output path is required');
}
this.commands.push(`dump_result ${outputPath}`);
return this;
}
/**
* Build the command string
* @returns {string}
*/
build() {
return this.commands.join('\n');
}
/**
* Get commands as array
* @returns {Array<string>}
*/
getCommands() {
return [...this.commands];
}
}
/**
* Create a solver command from parameters
* @param {Object} params
* @returns {string}
*/
export function buildSolverCommand(params) {
const {
pot,
effective_stack,
board,
range_ip,
range_oop,
bet_sizes,
solver_config,
output_path
} = params;
const builder = new CommandBuilder();
builder
.setPot(pot)
.setEffectiveStack(effective_stack)
.setBoard(board || '')
.setRange('ip', range_ip)
.setRange('oop', range_oop);
// Add bet sizes if provided
if (bet_sizes) {
for (const position of ['ip', 'oop']) {
if (bet_sizes[position]) {
const positionBets = bet_sizes[position];
for (const street of POST_FLOP_STREETS) {
if (positionBets[street]) {
const streetBets = positionBets[street];
for (const [action, sizes] of Object.entries(streetBets)) {
if (sizes) {
builder.setBetSizes(position, street, action, sizes);
}
}
}
}
}
}
}
builder
.buildTree()
.setSolverConfig(solver_config || {})
.startSolve()
.setDumpRounds(solver_config?.dump_rounds || DEFAULT_SOLVER_CONFIG.dump_rounds);
if (output_path) {
builder.dumpResult(output_path);
}
return builder.build();
}
export default CommandBuilder;