import tkinter as tk
from tkinter import messagebox, font
import random
import time
import threading
import math
# ==================== 游戏常量 ====================
WORLD_WIDTH = 4000 # 世界地图宽度
WORLD_HEIGHT = 4000 # 世界地图高度
CANVAS_WIDTH = 1024 # 视口宽度
CANVAS_HEIGHT = 668 # 视口高度
VIEW_RADIUS = 450 # 玩家视野半径
VIEW_ANGLE = math.pi / 2 # 90°扇形视野
PLAYER_RADIUS = 16 # 玩家碰撞半径
AI_RADIUS = 15 # AI碰撞半径
PLAYER_SPEED = 5 # 玩家移动速度
AI_SPEED = 3 # AI移动速度
BULLET_DAMAGE = 34 # 子弹伤害
HIT_CHANCE = 0.7 # 基础命中率
BULLET_LIFETIME = 50 # 子弹显示时长(ms)
AI_UPDATE_INTERVAL = 100 # AI决策间隔(ms)
DEFENDER_ENGAGE_RANGE = 300 # 交战射程,进入后停止移动
class Game:
def __init__(self, root):
self.root = root
self.root.title("SCP: Comms Down设施失联")
self.root.geometry("1024x768")
self.root.resizable(False, False)
# 中文字体
self.font_family = "SimHei"
# 游戏核心状态
self.game_active = False
self.defender_wins = False
self.attacker_wins = False
# 计时器
self.game_timer = 300 # 5分钟对局
self.timer_running = False
# 玩家与AI数量:进攻方1玩家+9AI共10人,防守方20人
self.player_count = 1
self.ai_count = 9
self.total_players = self.player_count + self.ai_count
# 游戏实体
self.defenders = []
self.attackers = []
self.bullets = []
self.damage_texts = []
self.obstacles = []
# 相机系统
self.camera_x = 0
self.camera_y = 0
# 玩家输入
self.key_pressed = {"w": False, "a": False, "s": False, "d": False}
self.mouse_x = CANVAS_WIDTH // 2
self.mouse_y = CANVAS_HEIGHT // 2
# AI更新计时器
self.ai_update_timer = 0
# 创建开始界面
self.create_start_screen()
# ==================== 界面系统 ====================
def create_start_screen(self):
for widget in self.root.winfo_children():
widget.destroy()
start_frame = tk.Frame(self.root, bg="black")
start_frame.pack(fill=tk.BOTH, expand=True)
title_font = font.Font(family=self.font_family, size=36, weight="bold")
title_label = tk.Label(start_frame, text="SCP: Comms Down设施失联",
font=title_font, fg="white", bg="black")
title_label.pack(pady=50)
copyright_font = font.Font(family=self.font_family, size=12)
copyright_label = tk.Label(start_frame, text="BC2O270371",
font=copyright_font, fg="white", bg="black")
copyright_label.pack(pady=10)
controls_font = font.Font(family=self.font_family, size=14)
controls_label = tk.Label(start_frame, text="WASD移动 | 鼠标瞄准 | 左键射击",
font=controls_font, fg="white", bg="black")
controls_label.pack(pady=20)
button_frame = tk.Frame(start_frame, bg="black")
button_frame.pack(pady=100)
attacker_btn = tk.Button(button_frame, text="Nu-7进入设施", command=self.select_attacker,
font=(self.font_family, 16), bg="#F44336", fg="white",
width=15, height=2, cursor="hand2")
attacker_btn.pack(side=tk.LEFT, padx=20)
def select_attacker(self):
self.create_game_screen()
self.initialize_attackers()
self.initialize_defenders()
self.start_attack_phase()
self.setup_player_controls()
def create_game_screen(self):
for widget in self.root.winfo_children():
widget.destroy()
self.game_frame = tk.Frame(self.root, bg="#0f3460")
self.game_frame.pack(fill=tk.BOTH, expand=True)
self.canvas = tk.Canvas(self.game_frame, bg="#16213e",
width=CANVAS_WIDTH, height=CANVAS_HEIGHT)
self.canvas.pack()
# 底部状态栏
self.status_frame = tk.Frame(self.game_frame, bg="#0f3460", height=100)
self.status_frame.pack(fill=tk.X)
self.timer_label = tk.Label(self.status_frame, text="05:00",
font=(self.font_family, 24), fg="white", bg="#0f3460")
self.timer_label.pack(side=tk.LEFT, padx=20)
self.player_status_label = tk.Label(self.status_frame, text="进攻方 | 存活",
font=(self.font_family, 14), fg="white", bg="#0f3460")
self.player_status_label.pack(side=tk.LEFT, padx=20)
self.game_status_label = tk.Label(self.status_frame, text="战斗阶段",
font=(self.font_family, 14), fg="white", bg="#0f3460")
self.game_status_label.pack(side=tk.RIGHT, padx=20)
# 生成地图障碍物
self.generate_world_obstacles()
# ==================== 地图与碰撞 ====================
def generate_world_obstacles(self):
self.obstacles = []
# 地图边界墙体
self.obstacles.append((0, 0, WORLD_WIDTH, 20))
self.obstacles.append((0, WORLD_HEIGHT - 20, WORLD_WIDTH, WORLD_HEIGHT))
self.obstacles.append((0, 0, 20, WORLD_HEIGHT))
self.obstacles.append((WORLD_WIDTH - 20, 0, WORLD_WIDTH, WORLD_HEIGHT))
# 随机生成室内障碍物
random.seed(42) # 固定种子保证地图一致
for _ in range(150):
w = random.randint(80, 200)
h = random.randint(80, 200)
x = random.randint(100, WORLD_WIDTH - 100 - w)
y = random.randint(100, WORLD_HEIGHT - 100 - h)
self.obstacles.append((x, y, x + w, y + h))
def get_valid_spawn(self, x_min, x_max, y_min, y_max, radius):
"""在指定范围内生成不与障碍物碰撞的有效出生点"""
for _ in range(100):
x = random.randint(x_min, x_max)
y = random.randint(y_min, y_max)
if not self.check_wall_collision(x, y, radius):
return (x, y)
# 兜底方案
return (x_min + radius + 20, y_min + radius + 20)
def check_wall_collision(self, x, y, radius):
"""圆形单位与墙体/障碍物碰撞检测"""
# 地图边界
if x - radius < 0 or x + radius > WORLD_WIDTH:
return True
if y - radius < 0 or y + radius > WORLD_HEIGHT:
return True
# 障碍物碰撞(圆形-矩形)
for obs in self.obstacles:
ox1, oy1, ox2, oy2 = obs
closest_x = max(ox1, min(x, ox2))
closest_y = max(oy1, min(y, oy2))
dist_sq = (x - closest_x) ** 2 + (y - closest_y) ** 2
if dist_sq < radius ** 2:
return True
return False
def line_intersects_wall(self, x1, y1, x2, y2):
"""子弹线段与墙体碰撞检测(严格检测,确保不穿墙)"""
for obs in self.obstacles:
if self._line_rect_intersect(x1, y1, x2, y2, obs):
return True
return False
def _line_rect_intersect(self, x1, y1, x2, y2, rect):
"""线段与矩形相交检测(CCW算法)"""
rx1, ry1, rx2, ry2 = rect
def point_in_rect(px, py):
return rx1 <= px <= rx2 and ry1 <= py <= ry2
if point_in_rect(x1, y1) or point_in_rect(x2, y2):
return True
def ccw(ax, ay, bx, by, cx, cy):
return (cy - ay) * (bx - ax) > (by - ay) * (cx - ax)
def seg_intersect(sx1, sy1, sx2, sy2, sx3, sy3, sx4, sy4):
return (ccw(sx1,sy1,sx3,sy3,sx4,sy4) != ccw(sx2,sy2,sx3,sy3,sx4,sy4) and
ccw(sx1,sy1,sx2,sy2,sx3,sy3) != ccw(sx1,sy1,sx2,sy2,sx4,sy4))
edges = [
(rx1, ry1, rx2, ry1),
(rx2, ry1, rx2, ry2),
(rx2, ry2, rx1, ry2),
(rx1, ry2, rx1, ry1)
]
for edge in edges:
if seg_intersect(x1, y1, x2, y2, edge[0], edge[1], edge[2], edge[3]):
return True
return False
# ==================== 单位初始化 ====================
def initialize_defenders(self):
self.defenders = []
# 防守方共20人,生成在地图中部安全区域
for i in range(20):
x, y = self.get_valid_spawn(
WORLD_WIDTH//3, WORLD_WIDTH*2//3,
WORLD_HEIGHT//3, WORLD_HEIGHT*2//3,
AI_RADIUS
)
self.defenders.append({
"id": i, "x": x, "y": y,
"health": 100, "is_player": False, "role": "defender",
"target_pos": (x, y)
})
def initialize_attackers(self):
self.attackers = []
# 玩家单位 - 生成在左侧安全区域
player_x, player_y = self.get_valid_spawn(
50, 200,
WORLD_HEIGHT//2 - 100, WORLD_HEIGHT//2 + 100,
PLAYER_RADIUS
)
self.attackers.append({
"id": 0, "x": player_x, "y": player_y,
"health": 100, "is_player": True, "role": "attacker",
"direction": 0
})
# AI队友 - 共9个,分散生成在左右两侧安全区域
for i in range(1, self.total_players):
if random.choice([True, False]):
# 左侧出生
x, y = self.get_valid_spawn(50, 250, 100, WORLD_HEIGHT - 100, AI_RADIUS)
else:
# 右侧出生
x, y = self.get_valid_spawn(WORLD_WIDTH - 250, WORLD_WIDTH - 50, 100, WORLD_HEIGHT - 100, AI_RADIUS)
self.attackers.append({
"id": i, "x": x, "y": y,
"health": 100, "is_player": False, "role": "attacker",
"target_pos": (x, y)
})
# ==================== 游戏流程 ====================
def start_attack_phase(self):
self.game_active = True
self.game_status_label.config(text="战斗阶段")
self.game_timer = 300
self.timer_label.config(text="{:02d}:{:02d}".format(self.game_timer // 60, self.game_timer % 60))
self.timer_running = True
# 启动计时线程
threading.Thread(target=self.run_game_timer, daemon=True).start()
# 启动主循环
self.game_loop()
def run_game_timer(self):
while self.game_timer > 0 and self.timer_running and not self.defender_wins and not self.attacker_wins:
time.sleep(1)
self.game_timer -= 1
self.root.after(0, lambda: self.timer_label.config(
text="{:02d}:{:02d}".format(self.game_timer // 60, self.game_timer % 60)))
if self.timer_running and not self.defender_wins and not self.attacker_wins:
self.root.after(0, self.check_win_conditions)
def game_loop(self):
if not self.game_active:
self.root.after(16, self.game_loop)
return
# 更新玩家与相机
self.update_player_position()
self.update_camera()
# 更新AI移动(每帧)
self.update_ai_movement()
# 更新子弹与伤害文字
self.update_effects()
# AI决策(间隔更新)
self.ai_update_timer += 16
if self.ai_update_timer >= AI_UPDATE_INTERVAL:
self.ai_update_timer = 0
self.update_ai_defense_actions()
self.update_ai_attack_actions()
# 渲染画面
self.render()
self.root.after(16, self.game_loop)
# ==================== 玩家控制 ====================
def setup_player_controls(self):
self.root.bind("<KeyPress>", self.on_key_press)
self.root.bind("<KeyRelease>", self.on_key_release)
self.root.bind("<Motion>", self.on_mouse_move)
self.root.bind("<Button-1>", self.on_mouse_click)
def on_key_press(self, event):
key = event.keysym.lower()
if key in self.key_pressed:
self.key_pressed[key] = True
def on_key_release(self, event):
key = event.keysym.lower()
if key in self.key_pressed:
self.key_pressed[key] = False
def on_mouse_move(self, event):
self.mouse_x = event.x
self.mouse_y = event.y
player = self.get_player()
if player and player["health"] > 0:
# 屏幕坐标转世界坐标
world_mx = event.x + self.camera_x
world_my = event.y + self.camera_y
player["direction"] = math.atan2(world_my - player["y"], world_mx - player["x"])
def on_mouse_click(self, event):
player = self.get_player()
if player and player["health"] > 0:
world_mx = event.x + self.camera_x
world_my = event.y + self.camera_y
angle = math.atan2(world_my - player["y"], world_mx - player["x"])
target = self.find_enemy_in_direction(player, angle)
if target:
self.shoot_at(player, target, angle)
def get_player(self):
return next((a for a in self.attackers if a["is_player"]), None)
def update_player_position(self):
player = self.get_player()
if not player or player["health"] <= 0:
return
dx, dy = 0, 0
if self.key_pressed["w"]: dy -= PLAYER_SPEED
if self.key_pressed["s"]: dy += PLAYER_SPEED
if self.key_pressed["a"]: dx -= PLAYER_SPEED
if self.key_pressed["d"]: dx += PLAYER_SPEED
# 对角线速度归一化
if dx != 0 and dy != 0:
norm = 1 / math.sqrt(2)
dx *= norm
dy *= norm
# 分轴碰撞检测(贴墙滑行)
new_x = player["x"] + dx
if not self.check_wall_collision(new_x, player["y"], PLAYER_RADIUS):
player["x"] = new_x
new_y = player["y"] + dy
if not self.check_wall_collision(player["x"], new_y, PLAYER_RADIUS):
player["y"] = new_y
def update_camera(self):
player = self.get_player()
if not player:
return
# 相机中心跟随玩家
target_x = player["x"] - CANVAS_WIDTH / 2
target_y = player["y"] - CANVAS_HEIGHT / 2
# 限制相机不超出地图边界
target_x = max(0, min(target_x, WORLD_WIDTH - CANVAS_WIDTH))
target_y = max(0, min(target_y, WORLD_HEIGHT - CANVAS_HEIGHT))
self.camera_x = target_x
self.camera_y = target_y
# ==================== AI系统 ====================
def update_ai_movement(self):
"""每帧更新所有AI的位置(平滑移动)"""
# 防守方AI
for defender in self.defenders:
if defender["health"] > 0 and defender["target_pos"]:
self.move_unit(defender, defender["target_pos"][0], defender["target_pos"][1])
# 进攻方AI
for attacker in self.attackers:
if attacker["is_player"] or attacker["health"] <= 0:
continue
if attacker["target_pos"]:
self.move_unit(attacker, attacker["target_pos"][0], attacker["target_pos"][1])
def update_ai_defense_actions(self):
"""防守方AI决策:感知敌人后,超出射程则靠近,进入射程则停火射击"""
for defender in self.defenders:
if defender["health"] <= 0:
continue
nearest_attacker = self.find_nearest_enemy(defender, self.attackers)
if nearest_attacker:
dist = self.distance(defender, nearest_attacker)
if dist < 400: # 400为敌人感知范围
if dist > DEFENDER_ENGAGE_RANGE:
# 距离大于交战射程,向敌人方向靠近
defender["target_pos"] = (
nearest_attacker["x"] + random.randint(-20, 20),
nearest_attacker["y"] + random.randint(-20, 20)
)
else:
# 进入有效射程,停止移动,原地瞄准
defender["target_pos"] = (defender["x"], defender["y"])
# 尝试射击(带穿墙检测)
angle = math.atan2(nearest_attacker["y"] - defender["y"],
nearest_attacker["x"] - defender["x"])
if random.random() < 0.3:
self.shoot_at(defender, nearest_attacker, angle)
else:
# 超出感知范围,随机巡逻
if random.random() < 0.1:
defender["target_pos"] = (
random.randint(200, WORLD_WIDTH - 200),
random.randint(200, WORLD_HEIGHT - 200)
)
else:
# 无敌人目标,随机巡逻
if random.random() < 0.1:
defender["target_pos"] = (
random.randint(200, WORLD_WIDTH - 200),
random.randint(200, WORLD_HEIGHT - 200)
)
def update_ai_attack_actions(self):
"""进攻方AI(队友)决策:直接追击+射击,逻辑与防守方一致
已确认:队友射击同样带穿墙检测,子弹从自身位置发出
"""
for attacker in self.attackers:
if attacker["is_player"] or attacker["health"] <= 0:
continue
nearest_defender = self.find_nearest_enemy(attacker, self.defenders)
if nearest_defender:
dist = self.distance(attacker, nearest_defender)
if dist > DEFENDER_ENGAGE_RANGE:
# 距离大于交战射程,向敌人靠近
attacker["target_pos"] = (
nearest_defender["x"] + random.randint(-20, 20),
nearest_defender["y"] + random.randint(-20, 20)
)
else:
# 进入有效射程,停止移动原地射击
attacker["target_pos"] = (attacker["x"], attacker["y"])
# 尝试射击(带穿墙检测,和敌人逻辑完全一致)
angle = math.atan2(nearest_defender["y"] - attacker["y"],
nearest_defender["x"] - attacker["x"])
if random.random() < 0.3:
self.shoot_at(attacker, nearest_defender, angle)
else:
# 无敌人时随机巡逻
if random.random() < 0.1:
attacker["target_pos"] = (
random.randint(200, WORLD_WIDTH - 200),
random.randint(200, WORLD_HEIGHT - 200)
)
# ==================== 战斗系统 ====================
def find_nearest_enemy(self, unit, enemies):
nearest = None
min_dist = float('inf')
for enemy in enemies:
if enemy["health"] > 0:
dist = self.distance(unit, enemy)
if dist < min_dist:
min_dist = dist
nearest = enemy
return nearest
def distance(self, u1, u2):
return math.sqrt((u1["x"] - u2["x"]) ** 2 + (u1["y"] - u2["y"]) ** 2)
def find_enemy_in_direction(self, shooter, angle):
"""查找瞄准方向上的敌人(带穿墙检测)"""
max_dist = 300
nearest = None
min_dist = float('inf')
enemies = self.attackers if shooter["role"] == "defender" else self.defenders
for enemy in enemies:
if enemy["health"] <= 0:
continue
enemy_angle = math.atan2(enemy["y"] - shooter["y"], enemy["x"] - shooter["x"])
angle_diff = abs(angle - enemy_angle)
if angle_diff > math.pi:
angle_diff = 2 * math.pi - angle_diff
if angle_diff < math.radians(30):
dist = self.distance(shooter, enemy)
if dist < max_dist and dist < min_dist:
# 子弹不能穿墙
if not self.line_intersects_wall(shooter["x"], shooter["y"],
enemy["x"], enemy["y"]):
min_dist = dist
nearest = enemy
return nearest
def shoot_at(self, shooter, target, angle):
"""射击:先做穿墙校验,再生成子弹
子弹起点严格等于射击者位置,终点严格等于目标位置
"""
# 严格穿墙校验,视线被挡则不生成子弹
if self.line_intersects_wall(shooter["x"], shooter["y"], target["x"], target["y"]):
return
# 子弹起点=射击者坐标,终点=目标坐标,确保弹道从枪口发出
self.bullets.append({
"x1": shooter["x"], "y1": shooter["y"],
"x2": target["x"], "y2": target["y"],
"time": BULLET_LIFETIME
})
# 命中判定
if random.random() < HIT_CHANCE:
target["health"] -= BULLET_DAMAGE
if target["health"] <= 0:
self.check_win_conditions()
else:
self.show_damage(target)
def show_damage(self, unit):
self.damage_texts.append({
"x": unit["x"], "y": unit["y"] - 20,
"time": 2000
})
def move_unit(self, unit, target_x, target_y):
"""单位移动(带碰撞检测)"""
dx = target_x - unit["x"]
dy = target_y - unit["y"]
dist = math.sqrt(dx * dx + dy * dy)
if dist > 2:
speed = PLAYER_SPEED if unit["is_player"] else AI_SPEED
move_x = (dx / dist) * speed
move_y = (dy / dist) * speed
radius = PLAYER_RADIUS if unit["is_player"] else AI_RADIUS
# 分轴碰撞
new_x = unit["x"] + move_x
if not self.check_wall_collision(new_x, unit["y"], radius):
unit["x"] = new_x
new_y = unit["y"] + move_y
if not self.check_wall_collision(unit["x"], new_y, radius):
unit["y"] = new_y
# ==================== 渲染系统 ====================
def update_effects(self):
"""更新子弹与伤害文字生命周期"""
# 子弹
new_bullets = []
for b in self.bullets:
b["time"] -= 16
if b["time"] > 0:
new_bullets.append(b)
self.bullets = new_bullets
# 伤害文字(向上飘动)
new_damage = []
for dt in self.damage_texts:
dt["time"] -= 16
dt["y"] -= 0.5
if dt["time"] > 0:
new_damage.append(dt)
self.damage_texts = new_damage
def render(self):
self.canvas.delete("all")
self.canvas.create_rectangle(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, fill="#16213e")
# 绘制障碍物(视口剔除)
for obs in self.obstacles:
x1 = obs[0] - self.camera_x
y1 = obs[1] - self.camera_y
x2 = obs[2] - self.camera_x
y2 = obs[3] - self.camera_y
if x2 < 0 or x1 > CANVAS_WIDTH or y2 < 0 or y1 > CANVAS_HEIGHT:
continue
self.canvas.create_rectangle(x1, y1, x2, y2, fill="#1a1a2e", outline="#e94560", width=1)
# 绘制防守方(蓝色)
for d in self.defenders:
if d["health"] <= 0:
continue
sx = d["x"] - self.camera_x
sy = d["y"] - self.camera_y
if sx < -50 or sx > CANVAS_WIDTH + 50 or sy < -50 or sy > CANVAS_HEIGHT + 50:
continue
self.canvas.create_oval(sx-AI_RADIUS, sy-AI_RADIUS,
sx+AI_RADIUS, sy+AI_RADIUS, fill="blue")
self.canvas.create_text(sx, sy, text=str(d["id"]), fill="white", font=(self.font_family, 10))
# 绘制进攻方(红色)
for a in self.attackers:
if a["health"] <= 0:
continue
sx = a["x"] - self.camera_x
sy = a["y"] - self.camera_y
if sx < -50 or sx > CANVAS_WIDTH + 50 or sy < -50 or sy > CANVAS_HEIGHT + 50:
continue
r = PLAYER_RADIUS if a["is_player"] else AI_RADIUS
self.canvas.create_oval(sx-r, sy-r, sx+r, sy+r, fill="red")
self.canvas.create_text(sx, sy, text=str(a["id"]), fill="white", font=(self.font_family, 10))
# 玩家朝向指示器
if a["is_player"]:
end_x = sx + math.cos(a["direction"]) * 30
end_y = sy + math.sin(a["direction"]) * 30
self.canvas.create_line(sx, sy, end_x, end_y, fill="yellow", width=2)
# 绘制子弹
for b in self.bullets:
x1 = b["x1"] - self.camera_x
y1 = b["y1"] - self.camera_y
x2 = b["x2"] - self.camera_x
y2 = b["y2"] - self.camera_y
self.canvas.create_line(x1, y1, x2, y2, fill="yellow", width=2)
# 绘制伤害数字
for dt in self.damage_texts:
sx = dt["x"] - self.camera_x
sy = dt["y"] - self.camera_y
self.canvas.create_text(sx, sy, text=str(BULLET_DAMAGE), fill="red", font=(self.font_family, 12))
# 绘制玩家视野遮罩
self.draw_vision_mask()
def draw_vision_mask(self):
"""
重写:正确实现90°扇形视野遮罩
采用「外矩形+内扇形」连通挖洞法,利用奇偶填充规则实现
效果:鼠标指向为扇形中心,90°视角,半径450,其余区域全黑
"""
player = self.get_player()
if not player or player["health"] <= 0:
self.canvas.create_rectangle(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, fill="black")
return
# 玩家屏幕坐标
px = player["x"] - self.camera_x
py = player["y"] - self.camera_y
angle = player["direction"]
start_a = angle - VIEW_ANGLE / 2 # 扇形左边界角度
end_a = angle + VIEW_ANGLE / 2 # 扇形右边界角度
# 生成扇形弧线顶点(30段平滑)
arc_points = []
for i in range(31):
a = start_a + (end_a - start_a) * i / 30
arc_points.append((
px + math.cos(a) * VIEW_RADIUS,
py + math.sin(a) * VIEW_RADIUS
))
# 构建遮罩多边形:外矩形顺时针 + 连通线 + 内扇形逆时针
mask_pts = []
# 1. 外轮廓:屏幕完整矩形(顺时针方向)
mask_pts.extend([0, 0])
mask_pts.extend([CANVAS_WIDTH, 0])
mask_pts.extend([CANVAS_WIDTH, CANVAS_HEIGHT])
mask_pts.extend([0, CANVAS_HEIGHT])
# 2. 连通线:从外矩形左下角连接到玩家中心
mask_pts.extend([px, py])
# 3. 内轮廓:扇形逆时针绘制(从终点倒回起点)
for pt in reversed(arc_points):
mask_pts.extend([pt[0], pt[1]])
# 4. 回到玩家中心,闭合内扇形
mask_pts.extend([px, py])
# 绘制黑色遮罩,扇形区域自动挖空透明
self.canvas.create_polygon(mask_pts, fill="black", outline="")
# ==================== 胜负判定 ====================
def check_win_conditions(self):
all_attackers_dead = all(a["health"] <= 0 for a in self.attackers)
all_defenders_dead = all(d["health"] <= 0 for d in self.defenders)
if all_attackers_dead:
self.defender_wins_game()
elif all_defenders_dead:
self.attacker_wins_game()
elif self.game_timer <= 0:
self.defender_wins_game()
def defender_wins_game(self):
self.timer_running = False
self.defender_wins = True
self.game_status_label.config(text="防守方胜利!")
messagebox.showinfo("游戏结束", "防守方胜利!")
self.root.after(300, self.create_start_screen)
def attacker_wins_game(self):
self.timer_running = False
self.attacker_wins = True
self.game_status_label.config(text="进攻方胜利!")
messagebox.showinfo("游戏结束", "进攻方胜利!")
self.root.after(300, self.create_start_screen)
if __name__ == "__main__":
root = tk.Tk()
game = Game(root)
root.mainloop()