1import { Button, createStyles, makeStyles, Theme, Typography } from '@material-ui/core';
2import { grey, orange, red } from '@material-ui/core/colors';
3import { Fireworks } from 'fireworks/lib/react';
4import * as React from 'react';
5
6import { isDefined } from '../common';
7import { StateBoard, StateTile } from '../protocol';
8import { TeamHue, teamSpecs } from '../teams';
9import { AspectDiv } from './aspectDiv';
10
11function neutralStyle(revealed: boolean, spymaster: boolean): React.CSSProperties {
12 return {
13 color: revealed ? 'white' : 'black',
14 backgroundColor: grey[revealed ? 500 : 200],
15 fontWeight: spymaster ? 'bold' : undefined,
16 };
17}
18
19function bombStyle(revealed: boolean, spymaster: boolean): React.CSSProperties {
20 return {
21 color: revealed ? 'white' : grey[900],
22 backgroundColor: grey[revealed ? 900 : 700],
23 fontWeight: spymaster ? 'bold' : undefined,
24 };
25}
26
27function teamStyle(teamHue: TeamHue, revealed: boolean, spymaster: boolean): React.CSSProperties {
28 return {
29 color: revealed ? 'white' : teamHue[900],
30 backgroundColor: teamHue[revealed ? 600 : 200],
31 fontWeight: spymaster ? 'bold' : undefined,
32 };
33}
34
35function tileStyle(tile: StateTile, spymaster: boolean): React.CSSProperties {
36 if (!isDefined(tile.view) || tile.view.neutral) {
37 return neutralStyle(tile.revealed, spymaster);
38 }
39
40 if (tile.view.bomb) {
41 return bombStyle(tile.revealed, spymaster);
42 }
43
44 const teamHue = teamSpecs[tile.view.team].hue;
45 return teamStyle(teamHue, tile.revealed, spymaster);
46}
47
48const useTileStyles = makeStyles((theme: Theme) =>
49 createStyles({
50 button: {
51 position: 'absolute',
52 top: 0,
53 left: 0,
54 height: '100%',
55 width: '100%',
56 textAlign: 'center',
57 [theme.breakpoints.down('sm')]: {
58 padding: '6px',
59 },
60 },
61 typo: {
62 wordWrap: 'break-word',
63 width: '100%',
64 fontSize: theme.typography.h6.fontSize,
65 [theme.breakpoints.down('sm')]: {
66 fontSize: theme.typography.button.fontSize,
67 lineHeight: '1rem',
68 },
69 },
70 explosionWrapper: {
71 zIndex: 100,
72 position: 'absolute',
73 margin: 'auto',
74 height: 0,
75 width: 0,
76 top: 0,
77 left: 0,
78 bottom: 0,
79 right: 0,
80 },
81 explosion: {
82 transform: 'translate(-50%, -50%)',
83 pointerEvents: 'none',
84 },
85 })
86);
87
88interface TileProps {
89 tile: StateTile;
90 onClick: () => void;
91 spymaster: boolean;
92 myTurn: boolean;
93 winner: boolean;
94}
95
96const Tile = ({ tile, onClick, spymaster, myTurn, winner }: TileProps) => {
97 const classes = useTileStyles();
98
99 const bombRevealed = !!(tile.revealed && tile.view?.bomb);
100 const alreadyExploded = React.useRef(bombRevealed);
101 const explode = bombRevealed && !alreadyExploded.current;
102
103 return (
104 <AspectDiv aspectRatio="75%">
105 <Button
106 type="button"
107 variant="contained"
108 className={classes.button}
109 onClick={onClick}
110 style={tileStyle(tile, spymaster)}
111 disabled={spymaster || !myTurn || winner || tile.revealed}
112 >
113 <Typography variant="h6" className={classes.typo}>
114 {tile.word}
115 </Typography>
116 </Button>
117 {explode ? (
118 <div className={classes.explosionWrapper}>
119 <div className={classes.explosion}>
120 <Fireworks
121 {...{
122 interval: 0,
123 colors: [red[700], orange[800], grey[500]],
124 x: 0,
125 y: 0,
126 }}
127 />
128 </div>
129 </div>
130 ) : null}
131 </AspectDiv>
132 );
133};
134
135export interface BoardProps {
136 words: StateBoard;
137 spymaster: boolean;
138 myTurn: boolean;
139 winner: boolean;
140 onClick: (row: number, col: number) => void;
141}
142
143const useStyles = makeStyles((theme: Theme) =>
144 createStyles({
145 root: {
146 display: 'grid',
147 gridGap: theme.spacing(0.5),
148 [theme.breakpoints.up('lg')]: {
149 gridGap: theme.spacing(1),
150 },
151 gridTemplateRows: (props: BoardProps) => `repeat(${props.words.length}, 1fr)`,
152 gridTemplateColumns: (props: BoardProps) => `repeat(${props.words[0].length}, 1fr)`,
153 },
154 })
155);
156
157export const Board = (props: BoardProps) => {
158 const classes = useStyles(props);
159
160 return (
161 <div className={classes.root}>
162 {props.words.map((arr, row) =>
163 arr.map((tile, col) => (
164 <div key={row * props.words.length + col}>
165 <Tile
166 tile={tile}
167 onClick={() => props.onClick(row, col)}
168 spymaster={props.spymaster}
169 myTurn={props.myTurn}
170 winner={props.winner}
171 />
172 </div>
173 ))
174 )}
175 </div>
176 );
177};