/** * Game config */ const number_levels = 2; const cell_width = 20; const cell_height = 20; /** * Cells content */ let i = 0; const EMPTY = i++; const SNAKE = i++; const WALL = i++; const FOOD = i++; delete i; /** * Get & return an element by its ID * * @param {String} id The ID of the element to get * @return {HTMLElement} The target element */ function byId(id) { return document.getElementById(id); } /** * Draw a line in a canvas rendering context * * @param {CanvasRenderingContext2D} ctx Target context * @param {Number} x1 Starting x * @param {Number} y1 Starting y * @param {Number} x2 Ending x * @param {Number} y2 Ending y */ function drawLine(ctx, x1, y1, x2, y2) { ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2,y2); ctx.stroke(); } // Get elements const startScreen = byId("startScreen") const gameScreen = byId("gameScreen") const levelChoicesTarget = byId("levelChoice") const startGameBtn = byId("startGameBtn") const stopGameBtn = byId("stopGameBtn") const canvasTarget = byId("canvasTarget") /** * Show main screen */ function showMainScreen() { startScreen.style.display = "unset"; gameScreen.style.display = "none"; } /** * Start a new game * * The scope of the function is the main * game scope */ async function startGame(gameID) { startScreen.style.display = "none"; gameScreen.style.display = "unset"; // Fetch level information let level; try { level = await (await fetch("levels/"+gameID+".json")).json(); } catch(e) { console.error(e); alert("Could not load game level!"); return; } // Create & apply the canvas const canvas = document.createElement("canvas"); canvas.width = cell_width * level.dimensions[1]; canvas.height = cell_height * level.dimensions[0]; canvasTarget.appendChild(canvas); const ctx = canvas.getContext('2d'); // Initialize the map & snake arrays let map = []; let snake = []; /// First with empty cells... for(let i = 0; i < level.dimensions[0]; i++) { let cell = []; for(let j = 0; j < level.dimensions[1]; j++) { cell.push(EMPTY); } map.push(cell) } /// ... then with cells content level.walls.forEach((w) => { map[w[0]-1][w[1]-1] = WALL }) level.food.forEach((f) => { map[f[0]-1][f[1]-1] = FOOD }) level.snake.forEach((s) => { map[s[0]-1][s[1]-1] = SNAKE snake.push([s[0]-1, s[1]-1]); }) /** * Step function * * I placed this function here to inherit * the map, snake, canvas & ctx variables... */ let interval = setInterval(() => step(), level.delay); function step() { console.log("render") if(!canvas.isConnected) clearInterval(interval) // Redraw screen ctx.clearRect(0, 0, canvas.width, canvas.height) // First, draw the grid for(let i = 0; i <= level.dimensions[1]; i++) { drawLine(ctx, i*cell_width, 0, i*cell_width, canvas.height) } for(let i = 0; i <= level.dimensions[0]; i++) { drawLine(ctx, 0, i*cell_height, canvas.width, i*cell_height, canvas.height) } // Now draw the map for(let y = 1; y <= map.length; y++) { for(let x = 1; x <= map[y-1].length; x++) { // Adapt rendering to the element to display switch(map[y-1][x-1]) { case WALL: ctx.fillStyle = "darkRed"; ctx.fillRect(x*cell_width, y*cell_height, cell_width, cell_height) break; case FOOD: ctx.fillStyle = "darkGreen"; ctx.fillRect(x*cell_width, y*cell_height, cell_width, cell_height) break; case SNAKE: ctx.fillStyle = "orange" ctx.fillRect(x*cell_width, y*cell_height, cell_width, cell_height) // Check if it is the head of the snake // If it is the case, we draw an eye to the snake const headPos = snake[snake.length-1]; if(headPos[0] == y-1 && headPos[1] == x-1) { ctx.fillStyle = "darkRed"; ctx.beginPath(); ctx.arc( (x+0.5)*cell_width, // x (y+0.5)*cell_height, // y 3, // width 0, // startAngle 2*Math.PI) // End angle ctx.fill(); console.log(x, y) } break; } } } } } /** * Change the currently active window */ function changeWindow() { // Make sure there are not canvas left in the background (to make sure // no interval is running for an old game) canvasTarget.innerHTML = "" // Try to get game ID const gameID = Number(window.location.hash.substr(1)); if(gameID > 0) startGame(gameID); else showMainScreen(); } // Initialize page /// Listen to events window.addEventListener("hashchange", (e) => changeWindow()) // Make game levels form lives for (let index = 1; index < number_levels + 1; index++) { levelChoicesTarget.innerHTML += "