mirror of
https://github.com/RichieCahill/dotfiles.git
synced 2026-04-17 13:08:19 -04:00
266 lines
8.7 KiB
Python
266 lines
8.7 KiB
Python
"""Bot for Splendor game."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import random
|
|
|
|
from .base import (
|
|
BASE_COLORS,
|
|
Action,
|
|
BuyCard,
|
|
BuyCardReserved,
|
|
Card,
|
|
GameState,
|
|
GemColor,
|
|
PlayerState,
|
|
ReserveCard,
|
|
Strategy,
|
|
TakeDifferent,
|
|
TakeDouble,
|
|
auto_discard_tokens,
|
|
get_legal_actions,
|
|
)
|
|
|
|
|
|
def can_bot_afford(player: PlayerState, card: Card) -> bool:
|
|
"""Check if player can afford card, using discounts + gold."""
|
|
missing = 0
|
|
gold = player.tokens["gold"]
|
|
for color, cost in card.cost.items():
|
|
missing += max(0, cost - player.discounts.get(color, 0) - player.tokens.get(color, 0))
|
|
if missing > gold:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
class RandomBot(Strategy):
|
|
"""Dumb bot that follows rules but doesn't think."""
|
|
|
|
def __init__(self, name: str) -> None:
|
|
super().__init__(name=name)
|
|
|
|
def choose_action(self, game: GameState, player: PlayerState) -> Action | None:
|
|
affordable: list[tuple[int, int]] = []
|
|
for tier, row in game.table_by_tier.items():
|
|
for idx, card in enumerate(row):
|
|
if can_bot_afford(player, card):
|
|
affordable.append((tier, idx))
|
|
if affordable and random.random() < 0.5:
|
|
tier, idx = random.choice(affordable)
|
|
return BuyCard(tier=tier, index=idx)
|
|
|
|
if random.random() < 0.2:
|
|
tier = random.choice([1, 2, 3])
|
|
row = game.table_by_tier.get(tier, [])
|
|
if row:
|
|
idx = random.randrange(len(row))
|
|
return ReserveCard(tier=tier, index=idx, from_deck=False)
|
|
|
|
if random.random() < 0.5:
|
|
colors_for_double = [c for c in BASE_COLORS if game.bank[c] >= 4]
|
|
if colors_for_double:
|
|
return TakeDouble(color=random.choice(colors_for_double))
|
|
|
|
colors_for_diff = [c for c in BASE_COLORS if game.bank[c] > 0]
|
|
random.shuffle(colors_for_diff)
|
|
return TakeDifferent(colors=colors_for_diff[:3])
|
|
|
|
def choose_discard(
|
|
self,
|
|
game: GameState,
|
|
player: PlayerState,
|
|
excess: int,
|
|
) -> dict[GemColor, int]:
|
|
return auto_discard_tokens(player, excess)
|
|
|
|
|
|
class PersonalizedBot(Strategy):
|
|
"""Dumb bot that follows rules but doesn't think."""
|
|
|
|
def __init__(self, name: str) -> None:
|
|
super().__init__(name=name)
|
|
|
|
def check_cards_in_tier(self, row: list[Card], player: PlayerState) -> bool:
|
|
"""Check if player can afford card, using discounts + gold."""
|
|
return [index for index, card in enumerate(row) if can_bot_afford(player, card)]
|
|
|
|
def choose_action(self, game: GameState, player: PlayerState) -> Action | None:
|
|
for tier in (1, 2, 3):
|
|
row = game.table_by_tier[tier]
|
|
if affordable := check_cards_in_tier(row, player):
|
|
index = random.choice(affordable)
|
|
return BuyCard(tier=tier, index=index)
|
|
|
|
colors_for_diff = [c for c in BASE_COLORS if game.bank[c] > 0]
|
|
random.shuffle(colors_for_diff)
|
|
return TakeDifferent(colors=colors_for_diff[:3])
|
|
|
|
def choose_discard(
|
|
self,
|
|
game: GameState,
|
|
player: PlayerState,
|
|
excess: int,
|
|
) -> dict[GemColor, int]:
|
|
return auto_discard_tokens(player, excess)
|
|
|
|
|
|
def check_cards_in_tier(row: list[Card], player: PlayerState) -> bool:
|
|
"""Check if player can afford card, using discounts + gold."""
|
|
return [index for index, card in enumerate(row) if can_bot_afford(player, card)]
|
|
|
|
|
|
class PersonalizedBot2(Strategy):
|
|
"""Dumb bot that follows rules but doesn't think."""
|
|
|
|
def __init__(self, name: str) -> None:
|
|
super().__init__(name=name)
|
|
|
|
def choose_action(self, game: GameState, player: PlayerState) -> Action | None:
|
|
tiers = (1, 2, 3)
|
|
for tier in tiers:
|
|
row = game.table_by_tier[tier]
|
|
if affordable := check_cards_in_tier(row, player):
|
|
index = random.choice(affordable)
|
|
return BuyCard(tier=tier, index=index)
|
|
|
|
if affordable := check_cards_in_tier(player.reserved, player):
|
|
index = random.choice(affordable)
|
|
return BuyCardReserved(index=index)
|
|
|
|
colors_for_diff = [c for c in BASE_COLORS if game.bank[c] > 0]
|
|
if len(colors_for_diff) >= 3:
|
|
random.shuffle(colors_for_diff)
|
|
return TakeDifferent(colors=colors_for_diff[:3])
|
|
|
|
for tier in tiers:
|
|
len_deck = len(game.decks_by_tier[tier])
|
|
if len_deck:
|
|
return ReserveCard(tier=tier, index=None, from_deck=True)
|
|
|
|
return TakeDifferent(colors=colors_for_diff[:3])
|
|
|
|
def choose_discard(
|
|
self,
|
|
game: GameState,
|
|
player: PlayerState,
|
|
excess: int,
|
|
) -> dict[GemColor, int]:
|
|
return auto_discard_tokens(player, excess)
|
|
|
|
|
|
def buy_card_reserved(player: PlayerState) -> Action | None:
|
|
if affordable := check_cards_in_tier(player.reserved, player):
|
|
index = random.choice(affordable)
|
|
return BuyCardReserved(index=index)
|
|
return None
|
|
|
|
|
|
def buy_card(game: GameState, player: PlayerState) -> Action | None:
|
|
for tier in (1, 2, 3):
|
|
row = game.table_by_tier[tier]
|
|
if affordable := check_cards_in_tier(row, player):
|
|
index = random.choice(affordable)
|
|
return BuyCard(tier=tier, index=index)
|
|
return None
|
|
|
|
|
|
def take_toekns(game: GameState) -> Action | None:
|
|
colors_for_diff = [color for color in BASE_COLORS if game.bank[color] > 0]
|
|
if len(colors_for_diff) >= 3:
|
|
random.shuffle(colors_for_diff)
|
|
return TakeDifferent(colors=colors_for_diff[: game.config.max_token_take])
|
|
return None
|
|
|
|
|
|
class PersonalizedBot3(Strategy):
|
|
"""Dumb bot that follows rules but doesn't think."""
|
|
|
|
def __init__(self, name: str) -> None:
|
|
super().__init__(name=name)
|
|
|
|
def choose_action(self, game: GameState, player: PlayerState) -> Action | None:
|
|
print(len(get_legal_actions(game, player)))
|
|
print(get_legal_actions(game, player))
|
|
if action := buy_card_reserved(player):
|
|
return action
|
|
if action := buy_card(game, player):
|
|
return action
|
|
|
|
colors_for_diff = [color for color in BASE_COLORS if game.bank[color] > 0]
|
|
if len(colors_for_diff) >= 3:
|
|
random.shuffle(colors_for_diff)
|
|
return TakeDifferent(colors=colors_for_diff[:3])
|
|
|
|
for tier in (1, 2, 3):
|
|
len_deck = len(game.decks_by_tier[tier])
|
|
if len_deck:
|
|
return ReserveCard(tier=tier, index=None, from_deck=True)
|
|
|
|
return TakeDifferent(colors=colors_for_diff[:3])
|
|
|
|
def choose_discard(
|
|
self,
|
|
game: GameState,
|
|
player: PlayerState,
|
|
excess: int,
|
|
) -> dict[GemColor, int]:
|
|
return auto_discard_tokens(player, excess)
|
|
|
|
|
|
def estimate_value_of_card(game: GameState, player: PlayerState, color: GemColor) -> int:
|
|
"""Estimate value of a color in the player's bank."""
|
|
return game.bank[color] - player.discounts.get(color, 0)
|
|
|
|
|
|
def estimate_value_of_token(game: GameState, player: PlayerState, color: GemColor) -> int:
|
|
"""Estimate value of a color in the player's bank."""
|
|
return game.bank[color] - player.discounts.get(color, 0)
|
|
|
|
|
|
class PersonalizedBot4(Strategy):
|
|
def __init__(self, name: str) -> None:
|
|
super().__init__(name=name)
|
|
|
|
def filter_actions(self, actions: list[Action]) -> list[Action]:
|
|
return [
|
|
action
|
|
for action in actions
|
|
if (isinstance(action, TakeDifferent) and len(action.colors) == 3) or not isinstance(action, TakeDifferent)
|
|
]
|
|
|
|
def choose_action(self, game: GameState, player: PlayerState) -> Action | None:
|
|
legal_actions = get_legal_actions(game, player)
|
|
print(len(legal_actions))
|
|
|
|
good_actions = self.filter_actions(legal_actions)
|
|
print(len(good_actions))
|
|
|
|
print(good_actions)
|
|
|
|
print(len(get_legal_actions(game, player)))
|
|
if action := buy_card_reserved(player):
|
|
return action
|
|
if action := buy_card(game, player):
|
|
return action
|
|
|
|
colors_for_diff = [color for color in BASE_COLORS if game.bank[color] > 0]
|
|
if len(colors_for_diff) >= 3:
|
|
random.shuffle(colors_for_diff)
|
|
return TakeDifferent(colors=colors_for_diff[:3])
|
|
|
|
for tier in (1, 2, 3):
|
|
len_deck = len(game.decks_by_tier[tier])
|
|
if len_deck:
|
|
return ReserveCard(tier=tier, index=None, from_deck=True)
|
|
|
|
return TakeDifferent(colors=colors_for_diff[:3])
|
|
|
|
def choose_discard(
|
|
self,
|
|
game: GameState,
|
|
player: PlayerState,
|
|
excess: int,
|
|
) -> dict[GemColor, int]:
|
|
return auto_discard_tokens(player, excess)
|