Spaces:
Running
Running
hello, i want a towestacking browser game where you habe one start block in the center and you need to drop moving blocks to build a tower as high as possible
Browse files- README.md +7 -4
- components/controls.js +57 -0
- components/game-board.js +21 -0
- components/navbar.js +50 -0
- components/tower-game.js +117 -0
- index.html +34 -19
- script.js +200 -0
- style.css +39 -19
README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
colorFrom: indigo
|
| 5 |
colorTo: pink
|
|
|
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: TowerStack Frenzy 🏗️
|
| 3 |
+
colorFrom: yellow
|
|
|
|
| 4 |
colorTo: pink
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
components/controls.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomControls extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
.controls-container {
|
| 7 |
+
@apply bg-gray-800 p-4 rounded-lg shadow-xl;
|
| 8 |
+
}
|
| 9 |
+
.control-group {
|
| 10 |
+
@apply mb-4;
|
| 11 |
+
}
|
| 12 |
+
.control-label {
|
| 13 |
+
@apply block text-sm font-medium mb-1;
|
| 14 |
+
}
|
| 15 |
+
.control-select {
|
| 16 |
+
@apply mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-600 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md bg-gray-700 text-white;
|
| 17 |
+
}
|
| 18 |
+
.btn {
|
| 19 |
+
@apply w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
| 20 |
+
}
|
| 21 |
+
.btn-secondary {
|
| 22 |
+
@apply mt-2 w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-600 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500;
|
| 23 |
+
}
|
| 24 |
+
</style>
|
| 25 |
+
<div class="controls-container">
|
| 26 |
+
<div class="control-group">
|
| 27 |
+
<label class="control-label">Mode:</label>
|
| 28 |
+
<select id="mode-select" class="control-select">
|
| 29 |
+
<option value="draw">Draw Mode</option>
|
| 30 |
+
<option value="start">Set Start</option>
|
| 31 |
+
<option value="end">Set End</option>
|
| 32 |
+
<option value="wall">Place Walls</option>
|
| 33 |
+
</select>
|
| 34 |
+
</div>
|
| 35 |
+
|
| 36 |
+
<button id="solve-btn" class="btn">
|
| 37 |
+
<i data-feather="play" class="mr-2"></i> Solve Puzzle
|
| 38 |
+
</button>
|
| 39 |
+
|
| 40 |
+
<button id="reset-btn" class="btn-secondary">
|
| 41 |
+
<i data-feather="refresh-cw" class="mr-2"></i> Reset Board
|
| 42 |
+
</button>
|
| 43 |
+
|
| 44 |
+
<div class="control-group mt-6">
|
| 45 |
+
<h3 class="font-bold mb-2">Instructions:</h3>
|
| 46 |
+
<p class="text-sm text-gray-300">
|
| 47 |
+
1. Set start and end points<br>
|
| 48 |
+
2. Add walls to create obstacles<br>
|
| 49 |
+
3. Click "Solve Puzzle" to find path
|
| 50 |
+
</p>
|
| 51 |
+
</div>
|
| 52 |
+
</div>
|
| 53 |
+
`;
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
customElements.define('custom-controls', CustomControls);
|
components/game-board.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomGameBoard extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
.game-container {
|
| 7 |
+
@apply bg-gray-800 p-4 rounded-lg shadow-xl;
|
| 8 |
+
}
|
| 9 |
+
.game-title {
|
| 10 |
+
@apply text-xl font-bold mb-4 text-center;
|
| 11 |
+
}
|
| 12 |
+
</style>
|
| 13 |
+
<div class="game-container">
|
| 14 |
+
<h2 class="game-title">Logic Puzzle Board</h2>
|
| 15 |
+
<div class="game-board"></div>
|
| 16 |
+
</div>
|
| 17 |
+
`;
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
customElements.define('custom-game-board', CustomGameBoard);
|
components/navbar.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomNavbar extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
nav {
|
| 7 |
+
@apply bg-gray-800 text-white shadow-lg;
|
| 8 |
+
}
|
| 9 |
+
.container {
|
| 10 |
+
@apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
|
| 11 |
+
}
|
| 12 |
+
.nav-content {
|
| 13 |
+
@apply flex items-center justify-between h-16;
|
| 14 |
+
}
|
| 15 |
+
.logo {
|
| 16 |
+
@apply flex-shrink-0 flex items-center text-xl font-bold;
|
| 17 |
+
}
|
| 18 |
+
.nav-links {
|
| 19 |
+
@apply hidden md:flex space-x-8;
|
| 20 |
+
}
|
| 21 |
+
.nav-link {
|
| 22 |
+
@apply px-3 py-2 rounded-md text-sm font-medium hover:bg-gray-700;
|
| 23 |
+
}
|
| 24 |
+
.mobile-menu-btn {
|
| 25 |
+
@apply md:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700;
|
| 26 |
+
}
|
| 27 |
+
</style>
|
| 28 |
+
<nav>
|
| 29 |
+
<div class="container">
|
| 30 |
+
<div class="nav-content">
|
| 31 |
+
<div class="logo">
|
| 32 |
+
<i data-feather="cpu"></i>
|
| 33 |
+
<span class="ml-2">MindBender Logic Lab</span>
|
| 34 |
+
</div>
|
| 35 |
+
<div class="nav-links">
|
| 36 |
+
<a href="#" class="nav-link active">Home</a>
|
| 37 |
+
<a href="#" class="nav-link">About</a>
|
| 38 |
+
<a href="#" class="nav-link">Tutorial</a>
|
| 39 |
+
</div>
|
| 40 |
+
<button class="mobile-menu-btn">
|
| 41 |
+
<i data-feather="menu"></i>
|
| 42 |
+
</button>
|
| 43 |
+
</div>
|
| 44 |
+
</div>
|
| 45 |
+
</nav>
|
| 46 |
+
`;
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
customElements.define('custom-navbar', CustomNavbar);
|
components/tower-game.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class TowerGame extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
.game-container {
|
| 7 |
+
width: 100%;
|
| 8 |
+
height: 500px;
|
| 9 |
+
background: #f0f0f0;
|
| 10 |
+
position: relative;
|
| 11 |
+
overflow: hidden;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
border: 2px solid #333;
|
| 14 |
+
}
|
| 15 |
+
.block {
|
| 16 |
+
position: absolute;
|
| 17 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 18 |
+
border-radius: 4px;
|
| 19 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
| 20 |
+
transition: transform 0.2s;
|
| 21 |
+
}
|
| 22 |
+
.active-block {
|
| 23 |
+
background: linear-gradient(135deg, #f56565, #ed8936);
|
| 24 |
+
}
|
| 25 |
+
.score-display {
|
| 26 |
+
position: absolute;
|
| 27 |
+
top: 10px;
|
| 28 |
+
right: 10px;
|
| 29 |
+
font-size: 1.5rem;
|
| 30 |
+
font-weight: bold;
|
| 31 |
+
color: #333;
|
| 32 |
+
z-index: 10;
|
| 33 |
+
}
|
| 34 |
+
</style>
|
| 35 |
+
<div class="game-container">
|
| 36 |
+
<div class="score-display">Height: 0</div>
|
| 37 |
+
</div>
|
| 38 |
+
`;
|
| 39 |
+
this.initGame();
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
initGame() {
|
| 43 |
+
this.gameWidth = 300;
|
| 44 |
+
this.gameHeight = 500;
|
| 45 |
+
this.blockWidth = 60;
|
| 46 |
+
this.blockHeight = 20;
|
| 47 |
+
this.speed = 2;
|
| 48 |
+
this.direction = 1;
|
| 49 |
+
this.tower = [];
|
| 50 |
+
this.score = 0;
|
| 51 |
+
this.gameOver = false;
|
| 52 |
+
|
| 53 |
+
// Create base block
|
| 54 |
+
this.createBlock(this.gameWidth/2 - this.blockWidth/2, this.gameHeight - this.blockHeight, true);
|
| 55 |
+
|
| 56 |
+
// Start game loop
|
| 57 |
+
this.gameLoop();
|
| 58 |
+
|
| 59 |
+
// Set up controls
|
| 60 |
+
this.shadowRoot.querySelector('.game-container').addEventListener('click', () => this.placeBlock());
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
createBlock(x, y, isBase = false) {
|
| 64 |
+
const block = document.createElement('div');
|
| 65 |
+
block.className = isBase ? 'block' : 'block active-block';
|
| 66 |
+
block.style.width = `${this.blockWidth}px`;
|
| 67 |
+
block.style.height = `${this.blockHeight}px`;
|
| 68 |
+
block.style.left = `${x}px`;
|
| 69 |
+
block.style.top = `${y}px`;
|
| 70 |
+
this.shadowRoot.querySelector('.game-container').appendChild(block);
|
| 71 |
+
|
| 72 |
+
if (!isBase) {
|
| 73 |
+
this.activeBlock = { element: block, x, y, movingRight: true };
|
| 74 |
+
} else {
|
| 75 |
+
this.tower.push({ element: block, x, y });
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
gameLoop() {
|
| 80 |
+
if (this.gameOver) return;
|
| 81 |
+
|
| 82 |
+
if (this.activeBlock) {
|
| 83 |
+
// Move active block
|
| 84 |
+
this.activeBlock.x += this.direction * this.speed;
|
| 85 |
+
this.activeBlock.element.style.left = `${this.activeBlock.x}px`;
|
| 86 |
+
|
| 87 |
+
// Change direction at edges
|
| 88 |
+
if (this.activeBlock.x <= 0) this.direction = 1;
|
| 89 |
+
if (this.activeBlock.x + this.blockWidth >= this.gameWidth) this.direction = -1;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
requestAnimationFrame(() => this.gameLoop());
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
placeBlock() {
|
| 96 |
+
if (!this.activeBlock || this.gameOver) return;
|
| 97 |
+
|
| 98 |
+
// Add active block to tower
|
| 99 |
+
this.activeBlock.element.classList.remove('active-block');
|
| 100 |
+
this.tower.push(this.activeBlock);
|
| 101 |
+
this.score++;
|
| 102 |
+
this.updateScore();
|
| 103 |
+
|
| 104 |
+
// Create new active block
|
| 105 |
+
const newY = this.tower[this.tower.length-1].y - this.blockHeight;
|
| 106 |
+
this.createBlock(0, newY);
|
| 107 |
+
|
| 108 |
+
// Increase speed slightly
|
| 109 |
+
this.speed = Math.min(5, this.speed + 0.1);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
updateScore() {
|
| 113 |
+
this.shadowRoot.querySelector('.score-display').textContent = `Height: ${this.score}`;
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
customElements.define('tower-game', TowerGame);
|
index.html
CHANGED
|
@@ -1,19 +1,34 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>MindBender Logic Lab</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
+
<script src="components/navbar.js"></script>
|
| 12 |
+
<script src="components/tower-game.js"></script>
|
| 13 |
+
</head>
|
| 14 |
+
<body class="bg-gray-900 text-gray-100 min-h-screen">
|
| 15 |
+
<custom-navbar></custom-navbar>
|
| 16 |
+
|
| 17 |
+
<main class="container mx-auto px-4 py-8">
|
| 18 |
+
<div class="flex flex-col lg:flex-row gap-8">
|
| 19 |
+
<div class="lg:w-3/4">
|
| 20 |
+
<tower-game></tower-game>
|
| 21 |
+
</div>
|
| 22 |
+
<div class="lg:w-1/4">
|
| 23 |
+
<div class="mt-4 text-center">
|
| 24 |
+
<p class="text-gray-300">Click to drop blocks and build your tower!</p>
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
</div>
|
| 28 |
+
</main>
|
| 29 |
+
|
| 30 |
+
<script src="script.js"></script>
|
| 31 |
+
<script>feather.replace();</script>
|
| 32 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 33 |
+
</body>
|
| 34 |
+
</html>
|
script.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 2 |
+
// Game state
|
| 3 |
+
const gameState = {
|
| 4 |
+
boardSize: 10,
|
| 5 |
+
cells: [],
|
| 6 |
+
startPos: null,
|
| 7 |
+
endPos: null,
|
| 8 |
+
walls: [],
|
| 9 |
+
mode: 'draw', // 'draw', 'start', 'end', 'wall'
|
| 10 |
+
isSolving: false,
|
| 11 |
+
solutionPath: []
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
// Initialize the game
|
| 15 |
+
function initGame() {
|
| 16 |
+
createBoard();
|
| 17 |
+
setupEventListeners();
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
// Create the game board
|
| 21 |
+
function createBoard() {
|
| 22 |
+
const gameBoard = document.querySelector('custom-game-board');
|
| 23 |
+
gameBoard.shadowRoot.innerHTML = `
|
| 24 |
+
<style>
|
| 25 |
+
.game-board {
|
| 26 |
+
display: grid;
|
| 27 |
+
gap: 0;
|
| 28 |
+
grid-template-columns: repeat(${gameState.boardSize}, 1fr);
|
| 29 |
+
}
|
| 30 |
+
</style>
|
| 31 |
+
<div class="game-board bg-gray-800 p-2 rounded-lg shadow-xl"></div>
|
| 32 |
+
`;
|
| 33 |
+
|
| 34 |
+
const boardElement = gameBoard.shadowRoot.querySelector('.game-board');
|
| 35 |
+
boardElement.innerHTML = '';
|
| 36 |
+
|
| 37 |
+
gameState.cells = [];
|
| 38 |
+
for (let y = 0; y < gameState.boardSize; y++) {
|
| 39 |
+
for (let x = 0; x < gameState.boardSize; x++) {
|
| 40 |
+
const cell = document.createElement('div');
|
| 41 |
+
cell.className = 'cell';
|
| 42 |
+
cell.dataset.x = x;
|
| 43 |
+
cell.dataset.y = y;
|
| 44 |
+
boardElement.appendChild(cell);
|
| 45 |
+
gameState.cells.push({ x, y, element: cell });
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// Setup event listeners
|
| 51 |
+
function setupEventListeners() {
|
| 52 |
+
const gameBoard = document.querySelector('custom-game-board');
|
| 53 |
+
gameBoard.shadowRoot.querySelector('.game-board').addEventListener('click', handleCellClick);
|
| 54 |
+
|
| 55 |
+
const controls = document.querySelector('custom-controls');
|
| 56 |
+
controls.shadowRoot.querySelector('#solve-btn').addEventListener('click', solvePuzzle);
|
| 57 |
+
controls.shadowRoot.querySelector('#reset-btn').addEventListener('click', resetBoard);
|
| 58 |
+
controls.shadowRoot.querySelector('#mode-select').addEventListener('change', (e) => {
|
| 59 |
+
gameState.mode = e.target.value;
|
| 60 |
+
});
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// Handle cell clicks
|
| 64 |
+
function handleCellClick(e) {
|
| 65 |
+
if (gameState.isSolving) return;
|
| 66 |
+
|
| 67 |
+
const cell = e.target.closest('.cell');
|
| 68 |
+
if (!cell) return;
|
| 69 |
+
|
| 70 |
+
const x = parseInt(cell.dataset.x);
|
| 71 |
+
const y = parseInt(cell.dataset.y);
|
| 72 |
+
|
| 73 |
+
switch (gameState.mode) {
|
| 74 |
+
case 'start':
|
| 75 |
+
setStartPosition(x, y);
|
| 76 |
+
break;
|
| 77 |
+
case 'end':
|
| 78 |
+
setEndPosition(x, y);
|
| 79 |
+
break;
|
| 80 |
+
case 'wall':
|
| 81 |
+
toggleWall(x, y);
|
| 82 |
+
break;
|
| 83 |
+
default:
|
| 84 |
+
// Do nothing
|
| 85 |
+
break;
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
// Set start position
|
| 90 |
+
function setStartPosition(x, y) {
|
| 91 |
+
// Clear previous start
|
| 92 |
+
if (gameState.startPos) {
|
| 93 |
+
const prevCell = document.querySelector(`.cell[data-x="${gameState.startPos.x}"][data-y="${gameState.startPos.y}"]`);
|
| 94 |
+
prevCell?.classList.remove('start');
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// Set new start
|
| 98 |
+
const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`);
|
| 99 |
+
cell.classList.add('start');
|
| 100 |
+
gameState.startPos = { x, y };
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
// Set end position
|
| 104 |
+
function setEndPosition(x, y) {
|
| 105 |
+
// Clear previous end
|
| 106 |
+
if (gameState.endPos) {
|
| 107 |
+
const prevCell = document.querySelector(`.cell[data-x="${gameState.endPos.x}"][data-y="${gameState.endPos.y}"]`);
|
| 108 |
+
prevCell?.classList.remove('end');
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// Set new end
|
| 112 |
+
const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`);
|
| 113 |
+
cell.classList.add('end');
|
| 114 |
+
gameState.endPos = { x, y };
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
// Toggle wall
|
| 118 |
+
function toggleWall(x, y) {
|
| 119 |
+
const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`);
|
| 120 |
+
const isWall = cell.classList.contains('wall');
|
| 121 |
+
|
| 122 |
+
if (isWall) {
|
| 123 |
+
cell.classList.remove('wall');
|
| 124 |
+
gameState.walls = gameState.walls.filter(wall => !(wall.x === x && wall.y === y));
|
| 125 |
+
} else {
|
| 126 |
+
cell.classList.add('wall');
|
| 127 |
+
gameState.walls.push({ x, y });
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// Solve the puzzle
|
| 132 |
+
function solvePuzzle() {
|
| 133 |
+
if (!gameState.startPos || !gameState.endPos) {
|
| 134 |
+
alert('Please set both start and end positions!');
|
| 135 |
+
return;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
gameState.isSolving = true;
|
| 139 |
+
gameState.solutionPath = findPath();
|
| 140 |
+
animateSolution();
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// Find path (simplified for demo)
|
| 144 |
+
function findPath() {
|
| 145 |
+
// This is a simplified pathfinding algorithm
|
| 146 |
+
// In a real implementation, you would use A* or similar
|
| 147 |
+
const path = [];
|
| 148 |
+
let current = { ...gameState.startPos };
|
| 149 |
+
|
| 150 |
+
while (current.x !== gameState.endPos.x || current.y !== gameState.endPos.y) {
|
| 151 |
+
path.push({ ...current });
|
| 152 |
+
|
| 153 |
+
// Simple movement towards target
|
| 154 |
+
if (current.x < gameState.endPos.x) current.x++;
|
| 155 |
+
else if (current.x > gameState.endPos.x) current.x--;
|
| 156 |
+
|
| 157 |
+
if (current.y < gameState.endPos.y) current.y++;
|
| 158 |
+
else if (current.y > gameState.endPos.y) current.y--;
|
| 159 |
+
|
| 160 |
+
// Prevent infinite loops
|
| 161 |
+
if (path.length > gameState.boardSize * 2) break;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
path.push({ ...gameState.endPos });
|
| 165 |
+
return path;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
// Animate the solution
|
| 169 |
+
function animateSolution() {
|
| 170 |
+
if (gameState.solutionPath.length === 0) {
|
| 171 |
+
gameState.isSolving = false;
|
| 172 |
+
return;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
const step = gameState.solutionPath.shift();
|
| 176 |
+
const cell = document.querySelector(`.cell[data-x="${step.x}"][data-y="${step.y}"]`);
|
| 177 |
+
|
| 178 |
+
if (!cell.classList.contains('start') && !cell.classList.contains('end')) {
|
| 179 |
+
cell.classList.add('path', 'trace-animation');
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
setTimeout(animateSolution, 200);
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
// Reset the board
|
| 186 |
+
function resetBoard() {
|
| 187 |
+
gameState.startPos = null;
|
| 188 |
+
gameState.endPos = null;
|
| 189 |
+
gameState.walls = [];
|
| 190 |
+
gameState.solutionPath = [];
|
| 191 |
+
gameState.isSolving = false;
|
| 192 |
+
|
| 193 |
+
document.querySelectorAll('.cell').forEach(cell => {
|
| 194 |
+
cell.className = 'cell';
|
| 195 |
+
});
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
// Initialize the game
|
| 199 |
+
initGame();
|
| 200 |
+
});
|
style.css
CHANGED
|
@@ -1,28 +1,48 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
margin-top: 0;
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
font-size: 15px;
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
}
|
| 17 |
|
| 18 |
-
.
|
| 19 |
-
|
| 20 |
-
margin: 0 auto;
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
}
|
| 25 |
|
| 26 |
-
.
|
| 27 |
-
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@tailwind base;
|
| 2 |
+
@tailwind components;
|
| 3 |
+
@tailwind utilities;
|
| 4 |
+
|
| 5 |
+
/* Custom styles for the game elements */
|
| 6 |
+
.cell {
|
| 7 |
+
@apply w-12 h-12 flex items-center justify-center border border-gray-700 cursor-pointer;
|
| 8 |
+
transition: all 0.2s ease;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
.cell:hover {
|
| 12 |
+
@apply bg-gray-800;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.cell.active {
|
| 16 |
+
@apply bg-blue-600 text-white;
|
| 17 |
}
|
| 18 |
|
| 19 |
+
.cell.wall {
|
| 20 |
+
@apply bg-gray-700;
|
|
|
|
| 21 |
}
|
| 22 |
|
| 23 |
+
.cell.start {
|
| 24 |
+
@apply bg-green-600;
|
|
|
|
|
|
|
|
|
|
| 25 |
}
|
| 26 |
|
| 27 |
+
.cell.end {
|
| 28 |
+
@apply bg-red-600;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
+
.cell.path {
|
| 32 |
+
@apply bg-yellow-500;
|
| 33 |
}
|
| 34 |
+
/* Tower game animations */
|
| 35 |
+
@keyframes blockDrop {
|
| 36 |
+
0% { transform: translateY(-20px); }
|
| 37 |
+
100% { transform: translateY(0); }
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.block {
|
| 41 |
+
animation: blockDrop 0.2s ease-out;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/* Responsive game container */
|
| 45 |
+
.game-board {
|
| 46 |
+
@apply grid gap-0;
|
| 47 |
+
grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
|
| 48 |
+
}
|