1
0

Restructuring of Board vs Table continues

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-02-01 16:47:25 -08:00
parent cdd5e0358f
commit 0bd0c2a9bd
3 changed files with 205 additions and 346 deletions

View File

@ -1,9 +1,7 @@
.Board { .Board {
display: flex; display: block;
position: absolute; position: absolute;
width: 100%; width: 100%;
overflow: hidden;
height: 100%; height: 100%;
justify-content: right;
} }

View File

@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useRef, useState, useEffect } from "react"; import React, { useCallback, useRef, useState, useEffect } from "react";
import "./Board.css"; import "./Board.css";
const base = process.env.PUBLIC_URL; const base = process.env.PUBLIC_URL;
@ -39,18 +39,7 @@ const hexagonRatio = 1.1547005,
tileHeight = 0.16, tileHeight = 0.16,
tileWidth = tileHeight * hexagonRatio, tileWidth = tileHeight * hexagonRatio,
roadSize = tileHeight * 0.5 * (5. / 8.), roadSize = tileHeight * 0.5 * (5. / 8.),
settlementSize = tileHeight * 0.5 - roadSize, settlementSize = tileHeight * 0.5 - roadSize;
diceSize = 0.05,
diceMargin = diceSize * 0.5 * Math.sqrt(2),
dice = [ {
pips: 0,
jitter: 0,
angle: 0
}, {
pips: 0,
jitter: 0,
angle: 0
} ];
const imageLoaded = (event) => { const imageLoaded = (event) => {
const image = event.target; const image = event.target;
@ -80,6 +69,7 @@ const loadImage = (file, drawFrame) => {
images[file].drawFrame = drawFrame; images[file].drawFrame = drawFrame;
return images[file]; return images[file];
} }
const image = new Image(); const image = new Image();
image.drawFrame = drawFrame; image.drawFrame = drawFrame;
images[file] = image; images[file] = image;
@ -115,21 +105,127 @@ const Tiles = (game, drawFrame) => {
return tiles; return tiles;
}; };
const gameSignature = (game) => {
if (!game) {
return "";
}
const signature =
game.borders.map(border => border.file.replace(/borders-(.).*/, "$1")).join('') +
game.pips.map(pip => pip.roll.toString()).join('') +
game.tiles.map(tile => tile.type.charAt(0)).join('');
return signature;
};
const Board = ({ game }) => { const Board = ({ game }) => {
const [mouse] = useState({x: 0, y: 0}); const [signature, setSignature] = useState(gameSignature(game));
const [mouse, setMouse] = useState({x: 0, y: 0, timer: null});
const [pips, setPips] = useState([]); const [pips, setPips] = useState([]);
const [borders, setBorders] = useState([]); const [borders, setBorders] = useState([]);
const [tabletop, setTabletop] = useState(null); const [tabletop, setTabletop] = useState(null);
const [tiles, setTiles] = useState([]);
const [closest, setClosest] = useState({
info: {},
tile: null,
road: null,
tradeToken: null,
settlement: null
});
const [minSize,setMinSize] = useState(0);
const radius = 0.317; const radius = 0.317;
const canvasRef = useRef(null); const canvasRef = useRef(null);
let minSize; const drawPips = useCallback((ctx) => {
const image = pips.image, pipSize = 0.06;
function drawTile(tile, angle, radius) {
tile.pos.x = Math.sin(-angle) * radius;
tile.pos.y = Math.cos(-angle) * radius;
const image = tile.image;
ctx.save();
ctx.rotate(angle);
ctx.translate(0., radius);
ctx.rotate(-angle + Math.PI * 1. / 6.);
ctx.drawImage(image,
tile.x * image.width, tile.y * image.height,
image.width, image.height / 4.,
-tileWidth * 0.5, -tileHeight * 0.5,
tileWidth, tileHeight);
ctx.restore();
}
function drawPip(pip, angle, radius, jitter) {
ctx.save();
ctx.rotate(angle);
ctx.translate(0., radius);
/* Offset into a random direction by a random amount */
ctx.rotate(Math.PI * 2. * jitter);
ctx.translate(0, tileHeight * 0.1 * jitter);
/* Undo random rotation for position, and add random rotation
* for pip placement */
ctx.rotate(-angle - Math.PI * 2. * jitter + jitter * 0.4);
ctx.drawImage(image,
pip.x * image.width, pip.y * image.height,
image.width / 6., image.height / 6.,
-pipSize * 0.5, -pipSize * 0.5,
pipSize, pipSize);
ctx.restore();
}
let angle,
rotation = radius,
index = 0, pip; //, roll = dice[0].pips + dice[1].pips;
/* Outer row */
angle = 0;
for (let i = 0; i < 12; i++) {
angle -= Math.PI * 2. / 12.;
pip = pips.pips[index++];
tiles[i].pip = pip;
drawTile(tiles[i], angle, rotation - (i % 2) * 0.04);
drawPip(pip, angle, rotation - (i % 2) * 0.04, tiles[i].jitter);
}
/* Middle row */
angle = Math.PI * 2. / 12.;
rotation = radius * 0.5;
for (let i = 12; i < 18; i++) {
angle -= Math.PI * 2. / 6.;
pip = pips.pips[index++];
tiles[i].pip = pip;
drawTile(tiles[i], angle, rotation);
drawPip(pip, angle, rotation, tiles[i].jitter);
}
/* Center */
let i = 18;
pip = pips.pips[index++];
tiles[i].pip = pip;
drawTile(tiles[i], 0, 0);
drawPip(pip, 0, 0, tiles[i].jitter);
}, [ tiles, pips]);
const drawBorders = useCallback((ctx) => {
ctx.rotate(Math.PI);
const offset = 0.18;
borders.forEach((border, index) => {
ctx.translate(0, radius);
const image = border.image;
ctx.drawImage(image,
-offset, 0,
0.5, 0.5 * image.height / image.width);
ctx.translate(0, -radius);
ctx.rotate(Math.PI * 2. / 6.);
});
}, [borders]);
const drawFrame = useCallback(() => { const drawFrame = useCallback(() => {
if (!canvasRef || !tabletop) { if (!canvasRef || !tabletop || !canvasRef.current) {
console.log("TODO: Put this in the correct location instead of drawFrame");
updateGame(game);
return; return;
} }
@ -141,17 +237,17 @@ const Board = ({ game }) => {
const canvas = canvasRef.current; const canvas = canvasRef.current;
if (canvas.width === 0 || canvas.height === 0) { if (canvas.width === 0 || canvas.height === 0) {
console.log("No dimensions to render in");
return; return;
} }
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height); // ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save(); ctx.save();
ctx.strokeStyle = 'white'; ctx.strokeStyle = 'white';
ctx.fillStyle = 'rgba(0, 0, 0, 0)'; ctx.fillStyle = 'rgba(0, 0, 0, 0)';
minSize = Math.min(canvas.height, canvas.width);
/* /*
* Tabletop tiling: * Tabletop tiling:
* Image width: 1080 * Image width: 1080
@ -235,7 +331,8 @@ const Board = ({ game }) => {
ctx.strokeStyle = "rgba(255, 255, 0, 0.5)"; ctx.strokeStyle = "rgba(255, 255, 0, 0.5)";
if (game.state !== 'lobby') { if (game.state !== 'lobby') {
const roll = dice[0].pips + dice[1].pips; const roll =
(game.dice[0] ? game.dice[0] : 0) + (game.dice[1] ? game.dice[1] : 0);
if (roll) tiles.forEach((tile) => { if (roll) tiles.forEach((tile) => {
if (tile.pip.roll === roll) { if (tile.pip.roll === roll) {
ctx.save(); ctx.save();
@ -301,115 +398,10 @@ const Board = ({ game }) => {
ctx.fill(); ctx.fill();
} }
ctx.save();
ctx.translate(tileWidth * -2.5, -tileWidth * 2);
ctx.rotate(Math.PI * -0.25)
if (dice[0].pips) {
ctx.translate(-0.5 * (diceSize + diceMargin), 0);
drawDie(ctx, dice[0]);
}
if (dice[1].pips) {
ctx.translate(diceSize + diceMargin, 0);
drawDie(ctx, dice[1]);
}
ctx.restore(); ctx.restore();
}, [ game, canvasRef, closest, mouse, minSize, drawBorders, drawPips, tabletop, tiles ]);
ctx.restore();
}, [ game ]);
const tiles = useMemo(() => Tiles(game, drawFrame), [ game, drawFrame]);
let offsetY = 0, width = 0;
let closest = {
info: {},
tile: null,
road: null,
tradeToken: null,
settlement: null
};
const drawDie = (ctx, die) => {
const radius = diceSize * 0.125,
offset = diceSize * 0.5 - radius,
pips = die.pips;
ctx.save();
ctx.rotate(die.angle);
ctx.translate(die.jitter, 0);
ctx.strokeStyle = "#666";
ctx.fillStyle = "#444";
ctx.beginPath();
ctx.arc(-offset, -offset, radius, Math.PI, Math.PI * 1.5);
ctx.arc(+offset, -offset, radius, Math.PI * 1.5, 0);
ctx.arc(+offset, +offset, radius, 0, Math.PI * 0.5);
ctx.arc(-offset, +offset, radius, Math.PI * 0.5, Math.PI);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.strokeStyle = "#bbb";
ctx.fillStyle = "#ddd";
/* center pip */
if (pips === 1 || pips === 3 || pips === 5) {
ctx.beginPath();
ctx.arc(0, 0, diceSize * 0.0625, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
/* upper left pip */
if (pips === 2 || pips === 3 || pips === 4 || pips === 5 || pips === 6) {
ctx.beginPath();
ctx.arc(-diceSize * 0.250, -diceSize * 0.250, diceSize * 0.0625, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
/* upper right pip */
if (pips === 4 || pips === 5 || pips === 6) {
ctx.beginPath();
ctx.arc(+diceSize * 0.250, -diceSize * 0.250, diceSize * 0.0625, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
/* lower right pip */
if (pips === 2 || pips === 3 || pips === 4 || pips === 5 || pips === 6) {
ctx.beginPath();
ctx.arc(+diceSize * 0.250, +diceSize * 0.250, diceSize * 0.0625, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
/* lower left pip */
if (pips === 4 || pips === 5 || pips === 6) {
ctx.beginPath();
ctx.arc(-diceSize * 0.250, +diceSize * 0.250, diceSize * 0.0625, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
/* middle left and right pip */
if (pips === 6) {
ctx.beginPath();
ctx.arc(-diceSize * 0.250, 0, diceSize * 0.0625, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(+diceSize * 0.250, 0, diceSize * 0.0625, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
ctx.restore();
}
const mouseMove = useCallback((event) => { const mouseMove = useCallback((event) => {
const canvas = event.target,
rect = canvas.parentElement.getBoundingClientRect();
let x, y; let x, y;
if (event.changedTouches && event.changedTouches.length > 0) { if (event.changedTouches && event.changedTouches.length > 0) {
@ -420,25 +412,22 @@ const Board = ({ game }) => {
y = event.clientY; y = event.clientY;
} }
if (offsetY) {
y -= offsetY;
}
/* Scale mouse.x and mouse.y relative to board */
mouse.x = (x - rect.left) /
(minSize / hexagonRatio) - 0.5 - tileHeight * 0.5;
mouse.y = (y - rect.top) /
(minSize / hexagonRatio) - 0.5 - tileHeight * 0.5;
/* Hide the mouse cursor circle after 0.5s */ /* Hide the mouse cursor circle after 0.5s */
if (mouse.timer) { if (mouse.timer) {
window.clearTimeout(mouse.timer); window.clearTimeout(mouse.timer);
} }
mouse.timer = window.setTimeout(() => { let timer = window.setTimeout(() => {
mouse.timer = null; mouse.timer = null;
window.requestAnimationFrame(drawFrame); window.requestAnimationFrame(drawFrame);
}, 500); }, 500);
/* Scale mouse.x and mouse.y relative to board */
setMouse({
x: x / (minSize / hexagonRatio) - 0.5 - tileHeight * 0.5,
y: y / (minSize / hexagonRatio) - 0.5 - tileHeight * 0.5,
timer: timer
});
let tmp = null; let tmp = null;
tiles.forEach((tile) => { tiles.forEach((tile) => {
@ -469,152 +458,40 @@ const Board = ({ game }) => {
closest.info.angle = closest.angle; closest.info.angle = closest.angle;
} }
setClosest(closest);
window.requestAnimationFrame(drawFrame); window.requestAnimationFrame(drawFrame);
}, [ drawFrame, minSize, mouse, tiles ]); }, [ drawFrame, closest, setClosest, setMouse, minSize, mouse, tiles ]);
const updateDimensions = useCallback(() => { const updateDimensions = useCallback(() => {
const hasToolbar = false; if (canvasRef.current.updateSizeTimer) {
clearTimeout(canvasRef.current.updateSizeTimer);
if (updateSizeTimer) {
clearTimeout(updateSizeTimer);
} }
const updateSizeTimer = setTimeout(() => { canvasRef.current.updateSizeTimer = setTimeout(() => {
const container = document.getElementById("root"), const width = canvasRef.current.offsetWidth,
offset = hasToolbar ? container.firstChild.offsetHeight : 0, height = canvasRef.current.offsetHeight;
height = window.innerHeight - offset;
const canvas = canvasRef.current; if (width !== canvasRef.current.width ||
height !== canvasRef.current.height) {
offsetY = offset; canvasRef.current.setAttribute("width", width);
width = window.innerWidth; canvasRef.current.setAttribute("height", height);
height = height; setMinSize(Math.min(height, width));
if (canvas) {
canvas.width = width;
canvas.height = height;
canvas.style.top = `${offset}px`;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
} }
updateSizeTimer = 0; canvasRef.current.updateSizeTimer = 0;
window.requestAnimationFrame(drawFrame); window.requestAnimationFrame(drawFrame);
}, 250); }, 250);
}, [ drawFrame, canvasRef ]); }, [ drawFrame, setMinSize ]);
useEffect(() => { const updateGame = useCallback((game) => {
if (!canvasRef.current) {
return;
}
const canvas = canvasRef.current;
canvas.addEventListener('mousemove', mouseMove);
canvas.addEventListener('touchmove', mouseMove);
canvas.addEventListener('resize', updateDimensions);
return () => {
canvas.removeEventListener('mousemove', mouseMove);
canvas.removeEventListener('touchmove', mouseMove);
canvas.removeEventListener('resize', updateDimensions);
};
}, [ mouseMove, updateDimensions ]);
const drawPips = (ctx) => {
const image = pips.image, pipSize = 0.06;
function drawTile(tile, angle, radius) {
tile.pos.x = Math.sin(-angle) * radius;
tile.pos.y = Math.cos(-angle) * radius;
const image = tile.image;
ctx.save();
ctx.rotate(angle);
ctx.translate(0., radius);
ctx.rotate(-angle + Math.PI * 1. / 6.);
ctx.drawImage(image,
tile.x * image.width, tile.y * image.height,
image.width, image.height / 4.,
-tileWidth * 0.5, -tileHeight * 0.5,
tileWidth, tileHeight);
ctx.restore();
}
function drawPip(pip, angle, radius, jitter) {
ctx.save();
ctx.rotate(angle);
ctx.translate(0., radius);
/* Offset into a random direction by a random amount */
ctx.rotate(Math.PI * 2. * jitter);
ctx.translate(0, tileHeight * 0.1 * jitter);
/* Undo random rotation for position, and add random rotation
* for pip placement */
ctx.rotate(-angle - Math.PI * 2. * jitter + jitter * 0.4);
ctx.drawImage(image,
pip.x * image.width, pip.y * image.height,
image.width / 6., image.height / 6.,
-pipSize * 0.5, -pipSize * 0.5,
pipSize, pipSize);
ctx.restore();
}
let angle,
rotation = radius,
index = 0, pip; //, roll = dice[0].pips + dice[1].pips;
/* Outer row */
angle = 0;
for (let i = 0; i < 12; i++) {
angle -= Math.PI * 2. / 12.;
pip = pips.pips[index++];
tiles[i].pip = pip;
drawTile(tiles[i], angle, rotation - (i % 2) * 0.04);
drawPip(pip, angle, rotation - (i % 2) * 0.04, tiles[i].jitter);
}
/* Middle row */
angle = Math.PI * 2. / 12.;
rotation = radius * 0.5;
for (let i = 12; i < 18; i++) {
angle -= Math.PI * 2. / 6.;
pip = pips.pips[index++];
tiles[i].pip = pip;
drawTile(tiles[i], angle, rotation);
drawPip(pip, angle, rotation, tiles[i].jitter);
}
/* Center */
let i = 18;
pip = pips.pips[index++];
tiles[i].pip = pip;
drawTile(tiles[i], 0, 0);
drawPip(pip, 0, 0, tiles[i].jitter);
}
const drawBorders = (ctx) => {
ctx.rotate(Math.PI);
const offset = 0.18;
borders.forEach((border, index) => {
ctx.translate(0, radius);
const image = border.image;
ctx.drawImage(image,
-offset, 0,
0.5, 0.5 * image.height / image.width);
ctx.translate(0, -radius);
ctx.rotate(Math.PI * 2. / 6.);
});
}
const updateGame = (game) => {
if (!game || game.state === "invalid") { if (!game || game.state === "invalid") {
return; return;
} }
setSignature(gameSignature(game));
setTiles(Tiles(game, drawFrame));
setPips({ setPips({
image: loadImage('pip-numbers.png', drawFrame), image: loadImage('pip-numbers.png', drawFrame),
pips: game.pips pips: game.pips
@ -625,17 +502,35 @@ const Board = ({ game }) => {
image: loadImage(border.file, drawFrame) image: loadImage(border.file, drawFrame)
}; };
})); }));
}, [drawFrame, setTiles, setPips, setTabletop, setBorders, setSignature]);
useEffect(() => {
if (!canvasRef.current) {
return;
} }
if (!game) { if (signature !== gameSignature(game)) {
console.log("Game not set with initial board. Rendering nothing."); updateGame(game);
return <></>;
} }
const canvas = canvasRef.current;
canvas.addEventListener('mousemove', mouseMove);
canvas.addEventListener('touchmove', mouseMove);
window.addEventListener('resize', updateDimensions);
console.log("useEffect()");
updateDimensions();
return () => {
canvas.removeEventListener('mousemove', mouseMove);
canvas.removeEventListener('touchmove', mouseMove);
window.removeEventListener('resize', updateDimensions);
};
}, [ mouseMove, updateDimensions, updateGame, signature, game ]);
return ( return (
<canvas className="Board" <canvas className="Board"
width="640px"
height="480px"
ref={canvasRef}> ref={canvasRef}>
</canvas> </canvas>
); );

View File

@ -360,8 +360,6 @@ const Players = ({ table }) => {
const players = []; const players = [];
if (!table.game) {
console.log("Why no game?");
for (let color in table.game.players) { for (let color in table.game.players) {
const item = table.game.players[color], inLobby = table.game.state === 'lobby'; const item = table.game.players[color], inLobby = table.game.state === 'lobby';
if (!inLobby && item.status === 'Not active') { if (!inLobby && item.status === 'Not active') {
@ -389,7 +387,6 @@ const Players = ({ table }) => {
</div> </div>
)); ));
} }
}
return ( return (
<Paper className="Players"> <Paper className="Players">
@ -426,8 +423,6 @@ class Table extends React.Component {
this.setGameState = this.setGameState.bind(this); this.setGameState = this.setGameState.bind(this);
this.shuffleTable = this.shuffleTable.bind(this); this.shuffleTable = this.shuffleTable.bind(this);
this.updateGame = this.updateGame.bind(this); this.updateGame = this.updateGame.bind(this);
this.imageLoadError = this.imageLoadError.bind(this);
this.imageLoaded = this.imageLoaded.bind(this);
this.setPlayerName = this.setPlayerName.bind(this); this.setPlayerName = this.setPlayerName.bind(this);
this.setSelected = this.setSelected.bind(this); this.setSelected = this.setSelected.bind(this);
this.updateMessage = this.updateMessage.bind(this); this.updateMessage = this.updateMessage.bind(this);
@ -453,22 +448,6 @@ class Table extends React.Component {
this.id = (props.router && props.router.params.id) ? props.router.params.id : 0; this.id = (props.router && props.router.params.id) ? props.router.params.id : 0;
} }
imageLoaded(event) {
const image = event.target;
console.log(`Done loading ${image.src}`);
image.removeEventListener("load", this.imageLoaded);
image.removeEventListener("error", this.imageLoadError);
window.requestAnimationFrame(this.drawFrame);
}
imageLoadError(event) {
const image = event.target;
console.log(`Error loading ${image.src}`);
image.removeEventListener("load", this.imageLoaded);
image.removeEventListener("error", this.imageLoadError);
this.setState({message: `Error loading ${image.src}`});
}
setSelected(key) { setSelected(key) {
if (this.loadTimer) { if (this.loadTimer) {
window.clearTimeout(this.loadTimer); window.clearTimeout(this.loadTimer);
@ -497,7 +476,6 @@ class Table extends React.Component {
this.setState({error: error.message}); this.setState({error: error.message});
}).then(() => { }).then(() => {
this.resetGameLoad(); this.resetGameLoad();
window.requestAnimationFrame(this.drawFrame);
}); });
} }
@ -535,7 +513,6 @@ class Table extends React.Component {
this.setState({error: error.message}); this.setState({error: error.message});
}).then(() => { }).then(() => {
this.resetGameLoad(); this.resetGameLoad();
window.requestAnimationFrame(this.drawFrame);
}); });
} }
@ -567,7 +544,6 @@ class Table extends React.Component {
this.setState({error: error.message}); this.setState({error: error.message});
}).then(() => { }).then(() => {
this.resetGameLoad(); this.resetGameLoad();
window.requestAnimationFrame(this.drawFrame);
}); });
} }
@ -733,8 +709,6 @@ class Table extends React.Component {
}; };
}); });
window.requestAnimationFrame(this.drawFrame);
if (this.game.state !== 'active') { if (this.game.state !== 'active') {
return; return;
} }
@ -787,22 +761,15 @@ class Table extends React.Component {
this.updateSizeTimer = setTimeout(() => { this.updateSizeTimer = setTimeout(() => {
const container = document.getElementById("root"), const container = document.getElementById("root"),
offset = hasToolbar ? container.firstChild.offsetHeight : 0, offset = hasToolbar
? container.firstChild.offsetHeight
: 0,
height = window.innerHeight - offset; height = window.innerHeight - offset;
this.offsetY = offset; this.offsetY = offset;
this.width = window.innerWidth; this.width = window.innerWidth;
this.height = height; this.height = height;
console.log("Update canvas size?");
/*
if (this.canvas) {
this.canvas.width = this.width;
this.canvas.height = this.height;
this.canvas.style.top = `${offset}px`;
this.canvas.style.width = `${this.width}px`;
this.canvas.style.height = `${this.height}px`;
}
*/
if (this.cards && this.cards.style) { if (this.cards && this.cards.style) {
this.cards.style.top = `${offset}px`; this.cards.style.top = `${offset}px`;
this.cards.style.width = `${this.width}px`; this.cards.style.width = `${this.width}px`;
@ -810,12 +777,11 @@ console.log("Update canvas size?");
} }
this.updateSizeTimer = 0; this.updateSizeTimer = 0;
// this.drawFrame();
}, 250); }, 250);
} }
updateGame(game) { updateGame(game) {
console.log("Update Game", game); // console.log("Update Game", game);
this.game = game; this.game = game;
this.setState({ game: game }); this.setState({ game: game });
} }
@ -966,7 +932,7 @@ console.log("Update canvas size?");
<Paper className="Message">{ this.state.message }</Paper> <Paper className="Message">{ this.state.message }</Paper>
{(this.state.pickName || !game.name) && <PlayerName table={this}/> } {(this.state.pickName || !game.name) && <PlayerName table={this}/> }
{(!this.state.pickName && game.name) && <> {(!this.state.pickName && game.name) && <>
<Players table={this} promoteGameState={this.promoteGameState}/> <Players table={this}/>
<Chat table={this} promoteGameState={this.promoteGameState}/> <Chat table={this} promoteGameState={this.promoteGameState}/>
<Action table={this}/> <Action table={this}/>
</> } </> }