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), ];