dzura/www/js/interface.js

856 lines
30 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",
HintValidDark: "#121859",
HintThreat: "#054719",
HintThreatDark: "#023311",
HintOpponent: "#49136b",
HintOpponentDark: "#2a0b3f",
HintInvalid: "#b71c1c",
HintInvalidDark: "#3f0808",
HintPlay: "#0d5672",
HintCheck: "#C62828",
},
TileStatus: {
Valid: 1,
Threat: 2,
Invalid: 3,
Opponent: 4,
Play: 5,
Check: 6,
},
Selection: class {
constructor(source, tile, hex) {
this.source = source;
this.tile = tile;
this.hex = hex;
}
},
resolve_board() {
for(let i = 0; i < INTERFACE_DATA.board_state.length; ++i) {
INTERFACE_DATA.board_state[i] = [0, 0];
}
if(INTERFACE_DATA.select !== null) { INTERFACE.resolve_piece(INTERFACE_DATA.select, 1); }
if(INTERFACE_DATA.hover !== null) { INTERFACE.resolve_piece(INTERFACE_DATA.hover, 0); }
},
resolve_piece(selection, zone) {
// Determine piece movement hints.
let movements = null;
let player = 0;
if(selection.source == 0) {
let piece_id = GAME_DATA.board.tiles[selection.tile].piece;
if(piece_id !== null) {
let piece = GAME_DATA.board.pieces[piece_id];
player = piece.player;
movements = GAME_DATA.movement_tiles(piece, selection.tile);
}
} else {
player = Math.floor(selection.tile / 7);
player ^= INTERFACE_DATA.player & 1;
if(INTERFACE_DATA.player == 2) { player ^= INTERFACE_DATA.rotate; }
movements = GAME_DATA.placement_tiles(selection.tile % 7, player);
}
if(movements !== null) {
// Generate hint for each potential movement.
for(let movement of movements) {
if(movement.valid) {
// Show valid/threat hints if piece belongs to player and is player turn.
if(INTERFACE_DATA.player == 2 || (player == INTERFACE_DATA.player && (GAME_DATA.turn & 1) == player)) {
if(GAME_DATA.board.tiles[movement.tile].threaten[+(!player)]) {
INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Threat;
} else {
INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Valid;
}
}
else {
INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Opponent;
}
} else {
INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Invalid;
}
}
}
},
hover(event) {
let initial_hover = INTERFACE_DATA.hover;
let apothem = INTERFACE_DATA.Ui.scale;
let radius = INTERFACE.Radius * apothem;
let halfradius = radius / 2;
let grid_offset_x = 1.5 * radius;
let hex_slope = 0.25 / 0.75;
INTERFACE_DATA.hover = null;
// Handle board area
if(event.offsetY >= INTERFACE_DATA.Ui.margin && event.offsetY < INTERFACE_DATA.Ui.margin + INTERFACE_DATA.Ui.area.y) {
if(event.offsetX >= INTERFACE_DATA.Ui.offset.x && event.offsetX < INTERFACE_DATA.Ui.offset.x + INTERFACE_DATA.Ui.board_width) {
let basis_x = INTERFACE_DATA.Ui.offset.x + halfradius;
let basis_y = INTERFACE_DATA.Ui.offset.y + (14 * apothem);
let x = (event.offsetX - basis_x) / grid_offset_x;
let y = -(event.offsetY - basis_y) / apothem;
let kx = Math.floor(x);
let ky = Math.floor(y);
let apo_offset = Math.abs(MATH.mod(y + (kx & 1), 2.0) - 1);
let rad_offset = MATH.mod(x, 1);
let rad_slope = 1 - (hex_slope * apo_offset);
let hx = kx + (rad_offset > rad_slope);
let hy = Math.floor((ky + hx) / 2.0);
if((INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate == 1) {
hx = 8 - hx;
hy = 8 - hy;
}
let hex = new MATH.Vec2(hx, hy);
if(HEX.is_valid(hex)) {
let tile = HEX.hex_to_tile(hex);
INTERFACE_DATA.hover = new INTERFACE.Selection(0, tile, hex);
}
}
// Handle pool area
else if(event.offsetX >= INTERFACE_DATA.Ui.pool_offset && event.offsetX < INTERFACE_DATA.Ui.offset.x + INTERFACE_DATA.Ui.area.x) {
let basis_x = INTERFACE_DATA.Ui.pool_offset + halfradius;
let basis_y = INTERFACE_DATA.Ui.offset.y + (3 * apothem);
let x = (event.offsetX - basis_x) / grid_offset_x;
let y = (event.offsetY - basis_y) / apothem;
let kx = Math.floor(x);
let ky = Math.floor(y);
let apo_offset = Math.abs(MATH.mod(y + (kx & 1), 2.0) - 1);
let rad_offset = MATH.mod(x, 1);
let rad_slope = 1 - (hex_slope * apo_offset);
let hx = kx + (rad_offset > rad_slope);
let hy = Math.floor((ky + hx) / 2.0);
let hex = new MATH.Vec2(hx, hy);
if(INTERFACE.Ui.pool_hex_is_valid(hex)) {
let tile = (hx * 7) + hy;
INTERFACE_DATA.hover = new INTERFACE.Selection(1, tile, hex);
}
}
}
if(initial_hover != INTERFACE_DATA.hover) { INTERFACE.draw(); }
},
unhover() {
let redraw = (INTERFACE_DATA.hover !== null);
INTERFACE_DATA.hover = null;
if(redraw) { INTERFACE.draw(); }
},
click() {
let initial_select = INTERFACE_DATA.select;
if(INTERFACE_DATA.hover !== null) {
if(INTERFACE.Ui.match_select(INTERFACE_DATA.hover, INTERFACE_DATA.select)) {
INTERFACE_DATA.select = null;
} else {
// Check if operation can be performed on new tile.
// Otherwise, switch selection.
let is_valid = false;
if(INTERFACE_DATA.select !== null && INTERFACE_DATA.hover.source == 0 && INTERFACE_DATA.player == (GAME_DATA.turn & 1)) {
let tile_state = INTERFACE_DATA.board_state[INTERFACE_DATA.hover.tile][1];
is_valid = (tile_state == INTERFACE.TileStatus.Valid || tile_state == INTERFACE.TileStatus.Threat);
}
// Handle player action.
if(is_valid) {
// Send message to server for online game.
if(INTERFACE_DATA.online) {
let move_data = INTERFACE_DATA.select.source | (INTERFACE_DATA.select.tile << 1) | (INTERFACE_DATA.hover.tile << 7);
MESSAGE_COMPOSE([
PACK.u16(OpCode.GamePlay),
PACK.u16(0),
PACK.u16(GAME_DATA.turn),
PACK.u16(move_data),
]);
}
// Apply action and change turn for offline game.
else {
let play = new GAME.Play(INTERFACE_DATA.select.source, INTERFACE_DATA.select.tile, INTERFACE_DATA.hover.tile);
INTERFACE_DATA.play = play;
GAME_DATA.process(play);
INTERFACE_DATA.player = +(!INTERFACE_DATA.player);
INTERFACE_DATA.rotate = +(!INTERFACE_DATA.rotate);
INTERFACE.draw();
}
INTERFACE_DATA.select = null;
}
// Handle new selection.
else {
INTERFACE_DATA.select = null;
// Select tile on board.
if(INTERFACE_DATA.hover.source == 0) {
if(GAME_DATA.board.tiles[INTERFACE_DATA.hover.tile].piece !== null) {
INTERFACE_DATA.select = INTERFACE_DATA.hover;
}
}
// Select tile in pools.
else {
let pool_player = Math.floor(INTERFACE_DATA.hover.tile / 7);
pool_player ^= INTERFACE_DATA.player & 1;
if(INTERFACE_DATA.player == 2) { pool_player ^= INTERFACE_DATA.rotate; }
let pool_piece = INTERFACE_DATA.hover.tile % 7;
if(GAME_DATA.pools[pool_player].pieces[pool_piece] > 0) {
INTERFACE_DATA.select = INTERFACE_DATA.hover;
}
}
}
}
}
// Clear selection if no tile is hovered.
else {
INTERFACE_DATA.select = null;
}
if(initial_select !== INTERFACE_DATA.select) {
INTERFACE.draw();
}
},
resize() {
let width = INTERFACE_DATA.canvas.width = INTERFACE_DATA.canvas.clientWidth;
let height = INTERFACE_DATA.canvas.height = INTERFACE_DATA.canvas.clientHeight;
let margin = INTERFACE_DATA.Ui.margin = Math.floor(Math.min(width, height) / 100);
let gui_width = width - (margin * 2);
let gui_height = height - (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;
INTERFACE_DATA.Ui.area.x = gui_width;
INTERFACE_DATA.Ui.area.y = gui_height;
INTERFACE_DATA.Ui.scale = gui_scale;
INTERFACE_DATA.Ui.offset.x = (width - gui_width) / 2;
INTERFACE_DATA.Ui.offset.y = (height - gui_height) / 2;
INTERFACE_DATA.Ui.board_width = Math.ceil(INTERFACE.BoardWidth * gui_scale);
INTERFACE_DATA.Ui.pool_offset = INTERFACE_DATA.Ui.offset.x + Math.floor(INTERFACE.PoolOffset * gui_scale);
},
draw() {
if(INTERFACE_DATA === null) return;
let canvas = INTERFACE_DATA.canvas;
let ctx = INTERFACE_DATA.context;
INTERFACE.resize();
INTERFACE.resolve_board();
let width = canvas.width;
let height = canvas.height;
let gui_margin = INTERFACE_DATA.Ui.margin;
let gui_offset = INTERFACE_DATA.Ui.offset;
let gui_scale = INTERFACE_DATA.Ui.scale;
// 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.69 * radius;
const TILE_SCALE = 0.9;
ctx.lineWidth = Math.min(gui_scale * 0.06, 3);
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 is_hover = INTERFACE.Ui.tile_is_hover(0, i);
let is_select = INTERFACE.Ui.tile_is_select(0, i);
let tile_state = INTERFACE_DATA.board_state[i][1];
let hover_state = INTERFACE_DATA.board_state[i][0];
let coord = HEX.tile_to_hex(i);
if((INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate == 1) {
coord.x = 8 - coord.x;
coord.y = 8 - coord.y;
}
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);
let piece = null;
if(tile.piece !== null) { piece = GAME_DATA.board.pieces[tile.piece]; }
// Draw border
ctx.fillStyle = INTERFACE.Color.TileBorder;
if(tile.piece !== null) {
if(GAME_DATA.board.pieces[tile.piece].player == GAME.Const.Player.Dawn) { ctx.fillStyle = INTERFACE.Color.DawnDark; }
else { ctx.fillStyle = INTERFACE.Color.DuskDark; }
}
if(is_hover) {
ctx.fillStyle = INTERFACE.Color.HintHover;
}
ctx.beginPath();
draw.hex();
ctx.fill();
// Draw background.
// Select indicator color or default to tile color.
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;
}
if(GAME_DATA.turn > 0 && (INTERFACE_DATA.play.to == i || (INTERFACE_DATA.play.source == 0 && INTERFACE_DATA.play.from == i))) {
ctx.fillStyle = INTERFACE.Color.HintPlay;
} else if(GAME_DATA.state.check && piece !== null && piece.piece == GAME.Const.PieceId.Omen && piece.player == (GAME_DATA.turn & 1)) {
ctx.fillStyle = INTERFACE.Color.HintCheck;
}
switch(hover_state) {
case INTERFACE.TileStatus.Valid: ctx.fillStyle = INTERFACE.Color.HintValidDark; break;
case INTERFACE.TileStatus.Threat: ctx.fillStyle = INTERFACE.Color.HintThreatDark; break;
case INTERFACE.TileStatus.Invalid: ctx.fillStyle = INTERFACE.Color.HintInvalidDark; break;
case INTERFACE.TileStatus.Opponent: ctx.fillStyle = INTERFACE.Color.HintOpponentDark; break;
}
switch(tile_state) {
case INTERFACE.TileStatus.Valid: ctx.fillStyle = INTERFACE.Color.HintValid; break;
case INTERFACE.TileStatus.Threat: ctx.fillStyle = INTERFACE.Color.HintThreat; break;
case INTERFACE.TileStatus.Invalid: ctx.fillStyle = INTERFACE.Color.HintInvalid; break;
case INTERFACE.TileStatus.Opponent: ctx.fillStyle = INTERFACE.Color.HintOpponent; break;
}
if(is_select) {
ctx.fillStyle = INTERFACE.Color.HintSelect;
}
ctx.beginPath();
draw.hex(0.94);
ctx.fill();
// Draw tile content
if(piece !== null) {
// Draw border hints
if(!is_hover) { draw.hints(piece); }
// Draw piece icon
if(INTERFACE_DATA.mirror && (piece.player ^ (INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate) != 0) {
ctx.rotate(Math.PI);
}
if(piece.promoted) { ctx.drawImage(GAME_ASSET.Image.Promote, -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.); }
ctx.drawImage(GAME_ASSET.Image.Piece[piece.piece][piece.player], -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.);
}
ctx.restore();
}
ctx.font = Math.ceil(gui_scale / 2) + "px sans-serif";
let player_identity = GAME.Const.Player.Dawn;
let player_color = INTERFACE.Color.Dawn;
let opponent_color = INTERFACE.Color.Dusk;
if(INTERFACE_DATA.player == 1 || (INTERFACE_DATA.player == 2 && INTERFACE_DATA.rotate == 1)) {
player_identity = GAME.Const.Player.Dusk;
player_color = INTERFACE.Color.Dusk;
opponent_color = INTERFACE.Color.Dawn;
}
// Draw player pool
for(let i = 0; i < 7; ++i) {
let is_hover = INTERFACE.Ui.tile_is_hover(1, i);
let is_select = INTERFACE.Ui.tile_is_select(1, 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 border
if(is_select || is_hover || INTERFACE_DATA.player == (GAME_DATA.turn & 1) || (INTERFACE_DATA.player == 2 && player_identity == (GAME_DATA.turn & 1))) {
if(is_hover) { ctx.fillStyle = INTERFACE.Color.HintHover; }
else { ctx.fillStyle = player_color; }
ctx.beginPath();
draw.hex();
ctx.fill();
}
// Draw background if indicator is present.
if(is_select) { ctx.fillStyle = INTERFACE.Color.HintSelect; }
else { ctx.fillStyle = INTERFACE.Color.TileDark; }
ctx.beginPath();
draw.hex(0.94);
ctx.fill();
// Draw image
ctx.drawImage(GAME_ASSET.Image.Piece[i][+(player_identity == 1)], -icon_radius * 0.55, -icon_radius * 0.8, icon_radius * 1.6, icon_radius * 1.6);
// Draw count
ctx.fillStyle = player_color;
ctx.textBaseline = "middle";
ctx.textAlign = "left";
ctx.fillText(GAME_DATA.pools[+(player_identity == 1)].pieces[i], -0.6 * radius, 0);
ctx.restore();
}
// Draw opponent pool
for(let i = 0; i < 7; ++i) {
let is_hover = INTERFACE.Ui.tile_is_hover(1, 7 + i);
let is_select = INTERFACE.Ui.tile_is_select(1, 7 + i);
let gui_x = basis_x + (radius * 15.5);
let gui_y = basis_y - (10 - (2 * i)) * gui_scale;
ctx.save();
ctx.translate(gui_x, gui_y);
// Draw border
if(is_select || is_hover || (INTERFACE_DATA.player == 2 && player_identity != (GAME_DATA.turn & 1))) {
if(is_hover) { ctx.fillStyle = INTERFACE.Color.HintHover; }
else { ctx.fillStyle = opponent_color; }
ctx.beginPath();
draw.hex();
ctx.fill();
}
// Draw background if indicator is present.
if(is_select) { ctx.fillStyle = INTERFACE.Color.HintSelect; }
else { ctx.fillStyle = INTERFACE.Color.TileDark; }
ctx.beginPath();
draw.hex(0.94);
ctx.fill();
// Draw image
ctx.drawImage(GAME_ASSET.Image.Piece[i][+(player_identity != 1)], -icon_radius * 0.55, -icon_radius * 0.8, icon_radius * 1.6, icon_radius * 1.6);
// Draw count
ctx.fillStyle = opponent_color;
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillText(GAME_DATA.pools[+(player_identity != 1)].pieces[i], -0.5 * radius, 0);
ctx.restore();
}
// Draw informational text
let handle_pos = [
new MATH.Vec2(
basis_x + (radius * 10.75),
basis_y - (12 * gui_scale)
),
new MATH.Vec2(
basis_x + (radius * 10.75),
basis_y + (4 * gui_scale)
),
];
// Player handles
ctx.font = Math.ceil(gui_scale / 1.3) + "px sans-serif";
if(INTERFACE_DATA.handles[0] !== null) {
let pos = handle_pos[(1 ^ INTERFACE_DATA.player ^ INTERFACE_DATA.rotate) & 1];
ctx.fillStyle = INTERFACE.Color.Dawn;
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillText(INTERFACE_DATA.handles[0], pos.x, pos.y);
}
if(INTERFACE_DATA.handles[1] !== null) {
let pos = handle_pos[(INTERFACE_DATA.player ^ INTERFACE_DATA.rotate) & 1];
ctx.fillStyle = INTERFACE.Color.Dusk;
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillText(INTERFACE_DATA.handles[1], pos.x, pos.y);
}
// Tile information
ctx.font = Math.ceil(gui_scale / 2) + "px sans-serif";
if(INTERFACE_DATA.hover !== null) {
let text = "";
if(INTERFACE_DATA.hover.source == 0) {
text = INTERFACE.Ui.hex_to_alnum(INTERFACE_DATA.hover.hex);
let piece_id = GAME_DATA.board.tiles[INTERFACE_DATA.hover.tile].piece;
if(piece_id !== null) {
let piece = GAME_DATA.board.pieces[piece_id];
text += " " + GAME.Const.Piece[piece.piece].name;
if(piece.promoted) { text += "+"; }
}
} else {
text = " " + GAME.Const.Piece[INTERFACE_DATA.hover.tile % 7].name;
}
ctx.fillStyle = INTERFACE.Color.Text;
ctx.textBaseline = "top";
ctx.textAlign = "left";
ctx.fillText(text, 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
let message = null;
ctx.fillStyle = INTERFACE.Color.Text;
if(GAME_DATA.state.check) {
ctx.fillStyle = INTERFACE.Color.HintCheck;
if(GAME_DATA.state.checkmate) {
message = "Checkmate";
} else {
message = "Check";
}
}
if(message !== null) {
ctx.textBaseline = "bottom";
ctx.textAlign = "left";
ctx.fillText(message, gui_margin, height - gui_margin);
}
},
Draw: class {
constructor(ctx, scale) {
this.ctx = ctx;
this.scale = scale;
}
hex(scale=1) {
scale *= this.scale;
this.ctx.moveTo(INTERFACE.HexVertex[5].x * scale, INTERFACE.HexVertex[5].y * scale);
for(let i = 0; i < INTERFACE.HexVertex.length; ++i) {
this.ctx.lineTo(INTERFACE.HexVertex[i].x * scale, INTERFACE.HexVertex[i].y * scale);
}
}
hints(piece) {
let movement = piece.moves();
if((INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate) {
movement = movement.rotate();
}
let moves = movement.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;
}
}
},
Ui: {
tile_is_hover(source, tile) {
return INTERFACE_DATA.hover !== null
&& INTERFACE_DATA.hover.source == source
&& INTERFACE_DATA.hover.tile == tile;
},
tile_is_select(source, tile) {
return INTERFACE_DATA.select !== null
&& INTERFACE_DATA.select.source == source
&& INTERFACE_DATA.select.tile == tile;
},
pool_hex_is_valid(hex) {
return (hex.x >= 0 && hex.x < 2 && hex.y >= 0 && hex.y < 7);
},
hex_to_alnum(hex) {
return String.fromCharCode(65 + hex.x) + (hex.y + 1);
},
match_select(a, b) {
if(a !== null && b !== null) {
return (
a.source == b.source
&& a.tile == b.tile
&& a.hex.x == b.hex.x
&& a.hex.y == b.hex.y
);
}
return false;
},
},
init(data, online) {
GAME.init();
let token = null;
let player = 0;
if(data !== null) {
token = data.token;
player = data.mode;
}
INTERFACE_DATA = {
online: online,
token: token,
canvas: document.getElementById("game"),
context: null,
player: player,
rotate: 0,
mirror: false,
hover: null,
select: null,
handles: [null, null],
board_state: [ ],
play: null,
Ui: {
scale: 0,
margin: 0,
offset: new MATH.Vec2(),
area: new MATH.Vec2(),
board_width: 0,
pool_offset: 0,
},
};
for(let i = 0; i < 61; ++i) { INTERFACE_DATA.board_state.push([0, 0]); }
let canvas = INTERFACE_DATA.canvas;
if(canvas !== undefined) {
INTERFACE_DATA.context = canvas.getContext("2d");
canvas.addEventListener("mousemove", INTERFACE.hover);
canvas.addEventListener("mouseout", INTERFACE.unhover);
canvas.addEventListener("mousedown", INTERFACE.click);
window.addEventListener("resize", INTERFACE.draw);
if(INTERFACE_DATA.online) {
MESSAGE_COMPOSE([
PACK.u16(OpCode.GameState),
INTERFACE_DATA.token,
]);
} else {
INTERFACE.draw();
}
}
},
uninit() {
if(INTERFACE_DATA.online) {
MESSAGE_COMPOSE([
PACK.u16(OpCode.SessionLeave),
]);
}
if(INTERFACE_DATA !== null) {
MAIN.removeChild(INTERFACE_DATA.canvas);
INTERFACE_DATA = null;
}
},
reset() {
GAME.init();
INTERFACE_DATA.player = 0;
INTERFACE_DATA.rotate = 0;
INTERFACE.draw();
},
message(code, data) {
if(data === null) { return; }
switch(code) {
case OpCode.GameState: {
INTERFACE_DATA.player = data.player;
GAME_DATA.turn = data.turn;
INTERFACE_DATA.play = data.play;
if(data.dawn.length > 0) { INTERFACE_DATA.handles[0] = data.dawn; }
if(data.dusk.length > 0) { INTERFACE_DATA.handles[1] = data.dusk; }
// Clear piece placement.
for(let i = 0; i < GAME_DATA.board.tiles.length; ++i) {
GAME_DATA.board.tiles[i].piece = null;
}
// Update pools.
GAME_DATA.pools[0].pieces = data.pool_dawn;
GAME_DATA.pools[1].pieces = data.pool_dusk;
// Replace pieces list.
for(let i = 0; i < GAME_DATA.board.pieces.length; ++i) {
GAME_DATA.board.pieces[i] = data.pieces[i];
if(data.pieces[i] !== null) {
GAME_DATA.board.tiles[data.pieces[i].tile].piece = i;
}
}
GAME_DATA.update_board();
INTERFACE.draw();
} break;
case OpCode.GamePlay: {
if(data.status == Status.Ok && data.turn == GAME_DATA.turn) {
INTERFACE_DATA.play = data.play;
GAME_DATA.process(data.play);
INTERFACE.draw();
}
} break;
}
},
rotate() {
INTERFACE_DATA.rotate ^= 1;
INTERFACE.draw();
},
mirror() {
INTERFACE_DATA.mirror = !INTERFACE_DATA.mirror;
INTERFACE.draw();
},
retire() {
if(INTERFACE_DATA.online) {
MESSAGE_COMPOSE([
OpCode.SessionRetire,
INTERFACE_DATA.token,
]);
}
}
};
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),
];
INTERFACE.BoardWidth = INTERFACE.Radius * 14;
INTERFACE.PoolOffset = INTERFACE.BoardWidth;