/* ---------- Tile Map Management ---------- */
/* DOM Elements - only declare tilemap-specific elements */
let tilemapBtn;
/* State */
let isTilemapMode = false;
let tileMapGrid = [];
let gridWidth = 32; // Default grid size
let gridHeight = 24;
let baseTileSize = 64; // Base tile size - all tiles scale from this
let selectedTile = null; // Currently selected tile for painting
/* ---------- Tile Map Mode Controls ---------- */
function enableTilemapMode() {
try {
// Get workspace elements from DOM directly
const objectsBar = document.getElementById('objectsBar');
const linesBar = document.getElementById('linesBar');
const tankBar = document.getElementById('tankBar');
const metaEl = document.getElementById('meta');
// Hide workspace elements
if (objectsBar) objectsBar.style.display = 'none';
if (linesBar) linesBar.style.display = 'none';
if (tankBar) tankBar.style.display = 'none';
// Update meta text
if (metaEl) {
metaEl.textContent = 'Tile Map Mode: Click a tile to select it, then click grid squares to place it. Right-click to erase.';
}
// Update button appearance
if (tilemapBtn) {
tilemapBtn.style.background = 'var(--accent)';
tilemapBtn.style.color = '#000';
}
isTilemapMode = true;
// Create blank tile map
createBlankTileMap();
// Show tank for dragging tiles
showTileMapTank();
} catch (error) {
alert('Error enabling tilemap mode: ' + error.message);
}
}
function disableTilemapMode() {
try {
// Get workspace elements from DOM directly
const objectsBar = document.getElementById('objectsBar');
const linesBar = document.getElementById('linesBar');
const tankBar = document.getElementById('tankBar');
const metaEl = document.getElementById('meta');
// Show workspace elements
if (objectsBar) objectsBar.style.display = '';
if (linesBar) linesBar.style.display = '';
if (tankBar) tankBar.style.display = '';
// Restore meta text
if (metaEl) {
if (window.CoreAPI && window.CoreAPI.imgW && window.CoreAPI.imgH) {
metaEl.textContent = `Image: ${window.CoreAPI.imgW}ร${window.CoreAPI.imgH} โ tap tiles to add to the selected line`;
} else {
metaEl.textContent = 'Open an image, then tap grid tiles to add thumbnails to the selected line.';
}
}
// Reset button appearance
if (tilemapBtn) {
tilemapBtn.style.background = '';
tilemapBtn.style.color = '';
}
isTilemapMode = false;
// Hide tile map
hideTileMap();
// Hide tile map tank
hideTileMapTank();
} catch (error) {
alert('Error disabling tilemap mode: ' + error.message);
}
}
function toggleTilemapMode() {
if (isTilemapMode) {
disableTilemapMode();
} else {
enableTilemapMode();
}
}
/* ---------- Tile Scaling Utilities ---------- */
function getTileScale(tileData) {
// Calculate how many grid squares this tile should occupy
const scaleX = Math.ceil(tileData.w / baseTileSize);
const scaleY = Math.ceil(tileData.h / baseTileSize);
return { scaleX, scaleY };
}
function canPlaceTileAt(gridX, gridY, scaleX, scaleY) {
// Check if there's room for a tile of this size at this position
if (gridX + scaleX > gridWidth || gridY + scaleY > gridHeight) {
return false; // Doesn't fit in grid
}
// Check if all required squares are empty
for (let y = gridY; y < gridY + scaleY; y++) {
for (let x = gridX; x < gridX + scaleX; x++) {
if (tileMapGrid[y][x]) {
return false; // Square is occupied
}
}
}
return true;
}
function clearTileArea(gridX, gridY, scaleX, scaleY) {
// Clear all squares in the specified area
for (let y = gridY; y < gridY + scaleY; y++) {
for (let x = gridX; x < gridX + scaleX; x++) {
if (tileMapGrid[y][x]) {
removeTileFromGrid(x, y);
}
}
}
}
/* ---------- Blank Tile Map Creation ---------- */
let tileMapContainer = null;
function createBlankTileMap() {
try {
alert('Creating blank tile map...');
// Remove existing tile map if any
if (tileMapContainer) {
tileMapContainer.remove();
}
// Create tile map container
tileMapContainer = document.createElement('div');
tileMapContainer.id = 'tileMapContainer';
tileMapContainer.style.position = 'absolute';
tileMapContainer.style.left = '50px'; // Offset from edge so it's visible
tileMapContainer.style.top = '50px';
tileMapContainer.style.width = (gridWidth * baseTileSize) + 'px';
tileMapContainer.style.height = (gridHeight * baseTileSize) + 'px';
tileMapContainer.style.background = '#2a2a2a'; // More visible color
tileMapContainer.style.border = '3px solid #4fc3f7'; // Brighter border
tileMapContainer.style.zIndex = '15'; // Higher z-index
alert(`Grid size: ${gridWidth * baseTileSize}px ร ${gridHeight * baseTileSize}px`);
// Create grid pattern with base tile size
tileMapContainer.style.backgroundImage = `
linear-gradient(rgba(79, 195, 247, 0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(79, 195, 247, 0.3) 1px, transparent 1px)
`;
tileMapContainer.style.backgroundSize = `${baseTileSize}px ${baseTileSize}px`;
// Initialize empty grid
tileMapGrid = Array(gridHeight).fill().map(() => Array(gridWidth).fill(null));
const viewport = document.getElementById('viewport');
if (viewport) {
alert('Adding to viewport...');
viewport.appendChild(tileMapContainer);
alert('Tile map container added to viewport');
} else {
alert('ERROR: Viewport not found!');
return;
}
// Verify it was added
const added = document.getElementById('tileMapContainer');
if (added) {
alert('Verification: Tile map container exists in DOM');
} else {
alert('ERROR: Tile map container not found in DOM after adding');
}
// Add click handlers for tile selection and grid painting
setupTileSelection();
setupGridPainting();
} catch (error) {
alert('Error creating blank tile map: ' + error.message);
}
}
function hideTileMap() {
if (tileMapContainer) {
tileMapContainer.style.display = 'none';
}
}
/* ---------- Tile Map Tank (shows available tiles) ---------- */
let tileMapTank = null;
function showTileMapTank() {
try {
// Create or show tile map tank
if (!tileMapTank) {
tileMapTank = document.createElement('div');
tileMapTank.id = 'tileMapTank';
tileMapTank.style.position = 'fixed';
tileMapTank.style.bottom = '20px';
tileMapTank.style.left = '20px';
tileMapTank.style.right = '20px';
tileMapTank.style.height = '140px';
tileMapTank.style.background = '#161616';
tileMapTank.style.border = '1px solid #2a2a2a';
tileMapTank.style.borderRadius = '.6rem';
tileMapTank.style.padding = '.5rem';
tileMapTank.style.zIndex = '20';
tileMapTank.style.display = 'flex';
tileMapTank.style.flexDirection = 'column';
tileMapTank.style.gap = '.5rem';
document.body.appendChild(tileMapTank);
}
tileMapTank.style.display = 'flex';
updateTileMapTank();
} catch (error) {
alert('Error showing tile map tank: ' + error.message);
}
}
function hideTileMapTank() {
if (tileMapTank) {
tileMapTank.style.display = 'none';
}
}
function updateTileMapTank() {
if (!tileMapTank) return;
try {
tileMapTank.innerHTML = '';
// Create controls row
const controlsRow = document.createElement('div');
controlsRow.style.display = 'flex';
controlsRow.style.alignItems = 'center';
controlsRow.style.gap = '1rem';
controlsRow.style.marginBottom = '.5rem';
// Grid size controls
const sizeLabel = document.createElement('div');
sizeLabel.textContent = 'Base Size:';
sizeLabel.style.color = '#4fc3f7';
sizeLabel.style.fontWeight = 'bold';
sizeLabel.style.fontSize = '.9rem';
const baseSizeControls = document.createElement('div');
baseSizeControls.style.display = 'flex';
baseSizeControls.style.alignItems = 'center';
baseSizeControls.style.gap = '.3rem';
const baseSizeMinus = document.createElement('button');
baseSizeMinus.textContent = '-';
baseSizeMinus.style.background = '#333';
baseSizeMinus.style.border = '1px solid #555';
baseSizeMinus.style.color = '#eee';
baseSizeMinus.style.padding = '.2rem .5rem';
baseSizeMinus.style.borderRadius = '.3rem';
baseSizeMinus.style.cursor = 'pointer';
baseSizeMinus.style.fontSize = '.9rem';
baseSizeMinus.disabled = baseTileSize <= 8;
baseSizeMinus.addEventListener('click', () => changeBaseSize(baseTileSize / 2));
const baseSizeDisplay = document.createElement('span');
baseSizeDisplay.textContent = `${baseTileSize}px`;
baseSizeDisplay.style.color = '#eee';
baseSizeDisplay.style.minWidth = '40px';
baseSizeDisplay.style.textAlign = 'center';
baseSizeDisplay.style.fontSize = '.9rem';
const baseSizePlus = document.createElement('button');
baseSizePlus.textContent = '+';
baseSizePlus.style.background = '#333';
baseSizePlus.style.border = '1px solid #555';
baseSizePlus.style.color = '#eee';
baseSizePlus.style.padding = '.2rem .5rem';
baseSizePlus.style.borderRadius = '.3rem';
baseSizePlus.style.cursor = 'pointer';
baseSizePlus.style.fontSize = '.9rem';
baseSizePlus.disabled = baseTileSize >= 128;
baseSizePlus.addEventListener('click', () => changeBaseSize(baseTileSize * 2));
baseSizeControls.appendChild(baseSizeMinus);
baseSizeControls.appendChild(baseSizeDisplay);
baseSizeControls.appendChild(baseSizePlus);
const gridSizeLabel = document.createElement('div');
gridSizeLabel.textContent = 'Grid:';
gridSizeLabel.style.color = '#4fc3f7';
gridSizeLabel.style.fontWeight = 'bold';
gridSizeLabel.style.fontSize = '.9rem';
const widthControls = document.createElement('div');
widthControls.style.display = 'flex';
widthControls.style.alignItems = 'center';
widthControls.style.gap = '.3rem';
const widthMinus = document.createElement('button');
widthMinus.textContent = '-';
widthMinus.style.background = '#333';
widthMinus.style.border = '1px solid #555';
widthMinus.style.color = '#eee';
widthMinus.style.padding = '.2rem .5rem';
widthMinus.style.borderRadius = '.3rem';
widthMinus.style.cursor = 'pointer';
widthMinus.style.fontSize = '.9rem';
widthMinus.addEventListener('click', () => resizeGrid(gridWidth - 1, gridHeight));
const widthDisplay = document.createElement('span');
widthDisplay.textContent = `${gridWidth}`;
widthDisplay.style.color = '#eee';
widthDisplay.style.minWidth = '25px';
widthDisplay.style.textAlign = 'center';
widthDisplay.style.fontSize = '.9rem';
const widthPlus = document.createElement('button');
widthPlus.textContent = '+';
widthPlus.style.background = '#333';
widthPlus.style.border = '1px solid #555';
widthPlus.style.color = '#eee';
widthPlus.style.padding = '.2rem .5rem';
widthPlus.style.borderRadius = '.3rem';
widthPlus.style.cursor = 'pointer';
widthPlus.style.fontSize = '.9rem';
widthPlus.addEventListener('click', () => resizeGrid(gridWidth + 1, gridHeight));
const xLabel = document.createElement('span');
xLabel.textContent = 'ร';
xLabel.style.color = '#bbb';
xLabel.style.fontSize = '.9rem';
const heightControls = document.createElement('div');
heightControls.style.display = 'flex';
heightControls.style.alignItems = 'center';
heightControls.style.gap = '.3rem';
const heightMinus = document.createElement('button');
heightMinus.textContent = '-';
heightMinus.style.background = '#333';
heightMinus.style.border = '1px solid #555';
heightMinus.style.color = '#eee';
heightMinus.style.padding = '.2rem .5rem';
heightMinus.style.borderRadius = '.3rem';
heightMinus.style.cursor = 'pointer';
heightMinus.style.fontSize = '.9rem';
heightMinus.addEventListener('click', () => resizeGrid(gridWidth, gridHeight - 1));
const heightDisplay = document.createElement('span');
heightDisplay.textContent = `${gridHeight}`;
heightDisplay.style.color = '#eee';
heightDisplay.style.minWidth = '25px';
heightDisplay.style.textAlign = 'center';
heightDisplay.style.fontSize = '.9rem';
const heightPlus = document.createElement('button');
heightPlus.textContent = '+';
heightPlus.style.background = '#333';
heightPlus.style.border = '1px solid #555';
heightPlus.style.color = '#eee';
heightPlus.style.padding = '.2rem .5rem';
heightPlus.style.borderRadius = '.3rem';
heightPlus.style.cursor = 'pointer';
heightPlus.style.fontSize = '.9rem';
heightPlus.addEventListener('click', () => resizeGrid(gridWidth, gridHeight + 1));
widthControls.appendChild(widthMinus);
widthControls.appendChild(widthDisplay);
widthControls.appendChild(widthPlus);
heightControls.appendChild(heightMinus);
heightControls.appendChild(heightDisplay);
heightControls.appendChild(heightPlus);
controlsRow.appendChild(sizeLabel);
controlsRow.appendChild(baseSizeControls);
controlsRow.appendChild(gridSizeLabel);
controlsRow.appendChild(widthControls);
controlsRow.appendChild(xLabel);
controlsRow.appendChild(heightControls);
// Add clear button
const clearBtn = document.createElement('button');
clearBtn.textContent = '๐งน Clear';
clearBtn.style.background = '#444';
clearBtn.style.border = '1px solid #666';
clearBtn.style.color = '#eee';
clearBtn.style.padding = '.3rem .6rem';
clearBtn.style.borderRadius = '.3rem';
clearBtn.style.cursor = 'pointer';
clearBtn.style.fontSize = '.9rem';
clearBtn.style.marginLeft = 'auto';
clearBtn.addEventListener('click', clearTileMap);
controlsRow.appendChild(clearBtn);
tileMapTank.appendChild(controlsRow);
// Create tiles row
const tilesRow = document.createElement('div');
tilesRow.style.display = 'flex';
tilesRow.style.gap = '.5rem';
tilesRow.style.overflowX = 'auto';
tilesRow.style.alignItems = 'center';
tilesRow.style.flex = '1';
// Add title
const title = document.createElement('div');
title.textContent = 'Select:';
title.style.color = '#4fc3f7';
title.style.fontWeight = 'bold';
title.style.marginRight = '.5rem';
title.style.minWidth = 'fit-content';
title.style.fontSize = '.9rem';
tilesRow.appendChild(title);
// Add eraser tool first
const eraser = document.createElement('div');
eraser.className = 'tilemap-source-tile eraser';
eraser.style.position = 'relative';
eraser.style.width = '60px';
eraser.style.height = '60px';
eraser.style.border = selectedTile === null ? '3px solid #ff4444' : '1px solid #333';
eraser.style.borderRadius = '.35rem';
eraser.style.background = '#333';
eraser.style.cursor = 'pointer';
eraser.style.flexShrink = '0';
eraser.style.display = 'flex';
eraser.style.alignItems = 'center';
eraser.style.justifyContent = 'center';
eraser.style.fontSize = '18px';
eraser.textContent = '๐๏ธ';
eraser.title = 'Click to select eraser';
eraser.addEventListener('click', () => {
selectedTile = null;
updateTileMapTank();
});
tilesRow.appendChild(eraser);
// Get tiles from all objects and lines
if (window.WorkspaceAPI && window.WorkspaceAPI.workspace) {
const workspace = window.WorkspaceAPI.workspace;
workspace.objects.forEach(obj => {
obj.lines.forEach(line => {
line.items.forEach((tile, tileIndex) => {
const { scaleX, scaleY } = getTileScale(tile);
const tileEl = document.createElement('div');
tileEl.className = 'tilemap-source-tile';
tileEl.style.position = 'relative';
tileEl.style.width = '60px';
tileEl.style.height = '60px';
tileEl.style.border = selectedTile && selectedTile.id === tile.id ? '3px solid #4fc3f7' : '1px solid #333';
tileEl.style.borderRadius = '.35rem';
tileEl.style.overflow = 'hidden';
tileEl.style.background = '#111';
tileEl.style.cursor = 'pointer';
tileEl.style.flexShrink = '0';
const img = document.createElement('img');
img.src = tile.thumbDataURL;
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'contain';
img.style.imageRendering = 'pixelated';
img.draggable = false;
const badge = document.createElement('div');
badge.style.position = 'absolute';
badge.style.left = '2px';
badge.style.top = '2px';
badge.style.background = '#4fc3f7';
badge.style.color = '#000';
badge.style.borderRadius = '.2rem';
badge.style.padding = '.05rem .25rem';
badge.style.fontWeight = 'bold';
badge.style.fontSize = '.7rem';
badge.textContent = `#${tile.id}`;
// Add size indicator for multi-square tiles
if (scaleX > 1 || scaleY > 1) {
const sizeIndicator = document.createElement('div');
sizeIndicator.style.position = 'absolute';
sizeIndicator.style.bottom = '2px';
sizeIndicator.style.right = '2px';
sizeIndicator.style.background = 'rgba(0,0,0,0.8)';
sizeIndicator.style.color = '#ffff00';
sizeIndicator.style.padding = '.05rem .2rem';
sizeIndicator.style.borderRadius = '.2rem';
sizeIndicator.style.fontSize = '.65rem';
sizeIndicator.style.fontWeight = 'bold';
sizeIndicator.textContent = `${scaleX}ร${scaleY}`;
tileEl.appendChild(sizeIndicator);
}
tileEl.appendChild(img);
tileEl.appendChild(badge);
// Store tile data
tileEl.tileData = tile;
tileEl.title = `${tile.w}ร${tile.h}px (${scaleX}ร${scaleY} squares)`;
// Add click handler to select tile
tileEl.addEventListener('click', () => {
selectedTile = tile;
updateTileMapTank(); // Refresh to show selection
});
tilesRow.appendChild(tileEl);
});
});
});
}
tileMapTank.appendChild(tilesRow);
} catch (error) {
alert('Error updating tile map tank: ' + error.message);
}
}
/* ---------- Click-to-Select and Paint System ---------- */
function setupTileSelection() {
// Selection is handled in updateTileMapTank() click handlers
}
function setupGridPainting() {
if (!tileMapContainer) return;
try {
tileMapContainer.addEventListener('click', handleGridClick);
tileMapContainer.addEventListener('contextmenu', handleGridRightClick);
} catch (error) {
alert('Error setting up grid painting: ' + error.message);
}
}
function handleGridClick(e) {
e.preventDefault();
if (!tileMapContainer) return;
try {
const rect = tileMapContainer.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const gridX = Math.floor(x / baseTileSize);
const gridY = Math.floor(y / baseTileSize);
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
if (selectedTile === null) {
// Eraser mode - remove any tile that occupies this square
removeTileFromGrid(gridX, gridY);
} else {
// Paint selected tile
const { scaleX, scaleY } = getTileScale(selectedTile);
// Check if tile fits at this position
if (canPlaceTileAt(gridX, gridY, scaleX, scaleY)) {
placeTileOnGrid(gridX, gridY, selectedTile);
} else {
// Show message about why it can't be placed
if (gridX + scaleX > gridWidth || gridY + scaleY > gridHeight) {
alert(`Tile (${selectedTile.w}ร${selectedTile.h}) doesn't fit here. Need ${scaleX}ร${scaleY} free squares.`);
} else {
alert('Cannot place tile here - area is occupied.');
}
}
}
}
} catch (error) {
alert('Error handling grid click: ' + error.message);
}
}
function handleGridRightClick(e) {
e.preventDefault();
if (!tileMapContainer) return;
try {
const rect = tileMapContainer.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const gridX = Math.floor(x / baseTileSize);
const gridY = Math.floor(y / baseTileSize);
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
removeTileFromGrid(gridX, gridY);
}
} catch (error) {
alert('Error handling right click: ' + error.message);
}
}
function placeTileOnGrid(gridX, gridY, tileData) {
try {
const { scaleX, scaleY } = getTileScale(tileData);
// Create tile element that spans multiple grid squares
const tileEl = document.createElement('div');
tileEl.style.position = 'absolute';
tileEl.style.left = (gridX * baseTileSize) + 'px';
tileEl.style.top = (gridY * baseTileSize) + 'px';
tileEl.style.width = (scaleX * baseTileSize) + 'px';
tileEl.style.height = (scaleY * baseTileSize) + 'px';
tileEl.style.backgroundImage = `url(${tileData.thumbDataURL})`;
tileEl.style.backgroundSize = 'contain';
tileEl.style.backgroundRepeat = 'no-repeat';
tileEl.style.backgroundPosition = 'center';
tileEl.style.imageRendering = 'pixelated';
tileEl.style.border = '2px solid #4fc3f7';
tileEl.style.boxSizing = 'border-box';
// Add ID badge (always visible)
const idBadge = document.createElement('div');
idBadge.style.position = 'absolute';
idBadge.style.top = '2px';
idBadge.style.left = '2px';
idBadge.style.background = '#4fc3f7';
idBadge.style.color = '#000';
idBadge.style.padding = '1px 4px';
idBadge.style.borderRadius = '3px';
idBadge.style.fontSize = '10px';
idBadge.style.fontWeight = 'bold';
idBadge.style.lineHeight = '1';
idBadge.textContent = `#${tileData.id}`;
tileEl.appendChild(idBadge);
// Add size indicator for multi-square tiles
if (scaleX > 1 || scaleY > 1) {
const sizeIndicator = document.createElement('div');
sizeIndicator.style.position = 'absolute';
sizeIndicator.style.top = '2px';
sizeIndicator.style.right = '2px';
sizeIndicator.style.background = 'rgba(0,0,0,0.8)';
sizeIndicator.style.color = '#ffff00';
sizeIndicator.style.padding = '1px 4px';
sizeIndicator.style.borderRadius = '3px';
sizeIndicator.style.fontSize = '10px';
sizeIndicator.style.fontWeight = 'bold';
sizeIndicator.style.lineHeight = '1';
sizeIndicator.textContent = `${scaleX}ร${scaleY}`;
tileEl.appendChild(sizeIndicator);
}
tileMapContainer.appendChild(tileEl);
// Create tile reference object
const tileRef = {
element: tileEl,
data: tileData,
scaleX,
scaleY,
originX: gridX,
originY: gridY
};
// Mark all squares this tile occupies
for (let y = gridY; y < gridY + scaleY; y++) {
for (let x = gridX; x < gridX + scaleX; x++) {
tileMapGrid[y][x] = tileRef;
}
}
} catch (error) {
alert('Error placing tile on grid: ' + error.message);
}
}
function removeTileFromGrid(gridX, gridY) {
try {
const tileRef = tileMapGrid[gridY][gridX];
if (tileRef) {
// Remove the visual element
tileRef.element.remove();
// Clear all grid squares this tile occupied
for (let y = tileRef.originY; y < tileRef.originY + tileRef.scaleY; y++) {
for (let x = tileRef.originX; x < tileRef.originX + tileRef.scaleX; x++) {
if (y >= 0 && y < gridHeight && x >= 0 && x < gridWidth) {
tileMapGrid[y][x] = null;
}
}
}
}
} catch (error) {
alert('Error removing tile from grid: ' + error.message);
}
}
/* ---------- Base Size Control ---------- */
function changeBaseSize(newSize) {
try {
// Validate size (powers of 2 between 8 and 128)
if (newSize < 8 || newSize > 128 || !Number.isInteger(Math.log2(newSize))) {
alert('Base size must be 8, 16, 32, 64, or 128 pixels');
return;
}
// Confirm if there are tiles placed (since it will clear the map)
const hasTiles = tileMapGrid.some(row => row.some(cell => cell !== null));
if (hasTiles) {
if (!confirm(`Changing base size will clear the current tilemap. Continue?`)) {
return;
}
}
// Clear existing tiles
clearTileMap();
// Update base size
baseTileSize = newSize;
// Recreate the grid with new base size
if (tileMapContainer) {
tileMapContainer.style.width = (gridWidth * baseTileSize) + 'px';
tileMapContainer.style.height = (gridHeight * baseTileSize) + 'px';
tileMapContainer.style.backgroundSize = `${baseTileSize}px ${baseTileSize}px`;
}
// Update the tank display
updateTileMapTank();
} catch (error) {
alert('Error changing base size: ' + error.message);
}
}
/* ---------- Grid Resizing ---------- */
function resizeGrid(newWidth, newHeight) {
try {
// Minimum size constraints
if (newWidth < 1 || newHeight < 1) {
alert('Grid size must be at least 1x1');
return;
}
// Maximum size constraints to prevent performance issues
if (newWidth > 100 || newHeight > 100) {
alert('Grid size cannot exceed 100x100');
return;
}
const oldWidth = gridWidth;
const oldHeight = gridHeight;
// Create new grid array
const newGrid = Array(newHeight).fill().map(() => Array(newWidth).fill(null));
// Copy existing tiles to new grid (only tiles that fit in the new size)
for (let y = 0; y < Math.min(oldHeight, newHeight); y++) {
for (let x = 0; x < Math.min(oldWidth, newWidth); x++) {
if (tileMapGrid[y] && tileMapGrid[y][x]) {
newGrid[y][x] = tileMapGrid[y][x];
}
}
}
// Remove tiles that are outside the new grid bounds
if (newWidth < oldWidth || newHeight < oldHeight) {
for (let y = 0; y < oldHeight; y++) {
for (let x = 0; x < oldWidth; x++) {
if (tileMapGrid[y] && tileMapGrid[y][x]) {
// If tile is outside new bounds, remove it
if (x >= newWidth || y >= newHeight) {
tileMapGrid[y][x].element.remove();
}
}
}
}
}
// Update grid dimensions
gridWidth = newWidth;
gridHeight = newHeight;
tileMapGrid = newGrid;
// Update container size and redraw
if (tileMapContainer) {
tileMapContainer.style.width = (gridWidth * baseTileSize) + 'px';
tileMapContainer.style.height = (gridHeight * baseTileSize) + 'px';
tileMapContainer.style.backgroundSize = `${baseTileSize}px ${baseTileSize}px`;
}
// Update display
updateTileMapTank();
} catch (error) {
alert('Error resizing grid: ' + error.message);
}
}
function clearTileMap() {
try {
for (let y = 0; y < gridHeight; y++) {
for (let x = 0; x < gridWidth; x++) {
if (tileMapGrid[y][x]) {
removeTileFromGrid(x, y);
}
}
}
} catch (error) {
alert('Error clearing tile map: ' + error.message);
}
}
/* ---------- Export Functions ---------- */
function exportTileMap() {
try {
const mapData = [];
for (let y = 0; y < gridHeight; y++) {
const row = [];
for (let x = 0; x < gridWidth; x++) {
if (tileMapGrid[y][x]) {
row.push(tileMapGrid[y][x].data.id);
} else {
row.push(0); // Empty cell
}
}
mapData.push(row);
}
const exportData = {
width: gridWidth,
height: gridHeight,
tileSize: tileSize,
map: mapData,
timestamp: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'tilemap.json';
a.click();
URL.revokeObjectURL(url);
} catch (error) {
alert('Error exporting tile map: ' + error.message);
}
}
/* ---------- Event Handlers ---------- */
function initTilemapEvents() {
try {
if (tilemapBtn) {
tilemapBtn.addEventListener('click', toggleTilemapMode);
}
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (isTilemapMode) {
if (e.key === 'c' && e.ctrlKey) {
e.preventDefault();
clearTileMap();
} else if (e.key === 's' && e.ctrlKey) {
e.preventDefault();
exportTileMap();
} else if (e.key === 'Escape') {
disableTilemapMode();
}
}
});
} catch (error) {
alert('Failed to initialize tilemap events: ' + error.message);
}
}
/* ---------- Initialization ---------- */
function initializeTilemap() {
try {
// Get DOM elements - only the ones this module owns
tilemapBtn = document.getElementById('tilemapBtn');
// Check required DOM elements
if (!tilemapBtn) {
throw new Error('Tilemap button not found');
}
initTilemapEvents();
} catch (error) {
alert('Tilemap module failed to initialize: ' + error.message);
// Don't throw here since tilemap is an optional feature
}
}
// Export API for other modules
window.TilemapAPI = {
enableTilemapMode,
disableTilemapMode,
toggleTilemapMode,
isTilemapMode: () => isTilemapMode,
clearTileMap,
exportTileMap,
updateTileMapTank,
tileMapGrid: () => [...tileMapGrid] // Return copy
};