365 lines
11 KiB
JavaScript
365 lines
11 KiB
JavaScript
let INTERFACE_DATA = null;
|
|
|
|
const INTERFACE = {
|
|
Color: {
|
|
Background: "#101010",
|
|
Text: "#c0c0c0",
|
|
|
|
TileBorder: "#606060",
|
|
TileLight: "#383838",
|
|
TileMedium: "#242424",
|
|
TileDark: "#101010",
|
|
|
|
Dawn: "#ffe082",
|
|
DawnDark: "#ff6d00",
|
|
|
|
Dusk: "#f6a1bd",
|
|
DuskDark: "#c51162",
|
|
|
|
HintHover: "#71a1e8",
|
|
HintSelect: "#4a148c",
|
|
HintValid: "#1a237e",
|
|
HintAllowed: "#6a1b9a",
|
|
HintInvalid: "b71c1c",
|
|
HintPlay: "#083242",
|
|
HintWarn: "#054719",
|
|
HintCheck: "#C62828",
|
|
|
|
},
|
|
|
|
hover(event) {
|
|
|
|
},
|
|
|
|
click(event) {
|
|
|
|
},
|
|
|
|
draw() {
|
|
let canvas = INTERFACE_DATA.canvas;
|
|
let ctx = INTERFACE_DATA.context;
|
|
|
|
// Determine interface configuration
|
|
canvas.width = canvas.clientWidth;
|
|
canvas.height = canvas.clientHeight;
|
|
|
|
let width = canvas.width;
|
|
let height = canvas.height;
|
|
|
|
// Interface margin
|
|
let gui_margin = Math.floor(Math.min(width, height) / 100);
|
|
|
|
// Interface width, height, and scale
|
|
let gui_width = width - (gui_margin * 2);
|
|
let gui_height = height - (gui_margin * 2);
|
|
|
|
if(gui_width < gui_height * INTERFACE.Ratio) {
|
|
gui_height = Math.floor(gui_width / INTERFACE.Ratio);
|
|
} else {
|
|
gui_width = Math.floor(gui_height * INTERFACE.Ratio);
|
|
}
|
|
let gui_scale = gui_height * INTERFACE.Scale;
|
|
|
|
// Boundries to center interface
|
|
let gui_offset = new MATH.Vec2(
|
|
(width - gui_width) / 2,
|
|
(height - gui_height) / 2,
|
|
);
|
|
|
|
|
|
// Draw background
|
|
ctx.clearRect(0, 0, width, height);
|
|
ctx.fillStyle = INTERFACE.Color.Background;
|
|
ctx.fillRect(0, 0, width, height);
|
|
|
|
|
|
// Draw tiles
|
|
let radius = INTERFACE.Radius * gui_scale;
|
|
let basis_x = gui_offset.x + radius;
|
|
let basis_y = gui_offset.y + (13 * gui_scale);
|
|
|
|
const TILE_SCALE = 0.9;
|
|
ctx.lineWidth = gui_scale * 0.06;
|
|
|
|
let draw = new INTERFACE.Draw(ctx, TILE_SCALE * gui_scale);
|
|
|
|
for(let i = 0; i < GAME_DATA.board.tiles.length; ++i) {
|
|
let tile = GAME_DATA.board.tiles[i];
|
|
|
|
let coord = GAME_DATA.board.tile_to_hex(i);
|
|
let gui_x = basis_x + (1.5 * radius * coord.x);
|
|
let gui_y = basis_y - (2 * gui_scale * coord.y) + (gui_scale * coord.x);
|
|
|
|
ctx.save();
|
|
ctx.translate(gui_x, gui_y);
|
|
|
|
// Draw background.
|
|
// Select indicator color or default to tile color.
|
|
if(true) {
|
|
switch(MATH.mod(coord.x + coord.y, 3)) {
|
|
case 0: ctx.fillStyle = INTERFACE.Color.TileMedium; break;
|
|
case 1: ctx.fillStyle = INTERFACE.Color.TileLight; break;
|
|
case 2: ctx.fillStyle = INTERFACE.Color.TileDark; break;
|
|
}
|
|
} else {
|
|
|
|
}
|
|
ctx.beginPath();
|
|
draw.hex();
|
|
ctx.fill();
|
|
|
|
// Draw tile content
|
|
if(tile.piece !== null) {
|
|
let piece = GAME_DATA.board.pieces[tile.piece];
|
|
|
|
// Draw border
|
|
if(piece.player == GAME.Const.Player.Dawn) { ctx.strokeStyle = INTERFACE.Color.DawnDark; }
|
|
else { ctx.strokeStyle = INTERFACE.Color.DuskDark; }
|
|
ctx.beginPath();
|
|
draw.hex();
|
|
ctx.stroke();
|
|
|
|
// Draw border hints
|
|
draw.hints(piece);
|
|
|
|
// Draw piece
|
|
|
|
} else {
|
|
// Draw standard border
|
|
ctx.strokeStyle = INTERFACE.Color.TileBorder;
|
|
ctx.beginPath();
|
|
draw.hex();
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
|
|
// Draw player pool
|
|
for(let i = 0; i < 6; ++i) {
|
|
let gui_x = basis_x + (radius * 14);
|
|
let gui_y = basis_y - (9 - (2 * i)) * gui_scale;
|
|
|
|
ctx.save();
|
|
ctx.translate(gui_x, gui_y);
|
|
|
|
// Draw background if indicator is present.
|
|
if(true) {
|
|
ctx.fillStyle = INTERFACE.Color.TileDark;
|
|
ctx.beginPath();
|
|
draw.hex();
|
|
ctx.fill();
|
|
}
|
|
|
|
// Draw border
|
|
ctx.strokeStyle = INTERFACE.Color.Dawn;
|
|
ctx.beginPath();
|
|
draw.hex();
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
// Draw opponent pool
|
|
for(let i = 0; i < 6; ++i) {
|
|
let gui_x = basis_x + (radius * 15.5);
|
|
let gui_y = basis_y - (8 - (2 * i)) * gui_scale;
|
|
|
|
ctx.save();
|
|
ctx.translate(gui_x, gui_y);
|
|
|
|
// Draw background.
|
|
// Select indicator color or default to tile color.
|
|
if(true) {
|
|
ctx.fillStyle = INTERFACE.Color.TileDark;
|
|
} else {
|
|
|
|
}
|
|
ctx.beginPath();
|
|
draw.hex();
|
|
ctx.fill();
|
|
|
|
// Draw border
|
|
ctx.strokeStyle = INTERFACE.Color.Dusk;
|
|
ctx.beginPath();
|
|
draw.hex();
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
|
|
// Draw informational text
|
|
ctx.font = Math.ceil(gui_scale / 24) + "px sans-serif";
|
|
|
|
|
|
/*
|
|
// Dawn handle
|
|
ctx.fillStyle = INTERFACE.Color.Text;
|
|
ctx.textBaseline = "top";
|
|
ctx.textAlign = "center";
|
|
ctx.fillText(GAME_DATA.turn, width / 2, gui_margin);
|
|
|
|
|
|
// Dusk handle
|
|
ctx.fillStyle = INTERFACE.Color.Text;
|
|
ctx.textBaseline = "top";
|
|
ctx.textAlign = "center";
|
|
ctx.fillText(GAME_DATA.turn, width - gui_margin, gui_margin);
|
|
*/
|
|
|
|
// Number of moves
|
|
ctx.fillStyle = INTERFACE.Color.Text;
|
|
ctx.textBaseline = "top";
|
|
ctx.textAlign = "right";
|
|
ctx.fillText(GAME_DATA.turn, width - gui_margin, gui_margin);
|
|
|
|
// Game state message
|
|
if(INTERFACE_DATA.message !== null) {
|
|
ctx.fillStyle = INTERFACE.Color.Text;
|
|
ctx.textBaseline = "bottom";
|
|
ctx.textAlign = "left";
|
|
ctx.fillText(INTERFACE_DATA.message, gui_margin, height - gui_margin);
|
|
}
|
|
},
|
|
|
|
Draw: class {
|
|
constructor(ctx, scale) {
|
|
this.ctx = ctx;
|
|
this.scale = scale;
|
|
}
|
|
|
|
hex() {
|
|
this.ctx.moveTo(INTERFACE.HexVertex[5].x * this.scale, INTERFACE.HexVertex[5].y * this.scale);
|
|
for(let i = 0; i < INTERFACE.HexVertex.length; ++i) {
|
|
this.ctx.lineTo(INTERFACE.HexVertex[i].x * this.scale, INTERFACE.HexVertex[i].y * this.scale);
|
|
}
|
|
}
|
|
|
|
hints(piece) {
|
|
let descriptor = GAME.Const.Piece[piece.piece];
|
|
let moves = descriptor.moves;
|
|
if(piece.promoted) { moves = descriptor.pmoves; }
|
|
if(piece.player == GAME.Const.Player.Dusk) { moves = moves.rotate(); }
|
|
moves = moves.direction;
|
|
|
|
for(let mask = BITWISE.lsb(moves); moves > 0; mask = BITWISE.lsb(moves)) {
|
|
let move = BITWISE.ffs(mask);
|
|
let nmove = move % 12;
|
|
|
|
this.ctx.strokeStyle = (piece.player == GAME.Const.Player.Dawn)? INTERFACE.Color.Dawn : INTERFACE.Color.Dusk;
|
|
this.ctx.beginPath();
|
|
|
|
// draw edge marker
|
|
if(nmove < 6) {
|
|
let fr = INTERFACE.HexVertex[nmove];
|
|
let to = INTERFACE.HexVertex[(nmove + 1) % 6];
|
|
|
|
let dx = (to.x - fr.x) / 3.0;
|
|
let dy = (to.y - fr.y) / 3.0;
|
|
|
|
let fqx = fr.x + dx;
|
|
let fqy = fr.y + dy;
|
|
|
|
let tqx = fr.x + (2 * dx);
|
|
let tqy = fr.y + (2 * dy);
|
|
|
|
this.ctx.moveTo(fqx * this.scale, fqy * this.scale);
|
|
this.ctx.lineTo(tqx * this.scale, tqy * this.scale);
|
|
this.ctx.stroke();
|
|
}
|
|
|
|
// draw vertex marker
|
|
else {
|
|
let fr = INTERFACE.HexVertex[nmove % 6];
|
|
let mid = INTERFACE.HexVertex[(nmove + 1) % 6];
|
|
let to = INTERFACE.HexVertex[(nmove + 2) % 6];
|
|
|
|
let dx1 = (mid.x - fr.x) / 3.0;
|
|
let dy1 = (mid.y - fr.y) / 3.0;
|
|
|
|
let dx2 = (to.x - mid.x) / 3.0;
|
|
let dy2 = (to.y - mid.y) / 3.0;
|
|
|
|
let fqx = mid.x - dx1;
|
|
let fqy = mid.y - dy1;
|
|
|
|
let tqx = mid.x + dx2;
|
|
let tqy = mid.y + dy2;
|
|
|
|
this.ctx.moveTo(fqx * this.scale, fqy * this.scale);
|
|
this.ctx.lineTo(mid.x * this.scale, mid.y * this.scale);
|
|
this.ctx.lineTo(tqx * this.scale, tqy * this.scale);
|
|
this.ctx.stroke();
|
|
}
|
|
moves &= ~mask;
|
|
}
|
|
}
|
|
},
|
|
|
|
init(player_id) {
|
|
GAME.init();
|
|
|
|
INTERFACE_DATA = {
|
|
canvas: document.getElementById("game"),
|
|
context: null,
|
|
|
|
player_id: player_id,
|
|
|
|
message: null,
|
|
};
|
|
|
|
let canvas = INTERFACE_DATA.canvas;
|
|
if(canvas !== undefined) {
|
|
INTERFACE_DATA.context = canvas.getContext("2d");
|
|
|
|
canvas.addEventListener("mousemove", INTERFACE.hover);
|
|
canvas.addEventListener("mousedown", INTERFACE.click);
|
|
window.addEventListener("resize", INTERFACE.draw);
|
|
this.draw();
|
|
}
|
|
},
|
|
|
|
uninit() {
|
|
MAIN.removeChild(INTERFACE_DATA.canvas);
|
|
INTERFACE_DATA = null;
|
|
},
|
|
|
|
message(code, data) {
|
|
switch(code) {
|
|
case OpCode.GameState: {
|
|
// Build game state
|
|
|
|
INTERFACE.draw();
|
|
} break;
|
|
|
|
case OpCode.GamePlay: {
|
|
// Apply play to board
|
|
GAME_DATA.turn += 1;
|
|
|
|
INTERFACE.draw();
|
|
} break;
|
|
}
|
|
},
|
|
};
|
|
|
|
INTERFACE.Radius = 2.0 / Math.sqrt(3.0);
|
|
INTERFACE.HalfRadius = 1.0 / Math.sqrt(3.0);
|
|
INTERFACE.Scale = 1 / 18;
|
|
INTERFACE.Ratio = (17.5 * INTERFACE.Radius) * INTERFACE.Scale;
|
|
INTERFACE.HexVertex = [
|
|
// top face
|
|
new MATH.Vec2(-INTERFACE.HalfRadius, -1),
|
|
// top-right face
|
|
new MATH.Vec2(INTERFACE.HalfRadius, -1),
|
|
// bottom-right face
|
|
new MATH.Vec2(INTERFACE.Radius, 0),
|
|
// bottom face
|
|
new MATH.Vec2(INTERFACE.HalfRadius, 1),
|
|
// bottom-left face
|
|
new MATH.Vec2(-INTERFACE.HalfRadius, 1),
|
|
// top-left face
|
|
new MATH.Vec2(-INTERFACE.Radius, 0),
|
|
];
|