const FPS = 60;
const FRAME_TIME = 1000 / FPS;
let lastFrameTime = 0;
const state = {
canvas: null,
ctx: null
};
async function init() {
state.canvas = document.getElementById('gameCanvas');
state.ctx = state.canvas.getContext('2d');
await player.init();
Debug.init();
function resize() {
state.canvas.width = window.innerWidth;
state.canvas.height = window.innerHeight;
Camera.centerOn(player.position);
}
window.addEventListener('resize', resize);
resize();
state.canvas.addEventListener('click', handleClick);
requestAnimationFrame(gameLoop);
FogOfWar.init();
Items.init();
}
function drawHex(ctx, x, y, hex) {
const screen = HexGrid.toScreenCoordinates(hex, Camera);
// Only draw if hex is visible on screen (with padding)
if (!HexGrid.isInViewport(screen.x, screen.y, state.canvas)) {
return;
}
// Skip drawing completely if hex hasn't been revealed
if (!FogOfWar.isRevealed(hex)) {
return;
}
// Draw the hex fill
HexGrid.drawHexPath(ctx, screen.x, screen.y);
ctx.fillStyle = Config.colors.HEX_FILL;
ctx.fill();
// Only draw grid lines for currently visible hexes
// (fog of war will handle the grid lines for revealed but not visible hexes)
if (FogOfWar.isVisible(hex)) {
ctx.strokeStyle = Config.colors.GRID;
ctx.lineWidth = 1;
ctx.stroke();
}
}
function gameLoop(currentTime) {
requestAnimationFrame(gameLoop);
if (currentTime - lastFrameTime < Config.game.FRAME_TIME) {
return;
}
const deltaTime = Math.min(currentTime - lastFrameTime, Config.game.FRAME_TIME * 2);
lastFrameTime = currentTime;
// Update debug information
Debug.update(currentTime);
// Clear with background
state.ctx.fillStyle = Config.colors.BACKGROUND;
state.ctx.fillRect(0, 0, state.canvas.width, state.canvas.height);
// Round camera position to prevent sub-pixel rendering
Camera.x = Math.round(Camera.x);
Camera.y = Math.round(Camera.y);
player.update();
Camera.smoothFollow(player.getCurrentPosition());
if (player.hasMoved) {
FogOfWar.updateVisibility(player.position);
player.hasMoved = false;
}
// Draw everything in one pass to prevent flicker
HexGrid.getViewportHexes().forEach(hex => {
const pixel = HexGrid.toPixel(hex);
drawHex(state.ctx, Math.round(pixel.x), Math.round(pixel.y), hex);
});
Items.draw(state.ctx, HexGrid.toPixel.bind(HexGrid), Camera, HexGrid.SIZE);
player.draw(state.ctx, HexGrid.toPixel.bind(HexGrid), Camera, HexGrid.SIZE);
FogOfWar.draw(state.ctx);
InventoryUI.draw(state.ctx);
Debug.draw(state.ctx);
}
function handleClick(event) {
if (InventoryUI.isOpen) {
InventoryUI.toggleInventory();
return;
}
const rect = state.canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const hexCoord = HexGrid.fromPixel(x + Camera.x, y + Camera.y);
// Check if clicking on player's position
if (hexCoord.q === player.position.q && hexCoord.r === player.position.r) {
InventoryUI.toggleInventory();
} else {
player.moveTo(hexCoord);
}
}
init().catch(console.error);