178 lines
5.3 KiB
TypeScript
178 lines
5.3 KiB
TypeScript
|
import { Button, createStyles, makeStyles, Theme, Typography } from '@material-ui/core';
|
||
|
import { grey, orange, red } from '@material-ui/core/colors';
|
||
|
import { Fireworks } from 'fireworks/lib/react';
|
||
|
import * as React from 'react';
|
||
|
|
||
|
import { isDefined } from '../common';
|
||
|
import { StateBoard, StateTile } from '../protocol';
|
||
|
import { TeamHue, teamSpecs } from '../teams';
|
||
|
import { AspectDiv } from './aspectDiv';
|
||
|
|
||
|
function neutralStyle(revealed: boolean, spymaster: boolean): React.CSSProperties {
|
||
|
return {
|
||
|
color: revealed ? 'white' : 'black',
|
||
|
backgroundColor: grey[revealed ? 500 : 200],
|
||
|
fontWeight: spymaster ? 'bold' : undefined,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function bombStyle(revealed: boolean, spymaster: boolean): React.CSSProperties {
|
||
|
return {
|
||
|
color: revealed ? 'white' : grey[900],
|
||
|
backgroundColor: grey[revealed ? 900 : 700],
|
||
|
fontWeight: spymaster ? 'bold' : undefined,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function teamStyle(teamHue: TeamHue, revealed: boolean, spymaster: boolean): React.CSSProperties {
|
||
|
return {
|
||
|
color: revealed ? 'white' : teamHue[900],
|
||
|
backgroundColor: teamHue[revealed ? 600 : 200],
|
||
|
fontWeight: spymaster ? 'bold' : undefined,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function tileStyle(tile: StateTile, spymaster: boolean): React.CSSProperties {
|
||
|
if (!isDefined(tile.view) || tile.view.neutral) {
|
||
|
return neutralStyle(tile.revealed, spymaster);
|
||
|
}
|
||
|
|
||
|
if (tile.view.bomb) {
|
||
|
return bombStyle(tile.revealed, spymaster);
|
||
|
}
|
||
|
|
||
|
const teamHue = teamSpecs[tile.view.team].hue;
|
||
|
return teamStyle(teamHue, tile.revealed, spymaster);
|
||
|
}
|
||
|
|
||
|
const useTileStyles = makeStyles((theme: Theme) =>
|
||
|
createStyles({
|
||
|
button: {
|
||
|
position: 'absolute',
|
||
|
top: 0,
|
||
|
left: 0,
|
||
|
height: '100%',
|
||
|
width: '100%',
|
||
|
textAlign: 'center',
|
||
|
[theme.breakpoints.down('sm')]: {
|
||
|
padding: '6px',
|
||
|
},
|
||
|
},
|
||
|
typo: {
|
||
|
wordWrap: 'break-word',
|
||
|
width: '100%',
|
||
|
fontSize: theme.typography.h6.fontSize,
|
||
|
[theme.breakpoints.down('sm')]: {
|
||
|
fontSize: theme.typography.button.fontSize,
|
||
|
lineHeight: '1rem',
|
||
|
},
|
||
|
},
|
||
|
explosionWrapper: {
|
||
|
zIndex: 100,
|
||
|
position: 'absolute',
|
||
|
margin: 'auto',
|
||
|
height: 0,
|
||
|
width: 0,
|
||
|
top: 0,
|
||
|
left: 0,
|
||
|
bottom: 0,
|
||
|
right: 0,
|
||
|
},
|
||
|
explosion: {
|
||
|
transform: 'translate(-50%, -50%)',
|
||
|
pointerEvents: 'none',
|
||
|
},
|
||
|
})
|
||
|
);
|
||
|
|
||
|
interface TileProps {
|
||
|
tile: StateTile;
|
||
|
onClick: () => void;
|
||
|
spymaster: boolean;
|
||
|
myTurn: boolean;
|
||
|
winner: boolean;
|
||
|
}
|
||
|
|
||
|
const Tile = ({ tile, onClick, spymaster, myTurn, winner }: TileProps) => {
|
||
|
const classes = useTileStyles();
|
||
|
|
||
|
const bombRevealed = !!(tile.revealed && tile.view?.bomb);
|
||
|
const alreadyExploded = React.useRef(bombRevealed);
|
||
|
const explode = bombRevealed && !alreadyExploded.current;
|
||
|
|
||
|
return (
|
||
|
<AspectDiv aspectRatio="75%">
|
||
|
<Button
|
||
|
type="button"
|
||
|
variant="contained"
|
||
|
className={classes.button}
|
||
|
onClick={onClick}
|
||
|
style={tileStyle(tile, spymaster)}
|
||
|
disabled={spymaster || !myTurn || winner}
|
||
|
>
|
||
|
<Typography variant="h6" className={classes.typo}>
|
||
|
{tile.word}
|
||
|
</Typography>
|
||
|
</Button>
|
||
|
{explode ? (
|
||
|
<div className={classes.explosionWrapper}>
|
||
|
<div className={classes.explosion}>
|
||
|
<Fireworks
|
||
|
{...{
|
||
|
interval: 0,
|
||
|
colors: [red[700], orange[800], grey[500]],
|
||
|
x: 0,
|
||
|
y: 0,
|
||
|
}}
|
||
|
/>
|
||
|
</div>
|
||
|
</div>
|
||
|
) : null}
|
||
|
</AspectDiv>
|
||
|
);
|
||
|
};
|
||
|
|
||
|
export interface BoardProps {
|
||
|
words: StateBoard;
|
||
|
spymaster: boolean;
|
||
|
myTurn: boolean;
|
||
|
winner: boolean;
|
||
|
onClick: (row: number, col: number) => void;
|
||
|
}
|
||
|
|
||
|
const useStyles = makeStyles((theme: Theme) =>
|
||
|
createStyles({
|
||
|
root: {
|
||
|
display: 'grid',
|
||
|
gridGap: theme.spacing(0.5),
|
||
|
[theme.breakpoints.up('lg')]: {
|
||
|
gridGap: theme.spacing(1),
|
||
|
},
|
||
|
gridTemplateRows: (props: BoardProps) => `repeat(${props.words.length}, 1fr)`,
|
||
|
gridTemplateColumns: (props: BoardProps) => `repeat(${props.words[0].length}, 1fr)`,
|
||
|
},
|
||
|
})
|
||
|
);
|
||
|
|
||
|
export const Board = (props: BoardProps) => {
|
||
|
const classes = useStyles(props);
|
||
|
|
||
|
return (
|
||
|
<div className={classes.root}>
|
||
|
{props.words.map((arr, row) =>
|
||
|
arr.map((tile, col) => (
|
||
|
<div key={row * props.words.length + col}>
|
||
|
<Tile
|
||
|
tile={tile}
|
||
|
onClick={() => props.onClick(row, col)}
|
||
|
spymaster={props.spymaster}
|
||
|
myTurn={props.myTurn}
|
||
|
winner={props.winner}
|
||
|
/>
|
||
|
</div>
|
||
|
))
|
||
|
)}
|
||
|
</div>
|
||
|
);
|
||
|
};
|