feat: 优化在线玩家统计逻辑,更新游戏引擎构造函数并调整跳过回合物品行为。
This commit is contained in:
parent
7e5f77ffd4
commit
6b2f86da1e
@ -1,5 +1,5 @@
|
|||||||
# 全量服务部署 (后端 + 游戏服 + 数据库)
|
# 全量服务部署 (前端 + 后端 + 游戏服 + 数据库 + 中间件 + Nginx)
|
||||||
# 使用方法: docker-compose -f docker-compose.all.yml up -d
|
# 使用方法: docker-compose -f docker-compose.all.yml up -d --build
|
||||||
services:
|
services:
|
||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
# 1. 业务后端 (Bindbox Game Backend)
|
# 1. 业务后端 (Bindbox Game Backend)
|
||||||
@ -8,11 +8,11 @@ services:
|
|||||||
image: zfc931912343/bindbox-game:v1.15
|
image: zfc931912343/bindbox-game:v1.15
|
||||||
container_name: bindbox-game
|
container_name: bindbox-game
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
# ports:
|
||||||
- "9991:9991"
|
# - "9991:9991" (Internal only)
|
||||||
volumes:
|
volumes:
|
||||||
- ../bindbox_game/logs:/app/logs
|
- ../bindbox_game/logs:/app/logs
|
||||||
# - ../bindbox_game/configs:/app/configs # 指向 bindbox_game 目录下的配置
|
- ../bindbox_game/configs:/app/configs
|
||||||
environment:
|
environment:
|
||||||
- ACTIVE_ENV=pro
|
- ACTIVE_ENV=pro
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
@ -23,8 +23,27 @@ services:
|
|||||||
options:
|
options:
|
||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "3"
|
max-file: "3"
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
- redis
|
||||||
|
|
||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
# 2. 游戏数据库 (CockroachDB for Nakama)
|
# 2. 管理后台 (Admin Web)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
admin-web:
|
||||||
|
build: ../bindbox_game/web/admin
|
||||||
|
container_name: bindbox-admin-web
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 3. 游戏数据库 (CockroachDB for Nakama)
|
||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
nakama-db:
|
nakama-db:
|
||||||
image: cockroachdb/cockroach:latest-v23.1
|
image: cockroachdb/cockroach:latest-v23.1
|
||||||
@ -33,9 +52,7 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- nakama-db-data:/var/lib/cockroach
|
- nakama-db-data:/var/lib/cockroach
|
||||||
ports:
|
|
||||||
- "26257:26257"
|
|
||||||
- "8081:8080"
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ]
|
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ]
|
||||||
interval: 3s
|
interval: 3s
|
||||||
@ -50,14 +67,14 @@ services:
|
|||||||
options:
|
options:
|
||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "3"
|
max-file: "3"
|
||||||
|
|
||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
# 3. 游戏服务器 (Nakama)
|
# 4. 游戏服务器 (Nakama)
|
||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
nakama:
|
nakama:
|
||||||
image: zfc931912343/bindbox-saolei:v1.6
|
image: zfc931912343/bindbox-saolei:v1.6
|
||||||
container_name: nakama-server
|
container_name: nakama-server
|
||||||
environment:
|
environment:
|
||||||
# 直接使用服务名访问后端
|
|
||||||
- MINESWEEPER_BACKEND_URL=http://bindbox-game:9991/api/internal
|
- MINESWEEPER_BACKEND_URL=http://bindbox-game:9991/api/internal
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
entrypoint:
|
entrypoint:
|
||||||
@ -72,10 +89,7 @@ services:
|
|||||||
condition: service_started
|
condition: service_started
|
||||||
volumes:
|
volumes:
|
||||||
- nakama-data:/nakama/data
|
- nakama-data:/nakama/data
|
||||||
ports:
|
|
||||||
- "7350:7350"
|
|
||||||
- "7351:7351"
|
|
||||||
- "9100:9100"
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://bindbox-game:9991/" ]
|
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://bindbox-game:9991/" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
@ -88,9 +102,147 @@ services:
|
|||||||
options:
|
options:
|
||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "3"
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 5. MySQL Database (For Bindbox Backend)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: bindbox-mysql
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: "123456"
|
||||||
|
MYSQL_DATABASE: "bindbox_game"
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
command: --default-authentication-plugin=mysql_native_password
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 6. Redis (For Bindbox Backend)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
redis:
|
||||||
|
image: redis:7.0
|
||||||
|
container_name: bindbox-redis
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 7. Nginx Gateway
|
||||||
|
# ----------------------------------------------------
|
||||||
|
nginx:
|
||||||
|
image: nginx:latest
|
||||||
|
container_name: bindbox-nginx
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/conf.d:/etc/nginx/conf.d
|
||||||
|
- ./nginx/ssl:/etc/nginx/ssl
|
||||||
|
depends_on:
|
||||||
|
- bindbox-game
|
||||||
|
- admin-web
|
||||||
|
- nakama
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 8. Loki (Log Storage)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
loki:
|
||||||
|
image: grafana/loki:3.0.0
|
||||||
|
container_name: bindbox-loki
|
||||||
|
restart: always
|
||||||
|
# ports:
|
||||||
|
# - "3100:3100"
|
||||||
|
volumes:
|
||||||
|
- ./loki/loki-config.yaml:/etc/loki/local-config.yaml
|
||||||
|
- loki_data:/loki
|
||||||
|
command: -config.file=/etc/loki/local-config.yaml
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 9. Promtail (Log Collector)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
promtail:
|
||||||
|
image: grafana/promtail:3.0.0
|
||||||
|
container_name: bindbox-promtail
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./loki/promtail-config.yaml:/etc/promtail/config.yaml
|
||||||
|
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ../bindbox_game/logs:/var/log/bindbox-game:ro
|
||||||
|
command: -config.file=/etc/promtail/config.yaml
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
depends_on:
|
||||||
|
- loki
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 10. Grafana (Visualization)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana:latest
|
||||||
|
container_name: bindbox-grafana
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||||
|
- GF_USERS_ALLOW_SIGN_UP=false
|
||||||
|
volumes:
|
||||||
|
- grafana_data:/var/lib/grafana
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
depends_on:
|
||||||
|
- loki
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
nakama-db-data:
|
nakama-db-data:
|
||||||
nakama-data:
|
nakama-data:
|
||||||
|
mysql_data:
|
||||||
|
redis_data:
|
||||||
|
loki_data:
|
||||||
|
grafana_data:
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
bindbox_net:
|
bindbox_net:
|
||||||
name: bindbox_net
|
name: bindbox_net
|
||||||
|
|||||||
@ -1,55 +1,350 @@
|
|||||||
# 云端部署专用 - 扫雷游戏服务
|
# 全量服务部署 (云端/无源码版)
|
||||||
# 使用方法: docker-compose -f docker-compose.cloud.yml up -d
|
# 使用方法:
|
||||||
|
# 1. 确保已将 docker-compose.cloud.yml, configs/, nginx/, loki/ 目录上传到服务器同一目录
|
||||||
|
# 2. 确保 logs/ 目录存在 (mkdir logs)
|
||||||
|
# 3. 运行: docker-compose -f docker-compose.cloud.yml up -d
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 1. 业务后端 (Bindbox Game Backend)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
bindbox-game:
|
||||||
|
image: zfc931912343/bindbox-game:v1.15
|
||||||
|
container_name: bindbox-game
|
||||||
|
restart: always
|
||||||
|
# ports:
|
||||||
|
# - "9991:9991" (Internal only)
|
||||||
|
volumes:
|
||||||
|
# 改为挂载当前目录下的 logs 和 configs
|
||||||
|
- ./logs:/app/logs
|
||||||
|
- ./configs:/app/configs
|
||||||
|
environment:
|
||||||
|
- ACTIVE_ENV=pro
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
# MySQL 配置(覆盖编译时的默认值)
|
||||||
|
- MYSQL_ADDR=mysql:3306
|
||||||
|
- MYSQL_USER=root
|
||||||
|
- MYSQL_PASS=bindbox2025kdy
|
||||||
|
- MYSQL_NAME=bindbox_game
|
||||||
|
# Redis 配置
|
||||||
|
- REDIS_ADDR=redis:6379
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
- redis
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 2. 游戏数据库 (CockroachDB for Nakama)
|
||||||
|
# ----------------------------------------------------
|
||||||
nakama-db:
|
nakama-db:
|
||||||
image: cockroachdb/cockroach:latest-v23.1
|
image: cockroachdb/cockroach:latest-v23.1
|
||||||
container_name: nakama-db
|
container_name: nakama-db
|
||||||
command: start-single-node --insecure --store=attrs=ssd,path=/var/lib/cockroach/
|
command: start-single-node --insecure --store=attrs=ssd,path=/var/lib/cockroach/ --cache=.25 --max-sql-memory=.25
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- nakama-db-data:/var/lib/cockroach
|
- nakama-db-data:/var/lib/cockroach
|
||||||
ports:
|
|
||||||
- "26257:26257"
|
|
||||||
- "8081:8080"
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ]
|
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ]
|
||||||
interval: 3s
|
interval: 3s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 3. 游戏服务器 (Nakama)
|
||||||
|
# ----------------------------------------------------
|
||||||
nakama:
|
nakama:
|
||||||
image: zfc931912343/bindbox-saolei:v1.3
|
image: zfc931912343/bindbox-saolei:v1.6
|
||||||
container_name: nakama-server
|
container_name: nakama-server
|
||||||
environment:
|
environment:
|
||||||
# 使用 Docker 内部网络访问后端服务 (需确保在同一网络下)
|
- MINESWEEPER_BACKEND_URL=http://bindbox-game:9991/api/internal
|
||||||
- MINESWEEPER_BACKEND_URL=http://blindbox-mms-api:9991/api/internal
|
- TZ=Asia/Shanghai
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- "/bin/sh"
|
- "/bin/sh"
|
||||||
- "-ecx"
|
- "-ecx"
|
||||||
- "/nakama/nakama migrate up --database.address root@nakama-db:26257 && exec /nakama/nakama --name nakama1 --database.address root@nakama-db:26257 --logger.level DEBUG --session.token_expiry_sec 7200 --metrics.prometheus_port 9100 --runtime.path /nakama/modules"
|
- "/nakama/nakama migrate up --database.address root@nakama-db:26257 && exec /nakama/nakama --name nakama1 --database.address root@nakama-db:26257 --logger.level DEBUG --session.token_expiry_sec 7200 --metrics.prometheus_port 9100 --runtime.path /nakama/modules --matchmaker.interval_sec 1 --matchmaker.max_intervals 5"
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
nakama-db:
|
nakama-db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
bindbox-game:
|
||||||
|
condition: service_started
|
||||||
volumes:
|
volumes:
|
||||||
- nakama-data:/nakama/data
|
- nakama-data:/nakama/data
|
||||||
ports:
|
|
||||||
- "7350:7350"
|
|
||||||
- "7351:7351"
|
|
||||||
- "9100:9100"
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:7350/" ]
|
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://bindbox-game:9991/" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 4. MySQL Database
|
||||||
|
# ----------------------------------------------------
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: bindbox-mysql
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "3306:3306" # 临时开放外部访问,用完记得关闭!
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: "bindbox2025kdy"
|
||||||
|
MYSQL_DATABASE: "bindbox_game"
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
command: --default-authentication-plugin=mysql_native_password
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
- ./mysql/init:/docker-entrypoint-initdb.d # 初始化脚本
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 5. Redis
|
||||||
|
# ----------------------------------------------------
|
||||||
|
redis:
|
||||||
|
image: redis:7.0
|
||||||
|
container_name: bindbox-redis
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 6. Nginx Gateway (入口)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
nginx:
|
||||||
|
image: nginx:latest
|
||||||
|
container_name: bindbox-nginx
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/conf.d:/etc/nginx/conf.d
|
||||||
|
- ./nginx/ssl:/etc/nginx/ssl
|
||||||
|
- ./dist:/usr/share/nginx/html/admin
|
||||||
|
depends_on:
|
||||||
|
- bindbox-game
|
||||||
|
- nakama
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 7. Loki (日志存储)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
loki:
|
||||||
|
image: grafana/loki:3.0.0
|
||||||
|
container_name: bindbox-loki
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
# 必须上传 loki 目录到服务器
|
||||||
|
- ./loki/loki-config.yaml:/etc/loki/local-config.yaml
|
||||||
|
- loki_data:/loki
|
||||||
|
command: -config.file=/etc/loki/local-config.yaml
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 8. Promtail (日志采集)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
promtail:
|
||||||
|
image: grafana/promtail:3.0.0
|
||||||
|
container_name: bindbox-promtail
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./loki/promtail-config.yaml:/etc/promtail/config.yaml
|
||||||
|
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
# 采集当前目录下的 logs 文件夹
|
||||||
|
- ./logs:/var/log/bindbox-game:ro
|
||||||
|
command: -config.file=/etc/promtail/config.yaml
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
depends_on:
|
||||||
|
- loki
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 9. Grafana (日志界面)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana:latest
|
||||||
|
container_name: bindbox-grafana
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||||
|
- GF_USERS_ALLOW_SIGN_UP=false
|
||||||
|
volumes:
|
||||||
|
- grafana_data:/var/lib/grafana
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
depends_on:
|
||||||
|
- loki
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 10. Prometheus (指标采集)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
prometheus:
|
||||||
|
image: prom/prometheus:latest
|
||||||
|
container_name: bindbox-prometheus
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
|
- prometheus_data:/prometheus
|
||||||
|
command:
|
||||||
|
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||||
|
- '--storage.tsdb.path=/prometheus'
|
||||||
|
- '--web.enable-lifecycle'
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 11. Nginx Exporter (Nginx指标导出)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
nginx-exporter:
|
||||||
|
image: nginx/nginx-prometheus-exporter:latest
|
||||||
|
container_name: bindbox-nginx-exporter
|
||||||
|
restart: always
|
||||||
|
command:
|
||||||
|
- -nginx.scrape-uri=http://nginx:80/nginx_status
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
depends_on:
|
||||||
|
- nginx
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 12. Redis Exporter (Redis指标导出)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
redis-exporter:
|
||||||
|
image: oliver006/redis_exporter:latest
|
||||||
|
container_name: bindbox-redis-exporter
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- REDIS_ADDR=redis://redis:6379
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 13. MySQL Exporter (MySQL指标导出)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
mysql-exporter:
|
||||||
|
image: prom/mysqld-exporter:latest
|
||||||
|
container_name: bindbox-mysql-exporter
|
||||||
|
restart: always
|
||||||
|
command:
|
||||||
|
- --config.my-cnf=/etc/.my.cnf
|
||||||
|
volumes:
|
||||||
|
- ./mysql/.my.cnf:/etc/.my.cnf:ro
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# 14. Tempo (链路追踪)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
tempo:
|
||||||
|
image: grafana/tempo:latest
|
||||||
|
container_name: bindbox-tempo
|
||||||
|
restart: always
|
||||||
|
command: [ "-config.file=/etc/tempo/tempo-config.yaml" ]
|
||||||
|
volumes:
|
||||||
|
- ./tempo/tempo-config.yaml:/etc/tempo/tempo-config.yaml
|
||||||
|
- tempo_data:/var/tempo
|
||||||
|
networks:
|
||||||
|
- bindbox_net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
nakama-db-data:
|
nakama-db-data:
|
||||||
nakama-data:
|
nakama-data:
|
||||||
|
mysql_data:
|
||||||
|
redis_data:
|
||||||
|
loki_data:
|
||||||
|
grafana_data:
|
||||||
|
prometheus_data:
|
||||||
|
tempo_data:
|
||||||
|
|
||||||
|
|
||||||
# 必须加入后端服务所在的网络才能通过 service_name 访问
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
bindbox_net:
|
||||||
name: ${DOCKER_NETWORK_NAME:-bindbox_default}
|
name: bindbox_net
|
||||||
external: true
|
driver: bridge
|
||||||
|
|||||||
38
loki/loki-config.yaml
Normal file
38
loki/loki-config.yaml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
auth_enabled: false
|
||||||
|
|
||||||
|
server:
|
||||||
|
http_listen_port: 3100
|
||||||
|
grpc_listen_port: 0
|
||||||
|
|
||||||
|
common:
|
||||||
|
path_prefix: /loki
|
||||||
|
storage:
|
||||||
|
filesystem:
|
||||||
|
chunks_directory: /loki/chunks
|
||||||
|
rules_directory: /loki/rules
|
||||||
|
replication_factor: 1
|
||||||
|
ring:
|
||||||
|
instance_addr: 127.0.0.1
|
||||||
|
kvstore:
|
||||||
|
store: inmemory
|
||||||
|
|
||||||
|
schema_config:
|
||||||
|
configs:
|
||||||
|
- from: 2020-10-24
|
||||||
|
store: tsdb
|
||||||
|
object_store: filesystem
|
||||||
|
schema: v13
|
||||||
|
index:
|
||||||
|
prefix: index_
|
||||||
|
period: 24h
|
||||||
|
|
||||||
|
ruler:
|
||||||
|
alertmanager_url: http://localhost:9093
|
||||||
|
|
||||||
|
# Limit settings to prevent issues with large logs
|
||||||
|
limits_config:
|
||||||
|
reject_old_samples: true
|
||||||
|
reject_old_samples_max_age: 168h
|
||||||
|
ingestion_rate_mb: 20
|
||||||
|
ingestion_burst_size_mb: 30
|
||||||
|
volume_enabled: true
|
||||||
36
loki/promtail-config.yaml
Normal file
36
loki/promtail-config.yaml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
server:
|
||||||
|
http_listen_port: 9080
|
||||||
|
grpc_listen_port: 0
|
||||||
|
|
||||||
|
positions:
|
||||||
|
filename: /tmp/positions.yaml
|
||||||
|
|
||||||
|
clients:
|
||||||
|
- url: http://loki:3100/loki/api/v1/push
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
# 1. Scrape logs from Docker containers via socket (stdout/stderr)
|
||||||
|
- job_name: docker
|
||||||
|
docker_sd_configs:
|
||||||
|
- host: unix:///var/run/docker.sock
|
||||||
|
refresh_interval: 5s
|
||||||
|
# filters:
|
||||||
|
# - name: name
|
||||||
|
# values: ["bindbox-game"] # Optional: Filter specifically if desired, but user said "all monitor"
|
||||||
|
relabel_configs:
|
||||||
|
- source_labels: ['__meta_docker_container_name']
|
||||||
|
regex: '/(.*)'
|
||||||
|
target_label: 'container'
|
||||||
|
- source_labels: ['__meta_docker_container_log_stream']
|
||||||
|
target_label: 'logstream'
|
||||||
|
- source_labels: ['__meta_docker_container_label_logging_jobname']
|
||||||
|
target_label: 'job'
|
||||||
|
|
||||||
|
# 2. Scrape logs from mounted log files (application logs)
|
||||||
|
- job_name: file_logs
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- localhost
|
||||||
|
labels:
|
||||||
|
job: bindbox_app_logs
|
||||||
|
__path__: /var/log/bindbox-game/*.log
|
||||||
BIN
mysql/.DS_Store
vendored
Normal file
BIN
mysql/.DS_Store
vendored
Normal file
Binary file not shown.
5
mysql/.my.cnf
Normal file
5
mysql/.my.cnf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[client]
|
||||||
|
user = exporter
|
||||||
|
password = exporter123
|
||||||
|
host = mysql
|
||||||
|
port = 3306
|
||||||
4
mysql/init/01-create-exporter-user.sql
Normal file
4
mysql/init/01-create-exporter-user.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
-- 创建 MySQL Exporter 监控账号
|
||||||
|
CREATE USER IF NOT EXISTS 'exporter'@'%' IDENTIFIED BY 'exporter123';
|
||||||
|
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
78
nginx/conf.d/default.conf
Normal file
78
nginx/conf.d/default.conf
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name kdy.1024tool.vip;
|
||||||
|
|
||||||
|
# Nginx 状态监控端点 (HTTP)
|
||||||
|
location /nginx_status {
|
||||||
|
stub_status on;
|
||||||
|
access_log off;
|
||||||
|
allow 172.0.0.0/8;
|
||||||
|
allow 192.168.0.0/16; # Docker bridge network
|
||||||
|
allow 127.0.0.1;
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTPS Server
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name kdy.1024tool.vip;
|
||||||
|
|
||||||
|
# SSL Config
|
||||||
|
ssl_certificate /etc/nginx/ssl/kdy.1024tool.vip.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/kdy.1024tool.vip.key;
|
||||||
|
|
||||||
|
ssl_session_timeout 5m;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
# 1. Admin Frontend (Static Dist)
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html/admin;
|
||||||
|
index index.html index.htm;
|
||||||
|
try_files $uri $uri/ /index.html; # SPA Support
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Backend API
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://bindbox-game:9991/api/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. Nakama API (HTTP / GRPC / WebSocket)
|
||||||
|
location /v2/ {
|
||||||
|
proxy_pass http://nakama:7350/v2/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Nakama WebSocket
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://nakama:7350/ws;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Nginx 状态监控端点(仅内网访问)
|
||||||
|
location /nginx_status {
|
||||||
|
stub_status on;
|
||||||
|
access_log off;
|
||||||
|
allow 172.0.0.0/8; # Docker 内网
|
||||||
|
allow 10.0.0.0/8; # 内网
|
||||||
|
allow 127.0.0.1;
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
prometheus/prometheus.yml
Normal file
33
prometheus/prometheus.yml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
global:
|
||||||
|
scrape_interval: 15s
|
||||||
|
evaluation_interval: 15s
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
# Prometheus 自身监控
|
||||||
|
- job_name: 'prometheus'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['localhost:9090']
|
||||||
|
|
||||||
|
# Nginx 监控
|
||||||
|
- job_name: 'nginx'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['nginx-exporter:9113']
|
||||||
|
metrics_path: /metrics
|
||||||
|
|
||||||
|
# Redis 监控
|
||||||
|
- job_name: 'redis'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['redis-exporter:9121']
|
||||||
|
metrics_path: /metrics
|
||||||
|
|
||||||
|
# MySQL 监控
|
||||||
|
- job_name: 'mysql'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['mysql-exporter:9104']
|
||||||
|
metrics_path: /metrics
|
||||||
|
|
||||||
|
# Nakama 游戏服务监控 (如果开启了 metrics)
|
||||||
|
- job_name: 'nakama'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['nakama:9100']
|
||||||
|
metrics_path: /metrics
|
||||||
Binary file not shown.
@ -72,7 +72,8 @@ type GameState struct {
|
|||||||
TurnOrder []string `json:"turnOrder"`
|
TurnOrder []string `json:"turnOrder"`
|
||||||
CurrentTurnIndex int `json:"currentTurnIndex"`
|
CurrentTurnIndex int `json:"currentTurnIndex"`
|
||||||
Round int `json:"round"`
|
Round int `json:"round"`
|
||||||
GlobalTurnCount int `json:"globalTurnCount"` // 总回合数(用于狗狗技能)
|
GlobalTurnCount int `json:"globalTurnCount"` // 总回合数(用于狗狗技能)
|
||||||
|
LastDeadPlayerID string `json:"lastDeadPlayerId"` // 最后一个死亡的玩家ID(用于平局结算)
|
||||||
WinnerID string `json:"winnerId"`
|
WinnerID string `json:"winnerId"`
|
||||||
GameStarted bool `json:"gameStarted"`
|
GameStarted bool `json:"gameStarted"`
|
||||||
LastMoveTimestamp int64 `json:"lastMoveTimestamp"` // Unix时间戳(秒)
|
LastMoveTimestamp int64 `json:"lastMoveTimestamp"` // Unix时间戳(秒)
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/heroiclabs/nakama-common/runtime"
|
"github.com/heroiclabs/nakama-common/runtime"
|
||||||
)
|
)
|
||||||
@ -74,67 +73,36 @@ func RpcFindMyMatch(ctx context.Context, logger runtime.Logger, db *sql.DB, nk r
|
|||||||
return readObjects[0].Value, nil
|
return readObjects[0].Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RpcGetOnlineCount 返回当前在线玩家数量(基于心跳)
|
// RpcGetOnlineCount 返回当前在线玩家数量
|
||||||
func RpcGetOnlineCount(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
func RpcGetOnlineCount(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
||||||
userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
|
userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
|
||||||
if !ok || userID == "" {
|
if !ok || userID == "" {
|
||||||
return "", runtime.NewError("user not authenticated", 16)
|
return "", runtime.NewError("user not authenticated", 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 更新当前用户的心跳时间戳
|
// 获取所有活跃比赛中的玩家数
|
||||||
now := time.Now().Unix()
|
matches, err := nk.MatchList(ctx, 100, true, "", nil, nil, "*")
|
||||||
heartbeatData, _ := json.Marshal(map[string]int64{"ts": now})
|
|
||||||
|
|
||||||
_, err := nk.StorageWrite(ctx, []*runtime.StorageWrite{
|
|
||||||
{
|
|
||||||
Collection: "game_lobby",
|
|
||||||
Key: "heartbeat",
|
|
||||||
UserID: userID,
|
|
||||||
Value: string(heartbeatData),
|
|
||||||
PermissionRead: 0, // 不可读
|
|
||||||
PermissionWrite: 0, // 仅服务器可写
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to write heartbeat: %v", err)
|
logger.Warn("Failed to list matches: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 读取所有用户的心跳记录
|
|
||||||
cursor := ""
|
|
||||||
onlineCount := 0
|
|
||||||
expireThreshold := now - 60 // 60秒内有心跳的算在线
|
|
||||||
|
|
||||||
for {
|
|
||||||
// StorageList(ctx, callerID, collection, userID, limit, cursor)
|
|
||||||
objects, nextCursor, err := nk.StorageList(ctx, "", "game_lobby", "", 100, cursor)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Failed to list heartbeats: %v", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, obj := range objects {
|
|
||||||
var data map[string]int64
|
|
||||||
if err := json.Unmarshal([]byte(obj.Value), &data); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ts, ok := data["ts"]; ok && ts >= expireThreshold {
|
|
||||||
onlineCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nextCursor == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cursor = nextCursor
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 获取比赛中的玩家数
|
|
||||||
matches, _ := nk.MatchList(ctx, 100, true, "", nil, nil, "*")
|
|
||||||
inGameCount := 0
|
inGameCount := 0
|
||||||
for _, m := range matches {
|
for _, m := range matches {
|
||||||
inGameCount += int(m.GetSize())
|
inGameCount += int(m.GetSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用 Nakama 的 StreamCount 获取当前所有 WebSocket 连接数
|
||||||
|
// Mode 0 = Notifications stream, 所有已认证的 WebSocket 连接都会加入这个流
|
||||||
|
onlineCount, err := nk.StreamCount(0, "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to get stream count: %v", err)
|
||||||
|
onlineCount = inGameCount // 降级为比赛中玩家数
|
||||||
|
}
|
||||||
|
|
||||||
|
if onlineCount < 1 {
|
||||||
|
onlineCount = 1 // 至少自己在线
|
||||||
|
}
|
||||||
|
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
"online_count": onlineCount,
|
"online_count": onlineCount,
|
||||||
"match_count": len(matches),
|
"match_count": len(matches),
|
||||||
|
|||||||
@ -55,6 +55,7 @@ func (s *PoisonStrategy) Use(state *core.GameState, user *core.Player, ctx ItemC
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
target.Poisoned = true
|
target.Poisoned = true
|
||||||
|
target.PoisonSteps = 0
|
||||||
ctx.Logic.BroadcastEvent(core.GameEvent{
|
ctx.Logic.BroadcastEvent(core.GameEvent{
|
||||||
Type: "item", PlayerID: user.UserID, PlayerName: user.Username, TargetID: target.UserID, TargetName: target.Username, ItemID: "poison",
|
Type: "item", PlayerID: user.UserID, PlayerName: user.Username, TargetID: target.UserID, TargetName: target.Username, ItemID: "poison",
|
||||||
Message: fmt.Sprintf("☠️ %s 中毒了!", target.Username),
|
Message: fmt.Sprintf("☠️ %s 中毒了!", target.Username),
|
||||||
@ -80,14 +81,6 @@ func (s *ShieldStrategy) Use(state *core.GameState, user *core.Player, ctx ItemC
|
|||||||
type SkipStrategy struct{}
|
type SkipStrategy struct{}
|
||||||
|
|
||||||
func (s *SkipStrategy) Use(state *core.GameState, user *core.Player, ctx ItemContext) bool {
|
func (s *SkipStrategy) Use(state *core.GameState, user *core.Player, ctx ItemContext) bool {
|
||||||
if user.Character == "elephant" {
|
|
||||||
ctx.Logger.Info("Elephant refused skip")
|
|
||||||
ctx.Logic.BroadcastEvent(core.GameEvent{
|
|
||||||
Type: "ability", PlayerID: user.UserID, PlayerName: user.Username, ItemID: "skip",
|
|
||||||
Message: "🐘 大象无法使用该道具!",
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
user.SkipTurn = true
|
user.SkipTurn = true
|
||||||
user.Shield = true
|
user.Shield = true
|
||||||
ctx.Logic.BroadcastEvent(core.GameEvent{
|
ctx.Logic.BroadcastEvent(core.GameEvent{
|
||||||
|
|||||||
@ -65,6 +65,10 @@ func (m *MockGameLogic) BroadcastEvent(event core.GameEvent) {
|
|||||||
m.LastEvent = &event
|
m.LastEvent = &event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockGameLogic) SendPrivateEvent(targetID string, event core.GameEvent) {
|
||||||
|
m.LastEvent = &event
|
||||||
|
}
|
||||||
|
|
||||||
// --- Helper ---
|
// --- Helper ---
|
||||||
|
|
||||||
func createTestContext(logic GameLogic) ItemContext {
|
func createTestContext(logic GameLogic) ItemContext {
|
||||||
@ -217,8 +221,11 @@ func TestSkip(t *testing.T) {
|
|||||||
user.Character = "elephant"
|
user.Character = "elephant"
|
||||||
user.SkipTurn = false
|
user.SkipTurn = false
|
||||||
consumed = strategy.Use(state, user, ctx)
|
consumed = strategy.Use(state, user, ctx)
|
||||||
if consumed {
|
if !consumed {
|
||||||
t.Error("Elephant should refuse skip")
|
t.Error("Elephant should now be able to use skip")
|
||||||
|
}
|
||||||
|
if !user.SkipTurn {
|
||||||
|
t.Error("Elephant skip turn should be true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,8 +93,9 @@ func (e *GameEngine) ApplyDamage(state *core.GameState, target *core.Player, amo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if target.HP < 0 {
|
if target.HP <= 0 {
|
||||||
target.HP = 0
|
target.HP = 0
|
||||||
|
state.LastDeadPlayerID = target.UserID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -226,11 +226,12 @@ func TestItem_Skip_Effect(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestItem_Skip_ElephantCannotUse(t *testing.T) {
|
func TestItem_Skip_ElephantCanUse(t *testing.T) {
|
||||||
RunScenario(t, GameScenario{
|
RunScenario(t, GameScenario{
|
||||||
Name: "好人卡-大象无法使用",
|
Name: "好人卡-大象现在可以使用",
|
||||||
Players: []PlayerSetup{
|
Players: []PlayerSetup{
|
||||||
{ID: "elephant", Character: "elephant", HP: 5},
|
{ID: "elephant", Character: "elephant", HP: 5},
|
||||||
|
{ID: "p2", Character: "dog", HP: 4},
|
||||||
},
|
},
|
||||||
Grid: []CellSetup{
|
Grid: []CellSetup{
|
||||||
{Index: 0, Type: "item", ItemID: "skip"},
|
{Index: 0, Type: "item", ItemID: "skip"},
|
||||||
@ -239,8 +240,8 @@ func TestItem_Skip_ElephantCannotUse(t *testing.T) {
|
|||||||
{Type: "move", PlayerID: "elephant", Value: 0},
|
{Type: "move", PlayerID: "elephant", Value: 0},
|
||||||
},
|
},
|
||||||
Checks: []ScenarioCheck{
|
Checks: []ScenarioCheck{
|
||||||
{PlayerID: "elephant", Field: "skip_turn", Expected: false, Message: "大象无法使用好人卡"},
|
{PlayerID: "elephant", Field: "skip_turn", Expected: true, Message: "大象现在应该可以使用好人卡"},
|
||||||
{PlayerID: "elephant", Field: "shield", Expected: false, Message: "大象不应该获得护盾"},
|
{PlayerID: "elephant", Field: "shield", Expected: true, Message: "大象现在应该获得护盾"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -488,18 +489,18 @@ func TestCharacter_Dog_MagnifierAbility(t *testing.T) {
|
|||||||
}
|
}
|
||||||
state.Grid[99].Type = "bomb" // 放一个未揭示的炸弹
|
state.Grid[99].Type = "bomb" // 放一个未揭示的炸弹
|
||||||
|
|
||||||
// 直接设置 GlobalTurnCount 为 5,下一次操作将使其变为 6
|
// 狗狗独立步数设为 5,下一次操作将使其变为 6
|
||||||
state.GlobalTurnCount = 5
|
state.Players["dog"].DogStepCount = 5
|
||||||
|
|
||||||
// 狗狗操作,GlobalTurnCount 变为 6,6 % 6 == 0,应该触发
|
// 狗狗操作,DogStepCount 变为 6,6 % 6 == 0,应该触发
|
||||||
engine.HandleMove(state, "dog", 0)
|
engine.HandleMove(state, "dog", 0)
|
||||||
|
|
||||||
t.Logf("操作后 GlobalTurnCount=%d, RevealedCells=%d",
|
t.Logf("操作后 DogStepCount=%d, RevealedCells=%d",
|
||||||
state.GlobalTurnCount, len(state.Players["dog"].RevealedCells))
|
state.Players["dog"].DogStepCount, len(state.Players["dog"].RevealedCells))
|
||||||
|
|
||||||
// 检查是否触发了放大镜
|
// 检查是否触发了放大镜
|
||||||
if len(state.Players["dog"].RevealedCells) == 0 {
|
if len(state.Players["dog"].RevealedCells) == 0 {
|
||||||
t.Errorf("狗狗应该在 GlobalTurnCount=6 时触发放大镜能力")
|
t.Errorf("狗狗应该在 DogStepCount=6 时触发放大镜能力")
|
||||||
} else {
|
} else {
|
||||||
t.Logf("狗狗放大镜触发成功,揭示了 %d 个格子", len(state.Players["dog"].RevealedCells))
|
t.Logf("狗狗放大镜触发成功,揭示了 %d 个格子", len(state.Players["dog"].RevealedCells))
|
||||||
}
|
}
|
||||||
@ -667,6 +668,30 @@ func TestGameFlow_GameOver(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGameFlow_DrawLastManStanding(t *testing.T) {
|
||||||
|
engine, state := createScenarioState(GameScenario{
|
||||||
|
Players: []PlayerSetup{
|
||||||
|
{ID: "p1", Character: "dog", HP: 1},
|
||||||
|
{ID: "p2", Character: "cat", HP: 1},
|
||||||
|
},
|
||||||
|
Grid: []CellSetup{
|
||||||
|
{Index: 0, Type: "item", ItemID: "lightning"}, // 闪电对所有人造成1点伤害
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// p1 使用闪电,所有人同时死亡
|
||||||
|
// 按照逻辑,闪电会依次调用 ApplyDamage 给 p1, p2
|
||||||
|
// p2 是最后一个被处理的(也是最后一个 HP 归零的),所以应该是 LastDeadPlayerID
|
||||||
|
engine.HandleMove(state, "p1", 0)
|
||||||
|
|
||||||
|
if state.WinnerID != "p2" {
|
||||||
|
t.Errorf("平局时由于p2是处理列表最后一个死亡的,应该获胜。got winner=%s", state.WinnerID)
|
||||||
|
}
|
||||||
|
if state.GameStarted {
|
||||||
|
t.Error("游戏应该结束")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGameFlow_SafeAreaExpansion(t *testing.T) {
|
func TestGameFlow_SafeAreaExpansion(t *testing.T) {
|
||||||
engine, state := createScenarioState(GameScenario{
|
engine, state := createScenarioState(GameScenario{
|
||||||
Players: []PlayerSetup{
|
Players: []PlayerSetup{
|
||||||
|
|||||||
@ -88,7 +88,7 @@ func (e *GameEngine) CheckGameOver(state *core.GameState) bool {
|
|||||||
if len(alive) == 1 {
|
if len(alive) == 1 {
|
||||||
winnerID = alive[0]
|
winnerID = alive[0]
|
||||||
} else if len(alive) == 0 {
|
} else if len(alive) == 0 {
|
||||||
winnerID = "draw"
|
winnerID = state.LastDeadPlayerID
|
||||||
}
|
}
|
||||||
state.WinnerID = winnerID
|
state.WinnerID = winnerID
|
||||||
state.GameStarted = false
|
state.GameStarted = false
|
||||||
@ -102,7 +102,8 @@ func (e *GameEngine) CheckGameOver(state *core.GameState) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if winnerPlayer != nil && winnerPlayer.RealUserID > 0 {
|
if winnerPlayer != nil && winnerPlayer.RealUserID > 0 {
|
||||||
config.SettleGameWithBackend(e.Logger, winnerPlayer.RealUserID, winnerPlayer.Ticket, "", true, 100)
|
// 分数参数暂时传宝箱数,后端奖励由配置决定
|
||||||
|
config.SettleGameWithBackend(e.Logger, winnerPlayer.RealUserID, winnerPlayer.Ticket, "", true, winnerPlayer.ChestCount)
|
||||||
} else {
|
} else {
|
||||||
e.Logger.Error("Winner player %s has no RealUserID, cannot settle", winnerID)
|
e.Logger.Error("Winner player %s has no RealUserID, cannot settle", winnerID)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ func createTestEngine() (*GameEngine, *core.GameState) {
|
|||||||
charMgr := characters.NewCharacterManager(nil)
|
charMgr := characters.NewCharacterManager(nil)
|
||||||
itemMgr := items.NewItemManager()
|
itemMgr := items.NewItemManager()
|
||||||
|
|
||||||
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr)
|
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr, nil, nil)
|
||||||
|
|
||||||
// Create simplified state
|
// Create simplified state
|
||||||
p1 := &core.Player{UserID: "p1", Username: "P1", HP: 4, MaxHP: 4, Character: "dog", RevealedCells: make(map[int]string)}
|
p1 := &core.Player{UserID: "p1", Username: "P1", HP: 4, MaxHP: 4, Character: "dog", RevealedCells: make(map[int]string)}
|
||||||
|
|||||||
@ -81,7 +81,7 @@ func createScenarioState(scenario GameScenario) (*GameEngine, *core.GameState) {
|
|||||||
dispatcher := &MockDispatcher{}
|
dispatcher := &MockDispatcher{}
|
||||||
charMgr := characters.NewCharacterManager(nil)
|
charMgr := characters.NewCharacterManager(nil)
|
||||||
itemMgr := items.NewItemManager()
|
itemMgr := items.NewItemManager()
|
||||||
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr)
|
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr, nil, nil)
|
||||||
|
|
||||||
// 创建玩家
|
// 创建玩家
|
||||||
players := make(map[string]*core.Player)
|
players := make(map[string]*core.Player)
|
||||||
@ -406,7 +406,7 @@ func TestScenario_SafeAreaExpansion(t *testing.T) {
|
|||||||
dispatcher := &MockDispatcher{}
|
dispatcher := &MockDispatcher{}
|
||||||
charMgr := characters.NewCharacterManager(nil)
|
charMgr := characters.NewCharacterManager(nil)
|
||||||
itemMgr := items.NewItemManager()
|
itemMgr := items.NewItemManager()
|
||||||
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr)
|
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr, nil, nil)
|
||||||
|
|
||||||
// 创建简单网格测试扩散
|
// 创建简单网格测试扩散
|
||||||
// 布局 (3x3):
|
// 布局 (3x3):
|
||||||
|
|||||||
38
tempo/tempo-config.yaml
Normal file
38
tempo/tempo-config.yaml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
server:
|
||||||
|
http_listen_port: 3200
|
||||||
|
|
||||||
|
distributor:
|
||||||
|
receivers:
|
||||||
|
otlp:
|
||||||
|
protocols:
|
||||||
|
grpc:
|
||||||
|
endpoint: 0.0.0.0:4317
|
||||||
|
http:
|
||||||
|
endpoint: 0.0.0.0:4318
|
||||||
|
|
||||||
|
ingester:
|
||||||
|
trace_idle_period: 10s
|
||||||
|
max_block_bytes: 1_000_000
|
||||||
|
max_block_duration: 5m
|
||||||
|
|
||||||
|
compactor:
|
||||||
|
compaction:
|
||||||
|
compaction_window: 1h
|
||||||
|
max_block_bytes: 100_000_000
|
||||||
|
block_retention: 48h
|
||||||
|
compacted_block_retention: 10m
|
||||||
|
|
||||||
|
storage:
|
||||||
|
trace:
|
||||||
|
backend: local
|
||||||
|
local:
|
||||||
|
path: /var/tempo/traces
|
||||||
|
wal:
|
||||||
|
path: /var/tempo/wal
|
||||||
|
|
||||||
|
metrics_generator:
|
||||||
|
registry:
|
||||||
|
external_labels:
|
||||||
|
source: tempo
|
||||||
|
storage:
|
||||||
|
path: /var/tempo/generator/wal
|
||||||
Loading…
x
Reference in New Issue
Block a user