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