const canvas = document.getElementById('map'); const ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; const cellSize = 16; // Size of each cell in pixels const gridWidth = 100; // Number of cells in width const gridHeight = 100; // Number of cells in height let grid = []; let history = []; // undo stack let offsetX = 0; let offsetY = 0; let selectedNumber = 0; const initializeGrid = () => { grid = Array.from({ length: gridHeight }, () => Array(gridWidth).fill(0)); }; // a hack to preload map assets const preLoad = [ 'imgs/extracted-1688-map/MapParts/trees/50.png', 'imgs/extracted-1688-map/MapParts/hills/8.png', 'imgs/extracted-1688-map/MapParts/mountains/18.png', 'imgs/extracted-1688-map/MapParts/towns/9.png', 'imgs/extracted-1688-map/MapParts/cities/33.png', ].map(src => { let images = new Image(); images.src = src; return images; }); const drawGrid = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); if ( offsetX < 0 || offsetX > gridWidth * cellSize - canvas.width || offsetY < 0 || offsetY > gridHeight * cellSize - canvas.height ) { ctx.fillStyle = '#91f7bd'; ctx.fillRect(0, 0, canvas.width, canvas.height); } grid.forEach((row, i) => { row.forEach((state, j) => { let color = '#000'; let imageSrc = ''; switch (state) { case 0: color = 'beige'; break; case 1: imageSrc = 'imgs/extracted-1688-map/MapParts/trees/50.png' break; case 2: imageSrc = 'imgs/extracted-1688-map/MapParts/hills/8.png'; break; case 3: imageSrc = 'imgs/extracted-1688-map/MapParts/mountains/18.png'; break; case 4: imageSrc = 'imgs/extracted-1688-map/MapParts/towns/9.png'; break; case 5: imageSrc = 'imgs/extracted-1688-map/MapParts/cities/33.png'; break; case 6: color = '#f0f'; break; case 7: color = '#0ff'; break; case 8: color = '#f80'; break; case 9: color = '#08f'; break; } if (imageSrc) { ctx.fillStyle = 'beige'; ctx.fillRect(j * cellSize - offsetX, i * cellSize - offsetY, cellSize, cellSize); ctx.strokeStyle = '#ddd'; ctx.strokeRect(j * cellSize - offsetX, i * cellSize - offsetY, cellSize, cellSize); const image = new Image(); image.src = imageSrc; ctx.drawImage(image, j * cellSize - offsetX, i * cellSize - offsetY, cellSize, cellSize); } else { ctx.fillStyle = color; ctx.fillRect(j * cellSize - offsetX, i * cellSize - offsetY, cellSize, cellSize); ctx.strokeStyle = '#ddd'; ctx.strokeRect(j * cellSize - offsetX, i * cellSize - offsetY, cellSize, cellSize); } }); }); }; const saveGrid = () => { localStorage.setItem('grid', JSON.stringify(grid)); }; const loadGrid = () => { const savedGrid = localStorage.getItem('grid'); if (savedGrid) { grid = JSON.parse(savedGrid); } }; const handleKeyDown = (e) => { switch (e.key) { case 'ArrowUp': offsetY -= cellSize; break; case 'ArrowDown': offsetY += cellSize; break; case 'ArrowLeft': offsetX -= cellSize; break; case 'ArrowRight': offsetX += cellSize; break; case 'h': alert(`Controls: - Arrow keys to move the map - Click to place a tile - Number keys, 0 - 9, to select a tile type - Z to undo - C to clear the grid - E to export the grid as json - S to export the grid as a png - H to display this help text `); break; case 'z': if (history.length > 0) { const lastChange = history.pop(); // Pop last change from history grid[lastChange.y][lastChange.x] = lastChange.state; // Revert state drawGrid(); } break; case 'c': if (window.confirm('Are you sure you want to clear the grid?')) { initializeGrid(); drawGrid(); } break; case 'e': const mapName = prompt('Please enter a name for the map:'); if (mapName) { if (window.confirm(`Are you sure you want to export the grid as ${mapName}?`)) { exportGridAsJson(mapName); } } break; case 's': const imgName = prompt('Please enter a name for the image:'); if (imgName) { exportGridAsPng(imgName); } break; default: if (e.key >= '0' && e.key <= '9') { selectedNumber = parseInt(e.key); } break; } drawGrid(); }; const handleCanvasClick = (e) => { const x = Math.floor((e.clientX + offsetX) / cellSize); const y = Math.floor((e.clientY + offsetY) / cellSize); history.push({ x, y, state: grid[y][x] }); // Push current state to history grid[y][x] = selectedNumber; // Set state to selectedNumber drawGrid(); }; const exportGridAsJson = (mapName) => { const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(grid)); const downloadAnchorNode = document.createElement('a'); downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute("download", `${mapName}.json`); document.body.appendChild(downloadAnchorNode); // evidently this is required for firefox downloadAnchorNode.click(); downloadAnchorNode.remove(); }; const exportGridAsPng = (imgName) => { const dataUrl = canvas.toDataURL('image/png'); const downloadAnchorNode = document.createElement('a'); downloadAnchorNode.setAttribute("href", dataUrl); downloadAnchorNode.setAttribute("download", `${imgName}.png`); document.body.appendChild(downloadAnchorNode); downloadAnchorNode.click(); downloadAnchorNode.remove(); }; window.addEventListener('keydown', handleKeyDown); canvas.addEventListener('click', handleCanvasClick); window.onload = () => { initializeGrid(); loadGrid(); drawGrid(); }; window.onunload = saveGrid;