belote
Version:
A TypeScript library for playing Belote card game with bot support.
89 lines (88 loc) • 4.05 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BotAI = void 0;
const types_1 = require("./types");
const utils_1 = require("./utils");
class BotAI {
static decideBid(player) {
const colorCounts = { hearts: 0, diamonds: 0, clubs: 0, spades: 0 };
const colorValues = { hearts: 0, diamonds: 0, clubs: 0, spades: 0 };
for (const card of player.cards) {
colorCounts[card.color]++;
colorValues[card.color] += (0, utils_1.getCardValue)(card, card.color);
}
let bestColor = null;
let bestScore = 0;
for (const color of types_1.cardColors) {
const score = colorCounts[color] * 2 + colorValues[color] * 0.1;
if (score > bestScore) {
bestScore = score;
bestColor = color;
}
}
const minCardsForBid = 3;
const minScoreForBid = 6;
if (bestColor && colorCounts[bestColor] >= minCardsForBid && bestScore >= minScoreForBid) {
return bestColor;
}
if (player.isDealer) {
const rng = (0, utils_1.mulberry32)(Date.now());
return bestColor || types_1.cardColors[Math.floor(rng() * types_1.cardColors.length)];
}
return 'pass';
}
static decideCalling(player, adut) {
const allPossibleCalls = this.findAllPossibleCalls(player.cards, adut);
if (allPossibleCalls.length === 0)
return [];
allPossibleCalls.sort((a, b) => b.calling.type - a.calling.type);
return allPossibleCalls[0].cards;
}
static decideCardToPlay(player, currentTrick, adut) {
const allPlayerCards = [...player.cards, ...player.talon];
const legalCards = allPlayerCards.filter((card) => (0, utils_1.canPlayCard)(card, currentTrick, adut, allPlayerCards));
if (legalCards.length === 0)
throw new Error('No legal cards to play?');
else if (legalCards.length === 1)
return legalCards[0];
// Leading - if no cards played, choose best card.
if (currentTrick.length === 0)
return this.chooseBestLeadingCard(legalCards, adut);
// Following - try to win or play strategically.
else
return this.chooseBestFollowingCard(legalCards, currentTrick, adut);
}
static findAllPossibleCalls(cards, adut) {
const results = [];
for (let i = 1; i < (1 << cards.length); i++) {
const subset = [];
for (let j = 0; j < cards.length; j++) {
if (i & (1 << j)) {
subset.push(cards[j]);
}
}
const calling = (0, utils_1.validateCalling)(subset, adut);
if (calling)
results.push({ cards: subset, calling });
}
return results;
}
static chooseBestLeadingCard(cards, adut) {
// Prefer trump cards, then high-value cards.
const trumpCards = cards.filter((c) => c.color === adut);
if (trumpCards.length > 0) {
return trumpCards.reduce((best, current) => (0, utils_1.getCardValue)(current, adut) > (0, utils_1.getCardValue)(best, adut) ? current : best);
}
// Otherwise play highest value card.
return cards.reduce((best, current) => (0, utils_1.getCardValue)(current, adut) > (0, utils_1.getCardValue)(best, adut) ? current : best);
}
static chooseBestFollowingCard(cards, currentTrick, adut) {
const winningCards = cards.filter((card) => {
return currentTrick.every((playedCard) => (0, utils_1.canBeatCard)(card, playedCard, adut));
});
if (winningCards.length > 0)
return winningCards.reduce((best, current) => (0, utils_1.getCardValue)(current, adut) < (0, utils_1.getCardValue)(best, adut) ? current : best);
return cards.reduce((best, current) => (0, utils_1.getCardValue)(current, adut) < (0, utils_1.getCardValue)(best, adut) ? current : best);
}
}
exports.BotAI = BotAI;