diff --git a/card_game/__pycache__/ai.cpython-312.pyc b/card_game/__pycache__/ai.cpython-312.pyc index 3a0805a..c12a520 100644 Binary files a/card_game/__pycache__/ai.cpython-312.pyc and b/card_game/__pycache__/ai.cpython-312.pyc differ diff --git a/card_game/__pycache__/battlefield.cpython-312.pyc b/card_game/__pycache__/battlefield.cpython-312.pyc index 4ef0b95..6ceca93 100644 Binary files a/card_game/__pycache__/battlefield.cpython-312.pyc and b/card_game/__pycache__/battlefield.cpython-312.pyc differ diff --git a/card_game/__pycache__/config.cpython-312.pyc b/card_game/__pycache__/config.cpython-312.pyc index 7067ebf..b5b6e3e 100644 Binary files a/card_game/__pycache__/config.cpython-312.pyc and b/card_game/__pycache__/config.cpython-312.pyc differ diff --git a/card_game/__pycache__/main.cpython-312.pyc b/card_game/__pycache__/main.cpython-312.pyc index 42a1eb7..914cab7 100644 Binary files a/card_game/__pycache__/main.cpython-312.pyc and b/card_game/__pycache__/main.cpython-312.pyc differ diff --git a/card_game/__pycache__/player.cpython-312.pyc b/card_game/__pycache__/player.cpython-312.pyc index 2a4d497..0e209a4 100644 Binary files a/card_game/__pycache__/player.cpython-312.pyc and b/card_game/__pycache__/player.cpython-312.pyc differ diff --git a/card_game/__pycache__/ui.cpython-312.pyc b/card_game/__pycache__/ui.cpython-312.pyc index f4a5550..d17317d 100644 Binary files a/card_game/__pycache__/ui.cpython-312.pyc and b/card_game/__pycache__/ui.cpython-312.pyc differ diff --git a/card_game/ai.py b/card_game/ai.py index 68f6c24..3ed5be2 100644 --- a/card_game/ai.py +++ b/card_game/ai.py @@ -85,7 +85,7 @@ class AIPlayer: # Frontline units: attack enemy frontline > enemy support > capital for unit in ai.get_frontline_units(): - if not unit.can_attack or unit.has_attacked: + if not unit.can_attack or unit.has_attacked or not ai.can_afford_attack(unit): continue enemy_front = player.get_frontline_units() enemy_support = player.get_support_units() @@ -104,7 +104,7 @@ class AIPlayer: # Support units: attack enemy frontline > enemy support > capital (ranged only) for unit in ai.get_support_units(): - if not unit.can_attack or unit.has_attacked: + if not unit.can_attack or unit.has_attacked or not ai.can_afford_attack(unit): continue enemy_front = player.get_frontline_units() enemy_support = player.get_support_units() diff --git a/card_game/battlefield.py b/card_game/battlefield.py index 18ebd8f..1823de7 100644 --- a/card_game/battlefield.py +++ b/card_game/battlefield.py @@ -79,12 +79,17 @@ class Battlefield: def resolve_attack(self, attacker, defender): dead = [] + owner = self._get_unit_owner(attacker) + if owner: + attack_cost = owner.get_attack_cost(attacker) + if owner.provisions < attack_cost: + return dead + owner.provisions -= attack_cost atk = attacker.get_effective_attack() if "siege" in attacker.abilities and defender == "capital": atk *= 2 - owner = self._get_unit_owner(attacker) if (owner and owner.faction_id == "han" and attacker.unit_type == "archer" and defender == "capital"): atk += 1 diff --git a/card_game/config.py b/card_game/config.py index 7d3e3ba..ea6e19d 100644 --- a/card_game/config.py +++ b/card_game/config.py @@ -95,9 +95,10 @@ MAX_HAND_SIZE = 8 # --- Game Rules --- STARTING_CAPITAL_HP = 20 -MAX_PROVISIONS = 10 +MAX_PROVISIONS = 12 DECK_SIZE = 30 -STARTING_HAND_SIZE = 4 +FIRST_HAND_SIZE = 4 +SECOND_HAND_SIZE = 5 FATIGUE_START_DAMAGE = 1 # --- Rarity --- diff --git a/card_game/main.py b/card_game/main.py index 95a125f..63a4700 100644 --- a/card_game/main.py +++ b/card_game/main.py @@ -33,6 +33,7 @@ class Game: self.player_faction = None self.ai_faction = None self.custom_deck = None + self.player_goes_first = True self._load_custom_deck() self.custom_deck = None @@ -112,6 +113,16 @@ class Game: self.player_faction = fid self.custom_deck = None self._load_custom_deck() + self.state = "turn_order" + + # --- State: Turn Order --- + + def _handle_click_turn_order(self, pos): + if "first" in self.ui.menu_buttons and self.ui.menu_buttons["first"].collidepoint(pos): + self.player_goes_first = True + self.state = "deck_select" + elif "second" in self.ui.menu_buttons and self.ui.menu_buttons["second"].collidepoint(pos): + self.player_goes_first = False self.state = "deck_select" # --- State: Deck Select --- @@ -184,16 +195,30 @@ class Game: self.ui.deck_builder_cards.remove(cid) def _start_game(self): + from card_game.config import FIRST_HAND_SIZE, SECOND_HAND_SIZE self.battlefield = Battlefield(self.player_faction, self.ai_faction) + if self.player_goes_first: + self.battlefield.player.start_game(FIRST_HAND_SIZE) + self.battlefield.ai.start_game(SECOND_HAND_SIZE) + else: + self.battlefield.player.start_game(SECOND_HAND_SIZE) + self.battlefield.ai.start_game(FIRST_HAND_SIZE) if self.custom_deck: self.battlefield.player.deck.build(self.custom_deck) + hand_size = FIRST_HAND_SIZE if self.player_goes_first else SECOND_HAND_SIZE self.battlefield.player.hand = [] - for _ in range(4): + for _ in range(hand_size): self.battlefield.player.draw_card() - self.battlefield.start_game() + self.battlefield.turn_number = 1 + self.battlefield.frontline_controller = None + self.battlefield.current_turn = "player" + self.battlefield.player.start_turn(1) self.ai_player = AIPlayer(self.battlefield) self.ui.clear_selection() - self.state = "playing" + if self.player_goes_first: + self.state = "playing" + else: + self._start_ai_turn() # --- State: Playing --- @@ -267,7 +292,7 @@ class Game: opponent = self.battlefield.get_opponent(player) if unit.zone == "support": - if unit.can_attack and not unit.has_attacked: + if unit.can_attack and not unit.has_attacked and player.can_afford_attack(unit): self.ui.selected_unit = unit self.ui.target_mode = "attack" targets = list(opponent.get_frontline_units()) @@ -282,7 +307,7 @@ class Game: self.ui.selected_unit = unit self.ui.target_mode = "move" elif unit.zone == "frontline": - if unit.can_attack and not unit.has_attacked: + if unit.can_attack and not unit.has_attacked and player.can_afford_attack(unit): self.ui.selected_unit = unit self.ui.target_mode = "attack" targets = list(opponent.get_frontline_units()) @@ -417,8 +442,7 @@ class Game: y = zone_y + ZONE_HEIGHT // 2 else: x = _frontline_slot_x(unit.slot, n_fl) + FIELD_CARD_WIDTH // 2 - zone_y = FRONTLINE_Y if is_ai else (FRONTLINE_Y + half) - y = zone_y + half // 2 + y = FRONTLINE_Y + ZONE_HEIGHT // 2 return x, y try: @@ -518,6 +542,8 @@ class Game: def _handle_click(self, pos): if self.state == "menu": self._handle_click_menu(pos) + elif self.state == "turn_order": + self._handle_click_turn_order(pos) elif self.state == "deck_select": self._handle_click_deck_select(pos) elif self.state == "deck_build": @@ -537,6 +563,8 @@ class Game: def _draw(self): if self.state == "menu": self.ui.draw_menu() + elif self.state == "turn_order": + self.ui.draw_turn_order(self.player_faction) elif self.state == "deck_select": self.ui.draw_deck_select(self.player_faction) elif self.state == "deck_build": diff --git a/card_game/player.py b/card_game/player.py index 4547a59..894cf86 100644 --- a/card_game/player.py +++ b/card_game/player.py @@ -2,8 +2,9 @@ from card_game.config import ( STARTING_CAPITAL_HP, MAX_PROVISIONS, MAX_HAND_SIZE, - MAX_SUPPORT_SLOTS, MAX_FRONTLINE_SLOTS, DECK_SIZE, STARTING_HAND_SIZE, + MAX_SUPPORT_SLOTS, MAX_FRONTLINE_SLOTS, DECK_SIZE, FATIGUE_START_DAMAGE, FACTIONS, DECK_PRESETS, + FIRST_HAND_SIZE, SECOND_HAND_SIZE, ) from card_game.deck import Deck from card_game.card import Card @@ -33,14 +34,16 @@ class Player: preset = DECK_PRESETS[self.faction_id] self.deck.build(preset["cards"]) - def start_game(self): + def start_game(self, hand_size=None): self.build_deck() - for _ in range(STARTING_HAND_SIZE): + if hand_size is None: + hand_size = FIRST_HAND_SIZE + for _ in range(hand_size): self.draw_card() def start_turn(self, turn_number): self.turn_number = turn_number - gained = min(turn_number + 1, MAX_PROVISIONS) + gained = min(turn_number, MAX_PROVISIONS) self.provisions = gained self.max_provisions = gained @@ -176,6 +179,15 @@ class Player: if unit.current_hp < unit.max_hp: unit.current_hp = min(unit.max_hp, unit.current_hp + 1) + def get_attack_cost(self, unit): + cost = unit.op_cost + if self.faction_id == "yan" and unit.unit_type == "cavalry": + cost = max(0, cost - 1) + return cost + + def can_afford_attack(self, unit): + return self.provisions >= self.get_attack_cost(unit) + def cleanup_dead(self): for i in range(len(self.support_line)): u = self.support_line[i] diff --git a/card_game/ui.py b/card_game/ui.py index 410a0d7..31fd6fb 100644 --- a/card_game/ui.py +++ b/card_game/ui.py @@ -131,6 +131,30 @@ class UI: inst = self.font_sm.render("选择你的国家开始游戏", True, INK_WASH_3) self.screen.blit(inst, (WINDOW_WIDTH // 2 - inst.get_width() // 2, WINDOW_HEIGHT - 60)) + def draw_turn_order(self, player_faction): + ink_style.blit_paper_background(self.screen) + ink_style.blit_mountains(self.screen) + faction = FACTIONS[player_faction] + title = self.font_xl.render("选择先后手", True, FACTION_COLORS[player_faction]) + self.screen.blit(title, (WINDOW_WIDTH // 2 - title.get_width() // 2, 100)) + faction_text = self.font_lg.render(f"你的国家:{faction['name']}", True, PAPER_WHITE) + self.screen.blit(faction_text, (WINDOW_WIDTH // 2 - faction_text.get_width() // 2, 180)) + + self.menu_buttons = {} + for key, lbl, desc, y in [ + ("first", "先手", "先行动,起始4张手牌", 280), + ("second", "后手", "后行动,起始5张手牌", 370), + ]: + btn_w, btn_h = 300, 70 + rect = pygame.Rect(WINDOW_WIDTH // 2 - btn_w // 2, y, btn_w, btn_h) + ink_style.draw_ink_rect(self.screen, rect, FACTION_COLORS[player_faction], alpha=200) + pygame.draw.rect(self.screen, PAPER_WHITE, rect, 2, border_radius=5) + t = self.font_lg.render(lbl, True, PAPER_WHITE) + self.screen.blit(t, (rect.centerx - t.get_width() // 2, y + 10)) + d = self.font_sm.render(desc, True, LIGHT_GRAY) + self.screen.blit(d, (rect.centerx - d.get_width() // 2, y + 45)) + self.menu_buttons[key] = rect + def draw_deck_select(self, player_faction): ink_style.blit_paper_background(self.screen) ink_style.blit_mountains(self.screen) @@ -423,27 +447,23 @@ class UI: def _draw_frontline(self, battlefield): y = FRONTLINE_Y - half = ZONE_HEIGHT // 2 - - # Center divider line - pygame.draw.line(self.screen, INK_WASH_3, (0, y + half), (WINDOW_WIDTH, y + half), 1) lbl = self.font_sm.render("前 线", True, (ZHU_HONG[0], ZHU_HONG[1], ZHU_HONG[2])) self.screen.blit(lbl, (WINDOW_WIDTH // 2 - lbl.get_width() // 2, y + 3)) n_fl = MAX_FRONTLINE_SLOTS + uy = y + (ZONE_HEIGHT - FIELD_CARD_HEIGHT) // 2 + for i, unit in enumerate(battlefield.ai.frontline): if unit is None: continue ux = _frontline_slot_x(i, n_fl) - uy = y + (half - FIELD_CARD_HEIGHT) // 2 self._draw_field_unit(unit, ux, uy, False, battlefield.ai) for i, unit in enumerate(battlefield.player.frontline): if unit is None: continue ux = _frontline_slot_x(i, n_fl) - uy = y + half + (half - FIELD_CARD_HEIGHT) // 2 self._draw_field_unit(unit, ux, uy, True, battlefield.player) def _draw_capital(self, player, zone_y): @@ -659,10 +679,9 @@ class UI: ux = _support_slot_x(unit.slot) uy = zone_y + (zone_h - FIELD_CARD_HEIGHT) // 2 else: - half = ZONE_HEIGHT // 2 n_fl = MAX_FRONTLINE_SLOTS - zone_y = FRONTLINE_Y if is_ai else (FRONTLINE_Y + half) - zone_h = half + zone_y = FRONTLINE_Y + zone_h = ZONE_HEIGHT ux = _frontline_slot_x(unit.slot, n_fl) uy = zone_y + (zone_h - FIELD_CARD_HEIGHT) // 2 s = pygame.Surface((FIELD_CARD_WIDTH, FIELD_CARD_HEIGHT), pygame.SRCALPHA) @@ -681,12 +700,11 @@ class UI: # Move highlights if self.target_mode == "move": - half = ZONE_HEIGHT // 2 n_fl = MAX_FRONTLINE_SLOTS for i, slot in enumerate(battlefield.player.frontline): if slot is None: sx = _frontline_slot_x(i, n_fl) - sy = FRONTLINE_Y + half + (half - FIELD_CARD_HEIGHT) // 2 + sy = FRONTLINE_Y + (ZONE_HEIGHT - FIELD_CARD_HEIGHT) // 2 s = pygame.Surface((FIELD_CARD_WIDTH, FIELD_CARD_HEIGHT), pygame.SRCALPHA) s.fill((100, 180, 220, 50)) self.screen.blit(s, (sx, sy)) @@ -717,7 +735,7 @@ class UI: n_fl = MAX_FRONTLINE_SLOTS zones = [ (battlefield.player.support_line, PLAYER_SUPPORT_Y, ZONE_HEIGHT, battlefield.player, "support"), - (battlefield.player.frontline, FRONTLINE_Y + half, half, battlefield.player, "frontline"), + (battlefield.player.frontline, FRONTLINE_Y, ZONE_HEIGHT, battlefield.player, "frontline"), (battlefield.ai.support_line, ENEMY_SUPPORT_Y, ZONE_HEIGHT, battlefield.ai, "support"), (battlefield.ai.frontline, FRONTLINE_Y, half, battlefield.ai, "frontline"), ] @@ -750,12 +768,11 @@ class UI: def get_frontline_slot_at(self, pos, player): mx, my = pos - half = ZONE_HEIGHT // 2 n_fl = MAX_FRONTLINE_SLOTS - zone_y = FRONTLINE_Y + half + zone_y = FRONTLINE_Y for i, slot in enumerate(player.frontline): sx = _frontline_slot_x(i, n_fl) - sy = zone_y + (half - FIELD_CARD_HEIGHT) // 2 + sy = zone_y + (ZONE_HEIGHT - FIELD_CARD_HEIGHT) // 2 rect = pygame.Rect(sx, sy, FIELD_CARD_WIDTH, FIELD_CARD_HEIGHT) if rect.collidepoint(mx, my): return i