asciitorium
Version:
an ASCII CLUI framework
191 lines (190 loc) • 6.58 kB
JavaScript
import { AssetManager } from './AssetManager.js';
import { SoundManager } from './SoundManager.js';
export class GridMovement {
constructor(options) {
this.previousPosition = null;
// Use the provided map asset and player state
this.mapState = options.mapAsset;
this.playerState = options.player;
this.previousPosition = { x: options.player.value.x, y: options.player.value.y };
}
// Read-only access methods
getPlayerState() {
return this.playerState;
}
getPlayer() {
return this.playerState.value;
}
getMapState() {
return this.mapState;
}
getMapData() {
return this.mapState.value?.mapData ?? [];
}
getLegend() {
return this.mapState.value?.legend ?? {};
}
isReady() {
return this.mapState.value !== null;
}
// Legend interpretation methods
getLegendEntry(char) {
return this.getLegend()[char];
}
getCharAt(x, y) {
const mapData = this.getMapData();
if (y < 0 || y >= mapData.length)
return undefined;
if (x < 0 || x >= mapData[y].length)
return undefined;
return mapData[y][x];
}
isSolidChar(char) {
const entry = this.getLegend()[char];
return entry?.solid ?? false;
}
isSolid(x, y) {
const char = this.getCharAt(x, y);
if (char === undefined)
return true; // Out of bounds = solid
return this.isSolidChar(char);
}
// Movement methods
moveForward() {
const player = this.getPlayer();
const { dx, dy } = this.getDirectionVector(player.direction);
return this.movePlayerBy(dx, dy, player.direction);
}
moveBackward() {
const player = this.getPlayer();
const oppositeDir = this.getOppositeDirection(player.direction);
const { dx, dy } = this.getDirectionVector(oppositeDir);
// Move backward but keep facing the same direction
return this.movePlayerBy(dx, dy, player.direction);
}
turnLeft() {
const player = this.getPlayer();
const newDirection = this.getNewDirection(player.direction, 'left');
this.playerState.value = {
...player,
direction: newDirection,
};
}
turnRight() {
const player = this.getPlayer();
const newDirection = this.getNewDirection(player.direction, 'right');
this.playerState.value = {
...player,
direction: newDirection,
};
}
// Private helper methods
getDirectionVector(direction) {
switch (direction) {
case 'north':
return { dx: 0, dy: -1 };
case 'south':
return { dx: 0, dy: 1 };
case 'east':
return { dx: 2, dy: 0 }; // 2 units for east/west to match map aspect ratio
case 'west':
return { dx: -2, dy: 0 };
}
}
getOppositeDirection(direction) {
switch (direction) {
case 'north':
return 'south';
case 'south':
return 'north';
case 'east':
return 'west';
case 'west':
return 'east';
}
}
getNewDirection(current, turn) {
const directions = ['north', 'east', 'south', 'west'];
const currentIndex = directions.indexOf(current);
const offset = turn === 'left' ? -1 : 1;
const newIndex = (currentIndex + offset + 4) % 4;
return directions[newIndex];
}
movePlayerBy(dx, dy, direction) {
const player = this.getPlayer();
const mapData = this.getMapData();
const mapHeight = mapData.length;
const mapWidth = mapHeight > 0 ? mapData[0].length : 0;
if (mapWidth === 0 || mapHeight === 0)
return false;
const newX = Math.max(0, Math.min(mapWidth - 1, player.x + dx));
const newY = Math.max(0, Math.min(mapHeight - 1, player.y + dy));
// Check intermediate positions for horizontal movement (2 steps)
if (Math.abs(dx) === 2) {
const stepX = dx > 0 ? 1 : -1;
const midX = player.x + stepX;
if (this.isSolid(midX, player.y)) {
return false; // Blocked
}
}
// Check final destination
if (this.isSolid(newX, newY)) {
return false; // Blocked
}
// Trigger onExit sound for the old tile before moving
if (this.previousPosition) {
this.checkAndPlayExitSound(this.previousPosition.x, this.previousPosition.y);
}
// Move successful
this.playerState.value = {
x: newX,
y: newY,
direction,
};
// Update previous position
this.previousPosition = { x: newX, y: newY };
// Trigger onEnter sound for the new tile
this.checkAndPlayTileSound(newX, newY);
return true;
}
async checkAndPlayTileSound(x, y) {
const char = this.getCharAt(x, y);
if (!char)
return;
const legendEntry = this.getLegendEntry(char);
if (!legendEntry)
return;
try {
// Load the material asset to check for sound metadata
const materialAsset = await AssetManager.getMaterial(legendEntry.material);
// Check for onEnterSound in the material asset
if (materialAsset.onEnterSound) {
SoundManager.playSound(materialAsset.onEnterSound);
}
}
catch (error) {
// Silently ignore errors loading materials or playing sounds
console.debug('Could not check tile sound:', error);
}
}
async checkAndPlayExitSound(x, y) {
const char = this.getCharAt(x, y);
if (!char)
return;
const legendEntry = this.getLegendEntry(char);
if (!legendEntry)
return;
try {
// Load the material asset to check for sound metadata
const materialAsset = await AssetManager.getMaterial(legendEntry.material);
// Check for onExitSound in the material asset
if (materialAsset.onExitSound) {
SoundManager.playSound(materialAsset.onExitSound);
}
}
catch (error) {
// Silently ignore errors loading materials or playing sounds
console.debug('Could not check exit sound:', error);
}
}
}