Отличная идея! Минимакс с альфа-бета отсечением значительно улучшит силу игры компьютера. Вот улучшенная версия с минимаксом:
```python
import random
from typing import List, Tuple, Optional, Dict
from collections import defaultdict
import math
class Card:
def __init__(self, rank: str, suit: str):
self.rank = rank
self.suit = suit
self.value = self._get_value()
def _get_value(self) -> int:
values = {'6': 6, '7': 7, '8': 8, '9': 9, '10': 10,
'J': 11, 'Q': 12, 'K': 13, 'A': 14}
return values[self.rank]
def __str__(self):
return f"{self.rank}{self.suit}"
def __repr__(self):
return str(self)
def __eq__(self, other):
return self.rank == other.rank and self.suit == other.suit
def __hash__(self):
return hash((self.rank, self.suit))
class Deck:
def __init__(self):
ranks = ['6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
suits = ['♥', '♦', '♣', '♠']
self.cards = [Card(rank, suit) for suit in suits for rank in ranks]
random.shuffle(self.cards)
def deal(self, num_cards: int) -> List[Card]:
return [self.cards.pop() for _ in range(num_cards)]
class GameState:
"""Класс для представления состояния игры"""
def __init__(self, players_hands: List[List[Card]], table: List[Card],
trump: str, attacker: int, deck_size: int):
self.players_hands = [hand.copy() for hand in players_hands]
self.table = table.copy()
self.trump = trump
self.attacker = attacker
self.defender = 1 - attacker
self.deck_size = deck_size
def copy(self):
return GameState(self.players_hands, self.table, self.trump,
self.attacker, self.deck_size)
class FoolGameWithMinimax:
def __init__(self):
self.deck = Deck()
self.players = [[], []] # [0] - компьютер, [1] - игрок
self.trump = self.deck.cards[-1].suit
self.table = []
self.attacker = 1 # кто ходит первым
self.defender = 0 # кто отбивается
self.max_depth = 3 # глубина поиска минимакса
def deal_initial_cards(self):
for i in range(2):
self.players[i] = self.deck.deal(6)
self.sort_hand(self.players[i])
def sort_hand(self, hand: List[Card]):
hand.sort(key=lambda card: (card.suit != self.trump, card.value))
def get_game_state(self) -> GameState:
return GameState(self.players, self.table, self.trump,
self.attacker, len(self.deck.cards))
def evaluate_state(self, state: GameState, player: int) -> float:
"""Оценка состояния игры для указанного игрока"""
if not state.players_hands[player]:
return 1000 # выигрыш
if not state.players_hands[1 - player]:
return -1000 # проигрыш
score = 0
# Оценка по картам в руке
for i, hand in enumerate(state.players_hands):
player_factor = 1 if i == player else -1
hand_value = sum(card.value for card in hand)
# Бонус за козыри
trump_count = sum(1 for card in hand if card.suit == state.trump)
trump_bonus = trump_count * 5
# Штраф за много высоких карт (их сложнее сбросить)
high_cards_penalty = sum(max(0, card.value - 10) for card in hand) * 0.5
score += player_factor * (hand_value + trump_bonus - high_cards_penalty)
# Оценка стола
if state.table:
if state.attacker == player:
# Мы атакуем - хорошо иметь много карт на столе
score += len(state.table) * 2
else:
# Мы защищаемся - плохо иметь много карт на столе
score -= len(state.table) * 3
# Бонус за карты в колоде
score += state.deck_size * 0.1 * (1 if player == state.attacker else -1)
return score
def minimax(self, state: GameState, depth: int, alpha: float, beta: float,
maximizing_player: bool) -> Tuple[float, Optional[Tuple[Card, Optional[Card]]]]:
"""Минимакс с альфа-бета отсечением"""
if depth == 0 or self.is_terminal_state(state):
return self.evaluate_state(state, 0), None # 0 - компьютер
current_player = state.attacker if not state.table else state.defender
is_computer_turn = (current_player == 0)
if (maximizing_player and is_computer_turn) or (not maximizing_player and not is_computer_turn):
return self._max_value(state, depth, alpha, beta, maximizing_player)
else:
return self._min_value(state, depth, alpha, beta, maximizing_player)
def _max_value(self, state: GameState, depth: int, alpha: float, beta: float,
maximizing_player: bool) -> Tuple[float, Optional[Tuple]]:
best_value = -math.inf
best_move = None
for move in self.get_possible_moves(state):
new_state = self.apply_move(state, move)
value, _ = self.minimax(new_state, depth - 1, alpha, beta, not maximizing_player)
if value > best_value:
best_value = value
best_move = move
alpha = max(alpha, best_value)
if beta <= alpha:
break
return best_value, best_move
def _min_value(self, state: GameState, depth: int, alpha: float, beta: float,
maximizing_player: bool) -> Tuple[float, Optional[Tuple]]:
best_value = math.inf
best_move = None
for move in self.get_possible_moves(state):
new_state = self.apply_move(state, move)
value, _ = self.minimax(new_state, depth - 1, alpha, beta, not maximizing_player)
if value < best_value:
best_value = value
best_move = move
beta = min(beta, best_value)
if beta <= alpha:
break
return best_value, best_move
def get_possible_moves(self, state: GameState) -> List[Tuple[Card, Optional[Card]]]:
"""Возвращает все возможные ходы для текущего состояния"""
moves = []
current_player = state.attacker if not state.table else state.defender
if not state.table: # Атака
# Можно пойти любой картой
for card in state.players_hands[current_player]:
moves.append((card, None))
else: # Защита или дополнительная атака
if current_player == state.defender: # Защита
attacking_card = state.table[-1]
for card in state.players_hands[current_player]:
if self._can_beat(attacking_card, card, state.trump):
moves.append((card, None))
# Также можно взять карты
moves.append((None, None))
else: # Дополнительная атака
table_ranks = {card.rank for card in state.table}
for card in state.players_hands[current_player]:
if card.rank in table_ranks:
moves.append((card, None))
return moves
def apply_move(self, state: GameState, move: Tuple[Card, Optional[Card]]) -> GameState:
"""Применяет ход к состоянию и возвращает новое состояние"""
new_state = state.copy()
card, _ = move
if card is None: # Взять карты
new_state.players_hands[new_state.defender].extend(new_state.table)
new_state.table.clear()
new_state.attacker, new_state.defender = new_state.defender, new_state.attacker
else: # Сходить картой
# Убираем карту из руки игрока
new_state.players_hands[new_state.attacker if not new_state.table else new_state.defender].remove(card)
new_state.table.append(card)
# Если защита успешна и карты закончились - очищаем стол
if new_state.table and len(new_state.table) % 2 == 0:
if not self.can_continue_attack(new_state):
new_state.table.clear()
new_state.attacker, new_state.defender = new_state.defender, new_state.attacker
return new_state
def can_continue_attack(self, state: GameState) -> bool:
"""Можно ли продолжать атаку"""
if not state.table or len(state.players_hands[state.attacker]) == 0:
return False
table_ranks = {card.rank for card in state.table}
return any(card.rank in table_ranks for card in state.players_hands[state.attacker])
def is_terminal_state(self, state: GameState) -> bool:
"""Проверка конечного состояния"""
return (not state.players_hands[0] or not state.players_hands[1] or
(not state.deck_size and not state.table and
(not state.players_hands[state.attacker] or not state.players_hands[state.defender])))
def _can_beat(self, attacking: Card, defending: Card, trump: str) -> bool:
"""Может ли защищающая карта побить атакующую"""
if defending.suit == trump and attacking.suit != trump:
return True
elif defending.suit == attacking.suit and defending.value > attacking.value:
return True
return False
def find_best_move_minimax(self) -> Tuple[Optional[Card], Optional[Card]]:
"""Находит лучший ход с помощью минимакса"""
state = self.get_game_state()
_, best_move = self.minimax(state, self.max_depth, -math.inf, math.inf, True)
return best_move or (None, None)
def make_computer_move(self):
"""Компьютер делает ход на основе минимакса"""
print("Компьютер думает...")
card_to_play, _ = self.find_best_move_minimax()
if card_to_play:
self.players[0].remove(card_to_play)
self.table.append(card_to_play)
print(f"Компьютер ходит: {card_to_play}")
return True
else:
print("Компьютер берет карты")
self.players[0].extend(self.table)
self.table.clear()
self.sort_hand(self.players[0])
return False
def make_player_move(self, card_index: int) -> bool:
"""Игрок делает ход"""
if 0 <= card_index < len(self.players[1]):
card = self.players[1][card_index]
self.players[1].remove(card)
self.table.append(card)
print(f"Вы ходите: {card}")
return True
return False
def player_defend(self, card_index: int) -> bool:
"""Игрок защищается"""
if not self.table:
return False
attacking_card = self.table[-1]
if 0 <= card_index < len(self.players[1]):
defending_card = self.players[1][card_index]
if self._can_beat(attacking_card, defending_card, self.trump):
self.players[1].remove(defending_card)
self.table.append(defending_card)
print(f"Вы отбиваете: {defending_card}")
return True
print("Нельзя побить этой картой!")
return False
def replenish_hands(self):
"""Добираем карты из колоды"""
for i in range(2):
while len(self.players[i]) < 6 and self.deck.cards:
new_card = self.deck.deal(1)[0]
self.players[i].append(new_card)
self.sort_hand(self.players[i])
def display_game_state(self):
"""Показывает текущее состояние игры"""
print(f"\n{'='*50}")
print(f"Козырь: {self.trump}")
print(f"Карт в колоде: {len(self.deck.cards)}")
print(f"\nСтол: {self.table}")
print(f"\nВаши карты:")
for i, card in enumerate(self.players[1]):
print(f"{i}: {card}")
print(f"\nКарты компьютера: {len(self.players[0])} карт")
def main():
game = FoolGameWithMinimax()
game.deal_initial_cards()
print("Добро пожаловать в игру 'Дурак' с минимакс-ИИ!")
print("Компьютер использует алгоритм минимакс для поиска лучшего хода.")
while True:
game.display_game_state()
if game.attacker == 1: # ход игрока
if not game.table: # атака
print("\nВаш ход (атака). Выберите карту:")
try:
choice = int(input("Введите номер карты: "))
if game.make_player_move(choice):
game.attacker, game.defender = game.defender, game.attacker
except (ValueError, IndexError):
print("Неверный ввод!")
continue
else: # дополнительная атака
print("\nХотите подкинуть карту? (y/n/t - взять)")
action = input().lower()
if action == 'y':
print("Выберите карту для подкидывания:")
try:
choice = int(input("Введите номер карты: "))
if game.make_player_move(choice):
pass
else:
continue
except (ValueError, IndexError):
print("Неверный ввод!")
continue
elif action == 't':
print("Вы берете карты")
game.players[1].extend(game.table)
game.table.clear()
game.sort_hand(game.players[1])
game.replenish_hands()
game.attacker = 0
game.defender = 1
else:
# Завершаем раунд
game.table.clear()
game.replenish_hands()
game.attacker = 0
game.defender = 1
else: # ход компьютера
if not game.table: # атака
print("\nХод компьютера (атака):")
if game.make_computer_move():
game.attacker, game.defender = game.defender, game.attacker
else:
game.table.clear()
game.replenish_hands()
else: # защита компьютера
print("\nХод компьютера (защита):")
if game.make_computer_move():
pass
else:
print("Компьютер берет карты")
game.players[0].extend(game.table)
game.table.clear()
game.sort_hand(game.players[0])
game.replenish_hands()
game.attacker = 1
game.defender = 0
# Проверка конца игры
if not game.players[0] and not game.players[1]:
print("Ничья!")
break
elif not game.players[0]:
print("Компьютер проиграл! Вы выиграли!")
break
elif not game.players[1]:
print("Вы проиграли! Компьютер выиграл!")
break
if __name__ == "__main__":
main()
```
**Основные улучшения с минимаксом:**
1. **Класс GameState** - изолированное состояние игры для поиска
2. **Минимакс с альфа-бета отсечением** - просчет ходов на 3 шага вперед
3. **Функция оценки** учитывает:
- Количество и ценность карт
- Козыри
- Позицию на столе
- Карты в колоде
4. **Генерация всех возможных ходов** для каждого состояния
5. **Применение ходов** к копиям состояния для симуляции
**Преимущества минимакса:**
- Компьютер думает на несколько ходов вперед
- Учитывает ответные ходы игрока
- Выбирает стратегически выгодные позиции
- Избегает ловушек и использует слабости игрока
**Настройки:**
- `max_depth = 3` - можно увеличить для более сильной игры (но дольше расчет)
- Функцию оценки можно дополнить более сложными эвристиками
Теперь компьютер играет на качественно новом уровне!