因文档更新并不十分及时, 若文档与代码有出入, 以代码为准. (咕咕咕咕...
简单的代码对战游戏, 受到 generals.io 以及知乎上 Color Fight 启发 . Go fight!
游戏中兵力即 Cell 中的数值, 两个不同阵营的兵力交战时, 结果为相减 .
游戏中有 兵营(barback)、据点(portal)、障碍物(barrier)、基地(base) 四种建筑物
兵营: 间隔较小轮数增加在其中的兵力
据点: 提升据点内兵力的实力 (即在其中的兵力相当于乘以一个因子, 出了据点即失效)
障碍物: 无法通过与摧毁之地
基地: 每个玩家仅一个, 被摧毁即失败. (被占领方所拥有的Cell会归占领方所有)
都为最新版
go:(可能需要翻墙)
go get github.com/labstack/echo
go get github.com/go-sql-driver/mysql
go get golang.org/x/net/websocket
python:
pip3 install requests
(如果想用Chrome打开前端展示页面, Windows可能需要设置代码里 gamePlayer1.py:11 chromepath变量的值)
在 go/src
目录下新建文件 config.txt
内容格式为 dbuser;dbpass;dbtablename;web_server_port
(
数据库结构:
表名 userinfo
id int(11) AUTO_INCREMENT
username text utf8mb4_unicode_ci
password text utf8mb4_unicode_ci
email text utf8mb4_unicode_ci
status int(11) 0->未审核 1->审核通过
CreateAt datetime CURRENT_TIMESTAMP
UpdateAt datetime CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP
git clone https://github.com/userpro/CodeFight.git
cd CodeFight/go/src
go run main.go
cd example
python3 cleaner.py
# 如果需要启动多个 player 测试, 修改 cleaner.py:6 的 playernum 数量和 cleaner2.py:14 的roomtoken(需要先启动 cleaner.py 获得)
参考 go/src/example/CodeWar/Utils.py
简单将API封装了一个 class , 简单的主体框架可参考 go/src/example/template.py
, 也可以参考本人写的一个智障障bot go/src/example/cleaner.py (cleaner2.py是为了演示对战)
更多游戏环境参数的设置在 go/src/fight/config.go
中
如果基于 go/src/example/CodeWar/Utils.py
开发bot, 请多留意源码中的注释, 欢迎对其进行二次开发.
维护一个自己的map, 定期query来更新自己的map. 在对战过程中, 你可能会丢失当前Cell, 这时候需要map来寻找下一个可move的Cell.
Query 策略和 Move 策略可以分开, 以免出现激烈对战的时候, bot因为丢失Cell而策略失效. (由于query只能查询自己拥有的Cell周围的情况)
当出现bot策略失效时, 可以直接重启bot程序, 支持断线重连.
地图分两层 m1
, m2
每个格子就叫Cell吧. (坐标均从左上角开始 包括返回值)
m1
存放Cell数值
m2
存放Cell类型 (低8位有效)
对于m2
:
/*
地形 Cell Type
每个Cell Type唯一 不会重叠
*/
_space_ = 0x00 // 000- ---- 空地
_base_ = 0x20 // 001- ---- 基地(一个玩家仅一个, 即初始位置)
_barback_ = 0x40 // 010- ---- 军营(一个地图有随机个, 需要抢占, 每回合可加一个单位兵力)
_portal_ = 0x60 // 011- ---- 据点(一个地图有随机个, 需要抢占, 兵力在里面可提高防御力)
_barrier_ = 0x80 // 100- ---- 障碍(不可经过, 不可占领)
/* 阵营 User Id */
_system_ = 0x00 // ---0 0000 地图默认id
_visitor_ = 0x01 // ---0 0001 玩家默认id(加入游戏后, 玩家id会从此开始往后递增分配, 每个玩家id唯一)
_user_mask_ = 0x1f // 0001 1111
_type_mask_ = 0xe0 // 1110 0000
保证每个返回值都一定会有 message 和 status 两项
无特殊说明 status 返回 1 是成功, 0 是失败. (失败原因在 message 里)
注意: 优先判断 status , 在 status 为 0 时, 其他 json key 不一定存在 ( message 一定存在)
e.POST( "/user", register)
e.GET( "/user", login )
e.DELETE("/user", logout )
e.GET( "/room", query )
e.POST( "/room", join )
e.PUT( "/room", move )
e.DELETE("/room", leave )
e.GET("/room/start", isStart)
e.GET("/room/scoreboard", getScoreBoard)
名称 | 说明 |
---|---|
功能 | 注册( register ) |
请求方法 | POST |
URL | /user |
参数 | username=XXX&password=XXX&email=XXX |
示例 | /user?username=hi&password=pro&email=[email protected] |
返回值 | { "message": "XXX", "status": 1 } |
返回值说明 | status 为 1 仅表示信息注册成功, 需要审核通过才可以登录. |
名称 | 说明 |
---|---|
功能 | 登录 ( login ) |
请求方法 | GET |
URL | /user |
参数 | username=XXX&password=XXX |
示例 | /user?username=hi&password=pro |
返回值 | { "usertoken": "XXXX", "message": "XXX", "status": 1 } |
返回值说明 | 仅 status 为 1 时, 才会有 usertoken 项, 表示登录成功 当 status 为 2 时, 表示已经登录过, 为在线状态 |
名称 | 说明 |
---|---|
功能 | 登出 (logout) |
请求方法 | DELETE |
URL | /user |
参数 | usertoken=XXX |
示例 | /user?usertoken=XXX |
返回值 | 无 ( HTTP状态码为 NoContent 204 ) |
名称 | 说明 |
---|---|
功能 | 加入或者创建房间 ( join ) |
请求方法 | POST |
URL | /room |
参数 | 加入房间: roomtoken=XXX 创建房间: playernum=X&row=X&col=X |
示例 | 加入房间: /room?roomtoken=XXXX 创建房间: /room?playernum=X&row=X&col=X |
返回值 | { "id": X, "usertoken": "XXX", "roomtoken": "XXX", "playernum": "XXX", "row": XX, "col":XX, "barback": XX, "portal": XX, "barrier":XX, "message": "XXX", "status": X } |
返回值说明 | 返回所加入房间的信息, id为本局游戏你的id, barback 兵营数量, portal 据点数量, barrier 障碍物数量 |
名称 | 说明 |
---|---|
功能 | 离开房间 (leave) |
请求方法 | DELETE |
URL | /room |
参数 | usertoken=XXX |
示例 | /room?usertoken=XXX |
返回值 | { "message": "XXX", "status": 1 } |
名称 | 说明 |
---|---|
功能 | 查询游戏中对应 Cell 周围的信息 (query) |
请求方法 | GET |
URL | /room |
参数 | { "usertoken": "XXX", "roomtoken": "XXX", "loc": { "x": 1, "y": 3 } } |
返回值 | { "eyeshot": { "m1": [n][n], "m2": [n][n] }, "status": 1 } |
返回值说明 | m1 m2 分别指代地图的两个图层, size 一致, 标示了以 (x, y) 为中心的一个矩阵的视野, 具体矩阵的大小以实际返回为准, 可能会有调整, 对于超出地图的部分的值在m1中以 -1 填充 ( |
名称 | 说明 |
---|---|
功能 | 移动地图上指定 Cell 的兵力 (move) |
请求方法 | PUT |
URL | /room |
参数 | { "usertoken": "XXX", "roomtoken": "XXX", "radio": 1, "direction": 1, "loc": { "x": 1, "y": 2 } } |
参数说明 | radio: 1 -> all 调动 (x, y) 所有兵力 2 -> half 调动 (x, y) 1/2的兵力 3 -> quarter 调动 (x, y) 1/4的兵力 direction: 1 -> (x, y) => (x - 1, y) 2 -> (x, y) => (x, y + 1) 3 -> (x, y) => (x + 1, y) 4 -> (x, y) => (x, y - 1) 注意: loc的 (x, y) 必须是属于你的 Cell 才能查询 |
返回值 | { "length": 1, "status": 1 } |
返回值说明 | length 是目前操作序列的长度 |
名称 | 说明 |
---|---|
功能 | 查询游戏是否开始 (isStart) |
请求方法 | GET |
URL | /room/start |
参数 | usertoken=XXX&roomtoken=XXX |
示例 | /room/start?usertoken=XXX&roomtoken=XXX |
返回值 | { "x": 1, "y": 2, "row": 30, "col": 30, "status": 1 } |
返回值说明 | x, y 为你初始坐标, 也是你的Base坐标 row, col 即 map 尺寸 |
名称 | 说明 |
---|---|
功能 | 获取当前房间内所有玩家的得分详情 (getScoreBoard) |
请求方法 | GET |
URL | /room/scoreboard |
参数 | roomtoken=XXX |
示例 | /room/scoreboard?roomtoken=XXX |
返回值 | { "scoreboard": { "username1": score1, "username2": score2, .... }, "status": 1 } |
返回值说明 | scoreboard 是一个用户名和其得分的键值对 (注意: 请不要频繁调用, 会拖慢游戏进程) |
路径: go/src/public/view.html
仅前端展示页面的数据接口, 可定制自己的前端展示页面 (直接替换view.html, 不要改动文件名).
流程如下:
- 客户端 send => roomtoken到服务器 (注意 token 是通过模版标签
{{.}}
嵌入到页面的) - 客户端 receive <=游戏基本信息A( json格式数据)
- 客户端 receive <= 每次操作B( json格式数据)
A:
{
type: 1, // 拉取地图信息 只在最开始出现一次
value: {
time: 100, // 游戏总时长 (秒)
col: 30,
row: 30,
m1: [default_row][default_col], // default_row default_col是地图总共大小
m2: [default_row][default_col], // 但每个游戏实际使用地图的尺寸是之前row col所指定的
roomtoken: "7ac45ec7b7046c529ac650366700ec50", // roomtoken emmmm 顺便发的
userinfo: [ // 一个obj数组
{
id: 2, // userid 对应地图m2
name: "hipy", // username
score: 2, // 每个user占有的格子数量 前端维护接下来的变化
status: 1 // -1: 离线 0: 等待 1: 游戏中
}
]
}
}
B: (有三种)
{
type: 0 // 游戏结束
}
or
{
type: 2, // 指定点改变
value: {
loc: [ // 一个obj数组
{
x: 1,
y: 2,
m1: 23, // m1[x][y]=23
m2: 34 // m2[x][y]=34
},
{
x: 2,
y: 6,
m1: 3,
m2: 42
},
]
}
}
or
{
type: 3 // 全图m1不为0的Cell加1 (除了barrier)
}
or
{
type: 4 // 全图特殊cell加1 (军营, 基地)
}