test / index.html
mrwhy06's picture
Add 2 files
f5cf84e verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ball Sort Puzzle</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Arial', sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
overflow: hidden;
}
.game-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 800px;
padding: 20px;
}
h1 {
margin-bottom: 20px;
font-size: 2.5rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
text-align: center;
}
.tubes-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
width: 100%;
margin: 30px 0;
}
.tube {
width: 60px;
height: 200px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 5px 5px 0 0;
position: relative;
cursor: pointer;
border: 2px solid rgba(255, 255, 255, 0.3);
display: flex;
flex-direction: column-reverse;
align-items: center;
transition: transform 0.2s;
margin-bottom: 40px;
}
.tube::after {
content: '';
position: absolute;
bottom: -20px;
left: -2px;
right: -2px;
height: 20px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 0 0 5px 5px;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.tube:hover {
transform: translateY(-5px);
}
.tube.selected {
transform: scale(1.05);
box-shadow: 0 0 15px rgba(255, 255, 255, 0.7);
}
.ball {
width: 50px;
height: 50px;
border-radius: 50%;
margin-top: 5px;
transition: all 0.3s ease;
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: rgba(0, 0, 0, 0.5);
box-shadow: inset -5px -5px 10px rgba(0, 0, 0, 0.2);
}
.ball::after {
content: '';
position: absolute;
top: 10px;
left: 10px;
width: 15px;
height: 15px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.4);
}
.controls {
display: flex;
gap: 20px;
margin-top: 20px;
}
button {
padding: 10px 20px;
font-size: 1rem;
background-color: rgba(255, 255, 255, 0.2);
color: white;
border: 2px solid white;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: bold;
}
button:hover {
background-color: rgba(255, 255, 255, 0.4);
transform: translateY(-2px);
}
.level-info {
margin-top: 20px;
font-size: 1.2rem;
text-align: center;
}
.message {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 30px 50px;
border-radius: 10px;
font-size: 2rem;
display: none;
z-index: 100;
text-align: center;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}
.message button {
margin-top: 20px;
display: block;
width: 100%;
}
.move-counter {
font-size: 1.2rem;
margin-bottom: 20px;
background-color: rgba(0, 0, 0, 0.3);
padding: 10px 20px;
border-radius: 5px;
}
@media (max-width: 600px) {
.tube {
width: 50px;
height: 180px;
}
.ball {
width: 45px;
height: 45px;
}
h1 {
font-size: 1.8rem;
}
}
.floating-balls {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
}
.floating-ball {
position: absolute;
border-radius: 50%;
opacity: 0.3;
animation: float 15s infinite linear;
}
@keyframes float {
0% {
transform: translate(0, 0) rotate(0deg);
}
25% {
transform: translate(100px, 50px) rotate(90deg);
}
50% {
transform: translate(200px, -50px) rotate(180deg);
}
75% {
transform: translate(100px, 50px) rotate(270deg);
}
100% {
transform: translate(0, 0) rotate(360deg);
}
}
</style>
</head>
<body>
<div class="floating-balls" id="floatingBalls"></div>
<div class="game-container">
<h1>Ball Sort Puzzle</h1>
<div class="move-counter">Moves: <span id="moves">0</span></div>
<div class="tubes-container" id="tubes"></div>
<div class="level-info">Level: <span id="level">1</span></div>
<div class="controls">
<button id="btnNewGame">New Game</button>
<button id="btnUndo">Undo</button>
</div>
</div>
<div class="message" id="winMessage">
Level Complete!
<button id="btnNextLevel">Next Level</button>
</div>
<script>
// Game state
const gameState = {
tubes: [],
selectedTube: null,
moves: 0,
level: 1,
moveHistory: []
};
// Colors
const colors = [
'#FF5252', '#FF4081', '#E040FB', '#7C4DFF',
'#536DFE', '#448AFF', '#40C4FF', '#18FFFF',
'#64FFDA', '#69F0AE', '#B2FF59', '#EEFF41',
'#FFFF00', '#FFD740', '#FFAB40', '#FF6E40'
];
// DOM elements
const tubesContainer = document.getElementById('tubes');
const movesDisplay = document.getElementById('moves');
const levelDisplay = document.getElementById('level');
const btnNewGame = document.getElementById('btnNewGame');
const btnUndo = document.getElementById('btnUndo');
const winMessage = document.getElementById('winMessage');
const btnNextLevel = document.getElementById('btnNextLevel');
const floatingBalls = document.getElementById('floatingBalls');
// Initialize floating background balls
function createFloatingBalls() {
floatingBalls.innerHTML = '';
for (let i = 0; i < 20; i++) {
const ball = document.createElement('div');
ball.className = 'floating-ball';
const size = Math.random() * 100 + 50;
ball.style.width = `${size}px`;
ball.style.height = `${size}px`;
ball.style.background = colors[Math.floor(Math.random() * colors.length)];
ball.style.left = `${Math.random() * 100}%`;
ball.style.top = `${Math.random() * 100}%`;
ball.style.animationDuration = `${Math.random() * 20 + 10}s`;
floatingBalls.appendChild(ball);
}
}
// Initialize game
function initGame() {
gameState.tubes = [];
gameState.selectedTube = null;
gameState.moves = 0;
gameState.moveHistory = [];
movesDisplay.textContent = gameState.moves;
levelDisplay.textContent = gameState.level;
tubesContainer.innerHTML = '';
const tubeCount = Math.min(3 + gameState.level, 8);
const colorCount = Math.min(tubeCount - 2, colors.length);
// Create color set
const selectedColors = colors.slice(0, colorCount);
const allBalls = [];
// Create 4 balls for each color
selectedColors.forEach(color => {
for (let i = 0; i < 4; i++) {
allBalls.push(color);
}
});
// Shuffle balls
shuffleArray(allBalls);
// Distribute balls to tubes (first tubeCount - 2 tubes)
const ballsPerTube = allBalls.length / (tubeCount - 2);
for (let i = 0; i < tubeCount - 2; i++) {
const tubeBalls = [];
for (let j = 0; j < ballsPerTube; j++) {
tubeBalls.push(allBalls[i * ballsPerTube + j]);
}
gameState.tubes.push(tubeBalls);
}
// Add empty tubes (2)
for (let i = 0; i < 2; i++) {
gameState.tubes.push([]);
}
renderTubes();
}
// Helper function to shuffle an array
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
// Render tubes and balls
function renderTubes() {
tubesContainer.innerHTML = '';
gameState.tubes.forEach((tubeBalls, index) => {
const tubeElement = document.createElement('div');
tubeElement.className = 'tube';
if (gameState.selectedTube === index) {
tubeElement.classList.add('selected');
}
tubeElement.addEventListener('click', () => handleTubeClick(index));
tubeBalls.forEach((ballColor, ballIndex) => {
const ballElement = document.createElement('div');
ballElement.className = 'ball';
ballElement.style.backgroundColor = ballColor;
tubeElement.appendChild(ballElement);
});
tubesContainer.appendChild(tubeElement);
});
}
// Handle tube click
function handleTubeClick(tubeIndex) {
if (gameState.selectedTube === null) {
// Select a tube if it has balls
if (gameState.tubes[tubeIndex].length > 0) {
gameState.selectedTube = tubeIndex;
renderTubes();
}
} else {
// Try to move ball from selected tube to clicked tube
if (gameState.selectedTube === tubeIndex) {
// Deselect if same tube clicked
gameState.selectedTube = null;
renderTubes();
} else {
moveBall(gameState.selectedTube, tubeIndex);
}
}
}
// Move ball from one tube to another
function moveBall(fromTubeIndex, toTubeIndex) {
const fromTube = gameState.tubes[fromTubeIndex];
const toTube = gameState.tubes[toTubeIndex];
if (fromTube.length === 0) {
gameState.selectedTube = null;
renderTubes();
return;
}
// Check if move is valid
const topBallColor = fromTube[fromTube.length - 1];
// Can move if:
// 1. Destination tube is empty, OR
// 2. Destination tube's top ball is same color and has space
if (toTube.length === 0 ||
(toTube[toTube.length - 1] === topBallColor && toTube.length < 4)) {
// Store move in history
const prevState = JSON.parse(JSON.stringify(gameState.tubes));
gameState.moveHistory.push(prevState);
// Move the ball(s) of the same color from top
let ballsToMove = 0;
for (let i = fromTube.length - 1; i >= 0; i--) {
if (fromTube[i] === topBallColor && (toTube.length + ballsToMove) < 4) {
ballsToMove++;
} else {
break;
}
}
// Move the balls
const movedBalls = fromTube.splice(fromTube.length - ballsToMove, ballsToMove);
toTube.push(...movedBalls);
gameState.moves++;
movesDisplay.textContent = gameState.moves;
// Check for win condition
if (checkWin()) {
showWinMessage();
}
gameState.selectedTube = null;
renderTubes();
} else {
gameState.selectedTube = null;
renderTubes();
}
}
// Check if all tubes are complete (all balls of same color or empty)
function checkWin() {
return gameState.tubes.every(tube => {
if (tube.length === 0) return true;
if (tube.length < 4) return false;
const firstColor = tube[0];
return tube.every(ball => ball === firstColor);
});
}
// Show win message
function showWinMessage() {
winMessage.style.display = 'block';
}
// Undo last move
function undoMove() {
if (gameState.moveHistory.length > 0) {
gameState.tubes = gameState.moveHistory.pop();
gameState.moves++;
movesDisplay.textContent = gameState.moves;
gameState.selectedTube = null;
renderTubes();
}
}
// Next level
function nextLevel() {
gameState.level++;
winMessage.style.display = 'none';
initGame();
}
// Event listeners
btnNewGame.addEventListener('click', initGame);
btnUndo.addEventListener('click', undoMove);
btnNextLevel.addEventListener('click', nextLevel);
// Initialize
createFloatingBalls();
initGame();
// Add keyboard controls
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
gameState.selectedTube = null;
renderTubes();
} else if (e.key === 'z' && (e.ctrlKey || e.metaKey)) {
undoMove();
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
</html>