以开发一个井字棋游戏,游戏ID为2为例。文档只适合开发双人对战游戏,如果是其他人数的游戏需要修改一些如创建比赛对局的逻辑。

后端

1.创建InitState

InitState是一个游戏对局的初始状态,对于井字棋来说就是空的棋盘
新建文件gomoku_server\json\game_init_state\tic_tac_toe.json,内容如下

  1. {
  2. "chessboard": [
  3. [0,0,0],
  4. [0,0,0],
  5. [0,0,0]
  6. ]
  7. }

2.修改games.go文件

  • 修改gomoku_server\models\games.go中的ReadInitState函数,这个函数是用来读取创建的InitState
  1. func ReadInitState(gameID uint) string {
  2. switch gameID {
  3. case 1:
  4. {
  5. ...
  6. }
  7. case 2:
  8. {
  9. // 读取井字棋对局的初始状态
  10. data, err := ioutil.ReadFile(env.GetGameInitStateFilePath("tic_tac_toe.json"))
  11. if err != nil {
  12. panic(err)
  13. }
  14. return string(data)
  15. }
  16. default:
  17. ...
  18. }
  19. }
  • 修改gomoku_server\models\games.go中的initGame函数,这个函数是用来初始化GAMES

这里面的参数都应该是可以通过配置文件配置的,这里使用硬编码只是演示
如何通过配置文件配置参考五子棋的实现

  1. func initGame() {
  2. gomokuConfig := env.ReadGomokuGameConfig()
  3. GAMES = map[uint]*_game{
  4. 1: {
  5. ...
  6. },
  7. 2: {
  8. ID: 2,
  9. Name: "井字棋",
  10. PlayerCount: 2,
  11. DefaultTimeLimitNs: 1000 * 1000 * 1000,
  12. DefaultMemoryLimitBytes: 512 * 1024 * 1024,
  13. InitState: ReadInitState(2),
  14. JudgeTimeoutSec: 60 * 20,
  15. RankDurationSec: 60 * 60 * 24,
  16. },
  17. }
  18. }

ID是游戏ID
Name是游戏名称
PlayerCount是玩家数量
DefaultTimeLimitNs是默认的程序运行时间限制,单位纳秒
DefaultMemoryLimitBytes是默认的程序运行内存限制,单位字节
InitState是井字棋对局的初始状态
JudgeTimeoutSec是评测服务超时的时间,如果某场对局评测服务超时没有返回,对局会重新评测
RankDurationSec天梯比赛的周期时间

评测端

1.增加井字棋裁判

创建文件jd4/gomoku/tic_tac_toe_referee.py,内容如下,代码逻辑参考注释

  1. from gomoku.referee import Output
  2. from gomoku import referee
  3. from typing import Tuple, List
  4. import json
  5. class ticTacToeReferee(referee.Referee):
  6. def __init__(self, players: List[referee.Player], time_limit_ns: int, memory_limit_bytes: int):
  7. self.player_count = 2
  8. if len(players) != self.player_count:
  9. raise Exception(
  10. "length of players:{} is not equal to player_count:{}".format(len(players), self.player_count))
  11. super().__init__(players, time_limit_ns, memory_limit_bytes)
  12. self.chessboard = [[0 for i in range(3)] for j in range(3)]
  13. # 两名玩家得分
  14. self.score = [0, 0]
  15. # 记录当前运行的玩家
  16. self.player_index = 0
  17. def get_first_player(self) -> int:
  18. # 对局最开始会调用这个方法获得第一个操作的玩家
  19. # 返回的是players的下标,从0开始
  20. return 0
  21. def get_winner(self):
  22. for i in range(3):
  23. if self.chessboard[i][0] != 0:
  24. if self.chessboard[i][0] == self.chessboard[i][1] and self.chessboard[i][1] == self.chessboard[i][2]:
  25. return self.chessboard[i][0]
  26. if self.chessboard[0][i] != 0:
  27. if self.chessboard[0][i] == self.chessboard[1][i] and self.chessboard[1][i] == self.chessboard[2][i]:
  28. return self.chessboard[0][i]
  29. if self.chessboard[0][0] != 0:
  30. if self.chessboard[0][0] == self.chessboard[1][1] and self.chessboard[1][1] == self.chessboard[2][2]:
  31. return self.chessboard[0][0]
  32. if self.chessboard[0][2] != 0:
  33. if self.chessboard[0][2] == self.chessboard[1][1] and self.chessboard[1][1] == self.chessboard[2][0]:
  34. return self.chessboard[0][2]
  35. for i in range(3):
  36. for j in range(3):
  37. if self.chessboard[i][j] == 0:
  38. return 0
  39. # 平局
  40. return -1
  41. def judge(self, output: Output) -> Tuple[str, int, list, int]:
  42. # 每次玩家运行输出后会调用judge方法
  43. # 返回值分别是 序列化后的棋局状态文本,本轮状态,分数列表,下一个操作的玩家下标
  44. r = int
  45. c = int
  46. try:
  47. r = output.read_int()
  48. c = output.read_int()
  49. except Exception as e:
  50. # 输出格式错误
  51. self.score[(self.player_index + 1) % self.player_count] = 2
  52. return json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_OUTPUT_INVALID, self.score, 0
  53. if r < 0 or r >= 3 or c < 0 or c >= 3:
  54. # 落子超过范围,犯规
  55. self.score[(self.player_index + 1) % self.player_count] = 2
  56. return json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_PLAYER_OPERATION_INVALID, self.score, 0
  57. if self.chessboard[r][c] != 0:
  58. # 落子位置非空,犯规
  59. self.score[(self.player_index + 1) % self.player_count] = 2
  60. return json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_PLAYER_OPERATION_INVALID, self.score, 0
  61. self.chessboard[r][c] = self.player_index + 1
  62. winner = self.get_winner()
  63. if winner == 0:
  64. self.player_index = (self.player_index + 1) % self.player_count
  65. return json.dumps(
  66. {"chessboard": self.chessboard}), referee.MATCH_STATE_CONTINUE, self.score, self.player_index
  67. elif winner == -1:
  68. # 平局
  69. self.score[0] = 1
  70. self.score[1] = 1
  71. return json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_END, self.score, 0
  72. else:
  73. self.score[winner - 1] = 2
  74. return json.dumps({"chessboard": self.chessboard}), referee.MATCH_STATE_END, self.score, 0
  75. def judge_error(self) -> Tuple[str, list]:
  76. # 玩家由于运行超时,内存超限等原因导致无法judge时,会调用judge_error
  77. # 返回值分别是 序列化后的棋局状态文本,分数列表,
  78. self.score[(self.player_index + 1) % self.player_count] = 2
  79. return json.dumps({"chessboard": self.chessboard}), self.score
  80. def get_input(self) -> str:
  81. # 每次玩家运行前会调用这个函数获得玩家的输入
  82. input_str = str(self.player_index + 1) + "\n"
  83. for i in range(3):
  84. for j in range(3):
  85. input_str += str(self.chessboard[i][j]) + " "
  86. input_str += "\n"
  87. return input_str

2.修改judge.py

将jd4/judge.py文件改成如下内容,judge时对id为2的游戏对局调用井字棋裁判

  1. async def judge():
  2. ...
  3. if gameID == 1:
  4. r = GomokuReferee(
  5. ...
  6. elif gameID == 2:
  7. r = ticTacToeReferee(
  8. [Player(0, player["CodeContent"]["Language"],
  9. player["CodeContent"]["Content"]) for player in players],
  10. time_limit_ns,
  11. memory_limit_bytes)

前端

1.修改constants.js文件

修改 gomoku_web\src\constants.js 中的GAMES变量如下

  1. const GAMES = {
  2. 1: {
  3. Name: "五子棋",
  4. ...
  5. },
  6. 2:{
  7. Name:"井字棋",
  8. PlayerCount:2,
  9. Players: {
  10. 1:{
  11. Name:"X棋"
  12. },
  13. 2:{
  14. Name:"O棋"
  15. }
  16. }
  17. }
  18. };

2是游戏的唯一ID
Name:游戏的展示名字
PlayerCount:玩家数量
Players:从1开始,每个玩家的Name是玩家的角色

2.编写井字棋棋盘显示组件

创建gomoku_web\src\components\TicTacToeChessboard.js内容如下
这个组件只是简单的演示,显示了棋盘每个位置的值
image.png

  1. import React from "react";
  2. const TicTacToeChessboard = (props) => {
  3. if (props.detail == null) {
  4. return (
  5. <div>
  6. </div>
  7. )
  8. }
  9. const chessboard = JSON.parse(props.detail).chessboard;
  10. return (
  11. <div>
  12. {
  13. chessboard.map((row => (
  14. <div>
  15. {
  16. row.map(chess => (
  17. chess
  18. ))
  19. }
  20. <br/>
  21. </div>
  22. )))
  23. }
  24. </div>
  25. )
  26. };
  27. export default TicTacToeChessboard;

3.修改PageMatch.js

将gomoku_web\src\PageMatch.js修改为如下内容,使用井字棋棋盘的显示组件

  1. ...
  2. {
  3. gameID === 1 ? (
  4. <div style={{textAlign: "center"}}>
  5. <GomokuChessboard
  6. ...
  7. </div>
  8. ) : null
  9. }
  10. {
  11. gameID===2?(
  12. <div style={{textAlign: "center"}}>
  13. <TicTacToeChessboard
  14. detail={match.States[displayIndex - 1].Detail}/>
  15. </div>
  16. ):null
  17. }
  18. ...