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", HintValidBorder: "#5558fc", HintThreat: "#054719", HintThreatBorder: "#22b54e", HintOpponent: "#49136b", HintOpponentBorder: "#d74cef", HintInvalid: "#b71c1c", HintInvalidBorder: "#ed3636", HintPlay: "#083242", 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 target = 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) ^ 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); } if(is_valid) { 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), ]); INTERFACE_DATA.select = null; } else { INTERFACE_DATA.select = null; if(INTERFACE_DATA.hover.source == 0) { if(GAME_DATA.board.tiles[INTERFACE_DATA.hover.tile].piece !== null) { INTERFACE_DATA.select = INTERFACE_DATA.hover; } } else { let pool_player = Math.floor(INTERFACE_DATA.hover.tile / 7); pool_player ^= (INTERFACE_DATA.player & 1) ^ 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 * window.devicePixelRatio; let height = canvas.height * window.devicePixelRatio; 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 border_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; } } switch(border_state) { case INTERFACE.TileStatus.Valid: ctx.fillStyle = INTERFACE.Color.HintValidBorder; break; case INTERFACE.TileStatus.Threat: ctx.fillStyle = INTERFACE.Color.HintThreatBorder; break; case INTERFACE.TileStatus.Invalid: ctx.fillStyle = INTERFACE.Color.HintInvalidBorder; break; case INTERFACE.TileStatus.Opponent: ctx.fillStyle = INTERFACE.Color.HintOpponentBorder; break; } 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.state.check && piece !== null && piece.piece == GAME.Const.PieceId.Omen && piece.player == (GAME_DATA.turn & 1)) { ctx.fillStyle = INTERFACE.Color.HintCheck; } 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 && border_state == 0) { draw.hints(piece); } // Draw piece icon 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 descriptor = GAME.Const.Piece[piece.piece]; let moves = descriptor.moves; if(piece.promoted) { moves = descriptor.pmoves; } if(((piece.player ^ INTERFACE_DATA.player ^ INTERFACE_DATA.rotate) & 1) != 0) { moves = moves.rotate(); } moves = moves.direction; for(let mask = BITWISE.lsb(moves); moves > 0; mask = BITWISE.lsb(moves)) { let move = BITWISE.ffs(mask); if(move >= 12) { move = Math.floor((move - 12) / 2) + 6; } 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) { GAME.init(); INTERFACE_DATA = { token:data.token, canvas: document.getElementById("game"), context: null, player: data.mode, rotate: 0, hover: null, select: null, handles: [null, null], board_state: [ ], 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); MESSAGE_COMPOSE([ PACK.u16(OpCode.GameState), INTERFACE_DATA.token, ]); } }, uninit() { MESSAGE_COMPOSE([ PACK.u16(OpCode.SessionLeave), ]); if(INTERFACE_DATA !== null) { MAIN.removeChild(INTERFACE_DATA.canvas); INTERFACE_DATA = null; } }, message(code, data) { switch(code) { case OpCode.GameState: { console.log(data.player); INTERFACE_DATA.player = data.player; GAME_DATA.turn = data.turn; 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) { GAME_DATA.process(data.move); INTERFACE.draw(); } } break; } }, rotate() { INTERFACE_DATA.rotate = +(!INTERFACE_DATA.rotate); INTERFACE.draw(); } }; 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;