纯血HarmonyOS ArKTS NETX 5 打造小游戏实践:狼人杀(介绍版(附源文件)

发布于:2025-06-13 ⋅ 阅读:(17) ⋅ 点赞:(0)

本文章主要介绍狼人杀的功能提供思路,并不能没有真正实现

一、项目整体架构与技术选型

该狼人杀游戏基于ArkTS语言开发,采用HarmonyOS的声明式UI框架,通过组件化设计将游戏逻辑与界面渲染解耦。核心架构包含三大模块:

  • 状态管理模块:通过@State装饰器实现数据响应式,确保UI随游戏状态自动更新;
  • 界面渲染模块:利用ColumnGrid等容器组件实现分层布局,通过@Builder修饰符封装可复用UI组件;
  • 游戏逻辑模块:基于状态机模式管理日夜循环、角色行动与胜负判定,通过枚举类型规范流程控制。

二、状态管理与数据模型详解
1. 核心状态变量解析
@State private players: Player[] = [];         // 玩家列表,存储所有角色信息
@State private gamePhase: GamePhase = GamePhase.Night;  // 游戏阶段(日夜切换)
@State private nightPhase: NightPhase = NightPhase.Wolf; // 夜间行动顺序
@State private isVoting: boolean = false;       // 投票阶段标记
@State private voteResults: Map<number, number> = new Map(); // 投票结果统计
@State private witchHasAntidote: boolean = true; // 女巫解药状态
  • 响应式原理:当players数组中玩家的isAlive属性变更时,所有引用该数据的UI组件(如玩家卡片)会自动刷新;
  • 状态联动gamePhasenightPhase配合控制界面显示内容,例如夜间阶段仅显示狼人/预言家/女巫的操作按钮。
2. 玩家模型(Player类)设计
class Player {
  public isAlive: boolean = true;         // 存活状态
  public role: string;                    // 角色名称
  public isWolf: boolean;                 // 是否为狼人
  public hasUsedSkill: boolean = false;   // 技能使用标记(如猎人开枪)
  public index: number;                   // 玩家唯一索引

  constructor(role: string, index: number) {
    this.role = role;
    this.isWolf = role === '狼人';
    this.index = index;
  }

  public kill() {
    this.isAlive = false;
    this.isDead = true;
  }
}
  • 角色属性:通过isWolf字段区分阵营,role字段存储角色名称(村民/狼人/预言家等);
  • 状态切换kill()方法统一处理玩家死亡逻辑,确保UI与数据状态一致。

三、界面布局与组件化实现
1. 玩家列表的左右分栏布局
Scroll() {
  Grid() {
    // 左栏玩家(前半部分)
    ForEach(this.players.slice(0, this.players.length / 2), (player, index) => {
      GridItem() {
        this.renderPlayer(player, index, 'left')
      }
    })
    // 右栏玩家(后半部分)
    ForEach(this.players.slice(this.players.length / 2), (player, index) => {
      GridItem() {
        this.renderPlayer(player, index + this.players.length / 2, 'right')
      }
    })
  }
  .columnsTemplate('1fr 1fr')  // 两列等宽
  .width('100%')
  .height('45%')
  .padding(5)
}
  • 布局策略:使用Grid容器配合columnsTemplate('1fr 1fr')实现左右分栏,通过slice方法分割玩家数组;
  • 视觉区分:左栏玩家通过alignItems(HorizontalAlign.Start)靠左显示,右栏玩家靠右显示,死亡玩家卡片背景色设为#e0e0e0并降低透明度。
2. 游戏结束弹窗组件
@Builder
renderGameOverDialog() {
  Dialog() {
    Column() {
      Text('游戏结束!').fontSize(24).fontWeight(FontWeight.Bold)
      Text(`胜利方:${this.winner}`).fontSize(18)
      Scroll() {
        Column() {
          Text('玩家身份:').fontSize(16).fontWeight(FontWeight.Medium)
          ForEach(this.players, (player) => {
            Row() {
              Text(`${player.role} ${player.isWolf ? '🐺' : '👤'}`)
              Text(player.isAlive ? '存活' : '已淘汰')
                .fontColor(player.isAlive ? '#008000' : '#FF0000')
            }
          })
        }
        .width('100%')
        .padding(15)
        .backgroundColor('#f0f0f0')
      }
      .height('200vp')  // 固定弹窗内滚动区域高度
      Button('重新开始').onClick(() => this.initializeGame())
    }
    .width('80%')
    .backgroundColor('#FFFFFF')
    .borderRadius(15)
  }
  .maskColor('#00000080')  // 半透明遮罩层
  .alignment(Alignment.Center)
}
  • 交互设计:弹窗通过Dialog组件实现,半透明遮罩层maskColor增强沉浸式体验;
  • 信息展示:使用Row布局并排显示玩家角色与存活状态,绿色/红色字体区分存活/死亡状态。

四、游戏核心逻辑与规则实现
1. 夜间行动流程控制
private confirmNightAction() {
  switch (this.nightPhase) {
    case NightPhase.Wolf:
      // 狼人击杀逻辑
      if (this.selectedPlayer !== -1 && this.players[this.selectedPlayer].isAlive) {
        this.killedPlayerIndex = this.selectedPlayer;
        this.addNightMessage(`狼人选择了击杀 ${this.players[this.selectedPlayer].role}`);
        this.nightPhase = NightPhase.Seer;  // 切换至预言家阶段
      }
      break;
    case NightPhase.Seer:
      // 预言家查验逻辑
      if (this.selectedPlayer !== -1) {
        const isWolf = this.players[this.selectedPlayer].isWolf;
        this.addNightMessage(`预言家查验结果:${isWolf ? '狼人' : '好人'}`);
        this.nightPhase = NightPhase.Witch;  // 切换至女巫阶段
      }
      break;
    case NightPhase.Witch:
      // 女巫解药/毒药逻辑
      if (this.selectedPlayer === this.killedPlayerIndex && this.witchHasAntidote) {
        this.savedPlayerIndex = this.selectedPlayer;
        this.witchHasAntidote = false;
      } else if (this.witchHasPoison) {
        this.poisonedPlayerIndex = this.selectedPlayer;
        this.witchHasPoison = false;
      }
      this.endNight();  // 结束夜间,进入白天
      break;
  }
}
  • 阶段流转:通过nightPhase枚举值控制行动顺序,每个阶段完成后自动切换至下一阶段;
  • 技能限制:女巫解药仅能救被狼人击杀的玩家,毒药不可对同一目标使用两次。
2. 投票与淘汰机制
private endVote() {
  // 统计最高票数玩家
  let maxVotes = 0;
  let votedPlayerIndex = -1;
  this.voteResults.forEach((votes, index) => {
    if (votes > maxVotes) {
      maxVotes = votes;
      votedPlayerIndex = index;
    }
  });
  
  if (votedPlayerIndex !== -1) {
    this.players[votedPlayerIndex].kill();  // 淘汰玩家
    this.addDayMessage(`投票结束,${this.players[votedPlayerIndex].role} 被淘汰`);
    
    // 猎人技能触发
    if (this.players[votedPlayerIndex].role === '猎人' && !this.hunterHasShot) {
      const alivePlayers = this.players.filter(p => p.isAlive && p.index !== votedPlayerIndex);
      if (alivePlayers.length > 0) {
        const randomIndex = Math.floor(Math.random() * alivePlayers.length);
        alivePlayers[randomIndex].kill();  // 随机开枪
        this.addDayMessage(`猎人开枪带走了 ${alivePlayers[randomIndex].role}`);
      }
    }
  }
}
  • 票数处理:使用Map统计票数,支持平局判定(tiedPlayers.length > 1时无人淘汰);
  • 猎人规则:猎人死亡时若未开枪,自动随机选择存活玩家带走,体现规则严谨性。

五、边界条件与性能优化
1. 胜负判定逻辑
private checkGameStatus() {
  let wolfCount = 0, villagerCount = 0;
  this.players.forEach(player => {
    if (player.isAlive) {
      wolfCount += player.isWolf ? 1 : 0;
      villagerCount += !player.isWolf ? 1 : 0;
    }
  });
  
  if (wolfCount === 0) {
    this.winner = '好人阵营';
    this.isGameOver = true;
  } else if (wolfCount >= villagerCount) {
    this.winner = '狼人阵营';
    this.isGameOver = true;
  }
}
  • 实时计算:每次夜间/投票结束后触发,确保游戏状态及时更新;
  • 胜利条件:狼人全灭或狼人数量≥好人时,通过isGameOver标记触发结束流程。
2. 操作权限控制
private canSelectPlayer(index: number): boolean {
  if (!this.players[index].isAlive) return false;  // 死亡玩家不可选
  
  switch (this.nightPhase) {
    case NightPhase.Wolf: return !this.players[index].isWolf;  // 狼人不能杀自己
    case NightPhase.Seer: return true;  // 预言家可查验任意存活玩家
    case NightPhase.Witch: 
      // 女巫救人/毒人逻辑
      if (this.killedPlayerIndex === index) return this.witchHasAntidote;
      return this.witchHasPoison && index !== this.killedPlayerIndex;
    default: return false;
  }
}
  • 权限过滤:通过canSelectPlayer方法统一控制玩家选择权限,避免非法操作;
  • 阶段限制:狼人阶段不可选择其他狼人,女巫阶段根据解药/毒药状态限制目标。

六、代码可维护性与拓展性设计
  1. 枚举类型规范:通过GamePhaseNightPhase等枚举明确状态值,避免魔法数字,提升代码可读性;
  2. 复用组件封装:使用@Builder修饰符将玩家卡片、按钮组等UI元素封装为可复用组件,减少代码冗余;
  3. 独立方法拆分:将endNight()checkGameStatus()等逻辑拆分为独立方法,保持build()方法简洁;
  4. 角色配置拓展:通过修改roles数组可快速调整初始角色分配,为添加新角色(如守卫、白痴)预留接口。

七、附:源文件
@Preview
@Component
export struct play_10 {
  // 游戏状态
  @State private players: Player[] = [];
  @State private dayCount: number = 1;
  @State private gamePhase: GamePhase = GamePhase.Night;
  @State private nightPhase: NightPhase = NightPhase.Wolf;
  @State private gameStatus: GameStatus = GameStatus.Playing;
  @State private message: string = '游戏开始!请等待夜晚降临...';
  @State private selectedPlayer: number = -1;
  @State private killedPlayerIndex: number = -1;
  @State private savedPlayerIndex: number = -1;
  @State private poisonedPlayerIndex: number = -1;
  @State private isVoting: boolean = false;
  @State private voteResults: Map<number, number> = new Map();
  @State private isGameOver: boolean = false;
  @State private winner: string = '';
  @State private currentPlayerIndex: number = -1;
  @State private playerVoted: boolean = false;
  @State private nightMessages: string[] = [];
  @State private dayMessages: string[] = [];

  // 游戏配置
  private readonly roles = [
    '村民', '村民', '村民',
    '狼人', '狼人',
    '预言家', '女巫', '猎人'
  ];

  // 技能状态
  @State private witchHasAntidote: boolean = true;
  @State private witchHasPoison: boolean = true;
  @State private hunterHasShot: boolean = false;

  build() {
    Column() {
      // 游戏标题和状态(居中显示)
      Column() {
        Text('🐺 狼人杀游戏')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 5 })

        Text(`第${this.dayCount}天 ${this.getPhaseText()}`)
          .fontSize(18)
          .margin({ bottom: 15 })
      }
      .width('100%')
      .alignItems(HorizontalAlign.Center)

      // 游戏消息区域(居中显示)
      Scroll() {
        Column() {
          ForEach(this.getGameMessages(), (msg: string, index: number) => {
            Text(msg)
              .fontSize(14)
              .margin({ bottom: 5 })
              .width('90%')
              .textAlign(TextAlign.Start)
          })
        }
        .width('100%')
        .padding(10)
      }
      .width('100%')
      .height('25%')
      .backgroundColor('#f5f5f5')
      .borderRadius(10)
      .margin({ bottom: 15 })

      // 玩家列表(左右分栏显示)
      Scroll() {
        Grid() {
          // 左栏玩家
          ForEach(this.players.slice(0, this.players.length / 2), (player: Player, index: number) => {
            GridItem(){
              this.renderPlayer(player, index, 'left')
            }
          })

          // 右栏玩家
          ForEach(this.players.slice(this.players.length / 2), (player: Player, index: number) => {
            GridItem(){
              this.renderPlayer(player, index + this.players.length / 2, 'right')
            }
          })
        }
        .columnsTemplate('1fr 1fr')
        .width('100%')
        .height('45%')
        .padding(5)
      }
      .width('100%')

      // 操作按钮(居中显示)
      if (!this.isGameOver) {
        this.renderActionButtons()
      } else {
        this.renderGameOverScreen()
      }
    }
    .width('100%')
    .height('100%')
    .padding(10)
    .onAppear(() => {
      this.initializeGame();
    })
  }

  // 获取游戏消息
  private getGameMessages(): string[] {
    if (this.gamePhase === GamePhase.Night) {
      return this.nightMessages;
    } else {
      return this.dayMessages;
    }
  }

  // 渲染玩家信息(支持左右分栏)
  @Builder
  renderPlayer(player: Player, index: number, side: string) {
    Column() {
      // 玩家角色和状态
      Column() {
        Text(`${player.role} ${player.isWolf ? '🐺' : '👤'}`)
          .fontSize(14)
          .fontWeight(FontWeight.Medium)
          .margin({ bottom: 5 })

        Text(player.isAlive ? '存活' : '已淘汰')
          .fontSize(12)
          .fontColor(player.isAlive ? '#008000' : '#FF0000')
      }
      .width('90%')
      .alignItems(HorizontalAlign.Start)
      .padding(10)
      .backgroundColor(player.isAlive ? '#f0f0f0' : '#e0e0e0')
      .borderRadius(10)
      .margin({ bottom: 10 })

      // 操作按钮(根据游戏阶段显示)
      if (this.canSelectPlayer(index)) {
        Button(this.getActionButtonText(index))
          .onClick(() => {
            this.selectPlayer(index);
          })
          .width('90%')
          .backgroundColor(this.selectedPlayer === index ? '#007DFF' : '#f5f5f5')
          .fontColor(this.selectedPlayer === index ? '#FFFFFF' : '#212121')
          .margin({ top: 5 })
      } else if (this.isVoting && player.isAlive) {
        Button('投票')
          .onClick(() => {
            this.castVote(index);
          })
          .width('90%')
          .backgroundColor(this.selectedPlayer === index ? '#007DFF' : '#f5f5f5')
          .fontColor(this.selectedPlayer === index ? '#FFFFFF' : '#212121')
          .margin({ top: 5 })
      }
    }
    .width(side === 'left' ? '45%' : '45%')
    .alignItems(side === 'left' ? HorizontalAlign.Start : HorizontalAlign.End)
  }

  // 渲染操作按钮(居中显示)
  @Builder
  renderActionButtons() {
    Column() {
      // 夜间操作按钮
      if (this.gamePhase === GamePhase.Night) {
        Row() {
          Button('确认行动')
            .onClick(() => {
              this.confirmNightAction();
            })
            .width('40%')
            .margin({ right: '5%' })

          Button('跳过')
            .onClick(() => {
              this.skipNightAction();
            })
            .width('40%')
        }
        .width('90%')
        .margin({ top: 10 })
      }

      // 白天操作按钮
      else if (this.gamePhase === GamePhase.Day) {
        if (!this.isVoting) {
          Button('开始投票')
            .onClick(() => {
              this.startVote();
            })
            .width('60%')
            .margin({ top: 10 })
        } else {
          Button('结束投票')
            .onClick(() => {
              this.endVote();
            })
            .width('60%')
            .margin({ top: 10 })
        }
      }
    }
    .width('100%')
    .alignItems(HorizontalAlign.Center)
  }

  // 渲染游戏结束界面(居中显示)
  @Builder
  renderGameOverScreen() {
    Column() {
      Text('游戏结束!')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 15 })

      Text(`胜利方:${this.winner}`)
        .fontSize(22)
        .margin({ bottom: 20 })

      // 显示所有玩家身份
      Column() {
        Text('玩家身份:')
          .fontSize(18)
          .width('60%')
          .height(45)
          .borderRadius(25)
          .backgroundColor('#4CAF50')
          .fontColor('#FFFFFF')
          .onClick(() => {
            this.initializeGame();
          })
      }
      .width('80%')
      .padding(20)
      .borderRadius(16)
      .backgroundColor('#FFFFFF')
      .shadow({ color: '#00000020', radius: 10, offsetX: 0, offsetY: 4 }) // 添加阴影效果
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }

  // 初始化游戏
  private initializeGame() {
    this.players = [];
    this.dayCount = 1;
    this.gamePhase = GamePhase.Night;
    this.nightPhase = NightPhase.Wolf;
    this.gameStatus = GameStatus.Playing;
    this.message = '游戏开始!请等待夜晚降临...';
    this.selectedPlayer = -1;
    this.killedPlayerIndex = -1;
    this.savedPlayerIndex = -1;
    this.poisonedPlayerIndex = -1;
    this.isVoting = false;
    this.voteResults = new Map();
    this.isGameOver = false;
    this.winner = '';
    this.currentPlayerIndex = -1;
    this.playerVoted = false;
    this.nightMessages = ['游戏开始!请等待夜晚降临...'];
    this.dayMessages = [];

    // 重置技能状态
    this.witchHasAntidote = true;
    this.witchHasPoison = true;
    this.hunterHasShot = false;

    // 随机分配角色
    this.roles.forEach((role,index) => {
      this.players.push(new Player(role,index));
    });

    // 随机打乱玩家顺序
    this.players.sort(() => Math.random() - 0.5);

    // 开始第一个夜晚
    this.addNightMessage('夜晚降临,请闭眼...');
  }

  // 添加夜间消息
  private addNightMessage(message: string) {
    this.nightMessages.push(message);
    this.message = message;
  }

  // 添加白天消息
  private addDayMessage(message: string) {
    this.dayMessages.push(message);
    this.message = message;
  }

  // 确认夜间行动
  private confirmNightAction() {
    // 根据当前夜间阶段处理行动
    switch (this.nightPhase) {
      case NightPhase.Wolf:
        if (this.selectedPlayer !== -1 && this.players[this.selectedPlayer].isAlive) {
          this.killedPlayerIndex = this.selectedPlayer;
          this.addNightMessage(`狼人选择了击杀 ${this.players[this.selectedPlayer].role}`);
          this.selectedPlayer = -1;
          this.nightPhase = NightPhase.Seer;
        } else {
          this.addNightMessage('请选择要击杀的玩家!');
        }
        break;

      case NightPhase.Seer:
        if (this.selectedPlayer !== -1 && this.players[this.selectedPlayer].isAlive) {
          const isWolf = this.players[this.selectedPlayer].isWolf;
          this.addNightMessage(`预言家查验 ${this.players[this.selectedPlayer].role} 是 ${isWolf ? '狼人' : '好人'}`);
          this.selectedPlayer = -1;
          this.nightPhase = NightPhase.Witch;
        } else {
          this.addNightMessage('请选择要查验的玩家!');
        }
        break;

      case NightPhase.Witch:
        // 女巫可以救人或毒人
        if (this.selectedPlayer !== -1 && this.players[this.selectedPlayer].isAlive) {
          // 检查是否是被击杀的玩家
          if (this.selectedPlayer === this.killedPlayerIndex && this.witchHasAntidote) {
            this.savedPlayerIndex = this.selectedPlayer;
            this.witchHasAntidote = false;
            this.addNightMessage(`女巫使用了解药,救活了 ${this.players[this.selectedPlayer].role}`);
          } else if (this.selectedPlayer !== this.killedPlayerIndex && this.witchHasPoison) {
            this.poisonedPlayerIndex = this.selectedPlayer;
            this.witchHasPoison = false;
            this.addNightMessage(`女巫使用了毒药,毒死了 ${this.players[this.selectedPlayer].role}`);
          }
        }
        this.selectedPlayer = -1;

        // 结束夜晚,进入白天
        this.endNight();
        break;
    }
  }

  // 跳过夜间行动
  private skipNightAction() {
    switch (this.nightPhase) {
      case NightPhase.Wolf:
        this.addNightMessage('狼人今晚没有行动');
        this.nightPhase = NightPhase.Seer;
        break;

      case NightPhase.Seer:
        this.addNightMessage('预言家今晚没有查验');
        this.nightPhase = NightPhase.Witch;
        break;

      case NightPhase.Witch:
        // 直接结束夜晚
        this.endNight();
        break;
    }
    this.selectedPlayer = -1;
  }

  // 结束夜晚,进入白天
  private endNight() {
    // 处理夜间结果
    let nightResult = `天亮了,昨晚`;

    // 处理女巫的解药
    if (this.savedPlayerIndex !== -1) {
      nightResult += '平安夜';
    } else {
      // 处理击杀和毒杀
      const killedPlayers: number[] = [];

      if (this.killedPlayerIndex !== -1) {
        killedPlayers.push(this.killedPlayerIndex);
      }

      if (this.poisonedPlayerIndex !== -1 && this.poisonedPlayerIndex !== this.killedPlayerIndex) {
        killedPlayers.push(this.poisonedPlayerIndex);
      }

      if (killedPlayers.length > 0) {
        nightResult += '死亡的是:';
        killedPlayers.forEach(index => {
          this.players[index].kill();
          nightResult += `${this.players[index].role} `;
        });
      } else {
        nightResult += '平安夜';
      }
    }

    this.addDayMessage(nightResult);

    // 重置夜间状态
    this.gamePhase = GamePhase.Day;
    this.nightPhase = NightPhase.Wolf;
    this.killedPlayerIndex = -1;
    this.savedPlayerIndex = -1;
    this.poisonedPlayerIndex = -1;

    // 检查游戏是否结束
    this.checkGameStatus();
  }

  // 开始投票
  private startVote() {
    this.isVoting = true;
    this.addDayMessage('开始投票,请选择要投票的玩家');
    this.voteResults.clear();
    this.playerVoted = false;
  }

  // 投票
  private castVote(playerIndex: number) {
    if (this.players[playerIndex].isAlive && !this.playerVoted) {
      this.selectedPlayer = playerIndex;
      this.playerVoted = true;

      // 记录投票结果
      if (this.voteResults.has(playerIndex)) {
        this.voteResults.set(playerIndex, this.voteResults.get(playerIndex)! + 1);
      } else {
        this.voteResults.set(playerIndex, 1);
      }

      this.addDayMessage(`你投票给了 ${this.players[playerIndex].role}`);
    }
  }

  // 结束投票
  private endVote() {
    if (this.voteResults.size === 0) {
      this.addDayMessage('没有人被投票,白天结束');
    } else {
      // 找出得票最多的玩家
      let maxVotes = 0;
      let votedPlayerIndex = -1;
      let tiedPlayers: number[] = [];

      this.voteResults.forEach((votes, index) => {
        if (votes > maxVotes) {
          maxVotes = votes;
          votedPlayerIndex = index;
          tiedPlayers = [index];
        } else if (votes === maxVotes) {
          tiedPlayers.push(index);
        }
      });

      // 处理投票结果
      if (tiedPlayers.length > 1) {
        this.addDayMessage('投票平局,没有人被淘汰');
      } else if (votedPlayerIndex !== -1) {
        this.players[votedPlayerIndex].kill();
        this.addDayMessage(`投票结束,${this.players[votedPlayerIndex].role} 被投票出局`);

        // 处理猎人技能
        if (this.players[votedPlayerIndex].role === '猎人' && !this.hunterHasShot) {
          this.hunterHasShot = true;
          // 随机选择一名存活玩家开枪
          const alivePlayers = this.players.filter(p => p.isAlive && p.index !== votedPlayerIndex);
          if (alivePlayers.length > 0) {
            const randomIndex = Math.floor(Math.random() * alivePlayers.length);
            const shotPlayer = alivePlayers[randomIndex];
            shotPlayer.kill();
            this.addDayMessage(`猎人开枪带走了 ${shotPlayer.role}`);
          } else {
            this.addDayMessage('猎人没有可开枪的目标');
          }
        }
      }
    }

    this.isVoting = false;
    this.selectedPlayer = -1;
    this.playerVoted = false;

    // 检查游戏是否结束
    this.checkGameStatus();

    // 如果游戏继续,进入下一个夜晚
    if (this.gameStatus === GameStatus.Playing) {
      this.dayCount++;
      this.gamePhase = GamePhase.Night;
      this.nightMessages = [];
      this.addNightMessage(`第${this.dayCount}天夜晚降临,请闭眼...`);
    }
  }

  // 检查游戏状态
  private checkGameStatus() {
    // 计算存活的狼人和好人数量
    let wolfCount = 0;
    let villagerCount = 0;

    this.players.forEach(player => {
      if (player.isAlive) {
        if (player.isWolf) {
          wolfCount++;
        } else {
          villagerCount++;
        }
      }
    });

    // 判断胜利条件
    if (wolfCount === 0) {
      this.gameStatus = GameStatus.VillagersWin;
      this.winner = '好人阵营';
      this.isGameOver = true;
    } else if (wolfCount >= villagerCount) {
      this.gameStatus = GameStatus.WolvesWin;
      this.winner = '狼人阵营';
      this.isGameOver = true;
    }
  }

  // 选择玩家
  private selectPlayer(index: number) {
    this.selectedPlayer = index;
  }

  // 判断是否可以选择玩家
  private canSelectPlayer(index: number): boolean {
    if (!this.players[index].isAlive) {
      return false;
    }

    switch (this.nightPhase) {
      case NightPhase.Wolf:
        return !this.players[index].isWolf;

      case NightPhase.Seer:
        return true;

      case NightPhase.Witch:
        // 女巫可以救被击杀的玩家(如果有解药)或毒其他玩家(如果有毒药)
        if (this.killedPlayerIndex !== -1 && index === this.killedPlayerIndex) {
          return this.witchHasAntidote;
        }
        return this.witchHasPoison && index !== this.killedPlayerIndex;

      default:
        return false;
    }
  }

  // 获取行动按钮文本
  private getActionButtonText(index: number): string {
    switch (this.nightPhase) {
      case NightPhase.Wolf:
        return '击杀';

      case NightPhase.Seer:
        return '查验';

      case NightPhase.Witch:
        // 如果是被击杀的玩家,显示"救",否则显示"毒"
        if (this.killedPlayerIndex !== -1 && index === this.killedPlayerIndex) {
          return '救';
        }
        return '毒';

      default:
        return '选择';
    }
  }

  // 获取当前阶段文本
  private getPhaseText(): string {
    if (this.isGameOver) {
      return '游戏结束';
    }

    if (this.gamePhase === GamePhase.Night) {
      switch (this.nightPhase) {
        case NightPhase.Wolf:
          return '狼人行动';
        case NightPhase.Seer:
          return '预言家行动';
        case NightPhase.Witch:
          return '女巫行动';
        default:
          return '夜晚';
      }
    } else {
      if (this.isVoting) {
        return '投票阶段';
      }
      return '白天讨论';
    }
  }
}

// 玩家类
class Player {
  public isAlive: boolean = true;
  public isDead: boolean = false;
  public role: string;
  public isWolf: boolean;
  public hasUsedSkill: boolean = false; // 技能是否已使用(如猎人的枪)
  public index: number; // 玩家索引

  constructor(role: string, index: number) {
    this.role = role;
    this.isWolf = role === '狼人';
    this.index = index;
  }

  public kill() {
    this.isAlive = false;
    this.isDead = true;
  }
}

// 游戏阶段枚举
enum GamePhase {
  Night,
  Day
}

// 夜间阶段枚举
enum NightPhase {
  Wolf,
  Seer,
  Witch
}

// 游戏状态枚举
enum GameStatus {
  Playing,
  VillagersWin,
  WolvesWin
}