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;