@idealic/poker-engine
Version:
Poker game engine and hand evaluator
126 lines (107 loc) • 3.57 kB
text/typescript
import type { Hand } from '../Hand';
/**
* Checks if seats array is in ascending order
*/
function isSeatsAscending(seats: number[]): boolean {
for (let i = 1; i < seats.length; i++) {
if (seats[i] <= seats[i - 1]) {
return false;
}
}
return true;
}
/**
* Ensures all player-related arrays are sorted by seat order.
*
* Three cases:
* 1. No seats field or invalid: No sort needed
* 2. Valid seats in ascending order (can have gaps): No sort needed
* 3. Valid seats not in ascending order: Must sort all arrays
*
* @param hand - The hand to ensure seat ordering for
* @returns Hand with arrays potentially reordered by seat
*/
export function ensureSeatOrder<T extends Hand>(hand: T): T {
// Case 1: No seats field - assume sequential seating
if (!hand.seats || hand.seats.length === 0) {
return hand;
}
// Validate seats configuration - if invalid, skip sorting
if (!validateSeats(hand.seats, hand.players.length)) {
return hand;
}
// Case 2: Check if seats are already in ascending order
if (isSeatsAscending(hand.seats)) {
return hand;
}
// Case 3: Valid seats but not ascending - MUST SORT
return sortHandBySeatOrder(hand);
}
/**
* Sorts all player-related arrays based on seat order (ascending)
*/
function sortHandBySeatOrder<T extends Hand>(hand: T): T {
// Create sorting indices based on seat order
const seatIndices = hand
.seats!.map((seat, index) => ({ seat, index }))
.sort((a, b) => a.seat - b.seat);
// Helper to reorder any array based on seat indices
const reorder = <T>(arr: T[] | undefined): T[] | undefined => {
if (!arr) return undefined;
return seatIndices.map(({ index }) => arr[index]);
};
// Helper for required arrays (always defined)
const reorderRequired = <T>(arr: T[]): T[] => {
return seatIndices.map(({ index }) => arr[index]);
};
// Create new hand with all arrays reordered
const sortedHand: T = {
...hand,
// Required arrays
players: reorderRequired(hand.players),
startingStacks: reorderRequired(hand.startingStacks),
blindsOrStraddles: reorderRequired(hand.blindsOrStraddles),
seats: seatIndices.map(({ seat }) => seat), // Now in ascending order
// Optional arrays that should exist
antes: hand.antes ? reorderRequired(hand.antes) : [],
// Optional arrays
_inactive: reorder(hand._inactive),
_intents: reorder(hand._intents),
_deadBlinds: reorder(hand._deadBlinds),
_venueIds: reorder(hand._venueIds),
winnings: reorder(hand.winnings),
finishingStacks: reorder(hand.finishingStacks),
timeBanks: reorder(hand.timeBanks),
_heroIds: reorder(hand._heroIds),
};
return sortedHand;
}
/**
* Validates seat configuration for poker table
*/
export function validateSeats(seats: number[] | undefined, playerCount: number): boolean {
// No seats is valid (implies sequential)
if (!seats || seats.length === 0) {
return true;
}
// Must match player count
if (seats.length !== playerCount) {
return false;
}
// All seats must be positive integers in valid poker range [1-9]
if (!seats.every(seat => Number.isInteger(seat) && seat >= 1 && seat <= 9)) {
return false;
}
// No duplicate seats
const uniqueSeats = new Set(seats);
if (uniqueSeats.size !== seats.length) {
return false;
}
return true;
}
/**
* Creates default sequential seats for a given player count
*/
export function createDefaultSeats(playerCount: number): number[] {
return Array.from({ length: playerCount }, (_, i) => i + 1);
}