diff options
Diffstat (limited to 'html/rogue/js/player.js')
-rw-r--r-- | html/rogue/js/player.js | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/html/rogue/js/player.js b/html/rogue/js/player.js new file mode 100644 index 0000000..efecbdf --- /dev/null +++ b/html/rogue/js/player.js @@ -0,0 +1,241 @@ +// Player state and controls +const player = { + position: { q: 0, r: 0 }, // Current hex position + target: null, // Target hex to move to + path: [], // Array of hex coordinates to follow + movementProgress: 0, // Progress of current movement (0 to 1) + moveSpeed: Config.player.MOVE_SPEED, // Movement speed (0 to 1 per frame) + inventory: [], + + // Animation properties + animation: null, + sprites: [], + + // Initialize player + async init() { + this.position = { q: 0, r: 0 }; + this.target = null; + this.path = []; + this.inventory = []; + + // Load sprites + try { + const [sprite1, sprite2] = await Promise.all([ + Animation.loadImage('assets/home/goblin-01.png'), + Animation.loadImage('assets/home/goblin-02.png') + ]); + + this.sprites = [sprite1, sprite2]; + this.animation = Animation.createAnimation(this.sprites, 500); // 500ms per frame + } catch (error) { + console.error('Failed to load player sprites:', error); + } + + return this; + }, + + // Check if a hex coordinate is within grid bounds + isValidHex(hex) { + const halfGrid = Math.floor(HexGrid.GRID_SIZE / 2); + return Math.abs(hex.q) <= halfGrid && Math.abs(hex.r) <= halfGrid; + }, + + // Get neighbors that share an edge with the given hex + getEdgeNeighbors(hex) { + const directions = [ + {q: 1, r: 0}, // East + {q: 0, r: 1}, // Southeast + {q: -1, r: 1}, // Southwest + {q: -1, r: 0}, // West + {q: 0, r: -1}, // Northwest + {q: 1, r: -1} // Northeast + ]; + + // Only return neighbors that are within grid bounds + return directions + .map(dir => ({ + q: hex.q + dir.q, + r: hex.r + dir.r + })) + .filter(hex => this.isValidHex(hex)); + }, + + // Find path from current position to target + findPath(targetHex) { + const start = this.position; + const goal = targetHex; + + // Simple breadth-first search + const queue = [[start]]; + const visited = new Set(); + const key = hex => `${hex.q},${hex.r}`; + visited.add(key(start)); + + while (queue.length > 0) { + const path = queue.shift(); + const current = path[path.length - 1]; + + if (current.q === goal.q && current.r === goal.r) { + return path; + } + + const neighbors = this.getEdgeNeighbors(current); + for (const neighbor of neighbors) { + const neighborKey = key(neighbor); + if (!visited.has(neighborKey)) { + visited.add(neighborKey); + queue.push([...path, neighbor]); + } + } + } + + return null; // No path found + }, + + // Start moving to a target hex + moveTo(targetHex) { + // Only start new movement if we're not already moving and target is valid + if (!this.target) { + // Check if target is within grid bounds + if (!this.isValidHex(targetHex)) { + return; // Ignore movement request if target is out of bounds + } + + const path = this.findPath(targetHex); + if (path) { + // Filter out any path points that would go out of bounds + this.path = path.slice(1).filter(hex => this.isValidHex(hex)); + if (this.path.length > 0) { + this.target = this.path.shift(); + this.movementProgress = 0; + } + } + } + }, + + // Add item to inventory + addToInventory(item) { + this.inventory.push(item); + }, + + // Check for and collect items + checkForItems() { + const item = Items.getItem(this.position.q, this.position.r); + if (item) { + Items.removeItem(this.position.q, this.position.r); + this.addToInventory(item); + } + }, + + // Update player position + update() { + if (this.target) { + this.movementProgress += this.moveSpeed; + + if (this.movementProgress >= 1) { + this.position = this.target; + this.target = null; + this.movementProgress = 0; + this.hasMoved = true; + + // Check for items when reaching new position + this.checkForItems(); + + if (this.path.length > 0) { + this.target = this.path.shift(); + this.movementProgress = 0; + } + } + } + }, + + // Get current interpolated position + getCurrentPosition() { + if (!this.target) { + return this.position; + } + + // Interpolate between current position and target + return { + q: this.position.q + (this.target.q - this.position.q) * this.movementProgress, + r: this.position.r + (this.target.r - this.position.r) * this.movementProgress + }; + }, + + // Draw the player + draw(ctx, hexToPixel, camera, HEX_SIZE) { + const currentPos = this.getCurrentPosition(); + const pixelPos = hexToPixel(currentPos); + const screenX = pixelPos.x - camera.x; + const screenY = pixelPos.y - camera.y; + + if (this.animation && this.sprites.length > 0) { + // Get current sprite from animation + const currentSprite = this.animation.update(performance.now()); + + // Scale sprite to fit within hex + // Use slightly smaller than hex size to ensure it fits visually + const hexInnerSize = HEX_SIZE * 0.8; // 80% of hex size + const { width, height, scale } = Animation.scaleToFit( + currentSprite, + hexInnerSize * 2, // width + hexInnerSize * Math.sqrt(3) // height (hex height) + ); + + // Calculate position to center the sprite in the hex + const spriteX = screenX - width / 2; + const spriteY = screenY - height / 2; + + // Save context state + ctx.save(); + + // Optional: add a small bounce effect when moving + if (this.target) { + const bounce = Math.sin(performance.now() / 100) * 2; + ctx.translate(spriteX, spriteY + bounce); + } else { + ctx.translate(spriteX, spriteY); + } + + // Draw the sprite + ctx.drawImage( + currentSprite, + 0, 0, + width, + height + ); + + // Restore context state + ctx.restore(); + + // Debug: draw hex bounds if debug is enabled + if (Debug.isEnabled) { + ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)'; + ctx.beginPath(); + HexGrid.drawHexPath(ctx, screenX, screenY, HEX_SIZE * 0.8); + ctx.stroke(); + } + } else { + // Fallback to circle if sprites aren't loaded + ctx.fillStyle = Config.colors.PLAYER; + ctx.beginPath(); + ctx.arc(screenX, screenY, HEX_SIZE * Config.player.SIZE_RATIO, 0, Math.PI * 2); + ctx.fill(); + } + + // Draw path if needed + if (this.path.length > 0) { + ctx.strokeStyle = Config.colors.PLAYER + '4D'; + ctx.beginPath(); + let lastPos = this.target || this.position; + this.path.forEach(point => { + const from = hexToPixel(lastPos); + const to = hexToPixel(point); + ctx.moveTo(from.x - camera.x, from.y - camera.y); + ctx.lineTo(to.x - camera.x, to.y - camera.y); + lastPos = point; + }); + ctx.stroke(); + } + } +}; \ No newline at end of file |