以开发一个井字棋游戏,游戏ID为2为例。文档只适合开发双人对战游戏,如果是其他人数的游戏需要修改一些如创建比赛对局的逻辑。
后端
1.创建InitState
InitState是一个游戏对局的初始状态,对于井字棋来说就是空的棋盘
新建文件gomoku_server\json\game_init_state\tic_tac_toe.json,内容如下
{"chessboard": [[0,0,0],[0,0,0],[0,0,0]]}
2.修改games.go文件
- 修改gomoku_server\models\games.go中的ReadInitState函数,这个函数是用来读取创建的InitState
func ReadInitState(gameID uint) string {switch gameID {case 1:{...}case 2:{// 读取井字棋对局的初始状态data, err := ioutil.ReadFile(env.GetGameInitStateFilePath("tic_tac_toe.json"))if err != nil {panic(err)}return string(data)}default:...}}
- 修改gomoku_server\models\games.go中的initGame函数,这个函数是用来初始化GAMES
这里面的参数都应该是可以通过配置文件配置的,这里使用硬编码只是演示
如何通过配置文件配置参考五子棋的实现
func initGame() {gomokuConfig := env.ReadGomokuGameConfig()GAMES = map[uint]*_game{1: {...},2: {ID: 2,Name: "井字棋",PlayerCount: 2,DefaultTimeLimitNs: 1000 * 1000 * 1000,DefaultMemoryLimitBytes: 512 * 1024 * 1024,InitState: ReadInitState(2),JudgeTimeoutSec: 60 * 20,RankDurationSec: 60 * 60 * 24,},}}
ID是游戏ID
Name是游戏名称
PlayerCount是玩家数量
DefaultTimeLimitNs是默认的程序运行时间限制,单位纳秒
DefaultMemoryLimitBytes是默认的程序运行内存限制,单位字节
InitState是井字棋对局的初始状态
JudgeTimeoutSec是评测服务超时的时间,如果某场对局评测服务超时没有返回,对局会重新评测
RankDurationSec天梯比赛的周期时间
评测端
1.增加井字棋裁判
创建文件jd4/gomoku/tic_tac_toe_referee.py,内容如下,代码逻辑参考注释
from gomoku.referee import Outputfrom gomoku import refereefrom typing import Tuple, Listimport jsonclass ticTacToeReferee(referee.Referee):def __init__(self, players: List[referee.Player], time_limit_ns: int, memory_limit_bytes: int):self.player_count = 2if len(players) != self.player_count:raise Exception("length of players:{} is not equal to player_count:{}".format(len(players), self.player_count))super().__init__(players, time_limit_ns, memory_limit_bytes)self.chessboard = [[0 for i in range(3)] for j in range(3)]# 两名玩家得分self.score = [0, 0]# 记录当前运行的玩家self.player_index = 0def get_first_player(self) -> int:# 对局最开始会调用这个方法获得第一个操作的玩家# 返回的是players的下标,从0开始return 0def get_winner(self):for i in range(3):if self.chessboard[i][0] != 0:if self.chessboard[i][0] == self.chessboard[i][1] and self.chessboard[i][1] == self.chessboard[i][2]:return self.chessboard[i][0]if self.chessboard[0][i] != 0:if self.chessboard[0][i] == self.chessboard[1][i] and self.chessboard[1][i] == self.chessboard[2][i]:return self.chessboard[0][i]if self.chessboard[0][0] != 0:if self.chessboard[0][0] == self.chessboard[1][1] and self.chessboard[1][1] == self.chessboard[2][2]:return self.chessboard[0][0]if self.chessboard[0][2] != 0:if self.chessboard[0][2] == self.chessboard[1][1] and self.chessboard[1][1] == self.chessboard[2][0]:return self.chessboard[0][2]for i in range(3):for j in range(3):if self.chessboard[i][j] == 0:return 0# 平局return -1def judge(self, output: Output) -> Tuple[str, int, list, int]:# 每次玩家运行输出后会调用judge方法# 返回值分别是 序列化后的棋局状态文本,本轮状态,分数列表,下一个操作的玩家下标r = intc = inttry:r = output.read_int()c = output.read_int()except Exception as e:# 输出格式错误self.score[(self.player_index + 1) % self.player_count] = 2return json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_OUTPUT_INVALID, self.score, 0if r < 0 or r >= 3 or c < 0 or c >= 3:# 落子超过范围,犯规self.score[(self.player_index + 1) % self.player_count] = 2return json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_PLAYER_OPERATION_INVALID, self.score, 0if self.chessboard[r][c] != 0:# 落子位置非空,犯规self.score[(self.player_index + 1) % self.player_count] = 2return json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_PLAYER_OPERATION_INVALID, self.score, 0self.chessboard[r][c] = self.player_index + 1winner = self.get_winner()if winner == 0:self.player_index = (self.player_index + 1) % self.player_countreturn json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_CONTINUE, self.score, self.player_indexelif winner == -1:# 平局self.score[0] = 1self.score[1] = 1return json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_END, self.score, 0else:self.score[winner - 1] = 2return json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_END, self.score, 0def judge_error(self) -> Tuple[str, list]:# 玩家由于运行超时,内存超限等原因导致无法judge时,会调用judge_error# 返回值分别是 序列化后的棋局状态文本,分数列表,self.score[(self.player_index + 1) % self.player_count] = 2return json.dumps({"chessboard": self.chessboard}), self.scoredef get_input(self) -> str:# 每次玩家运行前会调用这个函数获得玩家的输入input_str = str(self.player_index + 1) + "\n"for i in range(3):for j in range(3):input_str += str(self.chessboard[i][j]) + " "input_str += "\n"return input_str
2.修改judge.py
将jd4/judge.py文件改成如下内容,judge时对id为2的游戏对局调用井字棋裁判
async def judge():...if gameID == 1:r = GomokuReferee(...elif gameID == 2:r = ticTacToeReferee([Player(0, player["CodeContent"]["Language"],player["CodeContent"]["Content"]) for player in players],time_limit_ns,memory_limit_bytes)
前端
1.修改constants.js文件
修改 gomoku_web\src\constants.js 中的GAMES变量如下
const GAMES = {1: {Name: "五子棋",...},2:{Name:"井字棋",PlayerCount:2,Players: {1:{Name:"X棋"},2:{Name:"O棋"}}}};
2是游戏的唯一ID
Name:游戏的展示名字
PlayerCount:玩家数量
Players:从1开始,每个玩家的Name是玩家的角色
2.编写井字棋棋盘显示组件
创建gomoku_web\src\components\TicTacToeChessboard.js内容如下
这个组件只是简单的演示,显示了棋盘每个位置的值
import React from "react";const TicTacToeChessboard = (props) => {if (props.detail == null) {return (<div></div>)}const chessboard = JSON.parse(props.detail).chessboard;return (<div>{chessboard.map((row => (<div>{row.map(chess => (chess))}<br/></div>)))}</div>)};export default TicTacToeChessboard;
3.修改PageMatch.js
将gomoku_web\src\PageMatch.js修改为如下内容,使用井字棋棋盘的显示组件
...{gameID === 1 ? (<div style={{textAlign: "center"}}><GomokuChessboard...</div>) : null}{gameID===2?(<div style={{textAlign: "center"}}><TicTacToeChessboarddetail={match.States[displayIndex - 1].Detail}/></div>):null}...
