游戏可以运行

This commit is contained in:
2026-05-24 12:34:49 +08:00
parent 0670598bf4
commit 34ce39930c
12 changed files with 94 additions and 31 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -85,7 +85,7 @@ class AIPlayer:
# Frontline units: attack enemy frontline > enemy support > capital # Frontline units: attack enemy frontline > enemy support > capital
for unit in ai.get_frontline_units(): 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 continue
enemy_front = player.get_frontline_units() enemy_front = player.get_frontline_units()
enemy_support = player.get_support_units() enemy_support = player.get_support_units()
@@ -104,7 +104,7 @@ class AIPlayer:
# Support units: attack enemy frontline > enemy support > capital (ranged only) # Support units: attack enemy frontline > enemy support > capital (ranged only)
for unit in ai.get_support_units(): 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 continue
enemy_front = player.get_frontline_units() enemy_front = player.get_frontline_units()
enemy_support = player.get_support_units() enemy_support = player.get_support_units()
+6 -1
View File
@@ -79,12 +79,17 @@ class Battlefield:
def resolve_attack(self, attacker, defender): def resolve_attack(self, attacker, defender):
dead = [] 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() atk = attacker.get_effective_attack()
if "siege" in attacker.abilities and defender == "capital": if "siege" in attacker.abilities and defender == "capital":
atk *= 2 atk *= 2
owner = self._get_unit_owner(attacker)
if (owner and owner.faction_id == "han" if (owner and owner.faction_id == "han"
and attacker.unit_type == "archer" and defender == "capital"): and attacker.unit_type == "archer" and defender == "capital"):
atk += 1 atk += 1
+3 -2
View File
@@ -95,9 +95,10 @@ MAX_HAND_SIZE = 8
# --- Game Rules --- # --- Game Rules ---
STARTING_CAPITAL_HP = 20 STARTING_CAPITAL_HP = 20
MAX_PROVISIONS = 10 MAX_PROVISIONS = 12
DECK_SIZE = 30 DECK_SIZE = 30
STARTING_HAND_SIZE = 4 FIRST_HAND_SIZE = 4
SECOND_HAND_SIZE = 5
FATIGUE_START_DAMAGE = 1 FATIGUE_START_DAMAGE = 1
# --- Rarity --- # --- Rarity ---
+35 -7
View File
@@ -33,6 +33,7 @@ class Game:
self.player_faction = None self.player_faction = None
self.ai_faction = None self.ai_faction = None
self.custom_deck = None self.custom_deck = None
self.player_goes_first = True
self._load_custom_deck() self._load_custom_deck()
self.custom_deck = None self.custom_deck = None
@@ -112,6 +113,16 @@ class Game:
self.player_faction = fid self.player_faction = fid
self.custom_deck = None self.custom_deck = None
self._load_custom_deck() 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" self.state = "deck_select"
# --- State: Deck Select --- # --- State: Deck Select ---
@@ -184,16 +195,30 @@ class Game:
self.ui.deck_builder_cards.remove(cid) self.ui.deck_builder_cards.remove(cid)
def _start_game(self): def _start_game(self):
from card_game.config import FIRST_HAND_SIZE, SECOND_HAND_SIZE
self.battlefield = Battlefield(self.player_faction, self.ai_faction) 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: if self.custom_deck:
self.battlefield.player.deck.build(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 = [] self.battlefield.player.hand = []
for _ in range(4): for _ in range(hand_size):
self.battlefield.player.draw_card() 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.ai_player = AIPlayer(self.battlefield)
self.ui.clear_selection() self.ui.clear_selection()
self.state = "playing" if self.player_goes_first:
self.state = "playing"
else:
self._start_ai_turn()
# --- State: Playing --- # --- State: Playing ---
@@ -267,7 +292,7 @@ class Game:
opponent = self.battlefield.get_opponent(player) opponent = self.battlefield.get_opponent(player)
if unit.zone == "support": 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.selected_unit = unit
self.ui.target_mode = "attack" self.ui.target_mode = "attack"
targets = list(opponent.get_frontline_units()) targets = list(opponent.get_frontline_units())
@@ -282,7 +307,7 @@ class Game:
self.ui.selected_unit = unit self.ui.selected_unit = unit
self.ui.target_mode = "move" self.ui.target_mode = "move"
elif unit.zone == "frontline": 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.selected_unit = unit
self.ui.target_mode = "attack" self.ui.target_mode = "attack"
targets = list(opponent.get_frontline_units()) targets = list(opponent.get_frontline_units())
@@ -417,8 +442,7 @@ class Game:
y = zone_y + ZONE_HEIGHT // 2 y = zone_y + ZONE_HEIGHT // 2
else: else:
x = _frontline_slot_x(unit.slot, n_fl) + FIELD_CARD_WIDTH // 2 x = _frontline_slot_x(unit.slot, n_fl) + FIELD_CARD_WIDTH // 2
zone_y = FRONTLINE_Y if is_ai else (FRONTLINE_Y + half) y = FRONTLINE_Y + ZONE_HEIGHT // 2
y = zone_y + half // 2
return x, y return x, y
try: try:
@@ -518,6 +542,8 @@ class Game:
def _handle_click(self, pos): def _handle_click(self, pos):
if self.state == "menu": if self.state == "menu":
self._handle_click_menu(pos) self._handle_click_menu(pos)
elif self.state == "turn_order":
self._handle_click_turn_order(pos)
elif self.state == "deck_select": elif self.state == "deck_select":
self._handle_click_deck_select(pos) self._handle_click_deck_select(pos)
elif self.state == "deck_build": elif self.state == "deck_build":
@@ -537,6 +563,8 @@ class Game:
def _draw(self): def _draw(self):
if self.state == "menu": if self.state == "menu":
self.ui.draw_menu() self.ui.draw_menu()
elif self.state == "turn_order":
self.ui.draw_turn_order(self.player_faction)
elif self.state == "deck_select": elif self.state == "deck_select":
self.ui.draw_deck_select(self.player_faction) self.ui.draw_deck_select(self.player_faction)
elif self.state == "deck_build": elif self.state == "deck_build":
+16 -4
View File
@@ -2,8 +2,9 @@
from card_game.config import ( from card_game.config import (
STARTING_CAPITAL_HP, MAX_PROVISIONS, MAX_HAND_SIZE, 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, FATIGUE_START_DAMAGE, FACTIONS, DECK_PRESETS,
FIRST_HAND_SIZE, SECOND_HAND_SIZE,
) )
from card_game.deck import Deck from card_game.deck import Deck
from card_game.card import Card from card_game.card import Card
@@ -33,14 +34,16 @@ class Player:
preset = DECK_PRESETS[self.faction_id] preset = DECK_PRESETS[self.faction_id]
self.deck.build(preset["cards"]) self.deck.build(preset["cards"])
def start_game(self): def start_game(self, hand_size=None):
self.build_deck() 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() self.draw_card()
def start_turn(self, turn_number): def start_turn(self, turn_number):
self.turn_number = turn_number self.turn_number = turn_number
gained = min(turn_number + 1, MAX_PROVISIONS) gained = min(turn_number, MAX_PROVISIONS)
self.provisions = gained self.provisions = gained
self.max_provisions = gained self.max_provisions = gained
@@ -176,6 +179,15 @@ class Player:
if unit.current_hp < unit.max_hp: if unit.current_hp < unit.max_hp:
unit.current_hp = min(unit.max_hp, unit.current_hp + 1) 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): def cleanup_dead(self):
for i in range(len(self.support_line)): for i in range(len(self.support_line)):
u = self.support_line[i] u = self.support_line[i]
+32 -15
View File
@@ -131,6 +131,30 @@ class UI:
inst = self.font_sm.render("选择你的国家开始游戏", True, INK_WASH_3) inst = self.font_sm.render("选择你的国家开始游戏", True, INK_WASH_3)
self.screen.blit(inst, (WINDOW_WIDTH // 2 - inst.get_width() // 2, WINDOW_HEIGHT - 60)) 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): def draw_deck_select(self, player_faction):
ink_style.blit_paper_background(self.screen) ink_style.blit_paper_background(self.screen)
ink_style.blit_mountains(self.screen) ink_style.blit_mountains(self.screen)
@@ -423,27 +447,23 @@ class UI:
def _draw_frontline(self, battlefield): def _draw_frontline(self, battlefield):
y = FRONTLINE_Y 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])) 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)) self.screen.blit(lbl, (WINDOW_WIDTH // 2 - lbl.get_width() // 2, y + 3))
n_fl = MAX_FRONTLINE_SLOTS n_fl = MAX_FRONTLINE_SLOTS
uy = y + (ZONE_HEIGHT - FIELD_CARD_HEIGHT) // 2
for i, unit in enumerate(battlefield.ai.frontline): for i, unit in enumerate(battlefield.ai.frontline):
if unit is None: if unit is None:
continue continue
ux = _frontline_slot_x(i, n_fl) ux = _frontline_slot_x(i, n_fl)
uy = y + (half - FIELD_CARD_HEIGHT) // 2
self._draw_field_unit(unit, ux, uy, False, battlefield.ai) self._draw_field_unit(unit, ux, uy, False, battlefield.ai)
for i, unit in enumerate(battlefield.player.frontline): for i, unit in enumerate(battlefield.player.frontline):
if unit is None: if unit is None:
continue continue
ux = _frontline_slot_x(i, n_fl) 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) self._draw_field_unit(unit, ux, uy, True, battlefield.player)
def _draw_capital(self, player, zone_y): def _draw_capital(self, player, zone_y):
@@ -659,10 +679,9 @@ class UI:
ux = _support_slot_x(unit.slot) ux = _support_slot_x(unit.slot)
uy = zone_y + (zone_h - FIELD_CARD_HEIGHT) // 2 uy = zone_y + (zone_h - FIELD_CARD_HEIGHT) // 2
else: else:
half = ZONE_HEIGHT // 2
n_fl = MAX_FRONTLINE_SLOTS n_fl = MAX_FRONTLINE_SLOTS
zone_y = FRONTLINE_Y if is_ai else (FRONTLINE_Y + half) zone_y = FRONTLINE_Y
zone_h = half zone_h = ZONE_HEIGHT
ux = _frontline_slot_x(unit.slot, n_fl) ux = _frontline_slot_x(unit.slot, n_fl)
uy = zone_y + (zone_h - FIELD_CARD_HEIGHT) // 2 uy = zone_y + (zone_h - FIELD_CARD_HEIGHT) // 2
s = pygame.Surface((FIELD_CARD_WIDTH, FIELD_CARD_HEIGHT), pygame.SRCALPHA) s = pygame.Surface((FIELD_CARD_WIDTH, FIELD_CARD_HEIGHT), pygame.SRCALPHA)
@@ -681,12 +700,11 @@ class UI:
# Move highlights # Move highlights
if self.target_mode == "move": if self.target_mode == "move":
half = ZONE_HEIGHT // 2
n_fl = MAX_FRONTLINE_SLOTS n_fl = MAX_FRONTLINE_SLOTS
for i, slot in enumerate(battlefield.player.frontline): for i, slot in enumerate(battlefield.player.frontline):
if slot is None: if slot is None:
sx = _frontline_slot_x(i, n_fl) 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 = pygame.Surface((FIELD_CARD_WIDTH, FIELD_CARD_HEIGHT), pygame.SRCALPHA)
s.fill((100, 180, 220, 50)) s.fill((100, 180, 220, 50))
self.screen.blit(s, (sx, sy)) self.screen.blit(s, (sx, sy))
@@ -717,7 +735,7 @@ class UI:
n_fl = MAX_FRONTLINE_SLOTS n_fl = MAX_FRONTLINE_SLOTS
zones = [ zones = [
(battlefield.player.support_line, PLAYER_SUPPORT_Y, ZONE_HEIGHT, battlefield.player, "support"), (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.support_line, ENEMY_SUPPORT_Y, ZONE_HEIGHT, battlefield.ai, "support"),
(battlefield.ai.frontline, FRONTLINE_Y, half, battlefield.ai, "frontline"), (battlefield.ai.frontline, FRONTLINE_Y, half, battlefield.ai, "frontline"),
] ]
@@ -750,12 +768,11 @@ class UI:
def get_frontline_slot_at(self, pos, player): def get_frontline_slot_at(self, pos, player):
mx, my = pos mx, my = pos
half = ZONE_HEIGHT // 2
n_fl = MAX_FRONTLINE_SLOTS n_fl = MAX_FRONTLINE_SLOTS
zone_y = FRONTLINE_Y + half zone_y = FRONTLINE_Y
for i, slot in enumerate(player.frontline): for i, slot in enumerate(player.frontline):
sx = _frontline_slot_x(i, n_fl) 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) rect = pygame.Rect(sx, sy, FIELD_CARD_WIDTH, FIELD_CARD_HEIGHT)
if rect.collidepoint(mx, my): if rect.collidepoint(mx, my):
return i return i