-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
235 lines (221 loc) · 7.57 KB
/
Copy pathscript.js
File metadata and controls
235 lines (221 loc) · 7.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/*
* Pixel World Map Game
*
* This script implements a simple pixel art game on top of an interactive
* world map. Players can select a colour from a palette and click on the
* map to fill grid cells. Each cell corresponds to a geographic region
* approximated by a fixed-size latitude/longitude rectangle. The state of
* the board is saved to the browser's localStorage so that your artwork
* persists between sessions on the same device.
*/
(() => {
// Display any unhandled errors on the page so we can debug issues when
// running in environments without access to the browser console. This
// handler appends a red banner with the error message near the top of
// the page. Remove or disable this in production.
// Optional: attach an error handler that displays errors on the page. If
// you prefer silent failures or have access to developer tools, you can
// comment out or remove this entire block.
/*
window.onerror = function (msg, url, line, col, error) {
const banner = document.createElement('div');
banner.style.position = 'absolute';
banner.style.top = '70px';
banner.style.left = '0';
banner.style.right = '0';
banner.style.backgroundColor = 'rgba(220, 0, 0, 0.85)';
banner.style.color = '#fff';
banner.style.padding = '10px';
banner.style.zIndex = '10000';
banner.style.fontSize = '14px';
banner.textContent = `${msg} at ${line}:${col}`;
document.body.appendChild(banner);
return false;
};
*/
// Define the grid dimensions. We divide the world into a grid of
// 180 rows (approx. 1 degree latitude resolution) and 360 columns
// (1 degree longitude resolution). You can adjust these values for
// more or less granularity, but larger grids may reduce performance.
const GRID_ROWS = 180;
const GRID_COLS = 360;
// Array of colours for the palette. These values were inspired by
// popular pixel art palettes and include a variety of hues and shades.
const COLOURS = [
'#FF4500', // red
'#FFA800', // orange
'#FFD635', // yellow
'#00A368', // green
'#7EED56', // light green
'#2450A4', // dark blue
'#3690EA', // blue
'#51E9F4', // light blue
'#811E9F', // purple
'#BE0039', // dark red
'#FF3881', // pink
'#6D001A', // maroon
'#000000', // black
'#898D90', // grey
'#D4D7D9', // light grey
'#FFFFFF' // white
];
// 2D array holding the colour for each grid cell. Null means empty.
let grid = [];
// Selected colour the user will paint with. Defaults to the first colour.
let selectedColour = COLOURS[0];
// References to the world map image, canvas and its context.
let worldImg, canvas, ctx;
// Current displayed dimensions of the map image
let mapWidth = 0;
let mapHeight = 0;
/**
* Initialize the grid array, loading saved data from localStorage if
* present. Otherwise, allocate a new empty grid.
*/
function initGrid() {
let saved;
try {
saved = localStorage.getItem('pixelGrid');
} catch (err) {
saved = null;
}
if (saved) {
try {
const data = JSON.parse(saved);
if (Array.isArray(data) && data.length === GRID_ROWS) {
grid = data;
return;
}
} catch (e) {
console.warn('Failed to load saved grid:', e);
}
}
// Initialize an empty grid if no saved data.
grid = new Array(GRID_ROWS);
for (let r = 0; r < GRID_ROWS; r++) {
grid[r] = new Array(GRID_COLS).fill(null);
}
}
/**
* Persist the current grid state to localStorage. This may fail on
* certain schemes (e.g. file://) so we wrap in a try/catch.
*/
function saveGrid() {
try {
localStorage.setItem('pixelGrid', JSON.stringify(grid));
} catch (e) {
// Ignore storage errors silently
}
}
/**
* Build the colour palette UI. Each swatch is clickable and selecting
* it updates the currently chosen colour. The selected swatch is
* visually highlighted via a CSS class.
*/
function initPalette() {
const paletteDiv = document.getElementById('palette');
COLOURS.forEach((colour, index) => {
const swatch = document.createElement('div');
swatch.className = 'color-swatch';
swatch.style.backgroundColor = colour;
swatch.addEventListener('click', () => {
selectedColour = colour;
document.querySelectorAll('.color-swatch').forEach(el => el.classList.remove('selected'));
swatch.classList.add('selected');
});
paletteDiv.appendChild(swatch);
// Select the first colour by default
if (index === 0) {
swatch.classList.add('selected');
}
});
}
/**
* Update the stored map dimensions based on the current size of the
* world map image in the DOM. Called on image load and window resize.
*/
function updateMapDimensions() {
if (!worldImg) return;
const rect = worldImg.getBoundingClientRect();
mapWidth = rect.width;
mapHeight = rect.height;
// Resize the canvas to match
canvas.width = mapWidth;
canvas.height = mapHeight;
}
/**
* Handle clicks on the map container. Convert the click coordinate
* relative to the world image into a grid cell, update its colour
* and redraw.
*/
function onMapClick(e) {
const container = document.getElementById('map-container');
const rect = container.getBoundingClientRect();
// Include scroll offsets, since the container may be scrolled
const x = e.clientX - rect.left + container.scrollLeft;
const y = e.clientY - rect.top + container.scrollTop;
if (x < 0 || y < 0 || x > mapWidth || y > mapHeight) return;
const cellWidth = mapWidth / GRID_COLS;
const cellHeight = mapHeight / GRID_ROWS;
const col = Math.floor(x / cellWidth);
const row = Math.floor(y / cellHeight);
if (row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS) {
grid[row][col] = selectedColour;
saveGrid();
draw();
}
}
/**
* Draw the current state of the grid onto the canvas. Only cells that
* have been coloured are rendered. The size and position of each
* rectangle is computed based on the pixel dimensions of the world
* map image. The canvas must be resized before drawing.
*/
function draw() {
if (!ctx) return;
// Clear the canvas
ctx.clearRect(0, 0, mapWidth, mapHeight);
const cellWidth = mapWidth / GRID_COLS;
const cellHeight = mapHeight / GRID_ROWS;
for (let r = 0; r < GRID_ROWS; r++) {
for (let c = 0; c < GRID_COLS; c++) {
const colour = grid[r][c];
if (!colour) continue;
const x = c * cellWidth;
const y = r * cellHeight;
ctx.fillStyle = colour;
ctx.fillRect(x, y, cellWidth, cellHeight);
}
}
}
/**
* Initialise the world map, palette and event listeners once the DOM is ready.
*/
function init() {
initGrid();
initPalette();
worldImg = document.getElementById('worldMap');
canvas = document.getElementById('pixelCanvas');
ctx = canvas.getContext('2d');
// When the image loads, update dimensions and draw
if (worldImg.complete) {
updateMapDimensions();
draw();
} else {
worldImg.addEventListener('load', () => {
updateMapDimensions();
draw();
});
}
// Update dimensions on resize
window.addEventListener('resize', () => {
updateMapDimensions();
draw();
});
// Click handler
const container = document.getElementById('map-container');
container.addEventListener('click', onMapClick);
}
// Kick off initialisation when the DOM content has loaded
window.addEventListener('DOMContentLoaded', init);
})();