o1-preview-test-app-1 / index.html
victor's picture
victor HF staff
Update index.html
cd788df verified
<!DOCTYPE html>
<html>
<head>
<title>Isometric SVG Grid with Sound and Responsive Layout</title>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
height: 100%;
width: 100%;
background-color: #333;
}
svg {
display: block;
}
</style>
</head>
<body>
<svg id="svgGrid" width="100%" height="100%"></svg>
<script>
(function() {
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.getElementById('svgGrid');
const gridSize = 20; // 20x20 grid
const tileSize = 40; // Base size for each tile
const tiles = [];
let lastClicked = null; // Keep track of the last clicked tile
let pathTiles = []; // Tiles in the current path
// Create AudioContext for playing sound
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Function to play click sound
function playClickSound() {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(440, audioContext.currentTime); // A4 note
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.1); // Play for 0.1 seconds
}
// Initialize the grid
initGrid();
// Add event listener for window resize
window.addEventListener('resize', onWindowResize);
function initGrid() {
// Remove any existing tiles from the SVG
while (svg.firstChild) {
svg.removeChild(svg.firstChild);
}
// Initialize tiles array
for (let y = 0; y < gridSize; y++) {
tiles[y] = tiles[y] || [];
for (let x = 0; x < gridSize; x++) {
const tileData = tiles[y][x] || {
x: x,
y: y,
element: null,
defaultColor: '#ccc',
isPath: false,
isClicked: false, // Indicates if the tile has been clicked
f: 0,
g: 0,
h: 0,
parent: null,
};
// Create the polygon element if it doesn't exist
if (!tileData.element) {
const polygon = document.createElementNS(svgNS, 'polygon');
polygon.setAttribute('fill', tileData.defaultColor);
polygon.setAttribute('stroke', '#999');
polygon.setAttribute('stroke-width', '1');
polygon.style.cursor = 'pointer';
// Randomly spawn obstacles (approximately 1 in 3 tiles)
if (Math.random() < 1 / 3) {
const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
tileData.defaultColor = randomColor;
tileData.isClicked = true; // Mark tile as clicked (obstacle)
polygon.setAttribute('fill', randomColor);
}
// Add event listener for interactivity
polygon.addEventListener('click', function() {
// Play sound
playClickSound();
const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
tileData.defaultColor = randomColor;
polygon.setAttribute('fill', randomColor);
// Highlight the path from the last clicked tile
if (lastClicked) {
clearPath(); // Clear previous path
resetTiles(); // Reset tile properties
// Mark the current tile as the end tile
const startTile = lastClicked;
const endTile = tileData;
// Temporarily set isClicked to false for pathfinding
const startWasClicked = startTile.isClicked;
const endWasClicked = endTile.isClicked;
startTile.isClicked = false;
endTile.isClicked = false;
const path = findPathAStar(startTile, endTile);
if (path.length > 0) {
animatePath(path);
} else {
alert('No path found!');
}
// After pathfinding, restore isClicked status
startTile.isClicked = startWasClicked;
endTile.isClicked = endWasClicked;
}
// Mark the tile as clicked after pathfinding
tileData.isClicked = true;
lastClicked = tileData; // Update last clicked tile
});
tileData.element = polygon;
svg.appendChild(polygon);
}
tiles[y][x] = tileData;
}
}
// Update positions of tiles
updateTilePositions();
}
function updateTilePositions() {
const width = window.innerWidth;
const height = window.innerHeight;
// Adjust tileSize to fit the screen
const scaleX = width / (gridSize * tileSize);
const scaleY = height / (gridSize * tileSize / 2);
const scale = Math.min(scaleX, scaleY);
const adjustedTileSize = tileSize * scale;
// Calculate grid dimensions
const gridWidth = gridSize * adjustedTileSize;
const gridHeight = gridSize * adjustedTileSize / 2;
// Calculate offsets to center the grid
const offsetX = (width - gridWidth) / 2;
const offsetY = (height - gridHeight) / 2;
// Origin point for the grid
const originX = offsetX + gridWidth / 2;
const originY = offsetY;
for (let y = 0; y < gridSize; y++) {
for (let x = 0; x < gridSize; x++) {
const tileData = tiles[y][x];
// Calculate the position of each tile
const isoX = (x - y) * (adjustedTileSize / 2);
const isoY = (x + y) * (adjustedTileSize / 4);
// Coordinates for the diamond shape
const points = [
{ x: originX + isoX, y: originY + isoY },
{ x: originX + isoX + adjustedTileSize / 2, y: originY + isoY + adjustedTileSize / 4 },
{ x: originX + isoX, y: originY + isoY + adjustedTileSize / 2 },
{ x: originX + isoX - adjustedTileSize / 2, y: originY + isoY + adjustedTileSize / 4 },
];
tileData.element.setAttribute('points', points.map(p => `${p.x},${p.y}`).join(' '));
}
}
}
function onWindowResize() {
updateTilePositions();
}
// Function to reset tile properties before pathfinding
function resetTiles() {
for (let y = 0; y < gridSize; y++) {
for (let x = 0; x < gridSize; x++) {
const tile = tiles[y][x];
tile.f = 0;
tile.g = 0;
tile.h = 0;
tile.parent = null;
}
}
}
// Function to find the shortest path using A* algorithm
function findPathAStar(start, end) {
const openList = [];
const closedList = [];
openList.push(start);
while (openList.length > 0) {
// Find the tile with the lowest f value
let lowestIndex = 0;
for (let i = 0; i < openList.length; i++) {
if (openList[i].f < openList[lowestIndex].f) {
lowestIndex = i;
}
}
const currentTile = openList[lowestIndex];
// If we've reached the end tile, reconstruct the path
if (currentTile === end) {
const path = [];
let curr = currentTile;
path.push(curr); // Include the end tile
while (curr.parent) {
curr = curr.parent;
path.push(curr);
}
path.reverse();
return path;
}
// Move current tile from open to closed list
openList.splice(lowestIndex, 1);
closedList.push(currentTile);
// Get neighbors
const neighbors = getNeighbors(currentTile);
for (let neighbor of neighbors) {
if (closedList.includes(neighbor) || (neighbor.isClicked && neighbor !== end)) {
// Ignore the neighbor which is already evaluated or clicked (except the end tile)
continue;
}
const tentative_gScore = currentTile.g + 1;
if (!openList.includes(neighbor)) {
// Discover a new node
neighbor.g = tentative_gScore;
neighbor.h = heuristic(neighbor, end);
neighbor.f = neighbor.g + neighbor.h;
neighbor.parent = currentTile;
openList.push(neighbor);
} else if (tentative_gScore < neighbor.g) {
// This is a better path
neighbor.g = tentative_gScore;
neighbor.f = neighbor.g + neighbor.h;
neighbor.parent = currentTile;
}
}
}
// No path found
return [];
}
// Heuristic function (Manhattan distance)
function heuristic(a, b) {
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
}
// Function to get neighbors of a tile
function getNeighbors(tile) {
const neighbors = [];
const dirs = [
{ x: 0, y: -1 }, // Up
{ x: 1, y: 0 }, // Right
{ x: 0, y: 1 }, // Down
{ x: -1, y: 0 }, // Left
// Uncomment below for diagonal movement
// { x: -1, y: -1 },
// { x: 1, y: -1 },
// { x: 1, y: 1 },
// { x: -1, y: 1 },
];
for (let dir of dirs) {
const nx = tile.x + dir.x;
const ny = tile.y + dir.y;
if (nx >= 0 && nx < gridSize && ny >= 0 && ny < gridSize) {
neighbors.push(tiles[ny][nx]);
}
}
return neighbors;
}
// Function to animate the path
function animatePath(path) {
pathTiles = path;
let index = 0;
function highlightNextTile() {
if (index < path.length) {
const tile = path[index];
// Skip tiles that were clicked (they keep their color)
if (!tile.isClicked || tile === path[0] || tile === path[path.length - 1]) {
tile.element.setAttribute('fill', '#888'); // Highlight color
tile.isPath = true;
}
index++;
setTimeout(highlightNextTile, 100); // Adjust the delay as needed
}
}
highlightNextTile();
}
// Function to clear the previous path
function clearPath() {
pathTiles.forEach(tile => {
// Only reset tiles that are not the clicked ones
if (!tile.isClicked || tile === pathTiles[0] || tile === pathTiles[pathTiles.length - 1]) {
tile.element.setAttribute('fill', tile.defaultColor);
tile.isPath = false;
}
});
pathTiles = [];
}
})();
</script>
</body>
</html>