diff --git a/frontend/src/pages/game.tsx b/frontend/src/pages/game.tsx index 6d357db..b2015e0 100644 --- a/frontend/src/pages/game.tsx +++ b/frontend/src/pages/game.tsx @@ -35,6 +35,8 @@ function useSender(sendNote: (r: ClientNote) => void, version: number): Sender { changeTurnTime: (seconds: number) => sendNote({ method: 'changeTurnTime', version, params: { seconds } }), addPacks: (packs: WordPack[]) => sendNote({ method: 'addPacks', version, params: { packs } }), removePack: (num: number) => sendNote({ method: 'removePack', version, params: { num } }), + changeHideBomb: (hideBomb: boolean) => + sendNote({ method: 'changeHideBomb', version, params: { hideBomb } }), }; }, [sendNote, version]); } diff --git a/frontend/src/pages/gameView.tsx b/frontend/src/pages/gameView.tsx index d76b07b..ef4e7a3 100644 --- a/frontend/src/pages/gameView.tsx +++ b/frontend/src/pages/gameView.tsx @@ -12,7 +12,18 @@ import { useTheme, } from '@material-ui/core'; import { green, orange } from '@material-ui/core/colors'; -import { Add, ArrowBack, Delete, Link, Person, Search, Timer, TimerOff } from '@material-ui/icons'; +import { + Add, + ArrowBack, + Delete, + Link, + Person, + Search, + Timer, + TimerOff, + Visibility, + VisibilityOff, +} from '@material-ui/icons'; import { ok as assertTrue } from 'assert'; import isArray from 'lodash/isArray'; import range from 'lodash/range'; @@ -39,6 +50,7 @@ export interface Sender { changeTurnTime: (seconds: number) => void; addPacks: (packs: { name: string; words: string[] }[]) => void; removePack: (num: number) => void; + changeHideBomb: (HideBomb: boolean) => void; } export interface GameViewProps { @@ -415,12 +427,9 @@ const Footer = ({ send, state, pState }: GameViewProps) => { const end = isDefined(state.winner); return ( - - - +
+
+ - + + + + + - - +
+
- - +
+
); }; diff --git a/frontend/src/pages/staticView.tsx b/frontend/src/pages/staticView.tsx index 586fa77..0049ac2 100644 --- a/frontend/src/pages/staticView.tsx +++ b/frontend/src/pages/staticView.tsx @@ -317,6 +317,7 @@ const props = { ], turnTime: 0, turnEnd: null, + hideBomb: false, }, pState: { playerID: 'acb830de-80e2-4eba-9b56-81b089fd3f12', diff --git a/frontend/src/protocol/index.ts b/frontend/src/protocol/index.ts index 0a88652..2d646ef 100644 --- a/frontend/src/protocol/index.ts +++ b/frontend/src/protocol/index.ts @@ -76,6 +76,10 @@ export const ClientNote = myzod method: myzod.literal('removePack'), params: myzod.object({ num: myzod.number() }), }), + myzod.object({ + method: myzod.literal('changeHideBomb'), + params: myzod.object({ hideBomb: myzod.boolean() }), + }), ]) ); @@ -129,6 +133,7 @@ export const State = myzod.object({ }) ), timer: StateTimer.optional().nullable(), + hideBomb: myzod.boolean(), }); export type ServerNote = Infer; diff --git a/internal/protocol/protocol.go b/internal/protocol/protocol.go index 2c80a18..6668d53 100644 --- a/internal/protocol/protocol.go +++ b/internal/protocol/protocol.go @@ -174,6 +174,13 @@ type ServerNote struct { Params interface{} `json:"params"` } +const ChangeHideBombMethod = ClientMethod("changeHideBomb") + +//easyjson:json +type ChangeHideBombParams struct { + HideBomb bool `json:"hideBomb"` +} + func StateNote(s *State) ServerNote { return ServerNote{ Method: "state", @@ -191,6 +198,7 @@ type State struct { WordsLeft []int `json:"wordsLeft"` Lists []*StateWordList `json:"lists"` Timer *StateTimer `json:"timer"` + HideBomb bool `json:"hideBomb"` } //easyjson:json diff --git a/internal/protocol/protocol_easyjson.go b/internal/protocol/protocol_easyjson.go index 722b21f..ed924a9 100644 --- a/internal/protocol/protocol_easyjson.go +++ b/internal/protocol/protocol_easyjson.go @@ -804,6 +804,8 @@ func easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol7(in *jlexer.L } (*out.Timer).UnmarshalEasyJSON(in) } + case "hideBomb": + out.HideBomb = bool(in.Bool()) default: in.AddError(&jlexer.LexerError{ Offset: in.GetPos(), @@ -948,6 +950,11 @@ func easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol7(out *jwriter (*in.Timer).MarshalEasyJSON(out) } } + { + const prefix string = ",\"hideBomb\":" + out.RawString(prefix) + out.Bool(bool(in.HideBomb)) + } out.RawByte('}') } @@ -2093,7 +2100,77 @@ func (v *ChangeNicknameParams) UnmarshalJSON(data []byte) error { func (v *ChangeNicknameParams) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol22(l, v) } -func easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol23(in *jlexer.Lexer, out *AddPacksParams) { +func easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol23(in *jlexer.Lexer, out *ChangeHideBombParams) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "hideBomb": + out.HideBomb = bool(in.Bool()) + default: + in.AddError(&jlexer.LexerError{ + Offset: in.GetPos(), + Reason: "unknown field", + Data: key, + }) + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol23(out *jwriter.Writer, in ChangeHideBombParams) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"hideBomb\":" + out.RawString(prefix[1:]) + out.Bool(bool(in.HideBomb)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v ChangeHideBombParams) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol23(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v ChangeHideBombParams) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol23(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *ChangeHideBombParams) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol23(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *ChangeHideBombParams) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol23(l, v) +} +func easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol24(in *jlexer.Lexer, out *AddPacksParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2158,7 +2235,7 @@ func easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol23(in *jlexer. in.Consumed() } } -func easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol23(out *jwriter.Writer, in AddPacksParams) { +func easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol24(out *jwriter.Writer, in AddPacksParams) { out.RawByte('{') first := true _ = first @@ -2184,25 +2261,25 @@ func easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol23(out *jwrite // MarshalJSON supports json.Marshaler interface func (v AddPacksParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol23(&w, v) + easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol24(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddPacksParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol23(w, v) + easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol24(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddPacksParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol23(&r, v) + easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol24(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddPacksParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol23(l, v) + easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol24(l, v) } func easyjsonE4425964Decode(in *jlexer.Lexer, out *struct { Name string `json:"name"` diff --git a/internal/server/server.go b/internal/server/server.go index 10df3c2..606f431 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -214,6 +214,8 @@ type Room struct { turnSeconds int turnDeadline *time.Time turnTimer *time.Timer + + hideBomb bool } type noteSender func(protocol.ServerNote) @@ -411,6 +413,13 @@ func (r *Room) handleNote(playerID game.PlayerID, note *protocol.ClientNote) err } r.room.RemovePack(params.Num) + case protocol.ChangeHideBombMethod: + var params protocol.ChangeHideBombParams + if err := json.Unmarshal(note.Params, ¶ms); err != nil { + return err + } + r.changeHideBomb(params.HideBomb) + default: log.Printf("unhandled method: %s", note.Method) } @@ -469,6 +478,7 @@ func (r *Room) createRoomState(spymaster bool) *protocol.State { Board: make([][]*protocol.StateTile, room.Board.Rows), WordsLeft: room.Board.WordCounts, Lists: make([]*protocol.StateWordList, len(room.WordLists)), + HideBomb: r.hideBomb, } if r.turnDeadline != nil { @@ -503,11 +513,17 @@ func (r *Room) createRoomState(spymaster bool) *protocol.State { } if spymaster || tile.Revealed || room.Winner != nil { - sTile.View = &protocol.StateView{ + view := &protocol.StateView{ Team: tile.Team, Neutral: tile.Neutral, Bomb: tile.Bomb, } + + if !tile.Revealed && room.Winner != nil && r.hideBomb { + view.Bomb = false + } + + sTile.View = view } tiles[col] = sTile @@ -608,3 +624,14 @@ func (r *Room) startTimer() { r.turnDeadline = &deadline r.turnTimer = time.AfterFunc(dur, r.timerEndTurn) } + +// Must be called with r.mu locked. +func (r *Room) changeHideBomb(HideBomb bool) { + if r.hideBomb == HideBomb { + return + } + + r.hideBomb = true + r.room.Version++ + r.sendAll() +}