Fix hex grid rendering - proper pointy-top hexagons with tight adjacency

This commit is contained in:
sokol
2026-02-21 17:36:19 +03:00
parent 25385de4d4
commit 139a732378

View File

@@ -5,10 +5,9 @@
import { HexMap, CELL_TYPES } from './map.js';
// Game constants
const HEX_SIZE = 20;
const HEX_SIZE = 18;
const MAP_SIZE = 20;
const ANIMATION_DURATION = 300;
const ISO_ANGLE = Math.PI / 6; // 30 degrees for isometric view
// Colors
const COLORS = {
@@ -95,13 +94,14 @@ class GameUI {
centerMap() {
const canvasWidth = this.canvas.width;
const canvasHeight = this.canvas.height;
// Calculate map dimensions for isometric view
const mapWidth = MAP_SIZE * HEX_SIZE * 2;
const mapHeight = MAP_SIZE * HEX_SIZE;
this.offsetX = canvasWidth / 2;
this.offsetY = HEX_SIZE * 2;
const sqrt3 = Math.sqrt(3);
// Map dimensions for pointy-top hex grid
const mapWidth = HEX_SIZE * sqrt3 * (MAP_SIZE + MAP_SIZE/2);
const mapHeight = HEX_SIZE * 1.5 * (MAP_SIZE - 1) + HEX_SIZE * 2;
this.offsetX = (canvasWidth - mapWidth) / 2 + HEX_SIZE * sqrt3;
this.offsetY = (canvasHeight - mapHeight) / 2 + HEX_SIZE;
}
setupEventListeners() {
@@ -115,27 +115,26 @@ class GameUI {
}
hexToPixel(q, r) {
// Isometric projection for pointy-top hex grid
// q axis goes top-left to bottom-right
// r axis goes top-right to bottom-left
const isoX = (q - r) * HEX_SIZE * 1.5;
const isoY = (q + r) * HEX_SIZE * 0.75;
// Pointy-top hex grid with proper adjacency
// For pointy-top: width = sqrt(3) * size, height = 2 * size
// Horizontal spacing = width = sqrt(3) * size
// Vertical spacing = 3/4 * height = 1.5 * size
const sqrt3 = Math.sqrt(3);
// Convert axial to pixel coordinates for pointy-top hex
const x = this.offsetX + HEX_SIZE * sqrt3 * (q + r/2);
const y = this.offsetY + HEX_SIZE * 1.5 * r;
return {
x: this.offsetX + isoX,
y: this.offsetY + isoY
};
return { x, y };
}
pixelToHex(x, y) {
const adjX = x - this.offsetX;
const adjY = y - this.offsetY;
// Reverse isometric projection
// From: isoX = (q - r) * HEX_SIZE * 1.5
// isoY = (q + r) * HEX_SIZE * 0.75
const q = (adjX / (HEX_SIZE * 1.5) + adjY / (HEX_SIZE * 0.75)) / 2;
const r = (adjY / (HEX_SIZE * 0.75) - adjX / (HEX_SIZE * 1.5)) / 2;
const sqrt3 = Math.sqrt(3);
const q = (adjX / (HEX_SIZE * sqrt3) - adjY / (HEX_SIZE * 1.5) / 2);
const r = adjY / (HEX_SIZE * 1.5);
const qi = Math.round(q);
const ri = Math.round(r);
@@ -147,26 +146,23 @@ class GameUI {
}
/**
* Get neighboring cells for isometric hex grid
* Directions for pointy-top hex in axial coordinates with isometric projection:
* hexToPixel: isoX = (q-r)*1.5, isoY = (q+r)*0.75
*
* Visual directions:
* - top: (-1, -1) - both decrease
* - bottom: (+1, +1) - both increase
* - top-left: (-1, 0) - q decreases
* - bottom-left: (0, +1) - r increases
* - top-right: (0, -1) - r decreases
* - bottom-right: (+1, 0) - q increases
* Get neighboring cells for pointy-top hex grid
* Directions for pointy-top hex in axial coordinates:
* - north-east: (+1, -1)
* - north-west: (0, -1)
* - west: (-1, 0)
* - south-west: (-1, +1)
* - south-east: (0, +1)
* - east: (+1, 0)
*/
getValidTargets(q, r) {
const directions = [
[+1, 0], // bottom-right
[+1, +1], // bottom (straight down)
[0, +1], // bottom-left
[-1, 0], // top-left
[-1, -1], // top (straight up)
[0, -1] // top-right
[+1, -1], // north-east
[0, -1], // north-west
[-1, 0], // west
[-1, +1], // south-west
[0, +1], // south-east
[+1, 0] // east
];
const targets = [];
@@ -185,31 +181,30 @@ class GameUI {
drawHex(q, r, fillStyle, strokeStyle = COLORS.stroke, lineWidth = 2) {
const { x, y } = this.hexToPixel(q, r);
// Draw elongated hexagon for isometric view
const hexWidth = HEX_SIZE * 1.3;
const hexHeight = HEX_SIZE * 0.75;
// Hexagon vertices for isometric view (elongated horizontally)
const vertices = [
{ x: x + hexWidth, y: y }, // right
{ x: x + hexWidth * 0.5, y: y - hexHeight }, // top-right
{ x: x - hexWidth * 0.5, y: y - hexHeight }, // top-left
{ x: x - hexWidth, y: y }, // left
{ x: x - hexWidth * 0.5, y: y + hexHeight }, // bottom-left
{ x: x + hexWidth * 0.5, y: y + hexHeight } // bottom-right
];
// Draw pointy-top hexagon - size matches grid spacing
const size = HEX_SIZE * 0.98; // Almost full size for tight fit
// Pointy-top hexagon vertices (flat sides left/right)
const vertices = [];
for (let i = 0; i < 6; i++) {
const angle = Math.PI / 6 + (Math.PI / 3) * i; // Start at 30 degrees
vertices.push({
x: x + size * Math.cos(angle),
y: y + size * Math.sin(angle)
});
}
this.ctx.beginPath();
this.ctx.moveTo(vertices[0].x, vertices[0].y);
for (let i = 1; i < vertices.length; i++) {
this.ctx.lineTo(vertices[i].x, vertices[i].y);
}
this.ctx.closePath();
this.ctx.fillStyle = fillStyle;
this.ctx.fill();
this.ctx.strokeStyle = strokeStyle;
this.ctx.lineWidth = lineWidth;
this.ctx.stroke();
@@ -247,18 +242,17 @@ class GameUI {
const ownerColor = cell.getOwner() === 1 ? COLORS.player1 : COLORS.player2;
// Draw border matching the hexagon shape
const hexWidth = HEX_SIZE * 1.3 - 3;
const hexHeight = HEX_SIZE * 0.75 - 3;
const vertices = [
{ x: x + hexWidth, y: y },
{ x: x + hexWidth * 0.5, y: y - hexHeight },
{ x: x - hexWidth * 0.5, y: y - hexHeight },
{ x: x - hexWidth, y: y },
{ x: x - hexWidth * 0.5, y: y + hexHeight },
{ x: x + hexWidth * 0.5, y: y + hexHeight }
];
const size = HEX_SIZE * 0.98 - 3;
const vertices = [];
for (let i = 0; i < 6; i++) {
const angle = Math.PI / 6 + (Math.PI / 3) * i;
vertices.push({
x: x + size * Math.cos(angle),
y: y + size * Math.sin(angle)
});
}
this.ctx.beginPath();
this.ctx.moveTo(vertices[0].x, vertices[0].y);
for (let i = 1; i < vertices.length; i++) {