CoolFace
Apppublic

matiqorb/spades-agent-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
bots.py174 linesDownload Raw Back to spades_engine
1from __future__ import annotations2 3from dataclasses import dataclass4import random5from typing import List6 7from .engine import Card, SpadesGame, team_for_player8 9 10@dataclass11class BaseBot:12    seed: int = 013 14    def __post_init__(self) -> None:15        self.rng = random.Random(self.seed)16 17    def choose_bid(self, game: SpadesGame, player: int) -> int:18        return max(1, min(13, len([c for c in game.hands[player] if c.rank >= 12])))19 20    def choose_card(self, game: SpadesGame, player: int) -> Card:21        legal = sorted(game.legal_cards(player))22        return legal[0]23 24 25class SafeBot(BaseBot):26    def choose_bid(self, game: SpadesGame, player: int) -> int:27        hand = game.hands[player]28        score = 0.029        for card in hand:30            if card.suit == "S":31                score += 0.4532                if card.rank >= 11:33                    score += 0.634            elif card.rank >= 13:35                score += 0.536            elif card.rank == 12:37                score += 0.238        return max(1, min(8, int(round(score))))39 40    def choose_card(self, game: SpadesGame, player: int) -> Card:41        legal = sorted(game.legal_cards(player))42        return legal[0]43 44 45class GreedyBot(BaseBot):46    def choose_bid(self, game: SpadesGame, player: int) -> int:47        hand = game.hands[player]48        score = 0.049        for card in hand:50            if card.suit == "S":51                score += 0.952            if card.rank >= 13:53                score += 0.754            elif card.rank == 12:55                score += 0.356        return max(2, min(13, int(round(score))))57 58    def choose_card(self, game: SpadesGame, player: int) -> Card:59        legal = sorted(game.legal_cards(player))60        lead = not bool(game.trick)61        if lead:62            return legal[-1]63        winning = _winning_candidates(game, legal)64        return winning[-1] if winning else legal[0]65 66 67class AdversarialBot(BaseBot):68    def choose_bid(self, game: SpadesGame, player: int) -> int:69        hand = game.hands[player]70        high = len([c for c in hand if c.rank >= 11])71        spades = len([c for c in hand if c.suit == "S"])72        return max(1, min(10, int(round((high + spades * 0.7) / 2.2))))73 74    def choose_card(self, game: SpadesGame, player: int) -> Card:75        legal = sorted(game.legal_cards(player))76        winning = _winning_candidates(game, legal)77        if winning:78            return winning[0]79        return legal[-1]80 81 82class ExpertPolicy(BaseBot):83    def choose_bid(self, game: SpadesGame, player: int) -> int:84        hand = game.hands[player]85        expected = 0.086        suit_counts = {s: len([c for c in hand if c.suit == s]) for s in ("C", "D", "H", "S")}87        for card in hand:88            if card.suit == "S":89                if card.rank >= 14:90                    expected += 1.091                elif card.rank >= 13:92                    expected += 0.893                elif card.rank >= 12:94                    expected += 0.695                elif card.rank >= 11:96                    expected += 0.4597                elif card.rank >= 9:98                    expected += 0.2599                else:100                    expected += 0.1101            elif card.rank == 14:102                expected += 0.8103            elif card.rank == 13:104                expected += 0.45 if suit_counts[card.suit] <= 2 else 0.6105            elif card.rank == 12:106                expected += 0.2 if suit_counts[card.suit] <= 2 else 0.35107        partner = (player + 2) % 4108        partner_bid = game.bid_history[partner] if game.bid_history[partner] is not None else 3109        aggression = 0.0 if partner_bid <= 3 else -0.4110        # Conservative bids reduce frequent contract crashes during long matches.111        return max(1, min(8, int(round(expected + aggression))))112 113    def choose_card(self, game: SpadesGame, player: int) -> Card:114        legal = sorted(game.legal_cards(player))115        if not game.trick:116            return self._lead_card(game, player, legal)117        return self._follow_card(game, player, legal)118 119    def _lead_card(self, game: SpadesGame, player: int, legal: List[Card]) -> Card:120        team = team_for_player(player)121        team_target = _team_target(game, team)122        team_tricks = game.tricks_won[team]123        remaining = len(game.hands[player])124        need = max(team_target - team_tricks, 0)125        if need >= remaining // 2:126            return legal[-1]127        return legal[0]128 129    def _follow_card(self, game: SpadesGame, player: int, legal: List[Card]) -> Card:130        winning = _winning_candidates(game, legal)131        team = team_for_player(player)132        opp = 1 - team133        team_need = max(_team_target(game, team) - game.tricks_won[team], 0)134        opp_need = max(_team_target(game, opp) - game.tricks_won[opp], 0)135        if winning:136            if team_need > opp_need:137                return winning[0]138            return winning[-1]139        if game.trick and any(c.suit == "S" for _, c in game.trick):140            return legal[0]141        return legal[-1]142 143 144def _team_target(game: SpadesGame, team: int) -> int:145    if team == 0:146        return (game.bid_history[0] or 0) + (game.bid_history[2] or 0)147    return (game.bid_history[1] or 0) + (game.bid_history[3] or 0)148 149 150def _winning_candidates(game: SpadesGame, legal_cards: List[Card]) -> List[Card]:151    if not game.trick:152        return []153    trial = []154    for card in legal_cards:155        copy = list(game.trick)156        copy.append((game.current_player, card))157        winner = _winner(copy)158        if winner == game.current_player:159            trial.append(card)160    return trial161 162 163def _winner(trick):164    lead_suit = trick[0][1].suit165    best_player, best_card = trick[0]166    for player, card in trick[1:]:167        if card.suit == "S" and best_card.suit != "S":168            best_player, best_card = player, card169        elif card.suit == best_card.suit and card.rank > best_card.rank:170            best_player, best_card = player, card171        elif best_card.suit != "S" and card.suit == lead_suit and card.rank > best_card.rank:172            best_player, best_card = player, card173    return best_player174