From 0bd0c2a9bd8df9b4163dae9de0254a518fe0fd47 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Tue, 1 Feb 2022 16:47:25 -0800 Subject: [PATCH] Restructuring of Board vs Table continues Signed-off-by: James Ketrenos --- client/src/Board.css | 4 +- client/src/Board.js | 451 +++++++++++++++++-------------------------- client/src/Table.js | 96 +++------ 3 files changed, 205 insertions(+), 346 deletions(-) diff --git a/client/src/Board.css b/client/src/Board.css index 596b5f9..6e47f96 100644 --- a/client/src/Board.css +++ b/client/src/Board.css @@ -1,9 +1,7 @@ .Board { - display: flex; + display: block; position: absolute; width: 100%; - overflow: hidden; height: 100%; - justify-content: right; } diff --git a/client/src/Board.js b/client/src/Board.js index d61bbe4..60f4664 100644 --- a/client/src/Board.js +++ b/client/src/Board.js @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useRef, useState, useEffect } from "react"; +import React, { useCallback, useRef, useState, useEffect } from "react"; import "./Board.css"; const base = process.env.PUBLIC_URL; @@ -39,18 +39,7 @@ const hexagonRatio = 1.1547005, tileHeight = 0.16, tileWidth = tileHeight * hexagonRatio, roadSize = tileHeight * 0.5 * (5. / 8.), - 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 - } ]; + settlementSize = tileHeight * 0.5 - roadSize; const imageLoaded = (event) => { const image = event.target; @@ -80,6 +69,7 @@ const loadImage = (file, drawFrame) => { images[file].drawFrame = drawFrame; return images[file]; } + const image = new Image(); image.drawFrame = drawFrame; images[file] = image; @@ -115,21 +105,127 @@ const Tiles = (game, drawFrame) => { 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 [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 [borders, setBorders] = useState([]); 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 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(() => { - if (!canvasRef || !tabletop) { - console.log("TODO: Put this in the correct location instead of drawFrame"); - updateGame(game); + if (!canvasRef || !tabletop || !canvasRef.current) { return; } @@ -141,17 +237,17 @@ const Board = ({ game }) => { const canvas = canvasRef.current; if (canvas.width === 0 || canvas.height === 0) { + console.log("No dimensions to render in"); return; } const ctx = canvas.getContext("2d"); - ctx.clearRect(0, 0, canvas.width, canvas.height); + // ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); ctx.strokeStyle = 'white'; ctx.fillStyle = 'rgba(0, 0, 0, 0)'; - minSize = Math.min(canvas.height, canvas.width); /* * Tabletop tiling: * Image width: 1080 @@ -235,7 +331,8 @@ const Board = ({ game }) => { ctx.strokeStyle = "rgba(255, 255, 0, 0.5)"; 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 (tile.pip.roll === roll) { ctx.save(); @@ -301,115 +398,10 @@ const Board = ({ game }) => { 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 ]); - - 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(); - } + }, [ game, canvasRef, closest, mouse, minSize, drawBorders, drawPips, tabletop, tiles ]); const mouseMove = useCallback((event) => { - const canvas = event.target, - rect = canvas.parentElement.getBoundingClientRect(); let x, y; if (event.changedTouches && event.changedTouches.length > 0) { @@ -419,25 +411,22 @@ const Board = ({ game }) => { x = event.clientX; 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 */ if (mouse.timer) { window.clearTimeout(mouse.timer); } - mouse.timer = window.setTimeout(() => { + let timer = window.setTimeout(() => { mouse.timer = null; window.requestAnimationFrame(drawFrame); }, 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; @@ -469,152 +458,40 @@ const Board = ({ game }) => { closest.info.angle = closest.angle; } + setClosest(closest); + window.requestAnimationFrame(drawFrame); - }, [ drawFrame, minSize, mouse, tiles ]); + }, [ drawFrame, closest, setClosest, setMouse, minSize, mouse, tiles ]); const updateDimensions = useCallback(() => { - const hasToolbar = false; - - if (updateSizeTimer) { - clearTimeout(updateSizeTimer); + if (canvasRef.current.updateSizeTimer) { + clearTimeout(canvasRef.current.updateSizeTimer); } - const updateSizeTimer = setTimeout(() => { - const container = document.getElementById("root"), - offset = hasToolbar ? container.firstChild.offsetHeight : 0, - height = window.innerHeight - offset; - - const canvas = canvasRef.current; - - offsetY = offset; - width = window.innerWidth; - height = height; + canvasRef.current.updateSizeTimer = setTimeout(() => { + const width = canvasRef.current.offsetWidth, + height = canvasRef.current.offsetHeight; - if (canvas) { - canvas.width = width; - canvas.height = height; - canvas.style.top = `${offset}px`; - canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; + if (width !== canvasRef.current.width || + height !== canvasRef.current.height) { + canvasRef.current.setAttribute("width", width); + canvasRef.current.setAttribute("height", height); + setMinSize(Math.min(height, width)); } - updateSizeTimer = 0; + canvasRef.current.updateSizeTimer = 0; window.requestAnimationFrame(drawFrame); }, 250); - }, [ drawFrame, canvasRef ]); + }, [ drawFrame, setMinSize ]); - useEffect(() => { - 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) => { + const updateGame = useCallback((game) => { if (!game || game.state === "invalid") { return; } - + + setSignature(gameSignature(game)); + + setTiles(Tiles(game, drawFrame)); setPips({ image: loadImage('pip-numbers.png', drawFrame), pips: game.pips @@ -625,17 +502,35 @@ const Board = ({ game }) => { image: loadImage(border.file, drawFrame) }; })); - } + }, [drawFrame, setTiles, setPips, setTabletop, setBorders, setSignature]); - if (!game) { - console.log("Game not set with initial board. Rendering nothing."); - return <>; - } + useEffect(() => { + if (!canvasRef.current) { + return; + } + + if (signature !== gameSignature(game)) { + updateGame(game); + } + + 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 ( ); diff --git a/client/src/Table.js b/client/src/Table.js index 35e2ea4..4b85044 100755 --- a/client/src/Table.js +++ b/client/src/Table.js @@ -360,35 +360,32 @@ const Players = ({ table }) => { const players = []; - if (!table.game) { - console.log("Why no game?"); - for (let color in table.game.players) { - const item = table.game.players[color], inLobby = table.game.state === 'lobby'; - if (!inLobby && item.status === 'Not active') { - continue; - } - const name = getPlayerName(table.game.sessions, color), - selectable = table.game.state === 'lobby' && (item.status === 'Not active' || table.game.color === color); - let toggleText = name ? name : "Available"; - players.push(( -
{ inLobby && selectable && toggleSelected(color) }} - key={`player-${color}`}> - - - { item.status + ' ' } - { item.status !== 'Not active' && Date.now() ? Date.now() : item.lastActive}/>} - )} /> - { !inLobby && table.game.color === color && - - } -
- )); + for (let color in table.game.players) { + const item = table.game.players[color], inLobby = table.game.state === 'lobby'; + if (!inLobby && item.status === 'Not active') { + continue; } + const name = getPlayerName(table.game.sessions, color), + selectable = table.game.state === 'lobby' && (item.status === 'Not active' || table.game.color === color); + let toggleText = name ? name : "Available"; + players.push(( +
{ inLobby && selectable && toggleSelected(color) }} + key={`player-${color}`}> + + + { item.status + ' ' } + { item.status !== 'Not active' && Date.now() ? Date.now() : item.lastActive}/>} + )} /> + { !inLobby && table.game.color === color && + + } +
+ )); } return ( @@ -426,8 +423,6 @@ class Table extends React.Component { this.setGameState = this.setGameState.bind(this); this.shuffleTable = this.shuffleTable.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.setSelected = this.setSelected.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; } - 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) { if (this.loadTimer) { window.clearTimeout(this.loadTimer); @@ -497,7 +476,6 @@ class Table extends React.Component { this.setState({error: error.message}); }).then(() => { this.resetGameLoad(); - window.requestAnimationFrame(this.drawFrame); }); } @@ -535,7 +513,6 @@ class Table extends React.Component { this.setState({error: error.message}); }).then(() => { this.resetGameLoad(); - window.requestAnimationFrame(this.drawFrame); }); } @@ -567,7 +544,6 @@ class Table extends React.Component { this.setState({error: error.message}); }).then(() => { this.resetGameLoad(); - window.requestAnimationFrame(this.drawFrame); }); } @@ -733,8 +709,6 @@ class Table extends React.Component { }; }); - window.requestAnimationFrame(this.drawFrame); - if (this.game.state !== 'active') { return; } @@ -787,22 +761,15 @@ class Table extends React.Component { this.updateSizeTimer = setTimeout(() => { const container = document.getElementById("root"), - offset = hasToolbar ? container.firstChild.offsetHeight : 0, + offset = hasToolbar + ? container.firstChild.offsetHeight + : 0, height = window.innerHeight - offset; this.offsetY = offset; this.width = window.innerWidth; 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) { this.cards.style.top = `${offset}px`; this.cards.style.width = `${this.width}px`; @@ -810,12 +777,11 @@ console.log("Update canvas size?"); } this.updateSizeTimer = 0; -// this.drawFrame(); }, 250); } updateGame(game) { - console.log("Update Game", game); +// console.log("Update Game", game); this.game = game; this.setState({ game: game }); } @@ -966,7 +932,7 @@ console.log("Update canvas size?"); { this.state.message } {(this.state.pickName || !game.name) && } {(!this.state.pickName && game.name) && <> - + }