Detailed changes
  
  
    
    @@ -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]);
 }
  
  
  
    
    @@ -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 (
-        <Grid container direction="row" justify="space-between" alignItems="flex-start" spacing={2}>
-            <Grid item xs style={{ textAlign: 'left' }}>
-                <ButtonGroup
-                    variant="outlined"
-                    style={{ marginBottom: '0.5rem', marginRight: '0.5rem', display: 'inline' }}
-                >
+        <div style={{ display: 'flex', justifyContent: 'space-between', alignContent: 'flex-start', flexWrap: 'wrap' }}>
+            <div style={{ display: 'flex', alignContent: 'flex-start', flexWrap: 'wrap' }}>
+                <ButtonGroup variant="outlined" style={{ marginBottom: '0.5rem', marginRight: '0.5rem' }}>
                     <Button
                         type="button"
                         variant={pState.spymaster ? undefined : 'contained'}
@@ -440,10 +449,25 @@ const Footer = ({ send, state, pState }: GameViewProps) => {
                         Spymaster
                     </Button>
                 </ButtonGroup>
-                <ButtonGroup
-                    variant="outlined"
-                    style={{ marginBottom: '0.5rem', marginRight: '0.5rem', display: 'inline' }}
-                >
+                <ButtonGroup variant="outlined" style={{ marginBottom: '0.5rem', marginRight: '0.5rem' }}>
+                    <Button
+                        type="button"
+                        variant={state.hideBomb ? undefined : 'contained'}
+                        onClick={() => send.changeHideBomb(false)}
+                        startIcon={<Visibility />}
+                    >
+                        Show bomb
+                    </Button>
+                    <Button
+                        type="button"
+                        variant={state.hideBomb ? 'contained' : undefined}
+                        onClick={() => send.changeHideBomb(true)}
+                        startIcon={<VisibilityOff />}
+                    >
+                        Hide bomb
+                    </Button>
+                </ButtonGroup>
+                <ButtonGroup variant="outlined" style={{ marginBottom: '0.5rem', marginRight: '0.5rem' }}>
                     <Button
                         type="button"
                         variant={isDefined(state.timer) ? undefined : 'contained'}
@@ -459,8 +483,8 @@ const Footer = ({ send, state, pState }: GameViewProps) => {
                         <Timer />
                     </Button>
                 </ButtonGroup>
-            </Grid>
-            <Grid item xs style={{ textAlign: 'right' }}>
+            </div>
+            <div>
                 <Button
                     type="button"
                     variant={end ? 'contained' : 'outlined'}
@@ -470,8 +494,8 @@ const Footer = ({ send, state, pState }: GameViewProps) => {
                 >
                     New game
                 </Button>
-            </Grid>
-        </Grid>
+            </div>
+        </div>
     );
 };
 
  
  
  
    
    @@ -317,6 +317,7 @@ const props = {
         ],
         turnTime: 0,
         turnEnd: null,
+        hideBomb: false,
     },
     pState: {
         playerID: 'acb830de-80e2-4eba-9b56-81b089fd3f12',
  
  
  
    
    @@ -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<typeof ServerNote>;
  
  
  
    
    @@ -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
  
  
  
    
    @@ -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"`
  
  
  
    
    @@ -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()
+}