
working on indicating what you rolled Signed-off-by: James Ketrenos <james.p.ketrenos@intel.com>
870 lines
24 KiB
JavaScript
Executable File
870 lines
24 KiB
JavaScript
Executable File
import React, { useState, useEffect } from "react";
|
|
|
|
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
|
|
} ];
|
|
|
|
const Tiles = (board) => {
|
|
const tiles = [ {
|
|
type: "wood", y: 0. / 4.
|
|
}, {
|
|
type: "wood", y: 1. / 4.
|
|
}, {
|
|
type: "wood", y: 2. / 4.
|
|
}, {
|
|
type: "wood", y: 3. / 4.
|
|
}, {
|
|
type: "wheat", y: 0. / 4.
|
|
}, {
|
|
type: "wheat", y: 1. / 4.
|
|
}, {
|
|
type: "wheat", y: 2. / 4.
|
|
}, {
|
|
type: "wheat", y: 3. / 4.
|
|
}, {
|
|
type: "stone", y: 0. / 4.
|
|
}, {
|
|
type: "stone", y: 1. / 4.
|
|
}, {
|
|
type: "stone", y: 2. / 4.
|
|
}, {
|
|
type: "sheep", y: 0. / 4.
|
|
}, {
|
|
type: "sheep", y: 1. / 4.
|
|
}, {
|
|
type: "sheep", y: 2. / 4.
|
|
}, {
|
|
type: "sheep", y: 3. / 4.
|
|
}, {
|
|
type: "brick", y: 0. / 4.
|
|
}, {
|
|
type: "brick", y: 1. / 4.
|
|
}, {
|
|
type: "brick", y: 2. / 4.
|
|
}, {
|
|
type: "robber", y: 0
|
|
} ];
|
|
|
|
[ "robber", "brick", "wood", "sheep", "stone", "wheat" ].forEach((type) => {
|
|
const image = new Image(),
|
|
file = "tiles-" + type + ".png";
|
|
tiles.forEach((tile) => {
|
|
if (tile.type == type) {
|
|
tile.image = image;
|
|
tile.x = 0;
|
|
tile.pos = { x: 0, y: 0 };
|
|
}
|
|
tile.jitter = 0;
|
|
});
|
|
image.addEventListener("load", (event) => {
|
|
console.log(`Done loading ${file}`);
|
|
window.requestAnimationFrame(board.drawFrame);
|
|
});
|
|
image.addEventListener("error", (event) => {
|
|
alert(`Error loading ${file}`);
|
|
});
|
|
image.src = `assets/gfx/${file}`;
|
|
});
|
|
|
|
return tiles;
|
|
};
|
|
|
|
const Pips = (board) => {
|
|
const image = new Image(),
|
|
file = 'pip-numbers.png';
|
|
|
|
let pips = [
|
|
{ roll: 0, pips: 0, y: 3. / 6., x: 0. / 6. },
|
|
{ roll: 5, pips: 4, y: 0. / 6., x: 0. / 6. },
|
|
{ roll: 2, pips: 1, y: 0. / 6., x: 1. / 6. },
|
|
{ roll: 6, pips: 5, y: 0. / 6., x: 2. / 6. },
|
|
{ roll: 3, pips: 2, y: 0. / 6., x: 3. / 6. },
|
|
{ roll: 8, pips: 5, y: 0. / 6., x: 4. / 6. },
|
|
{ roll: 10, pips: 3, y: 0. / 6., x: 5. / 6. },
|
|
{ roll: 9, pips: 4, y: 1. / 6., x: 0. / 6. },
|
|
{ roll: 12, pips: 1, y: 1. / 6., x: 1. / 6. },
|
|
{ roll: 11, pips: 2, y: 1. / 6., x: 2. / 6. },
|
|
{ roll: 4, pips: 3, y: 1. / 6., x: 3. / 6. },
|
|
{ roll: 8, pips: 5, y: 1. / 6., x: 4. / 6. },
|
|
{ roll: 10, pips: 3, y: 1. / 6., x: 5. / 6. },
|
|
{ roll: 9, pips: 4, y: 2. / 6., x: 0. / 6. },
|
|
{ roll: 4, pips: 3, y: 2. / 6., x: 1. / 6. },
|
|
{ roll: 5, pips: 4, y: 2. / 6., x: 2. / 6. },
|
|
{ roll: 6, pips: 6, y: 2. / 6., x: 3. / 6. },
|
|
{ roll: 3, pips: 2, y: 2. / 6., x: 4. / 6. },
|
|
{ roll: 11, pips: 2, y: 2. / 6., x: 5. / 6. }
|
|
];
|
|
|
|
image.addEventListener("load", (event) => {
|
|
console.log(`Done loading ${file}`);
|
|
window.requestAnimationFrame(board.drawFrame);
|
|
});
|
|
image.addEventListener("error", (event) => {
|
|
alert(`Error loading ${file}`);
|
|
});
|
|
image.src = `assets/gfx/${file}`;
|
|
|
|
return {
|
|
image: image,
|
|
pips: pips
|
|
};
|
|
};
|
|
|
|
const Border = (board, border) => {
|
|
const image = new Image(), file = border.file
|
|
border.image = image;
|
|
image.addEventListener("load", (event) => {
|
|
console.log(`Done loading ${file}`);
|
|
window.requestAnimationFrame(board.drawFrame);
|
|
});
|
|
image.addEventListener("error", (event) => {
|
|
alert(`Error loading ${file}`);
|
|
});
|
|
image.src = `assets/gfx/${file}`;
|
|
return border;
|
|
};
|
|
|
|
const Table = (board) => {
|
|
const image = new Image(), file = "table.png";
|
|
image.addEventListener("load", (event) => {
|
|
console.log(`Done loading ${file}`);
|
|
window.requestAnimationFrame(board.drawFrame);
|
|
});
|
|
image.addEventListener("error", (event) => {
|
|
alert(`Error loading ${file}`);
|
|
});
|
|
image.src = `assets/gfx/${file}`;
|
|
return image;
|
|
};
|
|
|
|
function shuffle(array) {
|
|
var currentIndex = array.length, temporaryValue, randomIndex;
|
|
|
|
// While there remain elements to shuffle...
|
|
while (0 !== currentIndex) {
|
|
|
|
// Pick a remaining element...
|
|
randomIndex = Math.floor(Math.random() * currentIndex);
|
|
currentIndex -= 1;
|
|
|
|
// And swap it with the current element.
|
|
temporaryValue = array[currentIndex];
|
|
array[currentIndex] = array[randomIndex];
|
|
array[randomIndex] = temporaryValue;
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
class Placard extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
}
|
|
render() {
|
|
return (
|
|
<div className="Placard"
|
|
style={{backgroundImage:`url(assets/gfx/placard-${this.props.type}.png)`}}>
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
class Development extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
}
|
|
render() {
|
|
const array = [];
|
|
for (let i = 0; i < this.props.count; i++) {
|
|
if (this.props.type.match(/-$/)) {
|
|
array.push(i + 1);//Math.ceil(Math.random() * this.props.max));
|
|
} else {
|
|
array.push("");
|
|
}
|
|
}
|
|
return (
|
|
<div className="Stack">
|
|
{ React.Children.map(array, i => (
|
|
<div className="Development"
|
|
style={{backgroundImage:`url(assets/gfx/card-${this.props.type}${i}.png)`}}>
|
|
</div>
|
|
)) }
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
class Resource extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
}
|
|
render() {
|
|
const array = new Array(Number(this.props.count ? this.props.count : 0));
|
|
return (
|
|
<>
|
|
{ array.length > 0 &&
|
|
<div className="Stack">
|
|
{ React.Children.map(array, i => (
|
|
<div className="Resource"
|
|
style={{backgroundImage:`url(assets/gfx/card-${this.props.type}.png)`}}>
|
|
</div>
|
|
)) }
|
|
</div>
|
|
}
|
|
</>
|
|
);
|
|
}
|
|
};
|
|
|
|
class Board extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
total: 0,
|
|
wood: 0,
|
|
sheep: 0,
|
|
brick: 0,
|
|
stone: 0,
|
|
wheat: 0
|
|
};
|
|
this.componentDidMount = this.componentDidMount.bind(this);
|
|
this.updateDimensions = this.updateDimensions.bind(this);
|
|
this.drawFrame = this.drawFrame.bind(this);
|
|
this.drawBorders = this.drawBorders.bind(this);
|
|
this.drawPips = this.drawPips.bind(this);
|
|
this.drawDie = this.drawDie.bind(this);
|
|
this.keyUp = this.keyUp.bind(this);
|
|
this.mouseMove = this.mouseMove.bind(this);
|
|
this.randomize = this.randomize.bind(this);
|
|
this.throwDice = this.throwDice.bind(this);
|
|
|
|
this.mouse = { x: 0, y: 0 };
|
|
this.radius = 0.317;
|
|
|
|
this.pips = Pips(this);
|
|
this.tiles = Tiles(this);
|
|
this.table = Table(this);
|
|
|
|
this.closest = {
|
|
info: {},
|
|
tile: null,
|
|
road: null,
|
|
tradeToken: null,
|
|
settlement: null
|
|
};
|
|
|
|
this.borders = [ {
|
|
file: 'borders-1.6.png', left: "sheep", right: "bank"
|
|
}, {
|
|
file: 'borders-2.1.png', center: "sheep"
|
|
}, {
|
|
file: 'borders-3.2.png', left: "wheat", right: "bank"
|
|
}, {
|
|
file: 'borders-4.3.png', center: "wood"
|
|
}, {
|
|
file: 'borders-5.4.png', left: "sheep", right: "bank"
|
|
}, {
|
|
file: 'borders-6.5.png', center: "bank"
|
|
} ].map((file) => {
|
|
return Border(this, file);
|
|
});
|
|
|
|
this.randomize();
|
|
}
|
|
|
|
randomize() {
|
|
//this.borders = shuffle(this.borders);
|
|
this.tiles = shuffle(this.tiles);
|
|
this.tiles.forEach((tile) => {
|
|
tile.roads = [];
|
|
tile.settlements = [];
|
|
tile.jitter = Math.random() - 0.5;
|
|
});
|
|
this.closest.tile = null;
|
|
window.requestAnimationFrame(this.drawFrame);
|
|
}
|
|
|
|
keyUp(event) {
|
|
if (event.keyCode == 78) { /* n */
|
|
this.randomize();
|
|
return;
|
|
}
|
|
|
|
if (event.keyCode == 32) { /* space */
|
|
this.throwDice();
|
|
return;
|
|
}
|
|
}
|
|
|
|
throwDice() {
|
|
for (let i = 0; i < 2; i++) {
|
|
dice[i] = {
|
|
pips: Math.ceil(Math.random() * 6),
|
|
angle: Math.random() * Math.PI * 2,
|
|
jitter: (Math.random() - 0.5) * diceSize * 0.125
|
|
}
|
|
}
|
|
window.requestAnimationFrame(this.drawFrame);
|
|
|
|
const sum = dice[0].pips + dice[1].pips;
|
|
if (sum == 7) { /* Robber! */
|
|
if (this.state.total > 7) {
|
|
let half = Math.ceil(this.state.total * 0.5);
|
|
this.state.total -= half;
|
|
while (half) {
|
|
switch (Math.floor(Math.random() * 5)) {
|
|
case 0: if (this.state.wood) { this.state.wood--; half--; } break;
|
|
case 1: if (this.state.sheep) { this.state.sheep--; half--; } break;
|
|
case 2: if (this.state.stone) { this.state.stone--; half--; } break;
|
|
case 3: if (this.state.brick) { this.state.brick--; half--; } break;
|
|
case 4: if (this.state.wheat) { this.state.wheat--; half--; } break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
this.tiles.forEach((tile) => {
|
|
if (tile.pip.roll != sum) {
|
|
return;
|
|
}
|
|
this.state[tile.type]++;
|
|
this.state.total++;
|
|
});
|
|
}
|
|
|
|
this.setState({
|
|
total: this.state.total,
|
|
wood: this.state.wood,
|
|
sheep: this.state.sheep,
|
|
stone: this.state.stone,
|
|
brick: this.state.brick,
|
|
wheat: this.state.wheat
|
|
});
|
|
}
|
|
|
|
|
|
mouseMove(event) {
|
|
const rect = this.canvas.parentElement.getBoundingClientRect();
|
|
let x, y;
|
|
|
|
if (event.changedTouches && event.changedTouches.length > 0) {
|
|
x = event.changedTouches[0].clientX;
|
|
y = event.changedTouches[0].clientY;
|
|
} else {
|
|
x = event.clientX;
|
|
y = event.clientY;
|
|
}
|
|
|
|
/* Scale mouse.x and mouse.y relative to board */
|
|
this.mouse.x = (x - rect.left) /
|
|
(this.minSize / hexagonRatio) - 0.5 - tileHeight * 0.5,
|
|
this.mouse.y = (y - rect.top) /
|
|
(this.minSize / hexagonRatio) - 0.5 - tileHeight * 0.5;
|
|
|
|
/* Hide the mouse cursor circle after 0.5s */
|
|
if (this.mouse.timer) {
|
|
window.clearTimeout(this.mouse.timer);
|
|
}
|
|
this.mouse.timer = window.setTimeout(() => {
|
|
this.mouse.timer = null;
|
|
window.requestAnimationFrame(this.drawFrame);
|
|
}, 500);
|
|
|
|
let closest = null;
|
|
|
|
this.tiles.forEach((tile) => {
|
|
const dX = tile.pos.x - this.mouse.x,
|
|
dY = tile.pos.y - this.mouse.y;
|
|
const distance = Math.sqrt(dX * dX + dY * dY);
|
|
if (distance > tileHeight * 0.75) {
|
|
return;
|
|
}
|
|
if (!closest || closest.distance > distance) {
|
|
closest = {
|
|
tile: tile,
|
|
distance: distance,
|
|
angle: (distance != 0.0) ? Math.atan2(dY, dX) : 0
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!closest) {
|
|
this.closest.tile = null;
|
|
this.closest.info.distance = -1;
|
|
this.closest.road = null;
|
|
this.closest.angle = 0;
|
|
this.closest.settlement = null;
|
|
this.closest.tradeToken = null;
|
|
} else {
|
|
if (this.closest.tile != closest.tile) {
|
|
this.closest.tile = closest.tile;
|
|
}
|
|
|
|
this.closest.info.distance = closest.distance,
|
|
this.closest.info.angle = closest.angle;
|
|
}
|
|
|
|
window.requestAnimationFrame(this.drawFrame);
|
|
}
|
|
|
|
updateDimensions() {
|
|
if (this.updateSizeTimer) {
|
|
clearTimeout(this.updateSizeTimer);
|
|
}
|
|
|
|
this.updateSizeTimer = setTimeout(() => {
|
|
this.canvas.width = this.canvas.offsetWidth;
|
|
this.canvas.height = this.canvas.offsetHeight;
|
|
this.width = this.canvas.parentElement.clientWidth;
|
|
this.height = this.canvas.parentElement.clientHeight;
|
|
this.updateSizeTimer = 0;
|
|
this.drawFrame();
|
|
}, 250);
|
|
}
|
|
|
|
drawFrame() {
|
|
if (!this.canvas) {
|
|
return;
|
|
}
|
|
|
|
const ctx = this.canvas.getContext("2d");
|
|
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
ctx.save();
|
|
ctx.strokeStyle = 'white';
|
|
ctx.filleStyle = 'rgba(0, 0, 0, 0)';
|
|
|
|
this.minSize = Math.min(this.canvas.height, this.canvas.width);
|
|
/*
|
|
* Table tiling:
|
|
* Image width: 1080
|
|
* Left start: 32
|
|
* Right edge: 1010 (1010 - 32 = 978)
|
|
*
|
|
* If the view is wider than taller, then
|
|
*/
|
|
const tableLeft = 32 * this.table.width / 1080,
|
|
tableRight = 1010 * this.table.width / 1080,
|
|
tableLeaf = 978 * this.table.width / 1080;
|
|
|
|
/* If view is taller than wide, tile the table vertically */
|
|
ctx.save();
|
|
if (this.canvas.height > this.canvas.width) {
|
|
const tableHeight = this.canvas.width * this.table.height / this.table.width;
|
|
for (let top = 0, step = 0; top < this.canvas.height; top += tableHeight, step++) {
|
|
if (step % 2) {
|
|
ctx.save();
|
|
ctx.translate(0, tableHeight - 1);
|
|
ctx.transform(1, 0, 0, -1, 0, 0);
|
|
ctx.drawImage(this.table,
|
|
0, 0,
|
|
this.table.width, this.table.height,
|
|
0, 0, this.canvas.width, this.canvas.width * this.table.height / this.table.width);
|
|
ctx.restore();
|
|
} else {
|
|
ctx.drawImage(this.table,
|
|
0, 0,
|
|
this.table.width, this.table.height,
|
|
0, 0,
|
|
this.canvas.width, this.canvas.width * this.table.height / this.table.width);
|
|
}
|
|
ctx.translate(0, tableHeight);
|
|
}
|
|
} else {
|
|
const tableWidth = this.canvas.height * this.table.width / this.table.height;
|
|
ctx.drawImage(this.table,
|
|
0, 0,
|
|
tableRight, this.table.height,
|
|
0, 0,
|
|
this.canvas.height * tableRight / this.table.height, this.canvas.height);
|
|
let left = this.canvas.height * tableRight / this.table.height;
|
|
while (left < this.canvas.width) {
|
|
ctx.drawImage(this.table,
|
|
tableLeft, 0,
|
|
tableLeaf, this.table.height,
|
|
left, 0,
|
|
this.canvas.height * tableLeaf / this.table.height, this.canvas.height);
|
|
left += this.canvas.height * tableLeaf / this.table.height;
|
|
}
|
|
}
|
|
ctx.restore();
|
|
ctx.scale(this.minSize / hexagonRatio, this.minSize / hexagonRatio);
|
|
ctx.translate(0.5 * hexagonRatio, 0.5 * hexagonRatio);
|
|
ctx.lineWidth = 2. / this.minSize;
|
|
|
|
/* Board dimensions:
|
|
* ________
|
|
* /___1__| \
|
|
* / / \6\
|
|
* /2/ \ \
|
|
* / / \/\
|
|
* \/\ / /
|
|
* \ \ /5/
|
|
* \3\______/_/
|
|
* \_|__4___/
|
|
* 0 0.3 0.6 1
|
|
*/
|
|
|
|
ctx.save();
|
|
this.drawBorders(ctx);
|
|
ctx.restore();
|
|
|
|
ctx.save();
|
|
this.drawPips(ctx);
|
|
ctx.restore();
|
|
|
|
if (this.closest.tile) {
|
|
ctx.save();
|
|
ctx.translate(this.closest.tile.pos.x, this.closest.tile.pos.y);
|
|
ctx.strokeStyle = "red";
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, tileHeight * 0.5, 0, Math.PI * 2.);
|
|
ctx.stroke();
|
|
|
|
/* road */
|
|
let angle = Math.round(this.closest.info.angle / (Math.PI / 3.)) * (Math.PI / 3.);
|
|
ctx.strokeStyle = "rgb(64, 64, 255)";
|
|
ctx.fillStyle = "rgb(0, 0, 255)";
|
|
ctx.rotate(angle);
|
|
ctx.translate(-tileHeight * 0.5, 0);
|
|
ctx.beginPath();
|
|
ctx.rect(-roadSize * 0.125, -roadSize * 0.5, roadSize * 0.25, roadSize);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.translate(tileHeight * 0.5, 0);
|
|
ctx.rotate(-angle);
|
|
|
|
/* village */
|
|
angle = (this.closest.info.angle - Math.PI / 6.);
|
|
angle = Math.round(angle / (Math.PI / 3.)) * (Math.PI / 3.);
|
|
angle += Math.PI / 6.;
|
|
ctx.strokeStyle = "rgb(64, 64, 255)";
|
|
ctx.fillStyle = "rgb(0, 0, 255)";
|
|
ctx.rotate(angle);
|
|
ctx.translate(-tileWidth * 0.5, 0);
|
|
ctx.rotate(-angle);
|
|
ctx.beginPath();
|
|
ctx.rect(-settlementSize * 0.5, -settlementSize * 0.5, settlementSize, settlementSize);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.rotate(angle);
|
|
ctx.translate(+tileWidth * 0.5, 0);
|
|
ctx.rotate(-angle);
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
/* For 0.5 after mouse movement, there is an on
|
|
* screen mouse helper. */
|
|
if (this.mouse.timer) {
|
|
ctx.strokeStyle = "rgba(0, 255, 255)";
|
|
ctx.fillStyle = "rgba(0, 255, 255, 0.25)";
|
|
ctx.beginPath();
|
|
ctx.arc(this.mouse.x, this.mouse.y,
|
|
tileHeight * 0.5, 0, Math.PI * 2.);
|
|
ctx.stroke();
|
|
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);
|
|
this.drawDie(ctx, dice[0]);
|
|
}
|
|
|
|
if (dice[1].pips) {
|
|
ctx.translate(diceSize + diceMargin, 0);
|
|
this.drawDie(ctx, dice[1]);
|
|
}
|
|
}
|
|
ctx.restore();
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
drawPips(ctx) {
|
|
const image = this.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,
|
|
radius = this.radius,
|
|
index = 1, pip, roll = dice[0].pips + dice[1].pips;
|
|
|
|
/* Outer row */
|
|
angle = 0;
|
|
for (let i = 0; i < 12; i++) {
|
|
angle -= Math.PI * 2. / 12.;
|
|
if (this.tiles[i].type == "robber") {
|
|
pip = this.pips.pips[0]
|
|
} else {
|
|
pip = this.pips.pips[index++];
|
|
}
|
|
this.tiles[i].pip = pip;
|
|
drawTile(this.tiles[i], angle, radius - (i % 2) * 0.04);
|
|
if (roll == pip.roll) {
|
|
ctx.scale(1.25, 1.25);
|
|
}
|
|
drawPip(pip, angle, radius - (i % 2) * 0.04, this.tiles[i].jitter);
|
|
if (roll == pip.roll) {
|
|
ctx.scale(0.8, 0.8);
|
|
}
|
|
}
|
|
|
|
/* Middle row */
|
|
angle = Math.PI * 2. / 12.;
|
|
radius = this.radius * 0.5;
|
|
for (let i = 12; i < 18; i++) {
|
|
angle -= Math.PI * 2. / 6.;
|
|
if (this.tiles[i].type == "robber") {
|
|
pip = this.pips.pips[0]
|
|
} else {
|
|
pip = this.pips.pips[index++];
|
|
}
|
|
this.tiles[i].pip = pip;
|
|
drawTile(this.tiles[i], angle, radius);
|
|
if (roll == pip.roll) {
|
|
ctx.scale(1.25, 1.25);
|
|
}
|
|
drawPip(pip, angle, radius, this.tiles[i].jitter);
|
|
if (roll == pip.roll) {
|
|
ctx.scale(0.8, 0.8);
|
|
}
|
|
}
|
|
|
|
/* Center */
|
|
let i = 18;
|
|
if (this.tiles[i].type == "robber") {
|
|
pip = this.pips.pips[0]
|
|
} else {
|
|
pip = this.pips.pips[index++];
|
|
}
|
|
this.tiles[i].pip = pip;
|
|
drawTile(this.tiles[i], 0, 0);
|
|
if (roll == pip.roll) {
|
|
ctx.scale(1.25, 1.25);
|
|
}
|
|
drawPip(pip, 0, 0, this.tiles[i].jitter);
|
|
if (roll == pip.roll) {
|
|
ctx.scale(0.8, 0.8);
|
|
}
|
|
}
|
|
|
|
drawBorders(ctx) {
|
|
ctx.rotate(Math.PI);
|
|
|
|
const offset = 0.18;
|
|
this.borders.forEach((border, index) => {
|
|
ctx.translate(0, this.radius);
|
|
|
|
const image = border.image;
|
|
|
|
ctx.drawImage(image,
|
|
-offset, 0,
|
|
0.5, 0.5 * image.height / image.width);
|
|
|
|
ctx.translate(0, -this.radius);
|
|
ctx.rotate(Math.PI * 2. / 6.);
|
|
});
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.start = new Date();
|
|
|
|
document.addEventListener("keyup", this.keyUp);
|
|
window.addEventListener("touchmove", this.mouseMove);
|
|
window.addEventListener("mousemove", this.mouseMove);
|
|
window.addEventListener("resize", this.updateDimensions);
|
|
|
|
this.updateDimensions();
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this.updateSizeTimer) {
|
|
clearTimeout(this.updateSizeTimer);
|
|
this.updateSizeTimer = 0;
|
|
}
|
|
document.removeEventListener("keyup", this.keyUp);
|
|
window.removeEventListener("mousemove", this.mouseMove);
|
|
window.removeEventListener("touchmove", this.mouseMove);
|
|
window.removeEventListener("resize", this.updateDimensions);
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<>
|
|
<canvas className="Board" ref={el => this.canvas = el}></canvas>
|
|
<div className="Cards">
|
|
<div>In hand</div>
|
|
<div className="Hand">
|
|
<Resource type="wood" count={this.state.wood}/>
|
|
<Resource type="wheat" count={this.state.wheat}/>
|
|
<Resource type="stone" count={this.state.stone}/>
|
|
<Resource type="brick" count={this.state.brick}/>
|
|
<Resource type="sheep" count={this.state.sheep}/>
|
|
</div>
|
|
<div>Available to play</div>
|
|
<div className="Hand">
|
|
<Development type="monopoly" count="1"/>
|
|
<Development type="army-" max="14" count="4"/>
|
|
<div className="Stack">
|
|
<Development type="vp-library" count="1"/>
|
|
<Development type="vp-market" count="1"/>
|
|
</div>
|
|
</div>
|
|
<div>Points</div>
|
|
<div className="Hand">
|
|
<div className="Stack">
|
|
<Development type="vp-library" count="1"/>
|
|
<Development type="vp-palace" count="1"/>
|
|
<Development type="army-" max="14" count="6"/>
|
|
</div>
|
|
</div>
|
|
<div className="Hand">
|
|
<Placard type="largest-army" count="1"/>
|
|
<Placard type="longest-road" count="1"/>
|
|
</div>
|
|
<div className="Statistics">
|
|
<div>Stats</div>
|
|
<div>
|
|
<div>Points: 7</div>
|
|
<div>Cards: {this.state.total} </div>
|
|
<div>Roads remaining: 4</div>
|
|
<div>Longest road: 7</div>
|
|
<div>Cities remaining: 4</div>
|
|
<div>Settlements remaining: 5</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
export default Board;
|