青云
青云
发布于 2026-02-13 / 28 阅读
0
0

AI狼人杀

一、说明

本章介绍全部由AI生成的玩家,完成狼人杀游戏。做这个目的纯属爱好,自从了解了一些AI相关的技术后,总想着做点有趣的东西,一边练技术,一边打发时间吧~~

采用的前后分离开发模式:

后端技术:Spring AI、Spring AI alibaba、SpringBoot、Mysql、Redis、SSE等等

前端技术:VUE、ElementUI等等

目前后端基本完工,正在开发前端页面中....

准备过年在家写完~~

当然,游戏还有很多小逻辑并不完善,后面会慢慢修改,也欢迎有兴趣的同学们一起探讨~~

二、代码

1.POM文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.5</version>
        <relativePath/>
    </parent>
    <groupId>com.zhan.weki</groupId>
    <artifactId>werewolf-kill</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <name>werewolf-kill</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0</spring-ai.version>
        <spring-ai-alibaba.version>1.0.0.2</spring-ai-alibaba.version>
        <spring-boot.version>3.4.5</spring-boot.version>
        <mybatis-plus.version>3.5.11</mybatis-plus.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
            <version>${spring-ai-alibaba.version}</version>
            <type>pom</type>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
            <version>${spring-ai-alibaba.version}</version>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <!-- 添加 Redis 向量数据库依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-redis</artifactId>
            <version>1.0.0</version>
        </dependency>

        <!-- MySQL 驱动(完整正确配置) -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
            <version>8.0.33</version> <!-- 固定版本,避免版本适配问题 -->
        </dependency>

        <!--mybatis-plus 持久层-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- SpringDoc OpenAPI 3.0 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.7.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.34</version>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.16</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.4</version>
        </dependency>

        <!-- 支持 Java 8 时间类型(LocalDateTime/LocalDate 等)的序列化/反序列化 -->
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <!-- Spring Boot 父工程已管理版本,无需手动指定 -->
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
            </plugin>
        </plugins>
    </build>
</project>

2.SQL脚本

-- 房间表
CREATE TABLE `room` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '房间ID',
  `room_name` varchar(50) NOT NULL COMMENT '房间名称',
  `player_count` int NOT NULL COMMENT '玩家数量',
  `game_stage` varchar(20) NOT NULL COMMENT '游戏阶段',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `status` int NOT NULL COMMENT '房间状态:0-未开始 1-进行中 2-已结束',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='狼人杀房间表';

-- AI玩家表
CREATE TABLE `ai_player` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '玩家ID',
  `room_id` bigint NOT NULL COMMENT '房间ID',
  `player_no` int NOT NULL COMMENT '玩家编号(1-12)',
  `role` varchar(20) NOT NULL COMMENT '角色',
  `ai_model_code` varchar(50) NOT NULL COMMENT '绑定的AI模型ID',
  `status` varchar(20) NOT NULL COMMENT '状态:ALIVE-存活 DEAD-死亡',
  `last_speak` text COMMENT '最后发言内容',
  PRIMARY KEY (`id`),
  KEY `idx_room_id` (`room_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI玩家表';

3.yml主配置文件

server:
  port: 8080

application:
  name: werewolf-kill

spring:
  profiles:
    active: dev

4.yml开发配置文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/gizz?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: xxxxxxxxxxxxxx
  # Redis配置(集群)
  data:
    redis:
      host: xxxxxxxxxxxxxx
      port: 6379
      timeout: 3000
      database: 0
      password: xxxxxxxxxxxxxx
      jedis:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
  ai:
    dashscope:
      api-key: sk-09c7b571687b46d5a2e25a03fbddxxxx
      client:
        connection-timeout: 30000
        read-timeout: 180000
        call-timeout: 240000
      retry:
        max-attempts: 10
        backoff:
          initial-interval: 5000ms
          multiplier: 1.5
# MyBatis-Plus配置
mybatis-plus:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

logging:
  file:
    name: wolf.log
    path: logs
  logback:
    rollingPolicy:
      maxFileSize: 100MB
      maxHistory: 30

5.config配置类

package com.zhan.game.config;

import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @author zhanxuewei
 */
@Configuration
public class ChatClientFactory {

    @Value("${spring.ai.dashscope.api-key}")
    private String apiKey;

    // 缓存已创建的 ChatClient 实例
    private final Map<String, ChatClient> chatClientCache = new HashMap<>();

    /**
     * 根据模型代码构建对应的 ChatClient
     *
     * @param modelCode 模型代码
     * @return 对应的 ChatClient 实例
     */
    public ChatClient buildChatClient(String modelCode) {
        // 检查缓存中是否存在
        if (chatClientCache.containsKey(modelCode)) {
            return chatClientCache.get(modelCode);
        }

        // 创建新的 ChatClient 并缓存
        ChatClient chatClient = createChatClient(modelCode);
        chatClientCache.put(modelCode, chatClient);
        return chatClient;
    }

    /**
     * 创建 ChatClient 实例
     *
     * @param modelCode 模型代码
     * @return ChatClient 实例
     */
    private ChatClient createChatClient(String modelCode) {
        DashScopeChatModel chatModel = DashScopeChatModel.builder()
                .dashScopeApi(DashScopeApi.builder().apiKey(apiKey).build())
                .defaultOptions(DashScopeChatOptions.builder().withModel(modelCode).build())
                .build();

        return ChatClient.builder(chatModel)
                .defaultOptions(ChatOptions.builder().model(modelCode).build())
                .build();
    }
}

6.接口文档配置

package com.zhan.game.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Knife4jConfig {
    @Bean
    public OpenAPI openAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("API接口文档")
                        .description("API接口文档")
                        .version("1.0.0")
                        .contact(new Contact()
                                .name("wolf")
                                .email("603085899@qq.com")));
    }
}

7.Redis配置

package com.zhan.game.config;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        // ========== 1. Key序列化(统一为字符串,无任何冲突) ==========
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);

        // ========== 2. Value序列化(支持所有类型,自动处理类型ID) ==========
        GenericJackson2JsonRedisSerializer jacksonSerializer = new GenericJackson2JsonRedisSerializer(buildObjectMapper() // 自定义ObjectMapper,解决类型ID解析问题
        );
        redisTemplate.setValueSerializer(jacksonSerializer);
        redisTemplate.setHashValueSerializer(jacksonSerializer);

        // ========== 3. 初始化模板 ==========
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 构建ObjectMapper,解决「Could not resolve type id」问题
     */
    private ObjectMapper buildObjectMapper() {
        ObjectMapper om = new ObjectMapper();
        // 1. 开启类型信息存储(序列化时记录类型,反序列化时识别)
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, // 放宽类型校验(兼容简单类型如String/Integer)
                ObjectMapper.DefaultTyping.NON_FINAL,  // 仅为非final类添加类型ID(避免String/Integer等基础类型报错)
                JsonTypeInfo.As.PROPERTY               // 类型ID作为属性存储(如@class: java.lang.String)
        );
        // 2. 其他配置(可选,提升兼容性)
        om.enableDefaultTyping();
        om.findAndRegisterModules(); // 注册所有Jackson模块(如Java8时间类型)
        return om;
    }
}

8.常量

package com.zhan.game.constants;

public interface ApiConstants {

    public static final String API_PREFIX = "/api/v1";

}

9.常量

package com.zhan.game.constants;

public interface GameConstants {

    String DEAD = "DEAD";

    String ALIVE = "ALIVE";

    int DEFAULT_PLAYER_COUNT = 12;

    int PLAYER_NO_START_INDEX = 1;

    int ROOM_STATUS_ACTIVE = 1;
}

10.统一接口返回封装

package com.zhan.game.common;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.io.Serializable;

/**
 * 通用响应对象
 */
public class ApiResult<T> implements Serializable {
    private static final long serialVersionUID = 899231L;

    private Long code;

    private String message;

    private T data;

    protected ApiResult() {
    }

    /**
     * 构造响应对象
     *
     * @param code    状态吗
     * @param message 返回信息
     * @param data    返回数据
     */
    public ApiResult(Long code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    /**
     * 响应成功结果
     *
     * @param <T> 范形对象
     * @return 返回范形对象
     */
    public static <T> ApiResult<T> success() {
        return new ApiResult<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null);
    }

    /**
     * 响应成功结果
     *
     * @param code    状态吗
     * @param message 返回消息
     * @param <T>     范形对象
     * @return 范形对象
     */
    public static <T> ApiResult<T> success(Long code, String message) {
        return new ApiResult<>(code, message, null);
    }

    /**
     * 响应成功结果
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> success(T data) {
        return new ApiResult<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
    }

    /**
     * 响应成功结果
     *
     * @param code
     * @param message
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> success(Long code, String message, T data) {
        return new ApiResult<>(code, message, data);
    }

    /**
     * 响应成功结果
     *
     * @param message
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> success(String message, T data) {
        return new ApiResult<>(ResultCode.SUCCESS.getCode(), message, data);
    }

    /**
     * 响应成功结果
     *
     * @param errorCode
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> success(IErrorCode errorCode, T data) {
        return new ApiResult<>(errorCode.getCode(), errorCode.getMessage(), data);
    }

    /**
     * 响应成功结果
     *
     * @param errorCode
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> success(IErrorCode errorCode) {
        return success(errorCode, null);
    }

    /**
     * 失败返回结果
     *
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> failed() {
        return new ApiResult<>(ResultCode.FAILED.getCode(), ResultCode.FAILED.getMessage(), null);
    }

    /**
     * 失败返回结果
     *
     * @param code
     * @param message
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> failed(Long code, String message) {
        return new ApiResult<>(code, message, null);
    }

    /**
     * 失败返回结果
     *
     * @param message
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> failed(String message) {
        return new ApiResult<>(ResultCode.FAILED.getCode(), message, null);
    }

    /**
     * 失败返回结果
     *
     * @param errorCode
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> failed(IErrorCode errorCode) {
        return new ApiResult<>(errorCode.getCode(), errorCode.getMessage(), null);
    }

    /**
     * 失败返回结果
     *
     * @param errorCode
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> failed(IErrorCode errorCode, T data) {
        return new ApiResult<>(errorCode.getCode(), errorCode.getMessage(), data);
    }

    /**
     * 失败返回结果
     *
     * @param message
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ApiResult<T> failed(String message, T data) {
        return new ApiResult<>(ResultCode.FAILED.getCode(), message, data);
    }


    public long getCode() {
        return code;
    }

    public void setCode(long code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "CommonResult{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

11.错误模型接口

package com.zhan.game.common;

/**
 * 错误模型接口
 */
public interface IErrorCode {

	// 状态码
	Long getCode();

	// 错误消息
	String getMessage();

}

12.返回结果枚举

package com.zhan.game.common;


/**
 * 枚举了一些常用API操作码
 */
public enum ResultCode implements IErrorCode {

    SUCCESS(200L, "操作成功"),
    FAILED(500L, "操作失败"),

    INVALID_ERROR(10005L, "参数格式校验失败"),
    PARAMETER_TYPE_ERROR(10006L, "参数类型错误"),
    OTHER_ERROR(10007L, "未知异常"),
    FILE_SIZE_TOO_BIG_ERROR(10008L, "上传文件大小超出限制"),
    ROOM_NOT_FOUND(2000L, "房间不存在"),
    GAME_OVER(2001L, "游戏已结束"),
    ADVANCE_GAME_STAGE_ERROR(2002L, "推进游戏阶段失败"),
    ROOM_NO_PLAYER(2003L, "房间内无玩家"),
    PLAYER_NOT_FOUND(2004L, "玩家编号不存在"),
    GET_AI_SPEAK_RECORD_ERROR(2005L, "获取发言失败"),
    GET_GAME_STATUS_ERROR(2006L, "获取状态失败"),
    PLAYER_DEAD_OR_NOT_FOUND(2007L, "玩家不存在或已死亡"),
    TRIGGER_AI_SPEAK_ERROR(2008L, "触发AI发言失败"),
    ROLE_NOT_FOUND(2009L, "角色不存在"),
    ROOM_EXIST(2010L, "房间已存在"),
    ROOM_CREATION_BUSY(2011L, "房间创建繁忙,请稍后再试"),

    ;

    private final Long code;
    private final String message;

    ResultCode(Long code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public Long getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}

13.控制器

package com.zhan.game.controller;

import com.zhan.game.common.ApiResult;
import com.zhan.game.constants.ApiConstants;
import com.zhan.game.entity.AiPlayer;
import com.zhan.game.entity.Room;
import com.zhan.game.service.AiPlayerService;
import com.zhan.game.service.GameService;
import com.zhan.game.vo.GameStageAdvanceVO;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping(ApiConstants.API_PREFIX + "/game")
@RequiredArgsConstructor
public class WerewolfController {

    @Resource
    private GameService gameService;

    @Resource
    private AiPlayerService aiPlayerService;

    /**
     * 创建狼人杀房间
     *
     * @param request 请求参数(roomName)
     * @return 房间信息
     */
    @PostMapping("/room/create")
    public ApiResult<Room> createRoom(@RequestBody Map<String, String> request) {
        String roomName = request.get("roomName");
        Room room = gameService.createRoom(roomName);
        return ApiResult.success(room);
    }

    /**
     * 查询房间内所有AI玩家
     *
     * @param roomId 房间ID
     * @return 玩家列表
     */
    @GetMapping("/room/players/{roomId}")
    public ApiResult<List<AiPlayer>> getRoomPlayers(@PathVariable Long roomId) {
        return ApiResult.success(aiPlayerService.getRoomPlayers(roomId));
    }

    // ========== 新增核心接口 ==========

    /**
     * 推进游戏阶段(核心接口:触发夜晚/白天流程)
     * 调用逻辑:创建房间后,调用此接口推进游戏,自动完成当前阶段的所有AI行动
     *
     * @param roomId 房间ID
     * @return 阶段推进结果(包含当前阶段、AI行动概要)
     */
    @PostMapping("/room/advance/{roomId}")
    public ApiResult<GameStageAdvanceVO> advanceGameStage(@PathVariable Long roomId) {
        GameStageAdvanceVO result = gameService.advanceGameStage(roomId);
        return ApiResult.success(result);
    }

    /**
     * 获取AI玩家的发言/行动记录(核心:查看AI对话内容)
     *
     * @param roomId   房间ID
     * @param playerNo 玩家编号(可选,不传则返回所有AI的发言)
     * @return 发言记录
     */
    @GetMapping("/room/player/speak/{roomId}")
    public ApiResult<List<AiPlayer>> getAiSpeakRecord(@PathVariable Long roomId, @RequestParam(required = false) Integer playerNo) {
        List<AiPlayer> record = gameService.getAiSpeakRecord(roomId, playerNo);
        return ApiResult.success(record);
    }

    /**
     * 获取房间当前游戏状态(全局视角)
     *
     * @param roomId 房间ID
     * @return 游戏状态(阶段、存活/死亡玩家、胜负结果等)
     */
    @GetMapping("/room/status/{roomId}")
    public ApiResult<Map<String, Object>> getGameStatus(@PathVariable Long roomId) {
        Map<String, Object> result = gameService.getGameStatus(roomId);
        return ApiResult.success(result);
    }

    /**
     * 手动触发单个AI玩家发言(测试用,可选)
     *
     * @param roomId   房间ID
     * @param playerNo 玩家编号
     * @param prompt   自定义发言提示词(可选,不传则用默认提示词)
     * @return 玩家发言内容
     */
    @PostMapping("/room/player/speak/{roomId}/{playerNo}")
    public ApiResult<Map<String, Object>> triggerAiSpeak(@PathVariable Long roomId,
                                                         @PathVariable Integer playerNo,
                                                         @RequestParam(required = false) String prompt) {
        Map<String, Object> result = gameService.triggerAiSpeak(roomId, playerNo, prompt);
        return ApiResult.success(result);
    }
}

14.实体类

package com.zhan.game.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

@Data
@TableName("ai_player")
public class AiPlayer implements Serializable {
    @TableId(type = IdType.AUTO)
    private Long id;             // 玩家ID
    private Long roomId;         // 房间ID
    private Integer playerNo;    // 玩家编号(1-12)
    private String role;         // 角色(关联RoleEnum)
    private String aiModelCode;    // 绑定的AI模型ID
    private String status;       // 状态:ALIVE-存活 DEAD-死亡
    private String lastSpeak;    // 最后发言内容
}
package com.zhan.game.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

@Data
@TableName("room")
public class Room implements Serializable {

    @TableId(type = IdType.AUTO)
    private Long id;             // 房间ID

    private String roomName;     // 房间名称

    private Integer playerCount; // 玩家数量(固定12)

    private String gameStage;    // 游戏阶段(关联GameStageEnum)

    @JsonSerialize(using = LocalDateTimeSerializer.class)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime; // 创建时间

    @JsonSerialize(using = LocalDateTimeSerializer.class)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime; // 更新时间

    private Integer status;      // 房间状态:0-未开始 1-进行中 2-已结束

}

15.游戏相关的枚举

package com.zhan.game.enums;

public enum GameResult {
    CONTINUE,
    WOLF_WIN,
    GOOD_WIN
}
package com.zhan.game.enums;

import lombok.Getter;

@Getter
public enum GameStageEnum {
    NIGHT("夜晚阶段", 1), // 狼人杀人、预言家验人、女巫用药、守卫守护
    DAY("白天阶段", 2),   // 发言、投票
    END("游戏结束", 3);

    private final String name;
    private final int order;

    GameStageEnum(String name, int order) {
        this.name = name;
        this.order = order;
    }
}
package com.zhan.game.enums;

import lombok.Getter;

@Getter
public enum ModelEnum {

    WEREWOLF("狼人", "deepseek-v3.2"),
    CIVILIAN("平民", "qwen3-max-preview"),
    SEER("预言家", "deepseek-v3.2"),
    WITCH("女巫", "deepseek-v3.2"),
    HUNTER("猎人", "deepseek-v3.2"),
    GUARD("守卫", "deepseek-v3.2"),

    ;

    private final String name;
    private final String code;

    ModelEnum(String name, String code) {
        this.name = name;
        this.code = code;
    }

    public static ModelEnum getModeCodeByName(String name) {
        for (ModelEnum modelEnum : ModelEnum.values()) {
            if (modelEnum.getName().equals(name)) {
                return modelEnum;
            }
        }
        return ModelEnum.WEREWOLF;
    }
}
package com.zhan.game.enums;

import lombok.Getter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Getter
public enum RoleEnum {

    WEREWOLF("狼人", 4),
    CIVILIAN("平民", 4),
    SEER("预言家", 1),
    WITCH("女巫", 1),
    HUNTER("猎人", 1),
    GUARD("守卫", 1);

    private final String name;
    private final int count; // 12人局该角色数量

    RoleEnum(String name, int count) {
        this.name = name;
        this.count = count;
    }

    // 获取12人局所有角色列表(按数量生成)
    public static List<RoleEnum> getFullRoles() {
        List<RoleEnum> roles = new ArrayList<>();
        for (RoleEnum role : RoleEnum.values()) {
            for (int i = 0; i < role.getCount(); i++) {
                roles.add(role);
            }
        }
        // 随机打乱顺序
        Collections.shuffle(roles);
        return roles;
    }

    public static RoleEnum getRoleByName(String name) {
        for (RoleEnum roleEnum : RoleEnum.values()) {
            if (roleEnum.getName().equals(name)) {
                return roleEnum;
            }
        }
        return null;
    }
}
package com.zhan.game.enums;

import lombok.Getter;

/**
 * 女巫药水状态枚举
 */
@Getter
public enum WitchStatusEnum {
    ANTIDOTE_AVAILABLE("解药可用", 1),
    ANTIDOTE_USED("解药已用", 0),
    POISON_AVAILABLE("毒药可用", 1),
    POISON_USED("毒药已用", 0);

    private final String desc;
    private final int status;

    WitchStatusEnum(String desc, int status) {
        this.desc = desc;
        this.status = status;
    }
}

16.Mapper类

package com.zhan.game.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhan.game.entity.AiPlayer;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface AiPlayerMapper extends BaseMapper<AiPlayer> {
    
}
package com.zhan.game.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhan.game.entity.Room;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface RoomMapper extends BaseMapper<Room> {

}

17.Service接口

package com.zhan.game.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.zhan.game.entity.Room;


public interface RoomService extends IService<Room> {

    void updateRoomStatus(Long roomId, int status);

    void updateRoomStage(Long roomId, String name);
    
}
package com.zhan.game.service;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public interface RedisService {

    // ========== 基础对象操作 ==========
    <T> void setObject(String key, T value);

    <T> void setObject(String key, T value, long timeout, TimeUnit unit);

    @SuppressWarnings("unchecked")
    <T> T getObject(String key, Class<T> clazz);

    // ========== List操作 ==========
    <T> void listRightPush(String key, T value);

    <T> void listRightPushAll(String key, List<T> values);

    @SuppressWarnings("unchecked")
    <T> List<T> listRange(String key, long start, long end);

    // ========== Hash操作 ==========
    <T> void hashPut(String key, String hashKey, T value);

    <T> void hashPutAll(String key, Map<String, T> map);

    @SuppressWarnings("unchecked")
    <T> T hashGet(String key, String hashKey, Class<T> clazz);

    // ========== 通用操作 ==========
    boolean hasKey(String key);

    void deleteKey(String key);

    /**
     * 获取Hash的所有键值对(解决entries读取的序列化问题)
     *
     * @param key Redis的Hash Key
     * @return 键值对Map(自动处理序列化/反序列化)
     */
    Map<Object, Object> hashGetAllEntries(String key);

    /**
     * 设置键值对,仅当键不存在时才设置(分布式锁的基础操作)
     * @param key 键
     * @param value 值
     * @param timeout 过期时间
     * @param unit 时间单位
     * @return 是否设置成功
     */
    Boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit);

    /**
     * 删除键值对,仅当键存在且值相等时才删除(用于释放分布式锁)
     * @param key 键
     * @param value 值
     * @return 是否删除成功
     */
    Boolean deleteIfEquals(String key, Object value);
}
package com.zhan.game.service;

import com.zhan.game.entity.AiPlayer;
import com.zhan.game.entity.Room;
import com.zhan.game.enums.GameResult;

import java.util.List;
import java.util.Map;

public interface GameService {


    Room createRoom(String roomName);

    List<AiPlayer> getRoomPlayers(Long roomId);

    List<AiPlayer> findDeadLastNight(Long roomId);

    void clearNightDeadRecord(Long roomId);

    void recordNightDeadPlayers(Long roomId, String killedByWolf, String poisonedByWitch, String guardTarget);

    void nightStage(Long roomId);

    void dayStage(Long roomId);

    GameResult checkWinOrLose(Long roomId);

    /**
     * 推进游戏阶段(核心接口:触发夜晚/白天流程)
     * 调用逻辑:创建房间后,调用此接口推进游戏,自动完成当前阶段的所有AI行动
     *
     * @param roomId 房间ID
     * @return 阶段推进结果(包含当前阶段、AI行动概要)
     */
    Map<String, Object> advanceGameStage(Long roomId);

    /**
     * 获取AI玩家的发言/行动记录(核心:查看AI对话内容)
     *
     * @param roomId   房间ID
     * @param playerNo 玩家编号(可选,不传则返回所有AI的发言)
     * @return 发言记录
     */
    Map<String, Object> getAiSpeakRecord(Long roomId, Integer playerNo);

    /**
     * 获取房间当前游戏状态(全局视角)
     *
     * @param roomId 房间ID
     * @return 游戏状态(阶段、存活/死亡玩家、胜负结果等)
     */
    Map<String, Object> getGameStatus(Long roomId);

    /**
     * 手动触发单个AI玩家发言(测试用,可选)
     *
     * @param roomId   房间ID
     * @param playerNo 玩家编号
     * @param prompt   自定义发言提示词(可选,不传则用默认提示词)
     * @return 玩家发言内容
     */
    Map<String, Object> triggerAiSpeak(Long roomId, Integer playerNo, String prompt);
}
package com.zhan.game.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.zhan.game.entity.AiPlayer;
import com.zhan.game.enums.RoleEnum;

import java.util.List;

public interface AiPlayerService extends IService<AiPlayer> {

    String aiAction(AiPlayer player, String prompt);

    List<AiPlayer> getRoomPlayers(Long roomId);

    void initWitchStatus(Long roomId);

    AiPlayer findByPlayerNo(List<AiPlayer> players, Integer no);

    void usePoison(Long roomId);

    boolean validateWitchTarget(List<AiPlayer> players, String poisonTarget, AiPlayer witch);

    List<AiPlayer> findAliveAll(Long roomId);

    void useAntidote(Long roomId);

    List<AiPlayer> findAliveListByRole(Long roomId, String role);

    long countAliveCivilian(List<AiPlayer> players);

    long countAliveGod(List<AiPlayer> players);

    boolean getWitchPoison(Long roomId);

    boolean getWitchAntidote(Long roomId);

    List<AiPlayer> findDeadLastNight(Long roomId);
}
package com.zhan.game.service;

import com.zhan.game.entity.AiPlayer;
import com.zhan.game.entity.Room;
import com.zhan.game.enums.GameResult;
import com.zhan.game.vo.GameStageAdvanceVO;

import java.util.List;
import java.util.Map;

public interface GameService {

    /**
     * 创建房间并生成12个AI玩家
     *
     * @param roomName 房间名称
     * @return 房间信息
     */
    Room createRoom(String roomName);

    /**
     * 清空夜间死亡记录(白天阶段结束后调用,避免跨夜混淆)
     *
     * @param roomId 房间ID
     */
    void clearNightDeadRecord(Long roomId);

    /**
     * 记录夜间死亡玩家(狼人刀杀/女巫毒杀)
     *
     * @param roomId          房间ID
     * @param killedByWolf    狼人刀杀目标(null表示被救/无刀人)
     * @param poisonedByWitch 女巫毒杀目标(null表示未毒人)
     * @param guardTarget     守卫守护目标(null表示未守护)
     */
    void recordNightDeadPlayers(Long roomId, String killedByWolf, String poisonedByWitch, String guardTarget);

    /**
     * 执行完整夜晚流程:守卫 -> 狼人 -> 预言家 -> 女巫
     */
    void nightStage(Long roomId);

    /**
     * 白天完整流程:
     * 1. 公布昨夜死讯
     * 2. 按顺序发言
     * 3. 公投
     * 4. 判胜负
     */
    void dayStage(Long roomId);

    /**
     * 检查胜负结果
     *
     * @param roomId 房间ID
     * @return 胜负结果
     */
    GameResult checkWinOrLose(Long roomId);

    /**
     * 推进游戏阶段(核心接口:触发夜晚/白天流程)
     * 调用逻辑:创建房间后,调用此接口推进游戏,自动完成当前阶段的所有AI行动
     *
     * @param roomId 房间ID
     * @return 阶段推进结果(包含当前阶段、AI行动概要)
     */
    GameStageAdvanceVO advanceGameStage(Long roomId);

    /**
     * 获取AI玩家的发言/行动记录(核心:查看AI对话内容)
     *
     * @param roomId   房间ID
     * @param playerNo 玩家编号(可选,不传则返回所有AI的发言)
     * @return 发言记录
     */
    List<AiPlayer> getAiSpeakRecord(Long roomId, Integer playerNo);

    /**
     * 获取房间当前游戏状态(全局视角)
     *
     * @param roomId 房间ID
     * @return 游戏状态(阶段、存活/死亡玩家、胜负结果等)
     */
    Map<String, Object> getGameStatus(Long roomId);

    /**
     * 手动触发单个AI玩家发言(测试用,可选)
     *
     * @param roomId   房间ID
     * @param playerNo 玩家编号
     * @param prompt   自定义发言提示词(可选,不传则用默认提示词)
     * @return 玩家发言内容
     */
    Map<String, Object> triggerAiSpeak(Long roomId, Integer playerNo, String prompt);

    /**
     * AI投票统计
     *
     * @param list       投票玩家列表
     * @param roomId     房间ID
     * @return 最终投票目标
     */
    String collectVoteFromAi(List<AiPlayer> list, Long roomId);
}

18.Service接口实现

package com.zhan.game.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhan.game.entity.Room;
import com.zhan.game.mapper.RoomMapper;
import com.zhan.game.service.RoomService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;


@Slf4j
@Service
@RequiredArgsConstructor
public class RoomServiceImpl extends ServiceImpl<RoomMapper, Room> implements RoomService {


    @Override
    public void updateRoomStatus(Long roomId, int status) {
        super.update(new LambdaUpdateWrapper<Room>()
                .set(Room::getStatus, status)
                .eq(Room::getId, roomId)
        );
    }

    @Override
    public void updateRoomStage(Long roomId, String name) {
        super.update(new LambdaUpdateWrapper<Room>()
                .set(Room::getGameStage, name)
                .eq(Room::getId, roomId)
        );
    }
}
package com.zhan.game.service.impl;

import com.zhan.game.service.RedisService;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * Redis通用工具类(统一处理对象/List/Hash存储)
 */
@Service
public class RedisServiceImpl implements RedisService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    // ========== 基础对象操作 ==========
    @Override
    public <T> void setObject(String key, T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    @Override
    public <T> void setObject(String key, T value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getObject(String key, Class<T> clazz) {
        Object value = redisTemplate.opsForValue().get(key);
        return value != null ? (T) value : null;
    }

    // ========== List操作 ==========
    @Override
    public <T> void listRightPush(String key, T value) {
        redisTemplate.opsForList().rightPush(key, value);
    }

    @Override
    public <T> void listRightPushAll(String key, List<T> values) {
        if (values != null && !values.isEmpty()) {
            redisTemplate.opsForList().rightPushAll(key, values.toArray());
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> List<T> listRange(String key, long start, long end) {
        return (List<T>) redisTemplate.opsForList().range(key, start, end);
    }

    // ========== Hash操作 ==========
    @Override
    public <T> void hashPut(String key, String hashKey, T value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    @Override
    public <T> void hashPutAll(String key, Map<String, T> map) {
        if (map != null && !map.isEmpty()) {
            redisTemplate.opsForHash().putAll(key, map);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T hashGet(String key, String hashKey, Class<T> clazz) {
        Object value = redisTemplate.opsForHash().get(key, hashKey);
        return value != null ? (T) value : null;
    }

    // ========== 通用操作 ==========
    @Override
    public boolean hasKey(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    @Override
    public void deleteKey(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 获取Hash的所有键值对(解决entries读取的序列化问题)
     *
     * @param key Redis的Hash Key
     * @return 键值对Map(自动处理序列化/反序列化)
     */
    @Override
    public Map<Object, Object> hashGetAllEntries(String key) {
        if (!hasKey(key)) {
            // 若key不存在,返回空Map而非null,避免业务代码空指针
            return new HashMap<>();
        }
        return redisTemplate.opsForHash().entries(key);
    }

    @Override
    public Boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
    }

    @Override
    public Boolean deleteIfEquals(String key, Object value) {
        return Objects.equals(redisTemplate.opsForValue().getAndDelete(key), value);
    }

}
package com.zhan.game.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhan.game.config.ChatClientFactory;
import com.zhan.game.constants.GameConstants;
import com.zhan.game.entity.AiPlayer;
import com.zhan.game.enums.RoleEnum;
import com.zhan.game.enums.WitchStatusEnum;
import com.zhan.game.mapper.AiPlayerMapper;
import com.zhan.game.service.AiPlayerService;
import com.zhan.game.service.RedisService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class AiPlayerServiceImpl extends ServiceImpl<AiPlayerMapper, AiPlayer> implements AiPlayerService {

    @Resource
    private ChatClientFactory chatClientFactory;

    @Resource
    private RedisService redisService;

    /**
     * 调用AI模型生成玩家发言/行动
     *
     * @param player AI玩家
     * @param prompt 提示词(根据游戏阶段/角色生成)
     * @return AI回复内容
     */
    @Override
    public String aiAction(AiPlayer player, String prompt) {
        log.info("{}号玩家({}), 提示词:{}", player.getPlayerNo(), player.getRole(), prompt);
        // 调用对应角色的AI模型
        ChatClient chatClient = chatClientFactory.buildChatClient(player.getAiModelCode());
        String reply = chatClient
                .prompt(prompt)
                .call()
                .content();
        log.info("{}号玩家({}), 回复内容:{}", player.getPlayerNo(), player.getRole(), reply);
        // 更新玩家最后发言
        player.setLastSpeak(reply);
        this.updateById(player);
        return reply;
    }


    @Override
    public List<AiPlayer> findAliveAll(Long roomId) {
        return super.list(new LambdaQueryWrapper<AiPlayer>()
                .eq(AiPlayer::getRoomId, roomId)
                .eq(AiPlayer::getStatus, GameConstants.ALIVE)
        );
    }


    // 查找存活角色列表
    @Override
    public List<AiPlayer> findAliveListByRole(Long roomId, String role) {
        return super.list(new LambdaQueryWrapper<AiPlayer>()
                .eq(AiPlayer::getRoomId, roomId)
                .eq(AiPlayer::getRole, role)
        );
    }

    // 根据玩家编号查找
    @Override
    public AiPlayer findByPlayerNo(List<AiPlayer> players, Integer no) {
        return players.stream()
                .filter(p -> p.getPlayerNo().equals(no))
                .findFirst()
                .orElse(null);
    }

    // 统计存活神民
    @Override
    public long countAliveGod(List<AiPlayer> players) {
        return players.stream()
                .filter(p -> List.of("SEER", "WITCH", "HUNTER", "GUARD").contains(p.getRole()))
                .filter(p -> GameConstants.ALIVE.equals(p.getStatus()))
                .count();
    }

    // 统计存活平民
    @Override
    public long countAliveCivilian(List<AiPlayer> players) {
        return players.stream()
                .filter(p -> p.getRole().equals("CIVILIAN"))
                .filter(p -> GameConstants.ALIVE.equals(p.getStatus()))
                .count();
    }

    @Override
    public List<AiPlayer> getRoomPlayers(Long roomId) {
        return super.list(new LambdaQueryWrapper<AiPlayer>()
                .eq(AiPlayer::getRoomId, roomId)
        );
    }

    /**
     * 初始化女巫药水状态(房间创建时调用)
     *
     * @param roomId 房间ID
     */
    @Override
    public void initWitchStatus(Long roomId) {
        // Redis键格式:room:witch:状态:房间ID
        redisService.setObject("room:witch:antidote:" + roomId, WitchStatusEnum.ANTIDOTE_AVAILABLE.getStatus(), 1, TimeUnit.HOURS);
        redisService.setObject("room:witch:poison:" + roomId, WitchStatusEnum.POISON_AVAILABLE.getStatus(), 1, TimeUnit.HOURS);
    }

    /**
     * 获取女巫解药状态
     *
     * @param roomId 房间ID
     * @return true-可用 false-已用
     */
    @Override
    public boolean getWitchAntidote(Long roomId) {
        Integer status = redisService.getObject("room:witch:antidote:" + roomId, Integer.class);
        return status != null && status == WitchStatusEnum.ANTIDOTE_AVAILABLE.getStatus();
    }

    /**
     * 获取女巫毒药状态
     *
     * @param roomId 房间ID
     * @return true-可用 false-已用
     */
    @Override
    public boolean getWitchPoison(Long roomId) {
        Integer status = redisService.getObject("room:witch:poison:" + roomId, Integer.class);
        return status != null && status == WitchStatusEnum.POISON_AVAILABLE.getStatus();
    }

    /**
     * 使用解药(更新状态)
     *
     * @param roomId 房间ID
     */
    @Override
    public void useAntidote(Long roomId) {
        redisService.setObject("room:witch:antidote:" + roomId, WitchStatusEnum.ANTIDOTE_USED.getStatus(), 1, TimeUnit.HOURS);
    }

    /**
     * 使用毒药(更新状态)
     *
     * @param roomId 房间ID
     */
    @Override
    public void usePoison(Long roomId) {
        redisService.setObject("room:witch:poison:" + roomId, WitchStatusEnum.POISON_USED.getStatus(), 1, TimeUnit.HOURS);
    }

    /**
     * 校验女巫目标合法性(不能毒自己、不能毒已死玩家)
     *
     * @param players      所有玩家
     * @param poisonTarget 毒药目标编号
     * @param witch        女巫本人
     * @return true-合法 false-不合法
     */
    @Override
    public boolean validateWitchTarget(List<AiPlayer> players, String poisonTarget, AiPlayer witch) {
        // 1. 校验参数有效性
        if (players == null || poisonTarget == null || witch == null) {
            return false;
        }

        // 2. 校验是否为数字且在合理范围内
        if (!poisonTarget.matches("\\d+")) {
            return false;
        }

        int targetNo;
        try {
            targetNo = Integer.parseInt(poisonTarget);
        } catch (NumberFormatException e) {
            return false;
        }

        // 3. 校验目标编号是否在合理范围内
        if (targetNo <= 0) { // 玩家编号通常从1开始
            return false;
        }

        // 4. 校验女巫编号是否有效
        Integer witchNo = witch.getPlayerNo();
        if (witchNo == null) {
            return false;
        }

        // 5. 不能毒自己
        if (targetNo == witchNo) {
            return false;
        }

        // 6. 不能毒已死亡玩家
        AiPlayer targetPlayer = findByPlayerNo(players, targetNo);
        return targetPlayer != null && GameConstants.ALIVE.equals(targetPlayer.getStatus());
    }


    /**
     * 核心方法:查找上一夜死亡的玩家
     *
     * @param roomId 房间ID
     * @return 上一夜死亡的AI玩家列表(空列表表示平安夜)
     */
    @Override
    public List<AiPlayer> findDeadLastNight(Long roomId) {
        // 验证roomId参数
        if (roomId == null || roomId <= 0) {
            return new ArrayList<>();
        }
        try {
            // 1. 从Redis获取上一夜死亡的玩家编号
            String json = redisService.getObject("room:night:dead:" + roomId, String.class);
            if (StringUtils.isBlank(json)) {
                return new ArrayList<>();
            }
            // 将死玩家编号转换为Set以提高查找效率
            List<Integer> deadNos = JSON.parseArray(json, Integer.class);
            Set<Integer> deadNoSet = new HashSet<>(deadNos);

            // 2. 获取房间所有玩家
            List<AiPlayer> allPlayers = getRoomPlayers(roomId);
            if (allPlayers == null || allPlayers.isEmpty()) {
                return new ArrayList<>();
            }

            // 3. 筛选出死亡玩家(编号匹配 + 状态为DEAD)
            return allPlayers.stream()
                    .filter(player -> player != null
                            && player.getPlayerNo() != null
                            && deadNoSet.contains(player.getPlayerNo())
                            && GameConstants.DEAD.equals(player.getStatus()))
                    .collect(Collectors.toList());
        } catch (Exception e) {
            // 记录异常日志,返回空列表作为安全默认值
            log.error("查询房间 {} 上一夜死亡玩家时发生异常", roomId, e);
            return new ArrayList<>();
        }
    }

}
package com.zhan.game.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zhan.game.common.ResultCode;
import com.zhan.game.constants.GameConstants;
import com.zhan.game.entity.AiPlayer;
import com.zhan.game.entity.Room;
import com.zhan.game.enums.GameResult;
import com.zhan.game.enums.GameStageEnum;
import com.zhan.game.enums.ModelEnum;
import com.zhan.game.enums.RoleEnum;
import com.zhan.game.exception.BizException;
import com.zhan.game.service.AiPlayerService;
import com.zhan.game.service.GameService;
import com.zhan.game.service.RedisService;
import com.zhan.game.service.RoomService;
import com.zhan.game.vo.GameStageAdvancePlayerVO;
import com.zhan.game.vo.GameStageAdvanceVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.zhan.game.enums.RoleEnum.WEREWOLF;
import static java.util.Arrays.stream;

@Slf4j
@Service
public class GameServiceImpl implements GameService {

    @Resource
    private RoomService roomService;

    @Resource
    private AiPlayerService aiPlayerService;

    @Resource
    private RedisService redisService;

    /**
     * 创建房间并生成12个AI玩家
     *
     * @param roomName 房间名称
     * @return 房间信息
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Room createRoom(String roomName) {
        String lockKey = "room:create:lock:" + roomName;
        String lockValue = UUID.randomUUID().toString();

        try {
            // 使用分布式锁防止并发创建同名房间
            Boolean lockAcquired = redisService.setIfAbsent(lockKey, lockValue, 30, TimeUnit.SECONDS);
            if (!lockAcquired) {
                throw new BizException(ResultCode.ROOM_CREATION_BUSY);
            }

            // 检查房间是否已存在
            Room existingRoom = roomService.getOne(new LambdaQueryWrapper<Room>()
                    .eq(Room::getRoomName, roomName)
                    .last("limit 1"));
            if (Objects.nonNull(existingRoom)) {
                throw new BizException(ResultCode.ROOM_EXIST);
            }

            // 1. 创建房间
            Room room = new Room();
            room.setRoomName(roomName);
            room.setPlayerCount(GameConstants.DEFAULT_PLAYER_COUNT);
            room.setGameStage(GameStageEnum.NIGHT.getName());
            room.setStatus(GameConstants.ROOM_STATUS_ACTIVE); // 1-进行中
            room.setCreateTime(LocalDateTime.now());
            room.setUpdateTime(LocalDateTime.now());
            roomService.save(room);

            // 生成AI玩家并分配身份
            // 2. 生成角色列表(随机打乱)
            List<RoleEnum> roles = RoleEnum.getFullRoles();

            // 3. 生成AI玩家,分配编号,绑定角色和AI模型
            List<AiPlayer> aiPlayers = new ArrayList<>();
            for (int i = 0; i < roles.size(); i++) {
                RoleEnum roleEnum = roles.get(i);
                AiPlayer player = new AiPlayer();
                player.setRoomId(room.getId());
                player.setPlayerNo(i + GameConstants.PLAYER_NO_START_INDEX); // 玩家编号1-12
                player.setRole(roleEnum.getName());
                player.setAiModelCode(ModelEnum.getModeCodeByName(roleEnum.getName()).getCode());
                player.setStatus(GameConstants.ALIVE); // 初始存活
                aiPlayers.add(player);
            }

            // 4. 保存AI玩家到数据库
            aiPlayerService.saveBatch(aiPlayers);

            // 5. 缓存房间和玩家信息到Redis(方便快速查询)
            try {
                redisService.setObject("room:" + room.getId(), room, 1, TimeUnit.HOURS);
                redisService.listRightPushAll("room:players:" + room.getId(), aiPlayers);
            } catch (Exception e) {
                log.warn("Redis缓存设置失败,不影响主要业务流程", e);
            }

            // 6. 初始化女巫药水状态
            aiPlayerService.initWitchStatus(room.getId());

            // 初始化全局游戏信息
            initGameGlobalInfo(room.getId());
            return room;
        } finally {
            // 释放分布式锁
            try {
                redisService.deleteIfEquals(lockKey, lockValue);
            } catch (Exception e) {
                log.warn("释放分布式锁失败", e);
            }
        }
    }

    /**
     * 清空夜间死亡记录(白天阶段结束后调用,避免跨夜混淆)
     *
     * @param roomId 房间ID
     */
    @Override
    public void clearNightDeadRecord(Long roomId) {
        redisService.deleteKey("room:night:dead:" + roomId);
    }

    /**
     * 记录夜间死亡玩家(狼人刀杀/女巫毒杀)
     *
     * @param roomId          房间ID
     * @param killedByWolf    狼人刀杀目标(null表示被救/无刀人)
     * @param poisonedByWitch 女巫毒杀目标(null表示未毒人)
     * @param guardTarget     守卫守护目标(null表示未守护)
     */
    @Override
    public void recordNightDeadPlayers(Long roomId, String killedByWolf, String poisonedByWitch, String guardTarget) {
        List<Integer> deadNos = new ArrayList<>();

        // 1. 处理狼人刀杀:未被守卫守护 + 未被解药拯救
        if (StringUtils.isNotBlank(killedByWolf) && !Objects.equals(killedByWolf, guardTarget)) {
            deadNos.add(Integer.parseInt(killedByWolf));
        }

        // 2. 处理女巫毒杀:有效目标
        if (StringUtils.isNotBlank(poisonedByWitch) && !Objects.equals("0", poisonedByWitch)) {
            deadNos.add(Integer.parseInt(poisonedByWitch));
        }

        // 3. 存入Redis:键格式 room:night:dead:房间ID,值为死亡玩家编号列表
        redisService.setObject("room:night:dead:" + roomId, JSON.toJSONString(deadNos), 1, TimeUnit.HOURS);
        log.info("房间{}:夜间死亡玩家编号列表 -> {}", roomId, deadNos);
    }

    /**
     * 执行完整夜晚流程:守卫 -> 狼人 -> 预言家 -> 女巫
     */
    @Override
    public void nightStage(Long roomId) {
        log.info("房间{}:开始执行完整夜晚流程", roomId);
        List<AiPlayer> players = aiPlayerService.getRoomPlayers(roomId);

        // ====================== 1. 守卫守护 ======================
        AiPlayer guard = players.stream()
                .filter(p -> p.getRole().equals(RoleEnum.GUARD.getName()) && GameConstants.ALIVE.equals(p.getStatus()))
                .findFirst()
                .orElse(null);
        String guardTarget = null;
        if (Objects.nonNull(guard)) {
            String prompt = """
                    你是狼人杀守卫,根据历史游戏信息和自己的判断,今晚请选择一名玩家守护(不能连续两晚守同一个人)。
                    只回复数字,不要多余内容。
                    """;
            guardTarget = aiPlayerService.aiAction(guard, prompt);
            redisService.setObject("room:guard:last:" + roomId, guardTarget, 1, TimeUnit.HOURS);
            // 记录守卫行动
            recordNightAction(roomId, "guardProtect", guardTarget);
        }

        // ====================== 2. 狼人刀人 ======================
        List<AiPlayer> werewolves = aiPlayerService.findAliveListByRole(roomId, WEREWOLF.getName());
        String killTarget = null;
        if (CollectionUtils.isNotEmpty(werewolves)) {
            killTarget = collectVoteFromAi(werewolves, roomId);
            // 记录狼人刀人行动
            recordNightAction(roomId, "wolfKill", killTarget);
        }

        // ====================== 3. 预言家查验 ======================
        AiPlayer seer = players.stream()
                .filter(p -> p.getRole().equals(RoleEnum.SEER.getName()) && GameConstants.ALIVE.equals(p.getStatus()))
                .findFirst()
                .orElse(null);
        String checkResult = null;
        if (Objects.nonNull(seer)) {
            String prompt = String.format("""
                    你是%s号预言家,晚上选择一名玩家查验身份。
                    规则:
                    1.只能回复1-12之间的数字(排除自己%s),不要任何多余内容(如文字、符号、空格)。
                    2.根据你的判断,优先查验你认为可能是狼人的其他玩家。
                    根据历史游戏信息和自己的判断,请直接回复目标玩家编号:
                    """, seer.getPlayerNo(), seer.getPlayerNo());
            String checkTarget = aiPlayerService.aiAction(seer, prompt);
            AiPlayer target = aiPlayerService.findByPlayerNo(players, Integer.parseInt(checkTarget));
            checkResult = target.getRole().equals(RoleEnum.WEREWOLF.getName()) ? "狼人" : "好人";
            redisService.setObject("room:seer:result:" + roomId + ":" + seer.getPlayerNo(), checkResult, 1, TimeUnit.HOURS);
            // 记录预言家查验行动
            recordNightAction(roomId, "seerCheck", checkTarget);
        }

        // ====================== 4. 女巫用药 ======================
        AiPlayer witch = players.stream()
                .filter(p -> p.getRole().equals(RoleEnum.WITCH.getName()) && GameConstants.ALIVE.equals(p.getStatus()))
                .findFirst()
                .orElse(null);
        aiPlayerService.initWitchStatus(roomId);
        boolean hasPoison = aiPlayerService.getWitchPoison(roomId);
        boolean hasAntidote = aiPlayerService.getWitchAntidote(roomId);
        boolean usedAntidote = false;
        String poisonTarget = null;
        String witchUse = "未用药";


        if (Objects.nonNull(witch)) {
            // 解药逻辑
            if (hasAntidote && StringUtils.isNotBlank(killTarget)) {
                String savePrompt = String.format("""
                        你是%d号女巫,当前解药可用、毒药%s。
                        今夜%d号玩家被狼人刀杀,你是否使用解药拯救他?
                        注意:解药整局只能用1次,且不能和毒药同晚使用。
                        根据历史游戏信息和自己的判断,仅回复:是 或 否,不要多余内容。
                        """, witch.getPlayerNo(), hasPoison ? "可用" : "已用", Integer.parseInt(killTarget));

                String saveAns = aiPlayerService.aiAction(witch, savePrompt).trim();
                if (Objects.equals(saveAns, "是")) {
                    aiPlayerService.useAntidote(roomId);
                    log.info("女巫{}号使用解药拯救了{}号玩家", witch.getPlayerNo(), killTarget);
                    witchUse = "使用解药拯救" + killTarget + "号玩家";
                    usedAntidote = true;
                    killTarget = null;

                }
            }

            // 毒药逻辑
            if (hasPoison && !usedAntidote) {
                List<AiPlayer> aliveAll = aiPlayerService.findAliveAll(roomId);
                List<String> aliveNos = aliveAll.stream()
                        .map(p -> String.valueOf(p.getPlayerNo()))
                        .toList();

                String poisonPrompt = String.format("""
                        你是%d号女巫,当前毒药可用、解药%s。
                        你可以选择毒杀一名玩家(整局仅1次机会),也可以选择不毒。
                        存活玩家编号:%s
                        根据历史游戏信息和自己的判断,给出你的回复。
                        规则:不能毒自己、不能毒已死玩家、不能和解药同晚使用。
                        不毒回复:0;毒杀回复对应玩家编号,仅回复数字,不要多余内容。
                        """, witch.getPlayerNo(), hasAntidote ? "可用" : "已用", String.join("、", aliveNos));

                poisonTarget = aiPlayerService.aiAction(witch, poisonPrompt).trim();
                if (!Objects.equals("0", poisonTarget) && aiPlayerService.validateWitchTarget(players, poisonTarget, witch)) {
                    aiPlayerService.usePoison(roomId);
                    AiPlayer poisoned = aiPlayerService.findByPlayerNo(players, Integer.parseInt(poisonTarget));
                    poisoned.setStatus(GameConstants.DEAD);
                    aiPlayerService.updateById(poisoned);
                    witchUse = "使用毒药毒杀" + poisonTarget + "号玩家";
                    log.info("女巫{}号使用毒药毒杀了{}号玩家", witch.getPlayerNo(), poisonTarget);
                }
            }
            // 记录女巫用药行动
            recordNightAction(roomId, "witchUse", witchUse);
        }

        // ====================== 最终死亡判定 ======================
        List<String> deadNos = new ArrayList<>();
        if (Objects.nonNull(killTarget) && !Objects.equals(killTarget, guardTarget)) {
            AiPlayer killed = aiPlayerService.findByPlayerNo(players, Integer.valueOf(killTarget));
            killed.setStatus(GameConstants.DEAD);
            aiPlayerService.updateById(killed);
            deadNos.add(killTarget);
        }
        if (Objects.nonNull(poisonTarget) && !Objects.equals("0", poisonTarget) && aiPlayerService.validateWitchTarget(players, poisonTarget, witch)) {
            deadNos.add(poisonTarget);
        }
        // 记录夜间死亡玩家
        // redisService.listRightPushAll("room:global:dead:" + roomId,deadNos);

        // 记录夜间死亡玩家(狼人刀杀/女巫毒杀)
        recordNightDeadPlayers(roomId, killTarget, poisonTarget, guardTarget);

        // 阶段切换
        roomService.updateRoomStage(roomId, GameStageEnum.DAY.getName());
    }

    /**
     * 白天完整流程:
     * 1. 公布昨夜死讯
     * 2. 按顺序发言
     * 3. 公投
     * 4. 判胜负
     */
    @Override
    public void dayStage(Long roomId) {
        List<AiPlayer> players = aiPlayerService.getRoomPlayers(roomId);
        List<AiPlayer> deadLastNight = aiPlayerService.findDeadLastNight(roomId);

        // ====================== 1. 公布死讯 ======================
        if (CollectionUtils.isNotEmpty(deadLastNight)) {
            String deadNos = deadLastNight.stream().map(p -> p.getPlayerNo().toString()).reduce((a, b) -> a + "、" + b).orElse("");
            log.info("昨夜死亡玩家:{}", deadNos);
        } else {
            log.info("昨夜平安夜");
        }

        // ====================== 2. 存活玩家依次发言 ======================
        List<AiPlayer> alive = aiPlayerService.findAliveAll(roomId);
        for (AiPlayer p : alive) {
            // 拼接完整提示词:全局信息 + 历史发言 + 角色专属信息 + 发言要求
            String nightActionInfo = getNightActionInfo(roomId, p);
            String historySpeak = getHistorySpeakText(roomId);
            String roleName = p.getRole();

            String prompt = String.format("""
                    【狼人杀游戏信息】
                    你的身份:%d号%s
                    场上局势:
                    %s
                    请根据场上局势、其他人发言和你的身份进行逻辑发言。
                    历史发言:
                    %s
                    发言要求:
                    1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
                    2. 狼人要隐藏身份,好人要找出狼人
                    3. 发言字数50-120字,只回复发言内容,不要多余文字
                    4. 不要说自己是AI,不要解释规则
                    5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
                    6. 语言像真人玩家一样自然、简短、有逻辑
                    7. 可以分析、站队、怀疑、保人、点狼坑
                    """, p.getPlayerNo(), roleName, nightActionInfo, historySpeak);

            // 调用AI发言
            String speakContent = aiPlayerService.aiAction(p, prompt).trim();
            // 记录当前玩家发言(供后续玩家查看)
            recordPlayerSpeak(roomId, p.getPlayerNo(), speakContent);
            // 更新玩家最后发言
            p.setLastSpeak(speakContent);
            aiPlayerService.updateById(p);

            log.info("{}号玩家({})发言:{}", p.getPlayerNo(), roleName, speakContent);
        }

        // ====================== 3. 公投放逐 ======================
        String voteOutTarget = collectVoteFromAi(alive, roomId);
        if (StringUtils.isNotBlank(voteOutTarget)) {
            AiPlayer voted = aiPlayerService.findByPlayerNo(players, Integer.valueOf(voteOutTarget));
            voted.setStatus(GameConstants.DEAD);
            aiPlayerService.updateById(voted);
            log.info("公投结果:{}号玩家被放逐,身份{}", voteOutTarget, voted.getRole());

            // 猎人被放逐触发开枪
            if (Objects.equals(voted.getRole(), RoleEnum.HUNTER.getName())) {
                hunterShoot(roomId, voted);
            }
        }

        // ====================== 4. 屠边判胜负 ======================
        GameResult result = checkWinOrLose(roomId);
        if (result != GameResult.CONTINUE) {
            roomService.updateRoomStatus(roomId, 2);
            roomService.updateRoomStage(roomId, GameStageEnum.END.getName());
            log.info("游戏结束,胜利者:{}", result);
        } else {
            roomService.updateRoomStage(roomId, GameStageEnum.NIGHT.getName());
        }
        clearNightDeadRecord(roomId);
        clearSpeakRecord(roomId);
    }

    /**
     * 检查胜负结果
     *
     * @param roomId 房间ID
     * @return 胜负结果
     */
    @Override
    public GameResult checkWinOrLose(Long roomId) {
        List<AiPlayer> players = aiPlayerService.getRoomPlayers(roomId);
        // 存活数量统计
        long wolfAlive = aiPlayerService.findAliveListByRole(roomId, RoleEnum.WEREWOLF.getName()).size();
        long godAlive = players.stream()
                .filter(p -> List.of(RoleEnum.SEER.getName(), RoleEnum.WITCH.getName(), RoleEnum.HUNTER.getName(), RoleEnum.GUARD.getName()).contains(p.getRole()))
                .filter(p -> GameConstants.ALIVE.equals(p.getStatus()))
                .count();
        long civilianAlive = aiPlayerService.findAliveListByRole(roomId, RoleEnum.CIVILIAN.getName()).size();

        // 狼人胜利条件:屠神(神全死)或 屠民(民全死),且至少1狼存活
        if (wolfAlive > 0 && (godAlive == 0 || civilianAlive == 0)) {
            log.info("狼人胜利:屠神(神全死)或 屠民(民全死),且至少1狼存活");
            return GameResult.WOLF_WIN;
        }
        // 好人胜利条件:狼全死
        if (wolfAlive == 0) {
            log.info("好人胜利:狼全死");
            return GameResult.GOOD_WIN;
        }
        // 游戏继续
        return GameResult.CONTINUE;
    }

    /**
     * 推进游戏阶段(核心接口:触发夜晚/白天流程)
     * 调用逻辑:创建房间后,调用此接口推进游戏,自动完成当前阶段的所有AI行动
     *
     * @param roomId 房间ID
     * @return 阶段推进结果(包含当前阶段、AI行动概要)
     */
    @Override
    public GameStageAdvanceVO advanceGameStage(Long roomId) {
        log.info("开始推进游戏阶段");
        GameStageAdvanceVO result = new GameStageAdvanceVO();
        try {
            // 1. 获取当前房间信息
            Room room = roomService.getById(roomId);
            if (Objects.isNull(room)) {
                throw new BizException(ResultCode.ROOM_NOT_FOUND);
            }
            if (Objects.equals(GameStageEnum.END.getName(), room.getGameStage())) {
                throw new BizException(ResultCode.GAME_OVER);
            }

            // 2. 根据当前阶段推进流程
            String currentStage = room.getGameStage();
            if (Objects.equals(GameStageEnum.NIGHT.getName(), currentStage)) {
                // 执行夜晚流程:守卫→狼人→预言家→女巫→死亡判定
                nightStage(roomId);

                result.setStage(GameStageEnum.DAY.getName());
                result.setStageMsg("夜晚阶段已完成,进入白天阶段");
                // 返回夜间死亡玩家
                List<AiPlayer> deadLastNight = aiPlayerService.findDeadLastNight(roomId);
                result.setDeadLastNight(deadLastNight.stream()
                        .map(p -> new GameStageAdvancePlayerVO(p.getPlayerNo(), p.getRole()))
                        .toList());
            } else if (Objects.equals(GameStageEnum.DAY.getName(), currentStage)) {
                // 执行白天流程:公布死讯→发言→公投→判胜负
                dayStage(roomId);
                Room updatedRoom = roomService.getById(roomId);
                result.setStage(updatedRoom.getGameStage());
                if (Objects.equals(GameStageEnum.END.getName(), updatedRoom.getGameStage())) {
                    GameResult gameResult = checkWinOrLose(roomId);
                    result.setStageMsg("游戏结束");
                    result.setGameResult(gameResult);
                } else {
                    result.setStageMsg("白天阶段已完成,进入夜晚阶段");
                }
                // 返回白天公投死亡玩家
                List<AiPlayer> allPlayers = aiPlayerService.getRoomPlayers(roomId);
                List<AiPlayer> deadLastNight = aiPlayerService.findDeadLastNight(roomId);
                List<AiPlayer> votedDead = allPlayers.stream()
                        .filter(player -> GameConstants.DEAD.equals(player.getStatus()) && !deadLastNight.contains(player))
                        .toList();
                result.setVotedDead(votedDead.stream()
                        .map(p -> new GameStageAdvancePlayerVO(p.getPlayerNo(), p.getRole()))
                        .toList());
            }
            result.setRoomId(roomId);
            return result;
        } catch (Exception e) {
            log.error("推进游戏阶段失败", e);
            throw new BizException(ResultCode.ADVANCE_GAME_STAGE_ERROR);
        }
    }

    /**
     * 获取AI玩家的发言/行动记录(核心:查看AI对话内容)
     *
     * @param roomId   房间ID
     * @param playerNo 玩家编号(可选,不传则返回所有AI的发言)
     * @return 发言记录
     */
    @Override
    public List<AiPlayer> getAiSpeakRecord(Long roomId, Integer playerNo) {
        try {
            List<AiPlayer> players = aiPlayerService.getRoomPlayers(roomId);
            if (CollectionUtils.isEmpty(players)) {
                throw new BizException(ResultCode.ROOM_NO_PLAYER);
            }

            // 1. 查询指定玩家的发言
            if (Objects.nonNull(playerNo)) {
                AiPlayer targetPlayer = players.stream()
                        .filter(p -> p.getPlayerNo().equals(playerNo))
                        .findFirst()
                        .orElse(null);
                if (Objects.isNull(targetPlayer)) {
                    throw new BizException(ResultCode.PLAYER_NOT_FOUND);
                }
                return List.of(targetPlayer);
            } else {
                // 2. 查询所有玩家的发言
                return players;
            }
        } catch (Exception e) {
            log.error("获取AI发言记录失败", e);
            throw new BizException(ResultCode.GET_AI_SPEAK_RECORD_ERROR);
        }
    }

    /**
     * 获取房间当前游戏状态(全局视角)
     *
     * @param roomId 房间ID
     * @return 游戏状态(阶段、存活/死亡玩家、胜负结果等)
     */
    @Override
    public Map<String, Object> getGameStatus(Long roomId) {
        Map<String, Object> result = new HashMap<>();
        try {
            Room room = roomService.getById(roomId);
            if (Objects.isNull(room)) {
                throw new BizException(ResultCode.ROOM_NOT_FOUND);
            }

            List<AiPlayer> allPlayers = aiPlayerService.getRoomPlayers(roomId);
            // 存活/死亡玩家分类
            List<AiPlayer> alivePlayers = allPlayers.stream().filter(p -> GameConstants.ALIVE.equals(p.getStatus())).toList();
            List<AiPlayer> deadPlayers = allPlayers.stream().filter(p -> GameConstants.DEAD.equals(p.getStatus())).toList();

            // 组装返回结果
            result.put("code", 200);
            result.put("roomInfo", Map.of(
                    "roomId", room.getId(),
                    "roomName", room.getRoomName(),
                    "gameStage", room.getGameStage(),
                    "gameStageName", GameStageEnum.valueOf(room.getGameStage()).getName(),
                    "status", room.getStatus()));
            result.put("alivePlayers", alivePlayers.stream()
                    .map(p -> Map.of(
                            "playerNo", p.getPlayerNo(),
                            "role", p.getRole()))
                    .toList());
            result.put("deadPlayers", deadPlayers.stream()
                    .map(p -> Map.of(
                            "playerNo", p.getPlayerNo(),
                            "role", p.getRole()))
                    .toList());
            // 游戏结束则返回胜负结果
            if (Objects.equals(GameStageEnum.END.getName(), room.getGameStage())) {
                result.put("gameResult", checkWinOrLose(roomId));
            }

            return result;
        } catch (Exception e) {
            log.error("获取游戏状态失败", e);
            throw new BizException(ResultCode.GET_GAME_STATUS_ERROR);
        }
    }

    /**
     * 手动触发单个AI玩家发言(测试用,可选)
     *
     * @param roomId   房间ID
     * @param playerNo 玩家编号
     * @param prompt   自定义发言提示词(可选,不传则用默认提示词)
     * @return 玩家发言内容
     */
    @Override
    public Map<String, Object> triggerAiSpeak(Long roomId, Integer playerNo, String prompt) {
        Map<String, Object> result = new HashMap<>();
        try {
            List<AiPlayer> players = aiPlayerService.getRoomPlayers(roomId);
            AiPlayer targetPlayer = players.stream()
                    .filter(p -> p.getPlayerNo().equals(playerNo) && GameConstants.ALIVE.equals(p.getStatus()))
                    .findFirst()
                    .orElse(null);
            if (Objects.isNull(targetPlayer)) {
                throw new BizException(ResultCode.PLAYER_DEAD_OR_NOT_FOUND);
            }

            // 使用自定义提示词或默认提示词
            String format = String.format("你是%d号%s,现在是游戏发言阶段,请根据局势发表你的观点,50-100字。", targetPlayer.getPlayerNo(), RoleEnum.valueOf(targetPlayer.getRole()).getName());
            String finalPrompt = StringUtils.isNotBlank(prompt) ? prompt : format;
            // 调用AI生成发言
            String speakContent = aiPlayerService.aiAction(targetPlayer, finalPrompt);

            result.put("code", 200);
            result.put("playerNo", playerNo);
            result.put("role", targetPlayer.getRole());
            result.put("speakContent", speakContent);
            return result;
        } catch (Exception e) {
            log.error("触发AI发言失败", e);
            throw new BizException(ResultCode.TRIGGER_AI_SPEAK_ERROR);
        }
    }

    /**
     * 猎人被放逐/刀杀触发开枪
     */
    private void hunterShoot(Long roomId, AiPlayer hunter) {
        List<AiPlayer> alivePlayers = aiPlayerService.findAliveAll(roomId);
        String prompt = String.format("""
                你是%d号猎人,被放逐/刀杀,可开枪带走一名玩家。
                存活玩家:%s
                请选择带走的玩家编号,只回复数字。
                """, hunter.getPlayerNo(), alivePlayers.stream().map(p -> p.getPlayerNo().toString()).collect(Collectors.joining("、")));

        String shootTarget = aiPlayerService.aiAction(hunter, prompt).trim();
        if (shootTarget.matches("\\d+")) {
            AiPlayer target = aiPlayerService.findByPlayerNo(alivePlayers, Integer.valueOf(shootTarget));
            if (Objects.nonNull(target)) {
                target.setStatus(GameConstants.DEAD);
                aiPlayerService.updateById(target);
                log.info("猎人{}号开枪带走了{}号玩家", hunter.getPlayerNo(), shootTarget);
            }
        }
    }

    /**
     * 初始化游戏全局信息(房间创建时调用)
     * 修复:移除空集合的rightPushAll,避免非空校验报错
     */
    public void initGameGlobalInfo(Long roomId) {
        // 1. 夜间行动记录:初始化空Hash(用String类型占位)
        redisService.hashPut("room:global:night:action:" + roomId, "init", "ok");

        // 2. 狼人队友列表:仅当有狼人时才存储(确保存入String类型)
        List<AiPlayer> wolves = aiPlayerService.findAliveListByRole(roomId, RoleEnum.WEREWOLF.getName());
        if (CollectionUtils.isNotEmpty(wolves)) {
            List<String> wolfNos = wolves.stream().map(p -> String.valueOf(p.getPlayerNo())) // 明确转为String
                    .filter(str -> !str.isEmpty()) // 过滤空值
                    .toList();
            if (CollectionUtils.isNotEmpty(wolfNos)) { // 二次校验,避免空列表
                redisService.listRightPushAll("room:global:wolf:teammate:" + roomId, wolfNos);
            }
        }
    }

    /**
     * 记录玩家发言(用于后续玩家查看)
     */
    public void recordPlayerSpeak(Long roomId, Integer playerNo, String speakContent) {
        if (playerNo == null || speakContent == null || speakContent.trim().isEmpty()) {
            log.warn("发言记录失败:玩家编号/内容为空,roomId={}, playerNo={}", roomId, playerNo);
            return;
        }
        String speakKey = "room:global:speak:" + roomId;
        String speakRecord = playerNo + ":" + speakContent.trim();
        // 首次调用时,Redis会自动创建空列表并添加元素(无需提前初始化)
        redisService.listRightPush(speakKey, speakRecord);
    }

    /**
     * 获取历史发言记录(拼接成文本)
     */
    public String getHistorySpeakText(Long roomId) {
        String speakKey = "room:global:speak:" + roomId;
        // 先判断key是否存在,避免返回null
        if (!redisService.hasKey(speakKey)) {
            return "暂无玩家发言";
        }
        // 获取全部发言记录(0到-1表示全量)
        List<Object> speakList = redisService.listRange(speakKey, 0, -1);
        if (CollectionUtils.isEmpty(speakList)) {
            return "暂无玩家发言";
        }
        List<String> list = speakList.stream().map(Object::toString).toList();
        StringBuilder sb = new StringBuilder();
        for (String speak : list) {
            String[] split = speak.split(":", 2); // 按第一个":"分割(避免发言含":")
            if (split.length == 2) { // 校验格式,避免数组越界
                sb.append(split[0]).append("号:").append(split[1]).append("\n");
            }
        }
        return sb.toString().trim(); // 去除末尾换行
    }

    /**
     * 记录夜间行动(如狼人刀人、预言家查验、女巫用药、守卫守护)
     */
    public void recordNightAction(Long roomId, String actionType, String value) {
        redisService.hashPut("room:global:night:action:" + roomId, actionType, value);
    }

    /**
     * 获取夜间行动信息
     */
    public String getNightActionInfo(Long roomId, AiPlayer player) {
        Map<Object, Object> actionMap = redisService.hashGetAllEntries("room:global:night:action:" + roomId);
        StringBuilder sb = new StringBuilder();

        // 通用信息:昨夜死亡玩家
        String json = redisService.getObject("room:night:dead:" + roomId, String.class);
        if (StringUtils.isNotBlank(json)) {
            List<String> deadLastNight = JSON.parseArray(json, String.class);
            List<String> list = deadLastNight.stream().map(Object::toString).toList();
            sb.append("昨夜死亡玩家:").append(String.join("、", list)).append("\n");
        } else {
            sb.append("昨夜平安夜\n");
        }

        // 角色专属信息
        String role = player.getRole();
        RoleEnum roleEnum = RoleEnum.getRoleByName(role);
        if (Objects.isNull(roleEnum)) {
            throw new BizException(ResultCode.ROLE_NOT_FOUND);
        }
        switch (roleEnum) {
            // 原狼人专属信息部分修改
            case WEREWOLF:
                // 狼人:知道队友+昨夜刀人目标(补充非空校验)
                String wolfTeammateKey = "room:global:wolf:teammate:" + roomId;
                List<String> wolfTeammates = new ArrayList<>();
                if (Boolean.TRUE.equals(redisService.hasKey(wolfTeammateKey))) {
                    // 关键修复:强制转换为String类型(避免Object解析失败)
                    List<Object> tempList = redisService.listRange(wolfTeammateKey, 0, -1);
                    if (tempList != null && !tempList.isEmpty()) {
                        wolfTeammates = tempList.stream().map(obj -> obj != null ? obj.toString() : "") // 转为字符串
                                .filter(str -> !str.isEmpty()) // 过滤空值
                                .toList();
                    }
                }
                sb.append("你的狼人队友:").append(wolfTeammates.isEmpty() ? "无" : String.join("、", wolfTeammates)).append("\n");
                sb.append("昨夜狼人刀人目标:").append(actionMap.getOrDefault("wolfKill", "无")).append("\n");
                break;
            case SEER:
                // 预言家:知道自己的查验结果
                String checkResult = redisService.getObject("room:seer:result:" + roomId + ":" + player.getPlayerNo(), String.class);
                String checkTarget = (String) actionMap.getOrDefault("seerCheck", "无");
                sb.append("你昨夜查验").append(checkTarget).append("号玩家,身份是:").append(checkResult).append("\n");
                break;
            case WITCH:
                // 女巫:知道药水状态+昨夜被刀玩家+自己用药情况
                boolean hasAntidote = aiPlayerService.getWitchAntidote(roomId);
                boolean hasPoison = aiPlayerService.getWitchPoison(roomId);
                sb.append("你的解药状态:").append(hasAntidote ? "可用" : "已用").append("\n");
                sb.append("你的毒药状态:").append(hasPoison ? "可用" : "已用").append("\n");
                sb.append("昨夜被刀玩家:").append(actionMap.getOrDefault("wolfKill", "无")).append("\n");
                sb.append("你昨夜用药情况:").append(actionMap.getOrDefault("witchUse", "未用药")).append("\n");
                break;
            case GUARD:
                // 守卫:知道自己昨夜守护目标
                sb.append("你昨夜守护的玩家:").append(actionMap.getOrDefault("guardProtect", "无")).append("\n");
                break;
            default:
                // 平民/猎人:只有通用信息
                break;
        }
        return sb.toString();
    }

    /**
     * 清空本轮发言记录(白天阶段结束后调用)
     */
    public void clearSpeakRecord(Long roomId) {
        redisService.deleteKey("room:global:speak:" + roomId);
    }

    /**
     * 生成放逐投票提示词(按角色定制,禁止自投)
     *
     * @param playerNo   玩家编号
     * @param role       玩家身份
     * @param suspectNos 怀疑的狼人编号列表
     * @return 精准提示词
     */
    public String generateVotePrompt(String playerNo, String role, List<String> suspectNos) {
        // 好人(预言家/女巫/守卫/平民)提示词
        if (!WEREWOLF.getName().equals(role)) {
            return String.format("""
                            你是%d号玩家,身份是%s(好人),正在参与放逐投票。
                            规则:
                            1. 绝对不能投票给自己(%d号);
                            2. 优先投票你怀疑的狼人(怀疑列表:%s);
                            3. 只回复1-12之间的数字,不要加任何文字、符号、括号;
                            4. 你的目标是投出狼人,不要投好人。
                            请直接回复你要投票的玩家编号:
                            """, Integer.parseInt(playerNo), role, Integer.parseInt(playerNo),
                    suspectNos.isEmpty() ? "无,优先投发言异常的玩家" : String.join("、", suspectNos));
        }
        // 狼人提示词(也禁止自投,优先投好人)
        else {
            return String.format("""
                    你是%d号玩家,身份是狼人,正在参与放逐投票。
                    规则:
                    1. 绝对不能投票给自己(%d号);
                    2. 优先投票你识别的好人/神牌;
                    3. 只回复1-12之间的数字,不要加任何文字、符号、括号;
                    4. 你的目标是投出好人,保护狼人队友。
                    请直接回复你要投票的玩家编号:
                    """, Integer.parseInt(playerNo), Integer.parseInt(playerNo));
        }
    }

    /**
     * AI投票统计
     *
     * @param list   投票玩家列表
     * @param roomId 房间ID
     * @return 最终投票目标
     */
    @Override
    public String collectVoteFromAi(List<AiPlayer> list, Long roomId) {
        Map<String, Integer> cnt = new HashMap<>();
        List<AiPlayer> alivePlayers = aiPlayerService.findAliveAll(roomId);
        List<String> aliveNos = alivePlayers.stream().map(p -> p.getPlayerNo().toString()).toList();

        for (AiPlayer p : list) {
            if (GameConstants.ALIVE.equals(p.getStatus())) {
                String prompt = "";
                if (Objects.equals(p.getRole(), RoleEnum.WEREWOLF.getName())) {
                    prompt = String.format("""
                            【狼人晚上集体刀人】
                            你是%d号玩家,身份是狼人,正在参与放逐投票。
                            历史发言:
                            %s
                            场上存活玩家:%s
                            规则:
                            1.优先投票你识别的好人/神牌
                            2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
                            3.优先投票你识别的好人/神牌。
                            4.你的目标是投出好人,保护狼人队友。
                            请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:
                            """, p.getPlayerNo(), getHistorySpeakText(roomId), alivePlayers.stream().map(item -> item.getPlayerNo().toString()).collect(Collectors.joining("、")));
                } else {
                    prompt = String.format("""
                            【狼人杀公投】
                            你是%d号玩家,身份是%s(好人),正在参与放逐投票。
                            历史发言:
                            %s
                            场上存活玩家:%s
                            规则:
                            1. 绝对不能投票给自己(%d号);
                            2. 优先投票你怀疑的狼人;
                            3. 只回复1-12之间的数字,不要加任何文字、符号、括号;
                            4. 你的目标是投出狼人,不要投好人。
                            请请根据历史游戏信息和自己的判断,投票放逐你怀疑的狼人,只回复玩家编号(数字),不要多余内容。
                            """, p.getPlayerNo(), p.getRole(), getHistorySpeakText(roomId), alivePlayers.stream().map(item -> item.getPlayerNo().toString()).collect(Collectors.joining("、")), p.getPlayerNo());
                }
                String vote = aiPlayerService.aiAction(p, prompt).trim();
                // 校验投票合法性:必须是数字、存活玩家、狼人不能投自己
                if (vote.matches("\\d+") && aliveNos.contains(vote)) {
                    cnt.put(vote, cnt.getOrDefault(vote, 0) + 1);
                }
            }
        }

        // 取票数最多的目标(无有效投票则随机选一个非狼人存活玩家)
        if (cnt.isEmpty()) {
            List<String> targetNos = alivePlayers.stream()
                    .filter(p -> !p.getRole().equals(RoleEnum.WEREWOLF.name()))
                    .map(p -> p.getPlayerNo().toString())
                    .toList();
            return targetNos.get(new Random().nextInt(targetNos.size()));
        }

        return cnt.entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .map(Map.Entry::getKey)
                .orElse(null);
    }
}

19.返回结果对象VO

package com.zhan.game.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameStageAdvancePlayerVO {

    private Integer playerNo;

    private String role;

}
package com.zhan.game.vo;

import com.zhan.game.enums.GameResult;
import lombok.Data;

import java.util.List;

@Data
public class GameStageAdvanceVO {

    private Long roomId;

    private String stage;

    private String stageMsg;

    private GameResult gameResult;

    private List<GameStageAdvancePlayerVO> votedDead;

    private List<GameStageAdvancePlayerVO> deadLastNight;

}

20.工具类

package com.zhan.game.utils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class GameUtil {

    public static String generateRoomName() {
        // 获取当前系统时间
        LocalDateTime now = LocalDateTime.now();
        // 定义日期时间格式:yyyyMMddhhmmss
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        // 格式化时间并拼接前缀 "AI狼人杀房间"
        return "AI狼人杀房间" + now.format(formatter);
    }
}

21.系统日志(测试)

2026-02-12T18:03:18.366+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.game.service.impl.GameServiceImpl    : 开始推进游戏阶段
2026-02-12T18:03:18.378+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.game.service.impl.GameServiceImpl    : 房间37:开始执行完整夜晚流程
2026-02-12T18:03:18.392+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 11号玩家(守卫), 提示词:你是狼人杀守卫,根据历史游戏信息和自己的判断,今晚请选择一名玩家守护(不能连续两晚守同一个人)。
只回复数字,不要多余内容。

2026-02-12T18:03:19.769+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 11号玩家(守卫), 回复内容:7
2026-02-12T18:03:19.930+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 2号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是2号玩家,身份是狼人,正在参与放逐投票。
历史发言:
暂无玩家发言
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:03:20.696+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 2号玩家(狼人), 回复内容:3
2026-02-12T18:03:20.737+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 6号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是6号玩家,身份是狼人,正在参与放逐投票。
历史发言:
暂无玩家发言
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:03:21.952+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 6号玩家(狼人), 回复内容:2
2026-02-12T18:03:22.012+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 10号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是10号玩家,身份是狼人,正在参与放逐投票。
历史发言:
暂无玩家发言
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:03:22.888+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 10号玩家(狼人), 回复内容:7
2026-02-12T18:03:22.961+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 12号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是12号玩家,身份是狼人,正在参与放逐投票。
历史发言:
暂无玩家发言
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:03:23.617+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 12号玩家(狼人), 回复内容:7
2026-02-12T18:03:23.705+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 3号玩家(预言家), 提示词:你是预言家,晚上选择一名玩家查验身份。
规则:规则:只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
根据历史游戏信息和自己的判断,请直接回复目标玩家编号:

2026-02-12T18:03:24.478+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 3号玩家(预言家), 回复内容:2
2026-02-12T18:03:24.673+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 8号玩家(女巫), 提示词:你是8号女巫,当前解药可用、毒药已用。
今夜7号玩家被狼人刀杀,你是否使用解药拯救他?
注意:解药整局只能用1次,且不能和毒药同晚使用。
根据历史游戏信息和自己的判断,仅回复:是 或 否,不要多余内容。

2026-02-12T18:03:25.499+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 8号玩家(女巫), 回复内容:否
2026-02-12T18:03:25.603+08:00  INFO 24336 --- [http-nio-8080-exec-3] c.z.game.service.impl.GameServiceImpl    : 房间37:夜间死亡玩家编号列表 -> []
2026-02-12T18:12:33.690+08:00  INFO 24336 --- [SpringApplicationShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete
2026-02-12T18:12:34.486+08:00  INFO 24336 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown        : Graceful shutdown complete
2026-02-12T18:12:34.495+08:00  INFO 24336 --- [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2026-02-12T18:12:34.508+08:00  INFO 24336 --- [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2026-02-12T18:12:40.710+08:00  INFO 26968 --- [main] com.zhan.game.GameApp                    : Starting GameApp using Java 17.0.17 with PID 26968 (D:\code99\werewolf-kill\target\classes started by 80008307 in D:\code99\werewolf-kill)
2026-02-12T18:12:40.714+08:00  INFO 26968 --- [main] com.zhan.game.GameApp                    : The following 1 profile is active: "dev"
2026-02-12T18:12:41.987+08:00  INFO 26968 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode
2026-02-12T18:12:41.991+08:00  INFO 26968 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2026-02-12T18:12:42.023+08:00  INFO 26968 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 15 ms. Found 0 Redis repository interfaces.
2026-02-12T18:12:42.822+08:00  INFO 26968 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2026-02-12T18:12:42.836+08:00  INFO 26968 --- [main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2026-02-12T18:12:42.837+08:00  INFO 26968 --- [main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.40]
2026-02-12T18:12:42.927+08:00  INFO 26968 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2026-02-12T18:12:42.928+08:00  INFO 26968 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2148 ms
2026-02-12T18:12:46.598+08:00  INFO 26968 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2026-02-12T18:12:46.615+08:00  INFO 26968 --- [main] com.zhan.game.GameApp                    : Started GameApp in 6.523 seconds (process running for 7.274)
2026-02-12T18:13:06.451+08:00  INFO 26968 --- [http-nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2026-02-12T18:13:06.451+08:00  INFO 26968 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2026-02-12T18:13:06.454+08:00  INFO 26968 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2026-02-12T18:13:06.540+08:00  INFO 26968 --- [http-nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2026-02-12T18:13:06.763+08:00  INFO 26968 --- [http-nio-8080-exec-1] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@7372be86
2026-02-12T18:13:06.765+08:00  INFO 26968 --- [http-nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2026-02-12T18:13:13.456+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.game.service.impl.GameServiceImpl    : 开始推进游戏阶段
2026-02-12T18:13:13.462+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.game.service.impl.GameServiceImpl    : 房间38:开始执行完整夜晚流程
2026-02-12T18:13:13.468+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 1号玩家(守卫), 提示词:你是狼人杀守卫,根据历史游戏信息和自己的判断,今晚请选择一名玩家守护(不能连续两晚守同一个人)。
只回复数字,不要多余内容。

2026-02-12T18:13:15.101+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 1号玩家(守卫), 回复内容:5
2026-02-12T18:13:15.206+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 5号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是5号玩家,身份是狼人,正在参与放逐投票。
历史发言:
暂无玩家发言
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:13:16.382+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 5号玩家(狼人), 回复内容:4
2026-02-12T18:13:16.433+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 7号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是7号玩家,身份是狼人,正在参与放逐投票。
历史发言:
暂无玩家发言
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:13:17.109+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 7号玩家(狼人), 回复内容:8
2026-02-12T18:13:17.149+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 10号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是10号玩家,身份是狼人,正在参与放逐投票。
历史发言:
暂无玩家发言
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:13:18.187+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 10号玩家(狼人), 回复内容:4
2026-02-12T18:13:18.256+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 12号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是12号玩家,身份是狼人,正在参与放逐投票。
历史发言:
暂无玩家发言
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:13:18.997+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 12号玩家(狼人), 回复内容:3
2026-02-12T18:13:19.037+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 6号玩家(预言家), 提示词:你是预言家,晚上选择一名玩家查验身份。
规则:规则:只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
根据历史游戏信息和自己的判断,请直接回复目标玩家编号:

2026-02-12T18:13:19.928+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 6号玩家(预言家), 回复内容:6
2026-02-12T18:13:20.098+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 9号玩家(女巫), 提示词:你是9号女巫,当前解药可用、毒药可用。
今夜4号玩家被狼人刀杀,你是否使用解药拯救他?
注意:解药整局只能用1次,且不能和毒药同晚使用。
根据历史游戏信息和自己的判断,仅回复:是 或 否,不要多余内容。

2026-02-12T18:13:20.753+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.g.service.impl.AiPlayerServiceImpl   : 9号玩家(女巫), 回复内容:是
2026-02-12T18:13:20.794+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.game.service.impl.GameServiceImpl    : 女巫9号使用解药拯救了4号玩家
2026-02-12T18:13:20.888+08:00  INFO 26968 --- [http-nio-8080-exec-3] c.z.game.service.impl.GameServiceImpl    : 房间38:夜间死亡玩家编号列表 -> []
2026-02-12T18:18:26.556+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 开始推进游戏阶段
2026-02-12T18:18:26.639+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 昨夜平安夜
2026-02-12T18:18:26.826+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 1号玩家(守卫), 提示词:【狼人杀游戏信息】
你的身份:1号守卫
场上局势:
昨夜死亡玩家:
你昨夜守护的玩家:5

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
暂无玩家发言
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:18:30.438+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 1号玩家(守卫), 回复内容:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2026-02-12T18:18:30.518+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 1号玩家(守卫)发言:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2026-02-12T18:18:30.680+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 2号玩家(平民), 提示词:【狼人杀游戏信息】
你的身份:2号平民
场上局势:
昨夜死亡玩家:

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:18:32.979+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 2号玩家(平民), 回复内容:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
2026-02-12T18:18:33.031+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 2号玩家(平民)发言:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
2026-02-12T18:18:33.169+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 3号玩家(猎人), 提示词:【狼人杀游戏信息】
你的身份:3号猎人
场上局势:
昨夜死亡玩家:

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:18:37.159+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 3号玩家(猎人), 回复内容:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
2026-02-12T18:18:37.235+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 3号玩家(猎人)发言:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
2026-02-12T18:18:37.372+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 4号玩家(平民), 提示词:【狼人杀游戏信息】
你的身份:4号平民
场上局势:
昨夜死亡玩家:

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:18:39.411+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 4号玩家(平民), 回复内容:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
2026-02-12T18:18:39.454+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 4号玩家(平民)发言:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
2026-02-12T18:18:39.650+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 5号玩家(狼人), 提示词:【狼人杀游戏信息】
你的身份:5号狼人
场上局势:
昨夜死亡玩家:
你的狼人队友:5、7、10、12
昨夜狼人刀人目标:4

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:18:44.416+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 5号玩家(狼人), 回复内容:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
2026-02-12T18:18:44.493+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 5号玩家(狼人)发言:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
2026-02-12T18:18:44.672+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 6号玩家(预言家), 提示词:【狼人杀游戏信息】
你的身份:6号预言家
场上局势:
昨夜死亡玩家:
你昨夜查验6号玩家,身份是:好人

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:18:48.676+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 6号玩家(预言家), 回复内容:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
2026-02-12T18:18:48.755+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 6号玩家(预言家)发言:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
2026-02-12T18:18:48.961+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 7号玩家(狼人), 提示词:【狼人杀游戏信息】
你的身份:7号狼人
场上局势:
昨夜死亡玩家:
你的狼人队友:5、7、10、12
昨夜狼人刀人目标:4

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:18:53.533+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 7号玩家(狼人), 回复内容:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
2026-02-12T18:18:53.634+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 7号玩家(狼人)发言:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
2026-02-12T18:18:53.812+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 8号玩家(平民), 提示词:【狼人杀游戏信息】
你的身份:8号平民
场上局势:
昨夜死亡玩家:

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:18:55.803+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 8号玩家(平民), 回复内容:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
2026-02-12T18:18:55.847+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 8号玩家(平民)发言:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
2026-02-12T18:18:56.060+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 9号玩家(女巫), 提示词:【狼人杀游戏信息】
你的身份:9号女巫
场上局势:
昨夜死亡玩家:
你的解药状态:已用
你的毒药状态:可用
昨夜被刀玩家:4
你昨夜用药情况:使用解药拯救4号玩家

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:18:59.825+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 9号玩家(女巫), 回复内容:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
2026-02-12T18:18:59.910+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 9号玩家(女巫)发言:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
2026-02-12T18:19:00.094+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 10号玩家(狼人), 提示词:【狼人杀游戏信息】
你的身份:10号狼人
场上局势:
昨夜死亡玩家:
你的狼人队友:5、7、10、12
昨夜狼人刀人目标:4

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:19:04.194+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 10号玩家(狼人), 回复内容:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
2026-02-12T18:19:04.276+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 10号玩家(狼人)发言:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
2026-02-12T18:19:04.412+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 11号玩家(平民), 提示词:【狼人杀游戏信息】
你的身份:11号平民
场上局势:
昨夜死亡玩家:

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:19:06.409+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 11号玩家(平民), 回复内容:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
2026-02-12T18:19:06.447+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 11号玩家(平民)发言:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
2026-02-12T18:19:06.638+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 12号玩家(狼人), 提示词:【狼人杀游戏信息】
你的身份:12号狼人
场上局势:
昨夜死亡玩家:
你的狼人队友:5、7、10、12
昨夜狼人刀人目标:4

请根据场上局势、其他人发言和你的身份进行逻辑发言。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
发言要求:
1. 请根据场上局势、其他人发言和你的身份进行逻辑发言
2. 狼人要隐藏身份,好人要找出狼人
3. 发言字数50-120字,只回复发言内容,不要多余文字
4. 不要说自己是AI,不要解释规则
5. 不要加任何前缀,比如“以XX视角”,直接开始说内容
6. 语言像真人玩家一样自然、简短、有逻辑
7. 可以分析、站队、怀疑、保人、点狼坑

2026-02-12T18:19:09.841+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 12号玩家(狼人), 回复内容:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
2026-02-12T18:19:09.923+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.game.service.impl.GameServiceImpl    : 12号玩家(狼人)发言:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
2026-02-12T18:19:10.044+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 1号玩家(守卫), 提示词:【狼人杀公投】
你是1号玩家,身份是守卫(好人),正在参与放逐投票。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
12号:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1. 绝对不能投票给自己(1号);
2. 优先投票你怀疑的狼人;
3. 只回复1-12之间的数字,不要加任何文字、符号、括号;
4. 你的目标是投出狼人,不要投好人。
请请根据历史游戏信息和自己的判断,投票放逐你怀疑的狼人,只回复玩家编号(数字),不要多余内容。

2026-02-12T18:19:10.728+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 1号玩家(守卫), 回复内容:6
2026-02-12T18:19:10.805+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 2号玩家(平民), 提示词:【狼人杀公投】
你是2号玩家,身份是平民(好人),正在参与放逐投票。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
12号:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1. 绝对不能投票给自己(2号);
2. 优先投票你怀疑的狼人;
3. 只回复1-12之间的数字,不要加任何文字、符号、括号;
4. 你的目标是投出狼人,不要投好人。
请请根据历史游戏信息和自己的判断,投票放逐你怀疑的狼人,只回复玩家编号(数字),不要多余内容。

2026-02-12T18:19:11.377+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 2号玩家(平民), 回复内容:6
2026-02-12T18:19:11.443+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 3号玩家(猎人), 提示词:【狼人杀公投】
你是3号玩家,身份是猎人(好人),正在参与放逐投票。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
12号:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1. 绝对不能投票给自己(3号);
2. 优先投票你怀疑的狼人;
3. 只回复1-12之间的数字,不要加任何文字、符号、括号;
4. 你的目标是投出狼人,不要投好人。
请请根据历史游戏信息和自己的判断,投票放逐你怀疑的狼人,只回复玩家编号(数字),不要多余内容。

2026-02-12T18:19:12.570+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 3号玩家(猎人), 回复内容:6
2026-02-12T18:19:12.638+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 4号玩家(平民), 提示词:【狼人杀公投】
你是4号玩家,身份是平民(好人),正在参与放逐投票。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
12号:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1. 绝对不能投票给自己(4号);
2. 优先投票你怀疑的狼人;
3. 只回复1-12之间的数字,不要加任何文字、符号、括号;
4. 你的目标是投出狼人,不要投好人。
请请根据历史游戏信息和自己的判断,投票放逐你怀疑的狼人,只回复玩家编号(数字),不要多余内容。

2026-02-12T18:19:13.190+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 4号玩家(平民), 回复内容:6
2026-02-12T18:19:13.294+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 5号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是5号玩家,身份是狼人,正在参与放逐投票。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
12号:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:19:13.946+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 5号玩家(狼人), 回复内容:6
2026-02-12T18:19:14.017+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 6号玩家(预言家), 提示词:【狼人杀公投】
你是6号玩家,身份是预言家(好人),正在参与放逐投票。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
12号:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1. 绝对不能投票给自己(6号);
2. 优先投票你怀疑的狼人;
3. 只回复1-12之间的数字,不要加任何文字、符号、括号;
4. 你的目标是投出狼人,不要投好人。
请请根据历史游戏信息和自己的判断,投票放逐你怀疑的狼人,只回复玩家编号(数字),不要多余内容。

2026-02-12T18:19:14.728+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 6号玩家(预言家), 回复内容:1
2026-02-12T18:19:14.797+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 7号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是7号玩家,身份是狼人,正在参与放逐投票。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
12号:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:19:16.043+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 7号玩家(狼人), 回复内容:6
2026-02-12T18:19:16.104+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 8号玩家(平民), 提示词:【狼人杀公投】
你是8号玩家,身份是平民(好人),正在参与放逐投票。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
12号:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1. 绝对不能投票给自己(8号);
2. 优先投票你怀疑的狼人;
3. 只回复1-12之间的数字,不要加任何文字、符号、括号;
4. 你的目标是投出狼人,不要投好人。
请请根据历史游戏信息和自己的判断,投票放逐你怀疑的狼人,只回复玩家编号(数字),不要多余内容。

2026-02-12T18:19:16.664+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 8号玩家(平民), 回复内容:6
2026-02-12T18:19:16.737+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 9号玩家(女巫), 提示词:【狼人杀公投】
你是9号玩家,身份是女巫(好人),正在参与放逐投票。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
12号:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1. 绝对不能投票给自己(9号);
2. 优先投票你怀疑的狼人;
3. 只回复1-12之间的数字,不要加任何文字、符号、括号;
4. 你的目标是投出狼人,不要投好人。
请请根据历史游戏信息和自己的判断,投票放逐你怀疑的狼人,只回复玩家编号(数字),不要多余内容。

2026-02-12T18:19:17.627+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 9号玩家(女巫), 回复内容:6
2026-02-12T18:19:17.721+08:00  INFO 26968 --- [http-nio-8080-exec-6] c.z.g.service.impl.AiPlayerServiceImpl   : 10号玩家(狼人), 提示词:【狼人晚上集体刀人】
你是10号玩家,身份是狼人,正在参与放逐投票。
历史发言:
1号:昨夜我守了5号,看来狼人刀了别人。5号现在应该是好人身份。昨晚空刀可能性不大,可能刀到了女巫或者猎人身上。现在需要重点听前置位的发言,看看谁在划水或者刻意带节奏。目前信息不多,我会重点注意对5号态度暧昧的玩家,狼人可能想脏5号身份。
2号:1号守5号?听着像真预言家报查验,但得防狼悍跳。5号如果真是金水先保着,但别急着站死队。现在狼可能在后置位憋刀,尤其要盯紧对5号阴阳怪气的。我暂时跟1号盘逻辑,看他后面怎么带节奏。
3号:1号跳守卫说守5号,那5号暂时当好人看。狼昨晚空刀可能不大,大概率刀在神身上,女巫没救人?今天出人可以保守点,先听后面发言。2号对1号态度有点暧昧,像在跟风但没完全认守卫,需要再听。我是强神,不急着跳,但狼如果抿身份可能会针对我。建议好人别乱炸身份,看看谁对5号有敌意。
4号:1号守5号听着合理,但得防狼悍跳。2、3号对1号态度都偏谨慎,尤其3号说“强神不跳”,有点刻意强调身份嫌疑。我暂跟1号盘逻辑,重点看谁急着带节奏出5号,狼大概率在后置位憋刀,今天先听4、5、6怎么聊。
5号:1号守我信息有点突然,暂时信他是真守卫。昨晚倒牌没出平安夜,女巫没救可能毒了人,或者狼刀在了神身上。2号、3号都在保1号但态度微妙,3号跳强神太急了,像在躲推。4号提到狼可能后置位憋刀,这点我认同,建议重点听5、6、7的发言。目前局势不明,好人别急着站队,尤其警惕对1号无条件跟风的。
6号:我才是真预言家,昨晚验了6号是好人。1号跳守卫但守5号逻辑矛盾,如果他是真守卫,狼人空刀概率极小。而且我作为6号预言家,验自己虽无收益,却印证1号信息有假。前置位2、3、4都在跟风1号,但3号强神跳得突兀,可能想躲推。建议好人回头,今天先出1号,我今晚会验5号身份。狼队可能在1、3、4里。
7号:6号说验自己就很离谱,预言家怎么可能浪费第一验查自己?明显悍跳。而且1号守卫逻辑清晰,守5号信息连贯,狼人空刀可能性很低。前置位都在盘正逻辑,6号却要出1号,强打抗推位,狼面很大。我建议今天先出6号,他如果是狼,狼队就是6号带的头,后面可能还有冲锋狼。女巫和猎人可以藏好,守卫继续守好关键位置。
8号:6号验自己太假,预言家第一晚不可能这么浪费验人。1号守5号逻辑通顺,狼大概率没空刀。7号分析到位,6号急着出1号像冲锋狼。我跟7号票6号,今天先出他,看今晚倒牌情况再盘。
9号:6号悍跳无疑,验自己完全不符合预言家行为。1号守卫信息与我掌握的昨夜刀口4号一致,证明1号是真守卫,4号是我用解药救的。狼队昨晚刀4没刀死,今天想推1号悍跳守卫,6号明显是冲锋狼。3号强神跳得有点早,但暂不定义。今天必须出6号,女巫毒药在手,狼队别想躲刀。
10号:6号悍跳预言家太明显了,验自己完全不符合预言家收益。9号跳女巫报刀口救了4号,和1号守卫信息能对应上,说明1、9两张神牌基本坐实。今天肯定要出6号,他的狼同伴可能在带节奏保他的人里。前置位3号强神跳得有点突兀,但暂时先不点。好人别分票,集中出6。
11号:9号女巫跳得及时,和1号守卫信息对上了,基本坐实神牌。6号验自己太假,铁狼冲锋。今天必须出6,他的狼队友可能在3、4里带节奏保他。好人别分票,先清明面狼,晚上再盘身份。
12号:6号验自己确实不像真预言家,而且9号女巫跳出来救了4号,和1号守卫的逻辑能对上,神牌面很大。今天出6没问题,但7号之前也踩6踩得很凶,不排除是狼踩狼做身份。好人别分票,但明天要仔细盘7号和8号的发言,可能有倒钩狼在里面。
场上存活玩家:1、2、3、4、5、6、7、8、9、10、11、12
规则:
1.优先投票你识别的好人/神牌
2.只能回复1-12之间的数字,不要任何多余内容(如文字、符号、空格)。
3.优先投票你识别的好人/神牌。
4.你的目标是投出好人,保护狼人队友。
请根据历史游戏信息和自己的判断,直接回复你要投票的玩家编号:

2026-02-12T18:19:17.910+08:00  INFO 26968 --- [SpringApplicationShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete


评论