mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-11 03:13:45 +00:00
feat(wire): carry a word's senses on PlayedWord and GameStarted
`message Sense {pos, gloss}`; fields 7 and 9, wire-compatible; `wsapi.Dictionary` gains `Meanings`; looked up once per move.
This commit is contained in:
1 parent
80f216d56b
commit
9e74f6f959
13 files changed
+450
-118
No files matched your search
@@ -171,6 +171,20 @@ message PlayedWord {
|
||||
// the table the chain also has to say whose the other words were, and a seat
|
||||
// id says that without the client matching display names.
|
||||
string player_id = 6;
|
||||
// What the word means, at most five senses. Empty when the dictionary has
|
||||
// no definition for the word; the client then says so rather than hiding
|
||||
// the row's panel, so every word in the chain behaves the same.
|
||||
repeated Sense meanings = 7;
|
||||
}
|
||||
|
||||
// Sense is one definition of a word as Wiktionary gives it: the part of
|
||||
// speech it sits under, in Vietnamese ("danh từ"), empty when the heading was
|
||||
// not one the builder knows, and the definition stripped of markup at at most
|
||||
// 200 characters. Plain text both: the client renders them as text, never as
|
||||
// markup.
|
||||
message Sense {
|
||||
string pos = 1;
|
||||
string gloss = 2;
|
||||
}
|
||||
|
||||
// PlayerSlot is one seat in the room, rendered for one recipient.
|
||||
@@ -235,6 +249,8 @@ message GameStarted {
|
||||
// Whose turn it is. my_turn above says whether it is yours; this says whose
|
||||
// it is when it is not, which a two-player game never had to.
|
||||
string turn_player_id = 8;
|
||||
// The opening word's senses, as PlayedWord.meanings for a played word.
|
||||
repeated Sense opening_meanings = 9;
|
||||
}
|
||||
|
||||
// TurnUpdate follows every turn change and goes to every player, serialized
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
"n
|
||||
"¤
|
||||
|
||||
hòa bìnhbình ×ø©‘3(0°ê:
|
||||
p1Ngư�i chơi 0:
|
||||
p2Khách m�i *(0:
|
||||
p3Khách 2 Bp1
|
||||
p3Khách 2 Bp1J4
|
||||
danh từ'Tình trạng không có chiến tranh.
|
||||
+5
-3
@@ -1,7 +1,9 @@
|
||||
*€
|
||||
|
||||
*É
|
||||
h
|
||||
|
||||
bình yên * binh yên2p2yên Àóù©‘3(@J
|
||||
bình yên * binh yên2p2:6
|
||||
|
||||
tÃnh từ(Yên là nh, không có gì xáo động.:
Xem bình an.yên Àóù©‘3(@J
|
||||
p1Người chơi 0J
|
||||
p2Khách mời *(0J
|
||||
p3Khách 2 Rp3
|
||||
Generated
+173
-88
@@ -1084,7 +1084,11 @@ type PlayedWord struct {
|
||||
// Which seat played it. by_me answers "was this mine"; with four people at
|
||||
// the table the chain also has to say whose the other words were, and a seat
|
||||
// id says that without the client matching display names.
|
||||
PlayerId string `protobuf:"bytes,6,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
||||
PlayerId string `protobuf:"bytes,6,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
||||
// What the word means, at most five senses. Empty when the dictionary has
|
||||
// no definition for the word; the client then says so rather than hiding
|
||||
// the row's panel, so every word in the chain behaves the same.
|
||||
Meanings []*Sense `protobuf:"bytes,7,rep,name=meanings,proto3" json:"meanings,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -1161,6 +1165,70 @@ func (x *PlayedWord) GetPlayerId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PlayedWord) GetMeanings() []*Sense {
|
||||
if x != nil {
|
||||
return x.Meanings
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sense is one definition of a word as Wiktionary gives it: the part of
|
||||
// speech it sits under, in Vietnamese ("danh từ"), empty when the heading was
|
||||
// not one the builder knows, and the definition stripped of markup at at most
|
||||
// 200 characters. Plain text both: the client renders them as text, never as
|
||||
// markup.
|
||||
type Sense struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Pos string `protobuf:"bytes,1,opt,name=pos,proto3" json:"pos,omitempty"`
|
||||
Gloss string `protobuf:"bytes,2,opt,name=gloss,proto3" json:"gloss,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Sense) Reset() {
|
||||
*x = Sense{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[15]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Sense) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Sense) ProtoMessage() {}
|
||||
|
||||
func (x *Sense) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[15]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Sense.ProtoReflect.Descriptor instead.
|
||||
func (*Sense) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{15}
|
||||
}
|
||||
|
||||
func (x *Sense) GetPos() string {
|
||||
if x != nil {
|
||||
return x.Pos
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Sense) GetGloss() string {
|
||||
if x != nil {
|
||||
return x.Gloss
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// PlayerSlot is one seat in the room, rendered for one recipient.
|
||||
//
|
||||
// The recipient's own row is in the list like everybody else's, marked by
|
||||
@@ -1192,7 +1260,7 @@ type PlayerSlot struct {
|
||||
|
||||
func (x *PlayerSlot) Reset() {
|
||||
*x = PlayerSlot{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[15]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1204,7 +1272,7 @@ func (x *PlayerSlot) String() string {
|
||||
func (*PlayerSlot) ProtoMessage() {}
|
||||
|
||||
func (x *PlayerSlot) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[15]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[16]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1217,7 +1285,7 @@ func (x *PlayerSlot) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use PlayerSlot.ProtoReflect.Descriptor instead.
|
||||
func (*PlayerSlot) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{15}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{16}
|
||||
}
|
||||
|
||||
func (x *PlayerSlot) GetPlayerId() string {
|
||||
@@ -1294,7 +1362,7 @@ type PlayerScore struct {
|
||||
|
||||
func (x *PlayerScore) Reset() {
|
||||
*x = PlayerScore{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[16]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[17]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1306,7 +1374,7 @@ func (x *PlayerScore) String() string {
|
||||
func (*PlayerScore) ProtoMessage() {}
|
||||
|
||||
func (x *PlayerScore) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[16]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[17]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1319,7 +1387,7 @@ func (x *PlayerScore) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use PlayerScore.ProtoReflect.Descriptor instead.
|
||||
func (*PlayerScore) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{16}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{17}
|
||||
}
|
||||
|
||||
func (x *PlayerScore) GetPlayerId() string {
|
||||
@@ -1387,14 +1455,16 @@ type GameStarted struct {
|
||||
Players []*PlayerScore `protobuf:"bytes,7,rep,name=players,proto3" json:"players,omitempty"`
|
||||
// Whose turn it is. my_turn above says whether it is yours; this says whose
|
||||
// it is when it is not, which a two-player game never had to.
|
||||
TurnPlayerId string `protobuf:"bytes,8,opt,name=turn_player_id,json=turnPlayerId,proto3" json:"turn_player_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
TurnPlayerId string `protobuf:"bytes,8,opt,name=turn_player_id,json=turnPlayerId,proto3" json:"turn_player_id,omitempty"`
|
||||
// The opening word's senses, as PlayedWord.meanings for a played word.
|
||||
OpeningMeanings []*Sense `protobuf:"bytes,9,rep,name=opening_meanings,json=openingMeanings,proto3" json:"opening_meanings,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GameStarted) Reset() {
|
||||
*x = GameStarted{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[17]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[18]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1406,7 +1476,7 @@ func (x *GameStarted) String() string {
|
||||
func (*GameStarted) ProtoMessage() {}
|
||||
|
||||
func (x *GameStarted) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[17]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[18]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1419,7 +1489,7 @@ func (x *GameStarted) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GameStarted.ProtoReflect.Descriptor instead.
|
||||
func (*GameStarted) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{17}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{18}
|
||||
}
|
||||
|
||||
func (x *GameStarted) GetOpeningWord() string {
|
||||
@@ -1478,6 +1548,13 @@ func (x *GameStarted) GetTurnPlayerId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GameStarted) GetOpeningMeanings() []*Sense {
|
||||
if x != nil {
|
||||
return x.OpeningMeanings
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TurnUpdate follows every turn change and goes to every player, serialized
|
||||
// once per recipient so by_me and my_turn are correct for each.
|
||||
//
|
||||
@@ -1501,7 +1578,7 @@ type TurnUpdate struct {
|
||||
|
||||
func (x *TurnUpdate) Reset() {
|
||||
*x = TurnUpdate{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[18]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[19]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1513,7 +1590,7 @@ func (x *TurnUpdate) String() string {
|
||||
func (*TurnUpdate) ProtoMessage() {}
|
||||
|
||||
func (x *TurnUpdate) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[18]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[19]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1526,7 +1603,7 @@ func (x *TurnUpdate) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use TurnUpdate.ProtoReflect.Descriptor instead.
|
||||
func (*TurnUpdate) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{18}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{19}
|
||||
}
|
||||
|
||||
func (x *TurnUpdate) GetPlayed() *PlayedWord {
|
||||
@@ -1596,7 +1673,7 @@ type MoveRejected struct {
|
||||
|
||||
func (x *MoveRejected) Reset() {
|
||||
*x = MoveRejected{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[19]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[20]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1608,7 +1685,7 @@ func (x *MoveRejected) String() string {
|
||||
func (*MoveRejected) ProtoMessage() {}
|
||||
|
||||
func (x *MoveRejected) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[19]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[20]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1621,7 +1698,7 @@ func (x *MoveRejected) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use MoveRejected.ProtoReflect.Descriptor instead.
|
||||
func (*MoveRejected) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{19}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{20}
|
||||
}
|
||||
|
||||
func (x *MoveRejected) GetReason() RejectReason {
|
||||
@@ -1663,7 +1740,7 @@ type GameOver struct {
|
||||
|
||||
func (x *GameOver) Reset() {
|
||||
*x = GameOver{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[20]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[21]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1675,7 +1752,7 @@ func (x *GameOver) String() string {
|
||||
func (*GameOver) ProtoMessage() {}
|
||||
|
||||
func (x *GameOver) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[20]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[21]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1688,7 +1765,7 @@ func (x *GameOver) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GameOver.ProtoReflect.Descriptor instead.
|
||||
func (*GameOver) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{20}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{21}
|
||||
}
|
||||
|
||||
func (x *GameOver) GetIWon() bool {
|
||||
@@ -1739,7 +1816,7 @@ type PlayerEliminated struct {
|
||||
|
||||
func (x *PlayerEliminated) Reset() {
|
||||
*x = PlayerEliminated{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[21]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[22]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1751,7 +1828,7 @@ func (x *PlayerEliminated) String() string {
|
||||
func (*PlayerEliminated) ProtoMessage() {}
|
||||
|
||||
func (x *PlayerEliminated) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[21]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[22]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1764,7 +1841,7 @@ func (x *PlayerEliminated) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use PlayerEliminated.ProtoReflect.Descriptor instead.
|
||||
func (*PlayerEliminated) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{21}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{22}
|
||||
}
|
||||
|
||||
func (x *PlayerEliminated) GetPlayerId() string {
|
||||
@@ -1814,7 +1891,7 @@ type ServerError struct {
|
||||
|
||||
func (x *ServerError) Reset() {
|
||||
*x = ServerError{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[22]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[23]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1826,7 +1903,7 @@ func (x *ServerError) String() string {
|
||||
func (*ServerError) ProtoMessage() {}
|
||||
|
||||
func (x *ServerError) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[22]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[23]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1839,7 +1916,7 @@ func (x *ServerError) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ServerError.ProtoReflect.Descriptor instead.
|
||||
func (*ServerError) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{22}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{23}
|
||||
}
|
||||
|
||||
func (x *ServerError) GetCode() string {
|
||||
@@ -1866,7 +1943,7 @@ type Pong struct {
|
||||
|
||||
func (x *Pong) Reset() {
|
||||
*x = Pong{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[23]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[24]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1878,7 +1955,7 @@ func (x *Pong) String() string {
|
||||
func (*Pong) ProtoMessage() {}
|
||||
|
||||
func (x *Pong) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[23]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[24]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1891,7 +1968,7 @@ func (x *Pong) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Pong.ProtoReflect.Descriptor instead.
|
||||
func (*Pong) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{23}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{24}
|
||||
}
|
||||
|
||||
func (x *Pong) GetClientTimeMs() int64 {
|
||||
@@ -1942,7 +2019,7 @@ type RoomState struct {
|
||||
|
||||
func (x *RoomState) Reset() {
|
||||
*x = RoomState{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[24]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[25]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1954,7 +2031,7 @@ func (x *RoomState) String() string {
|
||||
func (*RoomState) ProtoMessage() {}
|
||||
|
||||
func (x *RoomState) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[24]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[25]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1967,7 +2044,7 @@ func (x *RoomState) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomState.ProtoReflect.Descriptor instead.
|
||||
func (*RoomState) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{24}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{25}
|
||||
}
|
||||
|
||||
func (x *RoomState) GetRoomCode() string {
|
||||
@@ -2035,7 +2112,7 @@ type ChatMessage struct {
|
||||
|
||||
func (x *ChatMessage) Reset() {
|
||||
*x = ChatMessage{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[25]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[26]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -2047,7 +2124,7 @@ func (x *ChatMessage) String() string {
|
||||
func (*ChatMessage) ProtoMessage() {}
|
||||
|
||||
func (x *ChatMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[25]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[26]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -2060,7 +2137,7 @@ func (x *ChatMessage) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ChatMessage.ProtoReflect.Descriptor instead.
|
||||
func (*ChatMessage) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{25}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{26}
|
||||
}
|
||||
|
||||
func (x *ChatMessage) GetFromMe() bool {
|
||||
@@ -2114,7 +2191,7 @@ type ChatHistory struct {
|
||||
|
||||
func (x *ChatHistory) Reset() {
|
||||
*x = ChatHistory{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[26]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[27]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -2126,7 +2203,7 @@ func (x *ChatHistory) String() string {
|
||||
func (*ChatHistory) ProtoMessage() {}
|
||||
|
||||
func (x *ChatHistory) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[26]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[27]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -2139,7 +2216,7 @@ func (x *ChatHistory) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ChatHistory.ProtoReflect.Descriptor instead.
|
||||
func (*ChatHistory) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{26}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{27}
|
||||
}
|
||||
|
||||
func (x *ChatHistory) GetMessages() []*ChatMessage {
|
||||
@@ -2171,7 +2248,7 @@ type ServerMessage struct {
|
||||
|
||||
func (x *ServerMessage) Reset() {
|
||||
*x = ServerMessage{}
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[27]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[28]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -2183,7 +2260,7 @@ func (x *ServerMessage) String() string {
|
||||
func (*ServerMessage) ProtoMessage() {}
|
||||
|
||||
func (x *ServerMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[27]
|
||||
mi := &file_noitu_v1_game_proto_msgTypes[28]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -2196,7 +2273,7 @@ func (x *ServerMessage) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ServerMessage.ProtoReflect.Descriptor instead.
|
||||
func (*ServerMessage) Descriptor() ([]byte, []int) {
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{27}
|
||||
return file_noitu_v1_game_proto_rawDescGZIP(), []int{28}
|
||||
}
|
||||
|
||||
func (x *ServerMessage) GetPayload() isServerMessage_Payload {
|
||||
@@ -2433,7 +2510,7 @@ const file_noitu_v1_game_proto_rawDesc = "" +
|
||||
"session_id\x18\x01 \x01(\tR\tsessionId\x12!\n" +
|
||||
"\fresume_token\x18\x02 \x01(\tR\vresumeToken\x12)\n" +
|
||||
"\x10protocol_version\x18\x03 \x01(\rR\x0fprotocolVersion\x12+\n" +
|
||||
"\x11accepted_nickname\x18\x04 \x01(\tR\x10acceptedNickname\"\x9e\x01\n" +
|
||||
"\x11accepted_nickname\x18\x04 \x01(\tR\x10acceptedNickname\"\xcb\x01\n" +
|
||||
"\n" +
|
||||
"PlayedWord\x12\x12\n" +
|
||||
"\x04word\x18\x01 \x01(\tR\x04word\x12\x13\n" +
|
||||
@@ -2441,7 +2518,11 @@ const file_noitu_v1_game_proto_rawDesc = "" +
|
||||
"\x06points\x18\x03 \x01(\rR\x06points\x12\x1c\n" +
|
||||
"\tsyllables\x18\x04 \x01(\rR\tsyllables\x12\x14\n" +
|
||||
"\x05typed\x18\x05 \x01(\tR\x05typed\x12\x1b\n" +
|
||||
"\tplayer_id\x18\x06 \x01(\tR\bplayerId\"\xb5\x01\n" +
|
||||
"\tplayer_id\x18\x06 \x01(\tR\bplayerId\x12+\n" +
|
||||
"\bmeanings\x18\a \x03(\v2\x0f.noitu.v1.SenseR\bmeanings\"/\n" +
|
||||
"\x05Sense\x12\x10\n" +
|
||||
"\x03pos\x18\x01 \x01(\tR\x03pos\x12\x14\n" +
|
||||
"\x05gloss\x18\x02 \x01(\tR\x05gloss\"\xb5\x01\n" +
|
||||
"\n" +
|
||||
"PlayerSlot\x12\x1b\n" +
|
||||
"\tplayer_id\x18\x01 \x01(\tR\bplayerId\x12\x12\n" +
|
||||
@@ -2460,7 +2541,7 @@ const file_noitu_v1_game_proto_rawDesc = "" +
|
||||
"eliminated\x18\x05 \x01(\bR\n" +
|
||||
"eliminated\x12\x1c\n" +
|
||||
"\tconnected\x18\x06 \x01(\bR\tconnected\x12\x12\n" +
|
||||
"\x04rank\x18\a \x01(\rR\x04rank\"\xb4\x02\n" +
|
||||
"\x04rank\x18\a \x01(\rR\x04rank\"\xf0\x02\n" +
|
||||
"\vGameStarted\x12!\n" +
|
||||
"\fopening_word\x18\x01 \x01(\tR\vopeningWord\x12)\n" +
|
||||
"\x10current_syllable\x18\x02 \x01(\tR\x0fcurrentSyllable\x12\x17\n" +
|
||||
@@ -2469,7 +2550,8 @@ const file_noitu_v1_game_proto_rawDesc = "" +
|
||||
"\bturn_seq\x18\x05 \x01(\rR\aturnSeq\x12\"\n" +
|
||||
"\rturn_limit_ms\x18\x06 \x01(\rR\vturnLimitMs\x12/\n" +
|
||||
"\aplayers\x18\a \x03(\v2\x15.noitu.v1.PlayerScoreR\aplayers\x12$\n" +
|
||||
"\x0eturn_player_id\x18\b \x01(\tR\fturnPlayerId\"\xc9\x02\n" +
|
||||
"\x0eturn_player_id\x18\b \x01(\tR\fturnPlayerId\x12:\n" +
|
||||
"\x10opening_meanings\x18\t \x03(\v2\x0f.noitu.v1.SenseR\x0fopeningMeanings\"\xc9\x02\n" +
|
||||
"\n" +
|
||||
"TurnUpdate\x12,\n" +
|
||||
"\x06played\x18\x01 \x01(\v2\x14.noitu.v1.PlayedWordR\x06played\x12)\n" +
|
||||
@@ -2572,7 +2654,7 @@ func file_noitu_v1_game_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_noitu_v1_game_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||
var file_noitu_v1_game_proto_msgTypes = make([]protoimpl.MessageInfo, 28)
|
||||
var file_noitu_v1_game_proto_msgTypes = make([]protoimpl.MessageInfo, 29)
|
||||
var file_noitu_v1_game_proto_goTypes = []any{
|
||||
(Difficulty)(0), // 0: noitu.v1.Difficulty
|
||||
(RejectReason)(0), // 1: noitu.v1.RejectReason
|
||||
@@ -2592,19 +2674,20 @@ var file_noitu_v1_game_proto_goTypes = []any{
|
||||
(*ClientMessage)(nil), // 15: noitu.v1.ClientMessage
|
||||
(*Welcome)(nil), // 16: noitu.v1.Welcome
|
||||
(*PlayedWord)(nil), // 17: noitu.v1.PlayedWord
|
||||
(*PlayerSlot)(nil), // 18: noitu.v1.PlayerSlot
|
||||
(*PlayerScore)(nil), // 19: noitu.v1.PlayerScore
|
||||
(*GameStarted)(nil), // 20: noitu.v1.GameStarted
|
||||
(*TurnUpdate)(nil), // 21: noitu.v1.TurnUpdate
|
||||
(*MoveRejected)(nil), // 22: noitu.v1.MoveRejected
|
||||
(*GameOver)(nil), // 23: noitu.v1.GameOver
|
||||
(*PlayerEliminated)(nil), // 24: noitu.v1.PlayerEliminated
|
||||
(*ServerError)(nil), // 25: noitu.v1.ServerError
|
||||
(*Pong)(nil), // 26: noitu.v1.Pong
|
||||
(*RoomState)(nil), // 27: noitu.v1.RoomState
|
||||
(*ChatMessage)(nil), // 28: noitu.v1.ChatMessage
|
||||
(*ChatHistory)(nil), // 29: noitu.v1.ChatHistory
|
||||
(*ServerMessage)(nil), // 30: noitu.v1.ServerMessage
|
||||
(*Sense)(nil), // 18: noitu.v1.Sense
|
||||
(*PlayerSlot)(nil), // 19: noitu.v1.PlayerSlot
|
||||
(*PlayerScore)(nil), // 20: noitu.v1.PlayerScore
|
||||
(*GameStarted)(nil), // 21: noitu.v1.GameStarted
|
||||
(*TurnUpdate)(nil), // 22: noitu.v1.TurnUpdate
|
||||
(*MoveRejected)(nil), // 23: noitu.v1.MoveRejected
|
||||
(*GameOver)(nil), // 24: noitu.v1.GameOver
|
||||
(*PlayerEliminated)(nil), // 25: noitu.v1.PlayerEliminated
|
||||
(*ServerError)(nil), // 26: noitu.v1.ServerError
|
||||
(*Pong)(nil), // 27: noitu.v1.Pong
|
||||
(*RoomState)(nil), // 28: noitu.v1.RoomState
|
||||
(*ChatMessage)(nil), // 29: noitu.v1.ChatMessage
|
||||
(*ChatHistory)(nil), // 30: noitu.v1.ChatHistory
|
||||
(*ServerMessage)(nil), // 31: noitu.v1.ServerMessage
|
||||
}
|
||||
var file_noitu_v1_game_proto_depIdxs = []int32{
|
||||
0, // 0: noitu.v1.StartBotGame.difficulty:type_name -> noitu.v1.Difficulty
|
||||
@@ -2620,31 +2703,33 @@ var file_noitu_v1_game_proto_depIdxs = []int32{
|
||||
11, // 10: noitu.v1.ClientMessage.kick_player:type_name -> noitu.v1.KickPlayer
|
||||
12, // 11: noitu.v1.ClientMessage.leave_room:type_name -> noitu.v1.LeaveRoom
|
||||
13, // 12: noitu.v1.ClientMessage.send_chat:type_name -> noitu.v1.SendChat
|
||||
19, // 13: noitu.v1.GameStarted.players:type_name -> noitu.v1.PlayerScore
|
||||
17, // 14: noitu.v1.TurnUpdate.played:type_name -> noitu.v1.PlayedWord
|
||||
19, // 15: noitu.v1.TurnUpdate.players:type_name -> noitu.v1.PlayerScore
|
||||
1, // 16: noitu.v1.MoveRejected.reason:type_name -> noitu.v1.RejectReason
|
||||
2, // 17: noitu.v1.GameOver.reason:type_name -> noitu.v1.GameEndReason
|
||||
19, // 18: noitu.v1.GameOver.standings:type_name -> noitu.v1.PlayerScore
|
||||
2, // 19: noitu.v1.PlayerEliminated.reason:type_name -> noitu.v1.GameEndReason
|
||||
18, // 20: noitu.v1.RoomState.players:type_name -> noitu.v1.PlayerSlot
|
||||
28, // 21: noitu.v1.ChatHistory.messages:type_name -> noitu.v1.ChatMessage
|
||||
16, // 22: noitu.v1.ServerMessage.welcome:type_name -> noitu.v1.Welcome
|
||||
20, // 23: noitu.v1.ServerMessage.game_started:type_name -> noitu.v1.GameStarted
|
||||
21, // 24: noitu.v1.ServerMessage.turn_update:type_name -> noitu.v1.TurnUpdate
|
||||
22, // 25: noitu.v1.ServerMessage.move_rejected:type_name -> noitu.v1.MoveRejected
|
||||
23, // 26: noitu.v1.ServerMessage.game_over:type_name -> noitu.v1.GameOver
|
||||
25, // 27: noitu.v1.ServerMessage.error:type_name -> noitu.v1.ServerError
|
||||
26, // 28: noitu.v1.ServerMessage.pong:type_name -> noitu.v1.Pong
|
||||
27, // 29: noitu.v1.ServerMessage.room_state:type_name -> noitu.v1.RoomState
|
||||
28, // 30: noitu.v1.ServerMessage.chat_message:type_name -> noitu.v1.ChatMessage
|
||||
29, // 31: noitu.v1.ServerMessage.chat_history:type_name -> noitu.v1.ChatHistory
|
||||
24, // 32: noitu.v1.ServerMessage.player_eliminated:type_name -> noitu.v1.PlayerEliminated
|
||||
33, // [33:33] is the sub-list for method output_type
|
||||
33, // [33:33] is the sub-list for method input_type
|
||||
33, // [33:33] is the sub-list for extension type_name
|
||||
33, // [33:33] is the sub-list for extension extendee
|
||||
0, // [0:33] is the sub-list for field type_name
|
||||
18, // 13: noitu.v1.PlayedWord.meanings:type_name -> noitu.v1.Sense
|
||||
20, // 14: noitu.v1.GameStarted.players:type_name -> noitu.v1.PlayerScore
|
||||
18, // 15: noitu.v1.GameStarted.opening_meanings:type_name -> noitu.v1.Sense
|
||||
17, // 16: noitu.v1.TurnUpdate.played:type_name -> noitu.v1.PlayedWord
|
||||
20, // 17: noitu.v1.TurnUpdate.players:type_name -> noitu.v1.PlayerScore
|
||||
1, // 18: noitu.v1.MoveRejected.reason:type_name -> noitu.v1.RejectReason
|
||||
2, // 19: noitu.v1.GameOver.reason:type_name -> noitu.v1.GameEndReason
|
||||
20, // 20: noitu.v1.GameOver.standings:type_name -> noitu.v1.PlayerScore
|
||||
2, // 21: noitu.v1.PlayerEliminated.reason:type_name -> noitu.v1.GameEndReason
|
||||
19, // 22: noitu.v1.RoomState.players:type_name -> noitu.v1.PlayerSlot
|
||||
29, // 23: noitu.v1.ChatHistory.messages:type_name -> noitu.v1.ChatMessage
|
||||
16, // 24: noitu.v1.ServerMessage.welcome:type_name -> noitu.v1.Welcome
|
||||
21, // 25: noitu.v1.ServerMessage.game_started:type_name -> noitu.v1.GameStarted
|
||||
22, // 26: noitu.v1.ServerMessage.turn_update:type_name -> noitu.v1.TurnUpdate
|
||||
23, // 27: noitu.v1.ServerMessage.move_rejected:type_name -> noitu.v1.MoveRejected
|
||||
24, // 28: noitu.v1.ServerMessage.game_over:type_name -> noitu.v1.GameOver
|
||||
26, // 29: noitu.v1.ServerMessage.error:type_name -> noitu.v1.ServerError
|
||||
27, // 30: noitu.v1.ServerMessage.pong:type_name -> noitu.v1.Pong
|
||||
28, // 31: noitu.v1.ServerMessage.room_state:type_name -> noitu.v1.RoomState
|
||||
29, // 32: noitu.v1.ServerMessage.chat_message:type_name -> noitu.v1.ChatMessage
|
||||
30, // 33: noitu.v1.ServerMessage.chat_history:type_name -> noitu.v1.ChatHistory
|
||||
25, // 34: noitu.v1.ServerMessage.player_eliminated:type_name -> noitu.v1.PlayerEliminated
|
||||
35, // [35:35] is the sub-list for method output_type
|
||||
35, // [35:35] is the sub-list for method input_type
|
||||
35, // [35:35] is the sub-list for extension type_name
|
||||
35, // [35:35] is the sub-list for extension extendee
|
||||
0, // [0:35] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_noitu_v1_game_proto_init() }
|
||||
@@ -2666,7 +2751,7 @@ func file_noitu_v1_game_proto_init() {
|
||||
(*ClientMessage_LeaveRoom)(nil),
|
||||
(*ClientMessage_SendChat)(nil),
|
||||
}
|
||||
file_noitu_v1_game_proto_msgTypes[27].OneofWrappers = []any{
|
||||
file_noitu_v1_game_proto_msgTypes[28].OneofWrappers = []any{
|
||||
(*ServerMessage_Welcome)(nil),
|
||||
(*ServerMessage_GameStarted)(nil),
|
||||
(*ServerMessage_TurnUpdate)(nil),
|
||||
@@ -2685,7 +2770,7 @@ func file_noitu_v1_game_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_noitu_v1_game_proto_rawDesc), len(file_noitu_v1_game_proto_rawDesc)),
|
||||
NumEnums: 3,
|
||||
NumMessages: 28,
|
||||
NumMessages: 29,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
noituv1 "github.com/tiennm99dev/noitu/server/gen/noitu/v1"
|
||||
"github.com/tiennm99dev/noitu/server/internal/bot"
|
||||
"github.com/tiennm99dev/noitu/server/internal/dictionary"
|
||||
"github.com/tiennm99dev/noitu/server/internal/game"
|
||||
)
|
||||
|
||||
@@ -100,13 +101,27 @@ func Difficulty(d noituv1.Difficulty) (bot.Difficulty, bool) {
|
||||
//
|
||||
// byMe is the caller's business: the same move is sent to both players and
|
||||
// only this flag differs, so the room serializes one message per player rather
|
||||
// than broadcasting a single shared frame.
|
||||
func PlayedWord(m game.Move, byMe bool) *noituv1.PlayedWord {
|
||||
// than broadcasting a single shared frame. meanings is the word's senses from
|
||||
// the dictionary, the same for every recipient; the room looks them up once
|
||||
// per move and passes them in.
|
||||
func PlayedWord(m game.Move, byMe bool, meanings []dictionary.Sense) *noituv1.PlayedWord {
|
||||
return &noituv1.PlayedWord{
|
||||
Word: m.Word,
|
||||
Typed: m.Typed,
|
||||
ByMe: byMe,
|
||||
Points: uint32(m.Points),
|
||||
Syllables: uint32(m.Syllables),
|
||||
Meanings: Senses(meanings),
|
||||
}
|
||||
}
|
||||
|
||||
// Senses renders a word's dictionary senses for the wire. A word with none
|
||||
// yields an empty list, which the client shows as "no meaning yet" rather than
|
||||
// as a row without a panel.
|
||||
func Senses(senses []dictionary.Sense) []*noituv1.Sense {
|
||||
out := make([]*noituv1.Sense, 0, len(senses))
|
||||
for _, s := range senses {
|
||||
out = append(out, &noituv1.Sense{Pos: s.Pos, Gloss: s.Gloss})
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -172,14 +172,14 @@ func TestDifficultyMapping(t *testing.T) {
|
||||
func TestPlayedWordKeepsTypedInput(t *testing.T) {
|
||||
m := game.Move{Word: "hòa bình", Typed: "hoà bình", Syllables: 2, Points: 2}
|
||||
|
||||
mine := PlayedWord(m, true)
|
||||
mine := PlayedWord(m, true, nil)
|
||||
if mine.GetWord() != "hòa bình" || mine.GetTyped() != "hoà bình" {
|
||||
t.Errorf("canonical/typed pair lost: word=%q typed=%q", mine.GetWord(), mine.GetTyped())
|
||||
}
|
||||
if !mine.GetByMe() || mine.GetSyllables() != 2 || mine.GetPoints() != 2 {
|
||||
t.Errorf("unexpected rendering: %+v", mine)
|
||||
}
|
||||
if theirs := PlayedWord(m, false); theirs.GetByMe() {
|
||||
if theirs := PlayedWord(m, false, nil); theirs.GetByMe() {
|
||||
t.Error("by_me must be false for the opponent's copy of the same move")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package wsapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
|
||||
noituv1 "github.com/tiennm99dev/noitu/server/gen/noitu/v1"
|
||||
)
|
||||
|
||||
// definedChainDict is the forced chain with a meaning on the opening word and
|
||||
// on the human's first reply, and none on the rest: both shapes must cross
|
||||
// the wire, because the client shows a panel for every word.
|
||||
func definedChainDict() *testDict {
|
||||
return chainDict().
|
||||
define("a b", "danh từ", "Từ mở đầu.").
|
||||
define("b c", "động từ", "Nối tiếp.").
|
||||
define("b c", "", "Nghĩa thứ hai.")
|
||||
}
|
||||
|
||||
func assertSenses(t *testing.T, got []*noituv1.Sense, want ...[2]string) {
|
||||
t.Helper()
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("got %d senses %v, want %d", len(got), got, len(want))
|
||||
}
|
||||
for i, w := range want {
|
||||
if got[i].GetPos() != w[0] || got[i].GetGloss() != w[1] {
|
||||
t.Errorf("sense %d = (%q, %q), want (%q, %q)", i, got[i].GetPos(), got[i].GetGloss(), w[0], w[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBotGameCarriesMeanings checks the two messages that introduce a word to
|
||||
// the client: the opening in GameStarted and each move in TurnUpdate, for a
|
||||
// word with senses and for one without.
|
||||
func TestBotGameCarriesMeanings(t *testing.T) {
|
||||
_, url := newTestServer(t, definedChainDict(), Config{})
|
||||
c := dial(t, url)
|
||||
c.hello("Ăn")
|
||||
|
||||
c.send(&noituv1.ClientMessage{Payload: &noituv1.ClientMessage_StartBotGame{
|
||||
StartBotGame: &noituv1.StartBotGame{Difficulty: noituv1.Difficulty_DIFFICULTY_EASY},
|
||||
}})
|
||||
started := c.await("game_started").GetGameStarted()
|
||||
assertSenses(t, started.GetOpeningMeanings(), [2]string{"danh từ", "Từ mở đầu."})
|
||||
|
||||
c.submit("b c", started.GetTurnSeq())
|
||||
own := c.await("turn_update").GetTurnUpdate()
|
||||
if !own.GetPlayed().GetByMe() {
|
||||
t.Fatal("first update should be the player's own move")
|
||||
}
|
||||
assertSenses(t, own.GetPlayed().GetMeanings(), [2]string{"động từ", "Nối tiếp."}, [2]string{"", "Nghĩa thứ hai."})
|
||||
|
||||
// The bot's only move, "c d", has no definition: an empty list, not a
|
||||
// missing message.
|
||||
reply := c.await("turn_update").GetTurnUpdate()
|
||||
if reply.GetPlayed().GetWord() != "c d" {
|
||||
t.Fatalf("bot played %q, want c d", reply.GetPlayed().GetWord())
|
||||
}
|
||||
if n := len(reply.GetPlayed().GetMeanings()); n != 0 {
|
||||
t.Errorf("a word without a definition carried %d senses", n)
|
||||
}
|
||||
}
|
||||
|
||||
// TestResumeReplaysMeanings drops a player after a move and checks that the
|
||||
// replayed opening and last move both carry their senses, with no code path
|
||||
// of their own.
|
||||
func TestResumeReplaysMeanings(t *testing.T) {
|
||||
_, url := newTestServer(t, definedChainDict(), Config{TurnLimit: 10 * time.Second, GraceFor: 5 * time.Second})
|
||||
|
||||
host := dial(t, url)
|
||||
welcome := host.hello("Chủ phòng")
|
||||
host.send(&noituv1.ClientMessage{Payload: &noituv1.ClientMessage_CreateRoom{CreateRoom: &noituv1.CreateRoom{}}})
|
||||
code := host.await("room_state").GetRoomState().GetRoomCode()
|
||||
|
||||
guest := dial(t, url)
|
||||
guest.hello("Khách")
|
||||
guest.send(&noituv1.ClientMessage{Payload: &noituv1.ClientMessage_JoinRoom{
|
||||
JoinRoom: &noituv1.JoinRoom{RoomCode: code},
|
||||
}})
|
||||
readyAndStart(t, host, guest)
|
||||
hostStart := host.await("game_started").GetGameStarted()
|
||||
guest.await("game_started")
|
||||
|
||||
// Whoever was dealt the turn plays the only legal word.
|
||||
lead := guest
|
||||
if hostStart.GetMyTurn() {
|
||||
lead = host
|
||||
}
|
||||
lead.submit("b c", hostStart.GetTurnSeq())
|
||||
host.await("turn_update")
|
||||
guest.await("turn_update")
|
||||
|
||||
_ = host.conn.Close(websocket.StatusGoingAway, "")
|
||||
guest.await("room_state")
|
||||
|
||||
back := dial(t, url)
|
||||
back.send(&noituv1.ClientMessage{Payload: &noituv1.ClientMessage_Hello{Hello: &noituv1.Hello{
|
||||
ProtocolVersion: ProtocolVersion,
|
||||
Nickname: "Chủ phòng",
|
||||
ResumeToken: welcome.GetResumeToken(),
|
||||
}}})
|
||||
back.await("welcome")
|
||||
|
||||
restored := back.await("game_started").GetGameStarted()
|
||||
assertSenses(t, restored.GetOpeningMeanings(), [2]string{"danh từ", "Từ mở đầu."})
|
||||
replayed := back.await("turn_update").GetTurnUpdate()
|
||||
if replayed.GetPlayed().GetWord() != "b c" {
|
||||
t.Fatalf("replayed move = %q, want b c", replayed.GetPlayed().GetWord())
|
||||
}
|
||||
assertSenses(t, replayed.GetPlayed().GetMeanings(), [2]string{"động từ", "Nối tiếp."}, [2]string{"", "Nghĩa thứ hai."})
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
noituv1 "github.com/tiennm99dev/noitu/server/gen/noitu/v1"
|
||||
"github.com/tiennm99dev/noitu/server/internal/bot"
|
||||
"github.com/tiennm99dev/noitu/server/internal/dictionary"
|
||||
"github.com/tiennm99dev/noitu/server/internal/game"
|
||||
)
|
||||
|
||||
@@ -259,7 +260,9 @@ type room struct {
|
||||
}
|
||||
|
||||
// Dictionary is everything the transport layer needs from the wordlist: the
|
||||
// engine's own contract, plus a way to pick an opening.
|
||||
// engine's own contract, a way to pick an opening, and the meanings a word
|
||||
// travels to the client with. The engine never sees a meaning; only the room
|
||||
// attaches them, where it renders a word for a recipient.
|
||||
//
|
||||
// An interface rather than *dictionary.Store so a test can play a whole game
|
||||
// against a hand-built graph of a dozen words, where the expected outcome is
|
||||
@@ -268,6 +271,8 @@ type room struct {
|
||||
type Dictionary interface {
|
||||
game.Dictionary
|
||||
RandomOpeningWord(minOutDegree int) (string, error)
|
||||
// Meanings returns a canonical word's senses in order, nil for none.
|
||||
Meanings(word string) []dictionary.Sense
|
||||
}
|
||||
|
||||
func newRoom(h *hub, code string, turnLimit, graceFor, idleFor time.Duration) *room {
|
||||
@@ -749,6 +754,7 @@ func (r *room) sendGameStarted(s *seat, state game.State) {
|
||||
s.sess.send(&noituv1.ServerMessage{Payload: &noituv1.ServerMessage_GameStarted{
|
||||
GameStarted: &noituv1.GameStarted{
|
||||
OpeningWord: r.opening,
|
||||
OpeningMeanings: Senses(r.dict.Meanings(r.opening)),
|
||||
CurrentSyllable: state.Current,
|
||||
MyTurn: state.Turn == s.id,
|
||||
DeadlineUnixMs: state.Deadline.UnixMilli(),
|
||||
@@ -879,14 +885,25 @@ func (r *room) maybeScheduleBot() {
|
||||
// new player to act.
|
||||
func (r *room) broadcastTurn(move *game.Move) {
|
||||
state := r.engine.Snapshot()
|
||||
meanings := r.moveMeanings(move)
|
||||
for _, s := range r.seats {
|
||||
r.sendTurnUpdate(s, state, move)
|
||||
r.sendTurnUpdate(s, state, move, meanings)
|
||||
}
|
||||
}
|
||||
|
||||
// moveMeanings looks up a played word's senses once per move; they are the
|
||||
// same for every recipient. nil for no move.
|
||||
func (r *room) moveMeanings(move *game.Move) []dictionary.Sense {
|
||||
if move == nil {
|
||||
return nil
|
||||
}
|
||||
return r.dict.Meanings(move.Word)
|
||||
}
|
||||
|
||||
// sendTurnUpdate renders one position for one seat. by_me, my_turn and is_me
|
||||
// are all per-recipient, which is why there is no single shared frame.
|
||||
func (r *room) sendTurnUpdate(s *seat, state game.State, move *game.Move) {
|
||||
// are all per-recipient, which is why there is no single shared frame; the
|
||||
// move's meanings are not, and arrive looked up.
|
||||
func (r *room) sendTurnUpdate(s *seat, state game.State, move *game.Move, meanings []dictionary.Sense) {
|
||||
if s == nil || s.sess == nil {
|
||||
return
|
||||
}
|
||||
@@ -900,7 +917,7 @@ func (r *room) sendTurnUpdate(s *seat, state game.State, move *game.Move) {
|
||||
TurnPlayerId: string(state.Turn),
|
||||
}
|
||||
if move != nil {
|
||||
update.Played = PlayedWord(*move, move.Player == s.id)
|
||||
update.Played = PlayedWord(*move, move.Player == s.id, meanings)
|
||||
}
|
||||
s.sess.send(&noituv1.ServerMessage{Payload: &noituv1.ServerMessage_TurnUpdate{TurnUpdate: update}})
|
||||
}
|
||||
@@ -1191,7 +1208,7 @@ func (r *room) handleResume(m resumeInput) {
|
||||
r.sendGameStarted(s, state)
|
||||
if len(state.History) > 0 {
|
||||
last := state.History[len(state.History)-1]
|
||||
r.sendTurnUpdate(s, state, &last)
|
||||
r.sendTurnUpdate(s, state, &last, r.moveMeanings(&last))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ func serverVariants() map[string]*noituv1.ServerMessage {
|
||||
}}},
|
||||
"server_game_started": {Payload: &noituv1.ServerMessage_GameStarted{GameStarted: &noituv1.GameStarted{
|
||||
OpeningWord: "hòa bình",
|
||||
OpeningMeanings: []*noituv1.Sense{{Pos: "danh từ", Gloss: "Tình trạng không có chiến tranh."}},
|
||||
CurrentSyllable: "bình",
|
||||
MyTurn: true,
|
||||
DeadlineUnixMs: 1756998020000,
|
||||
@@ -88,6 +89,10 @@ func serverVariants() map[string]*noituv1.ServerMessage {
|
||||
Points: 2,
|
||||
Syllables: 2,
|
||||
PlayerId: "p2",
|
||||
Meanings: []*noituv1.Sense{
|
||||
{Pos: "tính từ", Gloss: "Yên lành, không có gì xáo động."},
|
||||
{Pos: "", Gloss: "Xem bình an."},
|
||||
},
|
||||
},
|
||||
CurrentSyllable: "yên",
|
||||
MyTurn: true,
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/coder/websocket"
|
||||
noituv1 "github.com/tiennm99dev/noitu/server/gen/noitu/v1"
|
||||
"github.com/tiennm99dev/noitu/server/internal/bot"
|
||||
"github.com/tiennm99dev/noitu/server/internal/dictionary"
|
||||
"github.com/tiennm99dev/noitu/server/internal/game"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -30,10 +31,13 @@ type testDict struct {
|
||||
// words maps a canonical word to its first and last syllable.
|
||||
words map[string][2]string
|
||||
opening string
|
||||
// senses holds the meanings of the few words a test gives one to; every
|
||||
// other word has none, which is also a case the client must handle.
|
||||
senses map[string][]dictionary.Sense
|
||||
}
|
||||
|
||||
func newTestDict(opening string, words ...string) *testDict {
|
||||
d := &testDict{words: map[string][2]string{}, opening: opening}
|
||||
d := &testDict{words: map[string][2]string{}, opening: opening, senses: map[string][]dictionary.Sense{}}
|
||||
for _, w := range append(words, opening) {
|
||||
parts := strings.Fields(w)
|
||||
d.words[w] = [2]string{parts[0], parts[len(parts)-1]}
|
||||
@@ -41,6 +45,14 @@ func newTestDict(opening string, words ...string) *testDict {
|
||||
return d
|
||||
}
|
||||
|
||||
// define gives a word one sense, so a test can see it arrive on the wire.
|
||||
func (d *testDict) define(word, pos, gloss string) *testDict {
|
||||
d.senses[word] = append(d.senses[word], dictionary.Sense{Pos: pos, Gloss: gloss})
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *testDict) Meanings(word string) []dictionary.Sense { return d.senses[word] }
|
||||
|
||||
func (d *testDict) Resolve(word string) (string, bool) {
|
||||
_, ok := d.words[word]
|
||||
return word, ok
|
||||
|
||||
+43
@@ -411,6 +411,15 @@ export declare type PlayedWord = Message<"noitu.v1.PlayedWord"> & {
|
||||
* @generated from field: string player_id = 6;
|
||||
*/
|
||||
playerId: string;
|
||||
|
||||
/**
|
||||
* What the word means, at most five senses. Empty when the dictionary has
|
||||
* no definition for the word; the client then says so rather than hiding
|
||||
* the row's panel, so every word in the chain behaves the same.
|
||||
*
|
||||
* @generated from field: repeated noitu.v1.Sense meanings = 7;
|
||||
*/
|
||||
meanings: Sense[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -419,6 +428,33 @@ export declare type PlayedWord = Message<"noitu.v1.PlayedWord"> & {
|
||||
*/
|
||||
export declare const PlayedWordSchema: GenMessage<PlayedWord>;
|
||||
|
||||
/**
|
||||
* Sense is one definition of a word as Wiktionary gives it: the part of
|
||||
* speech it sits under, in Vietnamese ("danh từ"), empty when the heading was
|
||||
* not one the builder knows, and the definition stripped of markup at at most
|
||||
* 200 characters. Plain text both: the client renders them as text, never as
|
||||
* markup.
|
||||
*
|
||||
* @generated from message noitu.v1.Sense
|
||||
*/
|
||||
export declare type Sense = Message<"noitu.v1.Sense"> & {
|
||||
/**
|
||||
* @generated from field: string pos = 1;
|
||||
*/
|
||||
pos: string;
|
||||
|
||||
/**
|
||||
* @generated from field: string gloss = 2;
|
||||
*/
|
||||
gloss: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.Sense.
|
||||
* Use `create(SenseSchema)` to create a new message.
|
||||
*/
|
||||
export declare const SenseSchema: GenMessage<Sense>;
|
||||
|
||||
/**
|
||||
* PlayerSlot is one seat in the room, rendered for one recipient.
|
||||
*
|
||||
@@ -600,6 +636,13 @@ export declare type GameStarted = Message<"noitu.v1.GameStarted"> & {
|
||||
* @generated from field: string turn_player_id = 8;
|
||||
*/
|
||||
turnPlayerId: string;
|
||||
|
||||
/**
|
||||
* The opening word's senses, as PlayedWord.meanings for a played word.
|
||||
*
|
||||
* @generated from field: repeated noitu.v1.Sense opening_meanings = 9;
|
||||
*/
|
||||
openingMeanings: Sense[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Generated
+21
-14
@@ -21,7 +21,7 @@ import { enumDesc, fileDesc, messageDesc, tsEnum } from "@bufbuild/protobuf/code
|
||||
* Describes the file noitu/v1/game.proto.
|
||||
*/
|
||||
export const file_noitu_v1_game = /*@__PURE__*/
|
||||
fileDesc("ChNub2l0dS92MS9nYW1lLnByb3RvEghub2l0dS52MSJJCgVIZWxsbxIYChBwcm90b2NvbF92ZXJzaW9uGAEgASgNEhQKDHJlc3VtZV90b2tlbhgCIAEoCRIQCghuaWNrbmFtZRgDIAEoCSI4CgxTdGFydEJvdEdhbWUSKAoKZGlmZmljdWx0eRgBIAEoDjIULm5vaXR1LnYxLkRpZmZpY3VsdHkiDAoKQ3JlYXRlUm9vbSIdCghKb2luUm9vbRIRCglyb29tX2NvZGUYASABKAkiLAoKU3VibWl0V29yZBIMCgR3b3JkGAEgASgJEhAKCHR1cm5fc2VxGAIgASgNIggKBlJlc2lnbiIZCghTZXRSZWFkeRINCgVyZWFkeRgBIAEoCCILCglTdGFydEdhbWUiHwoKS2lja1BsYXllchIRCglwbGF5ZXJfaWQYASABKAkiCwoJTGVhdmVSb29tIhgKCFNlbmRDaGF0EgwKBHRleHQYASABKAkiHgoEUGluZxIWCg5jbGllbnRfdGltZV9tcxgBIAEoAyKQBAoNQ2xpZW50TWVzc2FnZRIgCgVoZWxsbxgBIAEoCzIPLm5vaXR1LnYxLkhlbGxvSAASMAoOc3RhcnRfYm90X2dhbWUYAiABKAsyFi5ub2l0dS52MS5TdGFydEJvdEdhbWVIABIrCgtjcmVhdGVfcm9vbRgDIAEoCzIULm5vaXR1LnYxLkNyZWF0ZVJvb21IABInCglqb2luX3Jvb20YBCABKAsyEi5ub2l0dS52MS5Kb2luUm9vbUgAEisKC3N1Ym1pdF93b3JkGAUgASgLMhQubm9pdHUudjEuU3VibWl0V29yZEgAEiIKBnJlc2lnbhgGIAEoCzIQLm5vaXR1LnYxLlJlc2lnbkgAEh4KBHBpbmcYByABKAsyDi5ub2l0dS52MS5QaW5nSAASJwoJc2V0X3JlYWR5GAkgASgLMhIubm9pdHUudjEuU2V0UmVhZHlIABIpCgpzdGFydF9nYW1lGAogASgLMhMubm9pdHUudjEuU3RhcnRHYW1lSAASKwoLa2lja19wbGF5ZXIYCyABKAsyFC5ub2l0dS52MS5LaWNrUGxheWVySAASKQoKbGVhdmVfcm9vbRgMIAEoCzITLm5vaXR1LnYxLkxlYXZlUm9vbUgAEicKCXNlbmRfY2hhdBgNIAEoCzISLm5vaXR1LnYxLlNlbmRDaGF0SABCCQoHcGF5bG9hZEoECAgQCSJoCgdXZWxjb21lEhIKCnNlc3Npb25faWQYASABKAkSFAoMcmVzdW1lX3Rva2VuGAIgASgJEhgKEHByb3RvY29sX3ZlcnNpb24YAyABKA0SGQoRYWNjZXB0ZWRfbmlja25hbWUYBCABKAkibgoKUGxheWVkV29yZBIMCgR3b3JkGAEgASgJEg0KBWJ5X21lGAIgASgIEg4KBnBvaW50cxgDIAEoDRIRCglzeWxsYWJsZXMYBCABKA0SDQoFdHlwZWQYBSABKAkSEQoJcGxheWVyX2lkGAYgASgJIn4KClBsYXllclNsb3QSEQoJcGxheWVyX2lkGAEgASgJEgwKBG5hbWUYAiABKAkSDQoFaXNfbWUYAyABKAgSEAoIaXNfb3duZXIYBCABKAgSDQoFcmVhZHkYBSABKAgSEQoJY29ubmVjdGVkGAYgASgIEgwKBHdpbnMYByABKA0igQEKC1BsYXllclNjb3JlEhEKCXBsYXllcl9pZBgBIAEoCRIMCgRuYW1lGAIgASgJEg0KBWlzX21lGAMgASgIEg0KBXNjb3JlGAQgASgNEhIKCmVsaW1pbmF0ZWQYBSABKAgSEQoJY29ubmVjdGVkGAYgASgIEgwKBHJhbmsYByABKA0i0QEKC0dhbWVTdGFydGVkEhQKDG9wZW5pbmdfd29yZBgBIAEoCRIYChBjdXJyZW50X3N5bGxhYmxlGAIgASgJEg8KB215X3R1cm4YAyABKAgSGAoQZGVhZGxpbmVfdW5peF9tcxgEIAEoAxIQCgh0dXJuX3NlcRgFIAEoDRIVCg10dXJuX2xpbWl0X21zGAYgASgNEiYKB3BsYXllcnMYByADKAsyFS5ub2l0dS52MS5QbGF5ZXJTY29yZRIWCg50dXJuX3BsYXllcl9pZBgIIAEoCSLrAQoKVHVyblVwZGF0ZRIkCgZwbGF5ZWQYASABKAsyFC5ub2l0dS52MS5QbGF5ZWRXb3JkEhgKEGN1cnJlbnRfc3lsbGFibGUYAiABKAkSDwoHbXlfdHVybhgDIAEoCBIYChBkZWFkbGluZV91bml4X21zGAQgASgDEhAKCHR1cm5fc2VxGAUgASgNEhQKDGNoYWluX2xlbmd0aBgIIAEoDRImCgdwbGF5ZXJzGAkgAygLMhUubm9pdHUudjEuUGxheWVyU2NvcmUSFgoOdHVybl9wbGF5ZXJfaWQYCiABKAlKBAgGEAdKBAgHEAgiVgoMTW92ZVJlamVjdGVkEiYKBnJlYXNvbhgBIAEoDjIWLm5vaXR1LnYxLlJlamVjdFJlYXNvbhIMCgR3b3JkGAIgASgJEhAKCHR1cm5fc2VxGAMgASgNIo4BCghHYW1lT3ZlchINCgVpX3dvbhgBIAEoCBInCgZyZWFzb24YAiABKA4yFy5ub2l0dS52MS5HYW1lRW5kUmVhc29uEhQKDGNoYWluX2xlbmd0aBgEIAEoDRIoCglzdGFuZGluZ3MYBiADKAsyFS5ub2l0dS52MS5QbGF5ZXJTY29yZUoECAMQBEoECAUQBiKAAQoQUGxheWVyRWxpbWluYXRlZBIRCglwbGF5ZXJfaWQYASABKAkSDAoEbmFtZRgCIAEoCRINCgVpc19tZRgDIAEoCBInCgZyZWFzb24YBCABKA4yFy5ub2l0dS52MS5HYW1lRW5kUmVhc29uEhMKC3N1Z2dlc3Rpb25zGAUgAygJIiwKC1NlcnZlckVycm9yEgwKBGNvZGUYASABKAkSDwoHbWVzc2FnZRgCIAEoCSI2CgRQb25nEhYKDmNsaWVudF90aW1lX21zGAEgASgDEhYKDnNlcnZlcl90aW1lX21zGAIgASgDIrgBCglSb29tU3RhdGUSEQoJcm9vbV9jb2RlGAEgASgJEhEKCWNhbl9zdGFydBgDIAEoCBIlCgdwbGF5ZXJzGAkgAygLMhQubm9pdHUudjEuUGxheWVyU2xvdBITCgttYXhfcGxheWVycxgKIAEoDRITCgttaW5fcGxheWVycxgLIAEoDRIQCghncmFjZV9tcxgMIAEoDUoECAIQA0oECAQQBUoECAUQBkoECAYQB0oECAcQCEoECAgQCSJlCgtDaGF0TWVzc2FnZRIPCgdmcm9tX21lGAEgASgIEg4KBmF1dGhvchgCIAEoCRIMCgR0ZXh0GAMgASgJEhQKDHNlbnRfdW5peF9tcxgEIAEoAxIRCglwbGF5ZXJfaWQYBSABKAkiNgoLQ2hhdEhpc3RvcnkSJwoIbWVzc2FnZXMYASADKAsyFS5ub2l0dS52MS5DaGF0TWVzc2FnZSKYBAoNU2VydmVyTWVzc2FnZRIkCgd3ZWxjb21lGAEgASgLMhEubm9pdHUudjEuV2VsY29tZUgAEi0KDGdhbWVfc3RhcnRlZBgEIAEoCzIVLm5vaXR1LnYxLkdhbWVTdGFydGVkSAASKwoLdHVybl91cGRhdGUYBSABKAsyFC5ub2l0dS52MS5UdXJuVXBkYXRlSAASLwoNbW92ZV9yZWplY3RlZBgGIAEoCzIWLm5vaXR1LnYxLk1vdmVSZWplY3RlZEgAEicKCWdhbWVfb3ZlchgHIAEoCzISLm5vaXR1LnYxLkdhbWVPdmVySAASJgoFZXJyb3IYCSABKAsyFS5ub2l0dS52MS5TZXJ2ZXJFcnJvckgAEh4KBHBvbmcYCiABKAsyDi5ub2l0dS52MS5Qb25nSAASKQoKcm9vbV9zdGF0ZRgMIAEoCzITLm5vaXR1LnYxLlJvb21TdGF0ZUgAEi0KDGNoYXRfbWVzc2FnZRgNIAEoCzIVLm5vaXR1LnYxLkNoYXRNZXNzYWdlSAASLQoMY2hhdF9oaXN0b3J5GA4gASgLMhUubm9pdHUudjEuQ2hhdEhpc3RvcnlIABI3ChFwbGF5ZXJfZWxpbWluYXRlZBgPIAEoCzIaLm5vaXR1LnYxLlBsYXllckVsaW1pbmF0ZWRIAEIJCgdwYXlsb2FkSgQIAhADSgQIAxAESgQICBAJSgQICxAMKmkKCkRpZmZpY3VsdHkSGgoWRElGRklDVUxUWV9VTlNQRUNJRklFRBAAEhMKD0RJRkZJQ1VMVFlfRUFTWRABEhUKEURJRkZJQ1VMVFlfTUVESVVNEAISEwoPRElGRklDVUxUWV9IQVJEEAMqjgIKDFJlamVjdFJlYXNvbhIdChlSRUpFQ1RfUkVBU09OX1VOU1BFQ0lGSUVEEAASIwofUkVKRUNUX1JFQVNPTl9UT09fRkVXX1NZTExBQkxFUxABEhwKGFJFSkVDVF9SRUFTT05fV1JPTkdfTElOSxACEiMKH1JFSkVDVF9SRUFTT05fTk9UX0lOX0RJQ1RJT05BUlkQAxIeChpSRUpFQ1RfUkVBU09OX0FMUkVBRFlfVVNFRBAEEh8KG1JFSkVDVF9SRUFTT05fTk9UX1lPVVJfVFVSThAFEhkKFVJFSkVDVF9SRUFTT05fVElNRU9VVBAGEhsKF1JFSkVDVF9SRUFTT05fR0FNRV9PVkVSEAcqsQEKDUdhbWVFbmRSZWFzb24SHwobR0FNRV9FTkRfUkVBU09OX1VOU1BFQ0lGSUVEEAASGwoXR0FNRV9FTkRfUkVBU09OX1RJTUVPVVQQARIhCh1HQU1FX0VORF9SRUFTT05fTk9fTEVHQUxfTU9WRRACEiEKHUdBTUVfRU5EX1JFQVNPTl9PUFBPTkVLine truncated
|
||||
fileDesc("ChNub2l0dS92MS9nYW1lLnByb3RvEghub2l0dS52MSJJCgVIZWxsbxIYChBwcm90b2NvbF92ZXJzaW9uGAEgASgNEhQKDHJlc3VtZV90b2tlbhgCIAEoCRIQCghuaWNrbmFtZRgDIAEoCSI4CgxTdGFydEJvdEdhbWUSKAoKZGlmZmljdWx0eRgBIAEoDjIULm5vaXR1LnYxLkRpZmZpY3VsdHkiDAoKQ3JlYXRlUm9vbSIdCghKb2luUm9vbRIRCglyb29tX2NvZGUYASABKAkiLAoKU3VibWl0V29yZBIMCgR3b3JkGAEgASgJEhAKCHR1cm5fc2VxGAIgASgNIggKBlJlc2lnbiIZCghTZXRSZWFkeRINCgVyZWFkeRgBIAEoCCILCglTdGFydEdhbWUiHwoKS2lja1BsYXllchIRCglwbGF5ZXJfaWQYASABKAkiCwoJTGVhdmVSb29tIhgKCFNlbmRDaGF0EgwKBHRleHQYASABKAkiHgoEUGluZxIWCg5jbGllbnRfdGltZV9tcxgBIAEoAyKQBAoNQ2xpZW50TWVzc2FnZRIgCgVoZWxsbxgBIAEoCzIPLm5vaXR1LnYxLkhlbGxvSAASMAoOc3RhcnRfYm90X2dhbWUYAiABKAsyFi5ub2l0dS52MS5TdGFydEJvdEdhbWVIABIrCgtjcmVhdGVfcm9vbRgDIAEoCzIULm5vaXR1LnYxLkNyZWF0ZVJvb21IABInCglqb2luX3Jvb20YBCABKAsyEi5ub2l0dS52MS5Kb2luUm9vbUgAEisKC3N1Ym1pdF93b3JkGAUgASgLMhQubm9pdHUudjEuU3VibWl0V29yZEgAEiIKBnJlc2lnbhgGIAEoCzIQLm5vaXR1LnYxLlJlc2lnbkgAEh4KBHBpbmcYByABKAsyDi5ub2l0dS52MS5QaW5nSAASJwoJc2V0X3JlYWR5GAkgASgLMhIubm9pdHUudjEuU2V0UmVhZHlIABIpCgpzdGFydF9nYW1lGAogASgLMhMubm9pdHUudjEuU3RhcnRHYW1lSAASKwoLa2lja19wbGF5ZXIYCyABKAsyFC5ub2l0dS52MS5LaWNrUGxheWVySAASKQoKbGVhdmVfcm9vbRgMIAEoCzITLm5vaXR1LnYxLkxlYXZlUm9vbUgAEicKCXNlbmRfY2hhdBgNIAEoCzISLm5vaXR1LnYxLlNlbmRDaGF0SABCCQoHcGF5bG9hZEoECAgQCSJoCgdXZWxjb21lEhIKCnNlc3Npb25faWQYASABKAkSFAoMcmVzdW1lX3Rva2VuGAIgASgJEhgKEHByb3RvY29sX3ZlcnNpb24YAyABKA0SGQoRYWNjZXB0ZWRfbmlja25hbWUYBCABKAkikQEKClBsYXllZFdvcmQSDAoEd29yZBgBIAEoCRINCgVieV9tZRgCIAEoCBIOCgZwb2ludHMYAyABKA0SEQoJc3lsbGFibGVzGAQgASgNEg0KBXR5cGVkGAUgASgJEhEKCXBsYXllcl9pZBgGIAEoCRIhCghtZWFuaW5ncxgHIAMoCzIPLm5vaXR1LnYxLlNlbnNlIiMKBVNlbnNlEgsKA3BvcxgBIAEoCRINCgVnbG9zcxgCIAEoCSJ+CgpQbGF5ZXJTbG90EhEKCXBsYXllcl9pZBgBIAEoCRIMCgRuYW1lGAIgASgJEg0KBWlzX21lGAMgASgIEhAKCGlzX293bmVyGAQgASgIEg0KBXJlYWR5GAUgASgIEhEKCWNvbm5lY3RlZBgGIAEoCBIMCgR3aW5zGAcgASgNIoEBCgtQbGF5ZXJTY29yZRIRCglwbGF5ZXJfaWQYASABKAkSDAoEbmFtZRgCIAEoCRINCgVpc19tZRgDIAEoCBINCgVzY29yZRgEIAEoDRISCgplbGltaW5hdGVkGAUgASgIEhEKCWNvbm5lY3RlZBgGIAEoCBIMCgRyYW5rGAcgASgNIvwBCgtHYW1lU3RhcnRlZBIUCgxvcGVuaW5nX3dvcmQYASABKAkSGAoQY3VycmVudF9zeWxsYWJsZRgCIAEoCRIPCgdteV90dXJuGAMgASgIEhgKEGRlYWRsaW5lX3VuaXhfbXMYBCABKAMSEAoIdHVybl9zZXEYBSABKA0SFQoNdHVybl9saW1pdF9tcxgGIAEoDRImCgdwbGF5ZXJzGAcgAygLMhUubm9pdHUudjEuUGxheWVyU2NvcmUSFgoOdHVybl9wbGF5ZXJfaWQYCCABKAkSKQoQb3BlbmluZ19tZWFuaW5ncxgJIAMoCzIPLm5vaXR1LnYxLlNlbnNlIusBCgpUdXJuVXBkYXRlEiQKBnBsYXllZBgBIAEoCzIULm5vaXR1LnYxLlBsYXllZFdvcmQSGAoQY3VycmVudF9zeWxsYWJsZRgCIAEoCRIPCgdteV90dXJuGAMgASgIEhgKEGRlYWRsaW5lX3VuaXhfbXMYBCABKAMSEAoIdHVybl9zZXEYBSABKA0SFAoMY2hhaW5fbGVuZ3RoGAggASgNEiYKB3BsYXllcnMYCSADKAsyFS5ub2l0dS52MS5QbGF5ZXJTY29yZRIWCg50dXJuX3BsYXllcl9pZBgKIAEoCUoECAYQB0oECAcQCCJWCgxNb3ZlUmVqZWN0ZWQSJgoGcmVhc29uGAEgASgOMhYubm9pdHUudjEuUmVqZWN0UmVhc29uEgwKBHdvcmQYAiABKAkSEAoIdHVybl9zZXEYAyABKA0ijgEKCEdhbWVPdmVyEg0KBWlfd29uGAEgASgIEicKBnJlYXNvbhgCIAEoDjIXLm5vaXR1LnYxLkdhbWVFbmRSZWFzb24SFAoMY2hhaW5fbGVuZ3RoGAQgASgNEigKCXN0YW5kaW5ncxgGIAMoCzIVLm5vaXR1LnYxLlBsYXllclNjb3JlSgQIAxAESgQIBRAGIoABChBQbGF5ZXJFbGltaW5hdGVkEhEKCXBsYXllcl9pZBgBIAEoCRIMCgRuYW1lGAIgASgJEg0KBWlzX21lGAMgASgIEicKBnJlYXNvbhgEIAEoDjIXLm5vaXR1LnYxLkdhbWVFbmRSZWFzb24SEwoLc3VnZ2VzdGlvbnMYBSADKAkiLAoLU2VydmVyRXJyb3ISDAoEY29kZRgBIAEoCRIPCgdtZXNzYWdlGAIgASgJIjYKBFBvbmcSFgoOY2xpZW50X3RpbWVfbXMYASABKAMSFgoOc2VydmVyX3RpbWVfbXMYAiABKAMiuAEKCVJvb21TdGF0ZRIRCglyb29tX2NvZGUYASABKAkSEQoJY2FuX3N0YXJ0GAMgASgIEiUKB3BsYXllcnMYCSADKAsyFC5ub2l0dS52MS5QbGF5ZXJTbG90EhMKC21heF9wbGF5ZXJzGAogASgNEhMKC21pbl9wbGF5ZXJzGAsgASgNEhAKCGdyYWNlX21zGAwgASgNSgQIAhADSgQIBBAFSgQIBRAGSgQIBhAHSgQIBxAISgQICBAJImUKC0NoYXRNZXNzYWdlEg8KB2Zyb21fbWUYASABKAgSDgoGYXV0aG9yGAIgASgJEgwKBHRleHQYAyABKAkSFAoMc2VudF91bml4X21zGAQgASgDEhEKCXBsYXllcl9pZBgFIAEoCSI2CgtDaGF0SGlzdG9yeRInCghtZXNzYWdlcxgBIAMoCzIVLm5vaXR1LnYxLkNoYXRNZXNzYWdlIpgECg1TZXJ2ZXJNZXNzYWdlEiQKB3dlbGNvbWUYASABKAsyES5ub2l0dS52MS5XZWxjb21lSAASLQoMZ2FtZV9zdGFydGVkGAQgASgLMhUubm9pdHUudjEuR2FtZVN0YXJ0ZWRIABIrCgt0dXJuX3VwZGF0ZRgFIAEoCzIULm5vaXR1LnYxLlR1cm5VcGRhdGVIABIvCg1tb3ZlX3JlamVjdGVkGAYgASgLMhYubm9pdHUudjEuTW92ZVJlamVjdGVkSAASJwoJZ2FtZV9vdmVyGAcgASgLMhIubm9pdHUudjEuR2FtZU92ZXJIABImCgVlcnJvchgJIAEoCzIVLm5vaXR1LnYxLlNlcnZlckVycm9ySAASHgoEcG9uZxgKIAEoCzIOLm5vaXR1LnYxLlBvbmdIABIpCgpyb29tX3N0YXRlGAwgASgLMhMubm9pdHUudjEuUm9vbVN0YXRlSAASLQoMY2hhdF9tZXNzYWdlGA0gASgLMhUubm9pdHUudjEuQ2hhdE1lc3NhZ2VIABItCgxjaGF0X2hpc3RvcnkYDiABKAsyFS5ub2l0dS52MS5DaGF0SGlzdG9yeUgAEjcKEXBsYXllcl9lbGltaW5hdGVkGA8gASgLMhoubm9pdHUudjEuUGxheWVyRWxpbWluYXRlZEgAQgkKB3BheWxvYWRKBAgCEANKBAgDEARKBAgIEAlKBAgLEAwqaQoKRGlmZmljdWx0eRIaChZESUZGSUNVTFRZX1VOU1BFQ0lGSUVEEAASEwoPRElGRklDVUxUWV9FQVNZEAESFQoRRElGRklDVUxUWV9NRURJVU0QAhITCg9ESUZGSUNVTFRZX0hBUkQQAyqOAgoMUmVqZWN0UmVhc29uEh0KGVJFSkVDVF9SRUFTT05fVU5TUEVDSUZJRUQQABIjCh9SRUpFQ1RfUkVBU09OX1RPT19GRVdfU1lMTEFCTEVTEAESHAoYUkVKRUNUX1JFQVNPTl9XUk9OR19MSU5LEAISIwofUkVKRUNUX1JFQVNPTl9OT1RfSU5fRElDVElPTkFSWRADEh4KGlJFSkVDVF9SRUFTT05fQUxSRUFEWV9VU0VEEAQSHwobUkVKRUNUX1JFQVNPTl9OT1RfWU9VUl9UVVJOEAUSGQoVUkVKRUNUX1JFQVNPTl9USU1FT1VUEAYSGwoXUkVKRUNUX1JFQVNPTl9HQU1FX09WRVIQByqxAQoNR2FtZUVuZFJlYXNvbhIfChtHQU1Line truncated
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.Hello.
|
||||
@@ -128,96 +128,103 @@ export const WelcomeSchema = /*@__PURE__*/
|
||||
export const PlayedWordSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 14);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.Sense.
|
||||
* Use `create(SenseSchema)` to create a new message.
|
||||
*/
|
||||
export const SenseSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 15);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.PlayerSlot.
|
||||
* Use `create(PlayerSlotSchema)` to create a new message.
|
||||
*/
|
||||
export const PlayerSlotSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 15);
|
||||
messageDesc(file_noitu_v1_game, 16);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.PlayerScore.
|
||||
* Use `create(PlayerScoreSchema)` to create a new message.
|
||||
*/
|
||||
export const PlayerScoreSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 16);
|
||||
messageDesc(file_noitu_v1_game, 17);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.GameStarted.
|
||||
* Use `create(GameStartedSchema)` to create a new message.
|
||||
*/
|
||||
export const GameStartedSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 17);
|
||||
messageDesc(file_noitu_v1_game, 18);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.TurnUpdate.
|
||||
* Use `create(TurnUpdateSchema)` to create a new message.
|
||||
*/
|
||||
export const TurnUpdateSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 18);
|
||||
messageDesc(file_noitu_v1_game, 19);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.MoveRejected.
|
||||
* Use `create(MoveRejectedSchema)` to create a new message.
|
||||
*/
|
||||
export const MoveRejectedSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 19);
|
||||
messageDesc(file_noitu_v1_game, 20);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.GameOver.
|
||||
* Use `create(GameOverSchema)` to create a new message.
|
||||
*/
|
||||
export const GameOverSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 20);
|
||||
messageDesc(file_noitu_v1_game, 21);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.PlayerEliminated.
|
||||
* Use `create(PlayerEliminatedSchema)` to create a new message.
|
||||
*/
|
||||
export const PlayerEliminatedSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 21);
|
||||
messageDesc(file_noitu_v1_game, 22);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.ServerError.
|
||||
* Use `create(ServerErrorSchema)` to create a new message.
|
||||
*/
|
||||
export const ServerErrorSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 22);
|
||||
messageDesc(file_noitu_v1_game, 23);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.Pong.
|
||||
* Use `create(PongSchema)` to create a new message.
|
||||
*/
|
||||
export const PongSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 23);
|
||||
messageDesc(file_noitu_v1_game, 24);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.RoomState.
|
||||
* Use `create(RoomStateSchema)` to create a new message.
|
||||
*/
|
||||
export const RoomStateSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 24);
|
||||
messageDesc(file_noitu_v1_game, 25);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.ChatMessage.
|
||||
* Use `create(ChatMessageSchema)` to create a new message.
|
||||
*/
|
||||
export const ChatMessageSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 25);
|
||||
messageDesc(file_noitu_v1_game, 26);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.ChatHistory.
|
||||
* Use `create(ChatHistorySchema)` to create a new message.
|
||||
*/
|
||||
export const ChatHistorySchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 26);
|
||||
messageDesc(file_noitu_v1_game, 27);
|
||||
|
||||
/**
|
||||
* Describes the message noitu.v1.ServerMessage.
|
||||
* Use `create(ServerMessageSchema)` to create a new message.
|
||||
*/
|
||||
export const ServerMessageSchema = /*@__PURE__*/
|
||||
messageDesc(file_noitu_v1_game, 27);
|
||||
messageDesc(file_noitu_v1_game, 28);
|
||||
|
||||
/**
|
||||
* Describes the enum noitu.v1.Difficulty.
|
||||
|
||||
@@ -112,6 +112,22 @@ describe('generated wire types', () => {
|
||||
expect(turn.turnSeq).toBe(2);
|
||||
});
|
||||
|
||||
// The label and the definition travel as two fields, so the client decides
|
||||
// how a label is shown and never has to parse it back out of the text.
|
||||
it('carries a word\'s senses on the opening and on each move', () => {
|
||||
const start = decode('server_game_started');
|
||||
expect(start.payload.value.openingMeanings).toEqual([
|
||||
expect.objectContaining({ pos: 'danh từ', gloss: 'Tình trạng không có chiến tranh.' })
|
||||
]);
|
||||
|
||||
const turn = decode('server_turn_update');
|
||||
const senses = turn.payload.value.played.meanings;
|
||||
expect(senses).toHaveLength(2);
|
||||
expect(senses[0].pos).toBe('tính từ');
|
||||
expect(senses[1].pos).toBe('');
|
||||
expect(senses[1].gloss).toBe('Xem bình an.');
|
||||
});
|
||||
|
||||
it('decodes an empty client arm without losing its tag', () => {
|
||||
const msg = decode('client_create_room');
|
||||
expect(msg.payload.case).toBe('createRoom');
|
||||
|
||||
Reference in new issue
Block a user