Skip to content

liziyi-gh/Splendor

Repository files navigation

璀璨宝石

基本架构

简单的客户端/服务器架构,两者通过基于 TCP 的自定义消息格式通信, TCP 是字节流协议,只负责把一个个字节发送到对方主机,字节的含义由我们自己定义

服务器

服务器负责维护玩家状态信息,包括:

玩家编号
玩家就绪状态
玩家筹码状态
玩家宝石牌状态
玩家贵族牌状态

以及当前可获取的卡牌(下称棋盘)信息:

客户端

客户端需要负责与服务器通信以获取所有玩家状态,棋盘信息,绘制界面并处理用户交互

交互消息格式

API ID玩家编号消息长度(单位为 bytes)保留消息体
4 bytes8 bytes8 bytes8 bytes不定

API ID 是一个 32 位无符号整数

玩家编号是一个 64 位无符号整数

消息长度为 4 + 8 + 8 + 8 + 消息体长度,单位为字节

消息体统一为 json 字符串,可以为空,具体内容见下节

消息类型

API ID = 1: INIT

INIT 消息,客户端启动后向服务器发送的第一条消息,表示新客户端上线, 并且向服务器请求获得自己的玩家编号 具体地:

API ID玩家编号消息长度(单位为 bytes)保留消息体
10280None

API ID = 2: INIT_RESP

INIT RESP 消息是服务器对于 INIT 的回复,回复包含分配给自己的玩家编号, 以及所有其它玩家的玩家编号 具体地

API ID玩家编号消息长度(单位为 bytes)保留消息体
20运行时计算0string

string 是 json 格式字符串,一个示例如下

{
  "allocated_player_id": 3,
  "other_player_id": [1, 2]
}

这样表明客户端分配到的玩家编号为3,以后的消息就要将 3 填入玩家编号字段, 这盘游戏已经有两个玩家了,他们的编号是 1,2

API ID = 3: PLAYER_READY

PLAYER_READY 由客户端在玩家准备就绪时发送给服务器,并由服务器转发给其它玩家, 所以客户端既会发送这条消息又会接受到这条消息 具体地

API ID玩家编号消息长度(单位为 bytes)保留消息体
3运行时计算280None

API ID = 4: GAME_START

所有玩家准备就绪之后由服务器广播给所有玩家,消息体包含全局游戏信息

API ID玩家编号消息长度(单位为 bytes)保留消息体
40运行时计算0string
{
  "players_number": 3,
  "players_sequence": [1, 2, 3],
  "nobles_info": [1, 2, 3, 4],
  "levelOneCards_info": [5, 6, 7, 8],
  "levelTwoCards_info": [9, 10, 11, 12],
  "levelThreeCards_info": [13, 14, 15, 16]
}

API ID = 5: NEW_TURN

新的一个回合,由服务器发给所有玩家,指定该回合进行操作的玩家

{
  "new_turn_player": 1
}

表示下一个进行操作的玩家为 player_id 为 1 的玩家

API ID = 6: PLAYER_OPERATION

表示玩家进行的操作,经服务器检验有效后向所有客户端广播该操作。特别地,服务器以玩家编号为0广播该消息。

API ID玩家编号消息长度(单位为 bytes)保留消息体
6运行时计算运行时计算0string

示例1:

{
  "player_id": 1,
  "operation_type": "get_gems",
  "operation_info": [
    {
    "gems_type": "sapphire",
    "gems_number": 1
    },
    {
    "gems_type": "ruby",
    "gems_number": 1
    },
    {
    "gems_type": "diamond",
    "gems_number": 1
    }
  ]
}

即玩家1选择拿走一个蓝宝石,一个红宝石,一个钻石

示例2:

{
  "player_id": 1,
  "operation_type": "buy_card",
  "operation_info": [
    {
    "card_number": 1
    },
    {
    "gems_type": "sapphire",
    "gems_number": 1
    },
    {
    "gems_type": "ruby",
    "gems_number": 1
    },
    {
    "gems_type": "diamond",
    "gems_number": 1
    }
  ]
}

即玩家1购买卡牌号码为1的卡片

示例3:

{
  "player_id": 1,
  "operation_type": "fold_card",
  "operation_info": [
    {
    "card_number": 1
    }
  ]
}

即玩家1选择盖住1号卡牌

以下为服务器广播:

{
  "player_id": 1,
  "operation_type": "fold_card",
  "operation_info": [
    {
    "card_number": 1
    },
    {
      "golden_number": 1
    }
  ]
}

golden_number 为1则拿走一个黄金,为0则不拿。

示例4:

{
  "player_id": 1,
  "operation_type": "fold_card_unknown",
  "operation_info": [
    {
    "card_level": 1
    "card_number" : 10001
    }
  ]
}

即玩家1选择盖住1级牌库的1张牌

服务器广播会是

{
  "player_id": 1,
  "operation_type": "fold_card_unknown",
  "operation_info": [
    {
    "card_level": 1,
    "card_number" : 10001,
    "golden_number": 1
    }
  ]
}

示例5:

{
  "player_id": 1,
  "operation_type": "discard_gems",
  "operation_info": {
    "diamond": 1,
    "sapphire": 1
  }
}

玩家1 丢弃一个钻石筹码,一个蓝宝石筹码.

API ID = 7: PLAYER_OPERATION_INVALID

用以指示客户端上次发送的操作请求不合法

API ID玩家编号消息长度(单位为 bytes)保留消息体
7运行时计算280None

API ID = 8: NEW_PLAYER

服务器广播告知新玩家加入

API ID玩家编号消息长度(单位为 bytes)保留消息体
8运行时计算280None

API ID = 9: PLAYER_GET_NOBLE

用以传输玩家获得贵族牌信息。具体地,若当前仅可能获得一种贵族牌,服务器广播 该消息(示例1);若当前可能获得数种贵族牌,服务器向该客户端发送消息进行确认 (示例2),客户端使用该消息进行回复(如示例1),服务器经校验后进行广播。

API ID玩家编号消息长度(单位为 bytes)保留消息体
9运行时计算运行时计算0string

示例1:

{
  "player_id": 1,
  "noble_number": [1]
}

服务器发送:服务器广播玩家1获得1号贵族牌; 客户端发送:告知服务器玩家1选择1号贵族牌

示例2:

{
  "player_id": 1,
  "noble_number": [1,2]
}

服务器发送:服务器告知客户端玩家1可选择1号和2号贵族牌并要求回复

API ID = 10: NEW_CARD

服务器广播新卡。

API ID玩家编号消息长度(单位为 bytes)保留消息体
100运行时计算0string

示例:

{
  "card_number": 1
}

API ID = 11: DISCARD_GEMS

客户端执行合法的拿筹码操作后,若当前筹码数大于10,服务端将返回此消息

API ID玩家编号消息长度(单位为 bytes)保留消息体
11运行时计算运行时计算0string

实例:

{
  "number_to_discard": 2
}

代表客户端需要丢弃两个筹码,丢弃的筹码信息通过 API6 通知服务端。

API ID = 12: WINNER

服务器广播胜利玩家并结束该局游戏

API ID玩家编号消息长度(单位为 bytes)保留消息体
12运行时计算280None

交互流程图

@startuml
actor client1 as c1
entity server as s
actor client2 as c2

c1 -> s : INIT
s -> c1 : INIT_RESP
c2 -> s : INIT
s -> c2 : INIT_REP
c1 -> s : PLAYER_READY
s -> c1 : PLAYER_READY
s -> c2 : PLAYER_READY
c2 -> s : PLAYER_READY
s -> c1 : PLAYER_READY
s -> c2 : PLAYER_READY
s -> c1 : GAME_START
s -> c2 : GAME_START
s -> c1 : NEW_TURN(client1 先走)
s -> c2 : NEW_TURN(client1 先走)

@enduml

example_procedure.png

Code Style

一行不宜超过 80 列

永远不要使用 Tab 作为缩进

使用四个空格作为缩进

类名

驼峰命名法,即首字母大写,不使用下划线,如 LevelOneCard

方法名

首单词小写,其后驼峰,如 sendInitMsg

常量

全大写,使用下划线连接,如 PLAYER_READY_API_ID

空行

类方法之间空一行,普通函数空两行

运行

运行服务器

python3 startServer.py

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •