const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const canvas = document.getElementById('toad'); canvas.width = viewportWidth; canvas.height = viewportHeight; const context = canvas.getContext('2d'); const cellSize = 50; let shapes = []; const drawLine = (x1, y1, x2, y2) => { context.moveTo(x1, y1); context.lineTo(x2, y2); context.stroke(); }; const drawShape = (shape, x, y) => { context.beginPath(); context.strokeStyle = '#2d2d2d'; context.fillStyle = '#2d2d2d'; const size = cellSize * 0.8; const offset = (cellSize - size) / 2; if (shape === 'square') { context.rect(x + offset, y + offset, size, size); } else if (shape === 'triangle') { context.moveTo(x + cellSize / 2, y + offset); context.lineTo(x + offset, y + size + offset); context.lineTo(x + size + offset, y + size + offset); context.closePath(); } else if (shape === 'circle') { const radius = cellSize * 0.4; const centerX = x + cellSize / 2; const centerY = y + cellSize / 2; context.arc(centerX, centerY, radius, 0, 2 * Math.PI); } else if (shape === 'octagon') { const cellCenterX = x + cellSize / 2; const cellCenterY = y + cellSize / 2; const sideLength = cellSize / (2 + Math.sqrt(2)); const angle = (2 * Math.PI) / 8; context.moveTo(cellCenterX + sideLength * Math.cos(0), cellCenterY + sideLength * Math.sin(0)); [...Array(8).keys()].slice(1).forEach(i => { const newX = cellCenterX + sideLength * Math.cos(i * angle); const newY = cellCenterY + sideLength * Math.sin(i * angle); context.lineTo(newX, newY); }); context.closePath(); } else if (shape === 'pentagon') { const cellCenterX = x + cellSize / 2; const cellCenterY = y + cellSize / 2; const sideLength = cellSize / (1 + Math.sqrt(5 / 2)); const angle = (2 * Math.PI) / 5; context.moveTo(cellCenterX + sideLength * Math.cos(0), cellCenterY + sideLength * Math.sin(0)); [...Array(5).keys()].slice(1).forEach(i => { const newX = cellCenterX + sideLength * Math.cos(i * angle); const newY = cellCenterY + sideLength * Math.sin(i * angle); context.lineTo(newX, newY); }); context.closePath(); } else if (shape === 'diamond') { context.moveTo(x + cellSize / 2, y + offset); context.lineTo(x + offset, y + cellSize / 2); context.lineTo(x + cellSize / 2, y + size + offset); context.lineTo(x + size + offset, y + cellSize / 2); context.closePath(); } context.fill(); context.stroke(); shapes.push({ type: shape, x, y, color: '#2d2d2d' }); }; const createContextMenuOption = (shape) => { const option = document.createElement('li'); option.textContent = shape; option.style.cursor = 'pointer'; option.addEventListener('click', (event) => { contextMenu.style.display = 'none'; const cellX = Math.floor(lastRightClick.x / cellSize) * cellSize; const cellY = Math.floor(lastRightClick.y / cellSize) * cellSize; drawShape(shape.toLowerCase(), cellX, cellY); }); if (shape === 'Nope') { option.style.color = 'lightcoral'; } return option; }; const contextMenu = document.createElement('ul'); contextMenu.id = 'context-menu'; contextMenu.style.display = 'none'; contextMenu.style.position = 'fixed'; contextMenu.style.listStyle = 'none'; contextMenu.style.lineHeight = '1.25'; contextMenu.style.padding = '10px'; contextMenu.style.fontSize = '18px'; contextMenu.style.backgroundColor = 'white'; contextMenu.style.border = '1px solid black'; ['Square', 'Triangle', 'Circle', 'Octagon', 'Pentagon', 'Diamond', 'Nope'].forEach(shape => { const option = createContextMenuOption(shape); option.className = 'context-menu-item'; contextMenu.appendChild(option); }); // I mean, realistically shape should be label, but I got this far...so Nope is gonna be a shape document.body.appendChild(contextMenu); let lastRightClick = { x: 0, y: 0 }; canvas.addEventListener('click', (event) => { const cellX = Math.floor(event.clientX / cellSize) * cellSize; const cellY = Math.floor(event.clientY / cellSize) * cellSize; const clickedShape = shapes.find(shape => shape.x === cellX && shape.y === cellY); if (!clickedShape) { lastRightClick = { x: event.clientX, y: event.clientY }; contextMenu.style.display = 'block'; contextMenu.style.left = `${event.clientX}px`; contextMenu.style.top = `${event.clientY}px`; } }); const removeShape = (x, y) => { shapes = shapes.filter(shape => !(shape.x === x && shape.y === y)); drawGrid(); context.fillStyle = '#f0f0f0'; context.fillRect(x, y, cellSize, cellSize); } const drawGrid = () => { context.clearRect(0, 0, canvas.width, canvas.height); context.fillStyle = '#f0f0f0'; context.fillRect(0, 0, canvas.width, canvas.height); context.strokeStyle = 'white'; for (let x = 0; x < Math.ceil(viewportWidth / cellSize); x++) { drawLine(x * cellSize, 0, x * cellSize, viewportHeight); } for (let y = 0; y < Math.ceil(viewportHeight / cellSize); y++) { drawLine(0, y * cellSize, viewportWidth, y * cellSize); } shapes.forEach(shape => { drawShape(shape.type, shape.x, shape.y); }); } let beatCounter = 0; window.addEventListener('keydown', (event) => { if (event.key === ' ' || event.key === 'Enter') { beatCounter++; console.log(`Beat: ${beatCounter}`); } }); canvas.addEventListener('dblclick', (event) => { const cellX = Math.floor(event.clientX / cellSize) * cellSize; const cellY = Math.floor(event.clientY / cellSize) * cellSize; removeShape(cellX, cellY); }); drawGrid();