dzura/www/js/interface.js
2024-08-11 22:41:25 -07:00

381 lines
12 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);
let icon_radius = 0.7 * radius;
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];
let game_piece = GAME.Const.Piece[piece.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 icon
//if(piece.promoted) { ctx.drawImage(I_PROMOTE, -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.); }
//ctx.drawImage(game_piece.assets[piece.player], -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.);
} else {
// Draw standard border
ctx.strokeStyle = INTERFACE.Color.TileBorder;
ctx.beginPath();
draw.hex();
ctx.stroke();
}
ctx.restore();
}
ctx.font = Math.ceil(gui_scale / 2) + "px sans-serif";
// 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.fillStyle = INTERFACE.Color.Dawn;
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillText(GAME_DATA.dawn.pool.pieces[i], -0.5 * radius, 0);
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.fillStyle = INTERFACE.Color.Dusk;
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillText(GAME_DATA.dusk.pool.pieces[i], -0.5 * radius, 0);
ctx.restore();
}
// Draw informational text
ctx.font = Math.ceil(gui_scale / 2) + "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),
];