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 {
display: flex;
display: block;
position: absolute;
width: 100%;
overflow: hidden;
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";
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) {
@ -420,25 +412,22 @@ const Board = ({ game }) => {
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;
tiles.forEach((tile) => {
@ -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;
canvasRef.current.updateSizeTimer = setTimeout(() => {
const width = canvasRef.current.offsetWidth,
height = canvasRef.current.offsetHeight;
const canvas = canvasRef.current;
offsetY = offset;
width = window.innerWidth;
height = height;
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 (
<canvas className="Board"
width="640px"
height="480px"
ref={canvasRef}>
</canvas>
);

View File

@ -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((
<div
data-selectable={selectable}
data-selected={table.game.color === color}
className="PlayerEntry"
onClick={() => { inLobby && selectable && toggleSelected(color) }}
key={`player-${color}`}>
<PlayerColor color={color}/>
<ListItemText primary={toggleText} secondary={(
<>
{ item.status + ' ' }
{ item.status !== 'Not active' && <Moment fromNow date={item.lastActive > Date.now() ? Date.now() : item.lastActive}/>}
</>)} />
{ !inLobby && table.game.color === color &&
<Button onClick={() => toggleSelected(color)}>Quit</Button>
}
</div>
));
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((
<div
data-selectable={selectable}
data-selected={table.game.color === color}
className="PlayerEntry"
onClick={() => { inLobby && selectable && toggleSelected(color) }}
key={`player-${color}`}>
<PlayerColor color={color}/>
<ListItemText primary={toggleText} secondary={(
<>
{ item.status + ' ' }
{ item.status !== 'Not active' && <Moment fromNow date={item.lastActive > Date.now() ? Date.now() : item.lastActive}/>}
</>)} />
{ !inLobby && table.game.color === color &&
<Button onClick={() => toggleSelected(color)}>Quit</Button>
}
</div>
));
}
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?");
<Paper className="Message">{ this.state.message }</Paper>
{(this.state.pickName || !game.name) && <PlayerName table={this}/> }
{(!this.state.pickName && game.name) && <>
<Players table={this} promoteGameState={this.promoteGameState}/>
<Players table={this}/>
<Chat table={this} promoteGameState={this.promoteGameState}/>
<Action table={this}/>
</> }