<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Juego Estilo Xonix Avanzado</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
font-family: 'Inter', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
background-color: #1a202c;
color: #e2e8f0;
overflow: hidden;
}
#game-container {
background-color: #2d3748;
padding: 1rem;
border-radius: 0.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
position: relative; /* Para superposiciones */
}
canvas {
display: block;
background-color: #4a5568;
border-radius: 0.375rem;
}
#game-info {
display: flex;
justify-content: space-around;
width: 100%;
max-width: 600px;
padding: 0.75rem 0;
font-size: 1rem;
font-weight: 600;
color: #a0aec0;
}
/* Estilos para las superposiciones (mensajes y selección de modo) */
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.85);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 100;
color: white;
text-align: center;
padding: 1rem;
box-sizing: border-box;
border-radius: 0.5rem;
}
.overlay h2 {
font-size: 2rem;
margin-bottom: 1rem;
color: #63b3ed;
}
.overlay p {
font-size: 1.125rem;
margin-bottom: 1.5rem;
}
.overlay button, .mode-button {
background-color: #4299e1;
color: white;
font-weight: bold;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 0.375rem;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
margin-top: 0.5rem;
}
.overlay button:hover, .mode-button:hover {
background-color: #3182ce;
}
#mode-selection-overlay .mode-options {
display: flex;
flex-direction: column;
gap: 1rem;
}
.mode-button {
min-width: 200px;
}
.hidden {
display: none !important;
}
</style>
</head>
<body>
<div id="game-container">
<div id="game-info">
<span>Modo: <span id="mode-display">-</span></span> |
<span>Nivel: <span id="level-display">1</span></span> |
<span>Vidas: <span id="lives-display">5</span></span> |
<span>Área: <span id="area-display">0</span>%</span>
</div>
<canvas id="gameCanvas"></canvas>
<div id="mode-selection-overlay" class="overlay"> <h2>Elige un modo de juego:</h2>
<div class="mode-options">
<button id="mode-xonix-button" class="mode-button">Modo Área (Xonix)</button>
<button id="mode-isolate-button" class="mode-button">Modo Aislar Bolas</button>
</div>
</div>
<div id="message-overlay" class="overlay hidden">
<h2 id="message-title"></h2>
<p id="message-text"></p>
<button id="message-button">Continuar</button>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const modeDisplay = document.getElementById('mode-display');
const levelDisplay = document.getElementById('level-display');
const livesDisplay = document.getElementById('lives-display');
const areaDisplay = document.getElementById('area-display');
const messageOverlay = document.getElementById('message-overlay');
const messageTitle = document.getElementById('message-title');
const messageText = document.getElementById('message-text');
let messageButton = document.getElementById('message-button');
const modeSelectionOverlay = document.getElementById('mode-selection-overlay');
const modeXonixButton = document.getElementById('mode-xonix-button');
const modeIsolateButton = document.getElementById('mode-isolate-button');
const CANVAS_WIDTH = 600;
const CANVAS_HEIGHT = 400;
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
const PLAYER_SIZE = 10;
const PLAYER_SPEED = 10;
const LINE_DRAW_SPEED = 5;
const BALL_RADIUS = 8;
const TARGET_AREA_PERCENTAGE = 75;
const INITIAL_LIVES = 5;
const MAX_LEVELS = 10;
const INITIAL_BALLS_XONIX = 2;
const INITIAL_BALLS_ISOLATE = 3;
let gameMode = 'xonix';
let gameState;
let currentLevel;
let lives;
let balls = [];
let player;
let activeArea = { x: 0, y: 0, width: CANVAS_WIDTH, height: CANVAS_HEIGHT };
let filledAreas = [];
let walls = [];
let totalCanvasArea = CANVAS_WIDTH * CANVAS_HEIGHT;
let conqueredAreaPercentage;
const BALL_COLORS = ['#F56565', '#ED8936', '#ECC94B', '#48BB78', '#38B2AC', '#4299E1', '#9F7AEA', '#ED64A6'];
const FILLED_AREA_COLOR = '#2c5282';
const ACTIVE_AREA_COLOR = '#4A5568';
const PLAYER_COLOR = '#A0AEC0';
const LINE_COLOR = '#F7FAFC';
const WALL_COLOR = '#CBD5E0';
class Ball {
constructor(x, y, dx, dy, radius, color) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.color = color;
this.cell = null;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
update() {
this.x += this.dx;
this.y += this.dy;
let currentBoundaries = activeArea;
if (gameMode === 'isolate') {
currentBoundaries = { x: 0, y: 0, width: CANVAS_WIDTH, height: CANVAS_HEIGHT };
}
if (this.x + this.radius > currentBoundaries.x + currentBoundaries.width || this.x - this.radius < currentBoundaries.x) {
this.dx *= -1;
if (this.x + this.radius > currentBoundaries.x + currentBoundaries.width) this.x = currentBoundaries.x + currentBoundaries.width - this.radius;
if (this.x - this.radius < currentBoundaries.x) this.x = currentBoundaries.x + this.radius;
}
if (this.y + this.radius > currentBoundaries.y + currentBoundaries.height || this.y - this.radius < currentBoundaries.y) {
this.dy *= -1;
if (this.y + this.radius > currentBoundaries.y + currentBoundaries.height) this.y = currentBoundaries.y + currentBoundaries.height - this.radius;
if (this.y - this.radius < currentBoundaries.y) this.y = currentBoundaries.y + this.radius;
}
if (gameMode === 'isolate' && walls) {
for (let wall of walls) {
if (wall.orientation === 'vertical') {
const wallMinY = Math.min(wall.y1, wall.y2);
const wallMaxY = Math.max(wall.y1, wall.y2);
if (this.x + this.radius > wall.x1 - 1 && this.x - this.radius < wall.x1 + 1 &&
this.y + this.radius > wallMinY && this.y - this.radius < wallMaxY) {
if ((this.dx > 0 && this.x < wall.x1) || (this.dx < 0 && this.x > wall.x1)) {
this.dx *= -1;
this.x = (this.x < wall.x1) ? wall.x1 - this.radius - 0.1 : wall.x1 + this.radius + 0.1;
}
}
} else {
const wallMinX = Math.min(wall.x1, wall.x2);
const wallMaxX = Math.max(wall.x1, wall.x2);
if (this.y + this.radius > wall.y1 - 1 && this.y - this.radius < wall.y1 + 1 &&
this.x + this.radius > wallMinX && this.x - this.radius < wallMaxX) {
if ((this.dy > 0 && this.y < wall.y1) || (this.dy < 0 && this.y > wall.y1)) {
this.dy *= -1;
this.y = (this.y < wall.y1) ? wall.y1 - this.radius - 0.1 : wall.y1 + this.radius + 0.1;
}
}
}
}
}
}
}
function initPlayer() {
player = {
x: CANVAS_WIDTH / 2 - PLAYER_SIZE / 2,
y: 0,
edge: 'top',
drawingLine: null
};
}
function initLevel(level) {
currentLevel = level;
lives = INITIAL_LIVES;
activeArea = { x: 0, y: 0, width: CANVAS_WIDTH, height: CANVAS_HEIGHT };
filledAreas = [];
walls = [];
balls = [];
conqueredAreaPercentage = 0;
initPlayer();
let numBalls;
let baseSpeed;
const addedBalls = Math.floor((currentLevel - 1) / 2);
if (gameMode === 'xonix') {
numBalls = INITIAL_BALLS_XONIX + addedBalls;
baseSpeed = 1 + currentLevel * 0.25;
} else {
numBalls = INITIAL_BALLS_ISOLATE + addedBalls;
baseSpeed = 1.25 + currentLevel * 0.2;
}
numBalls = Math.max(gameMode === 'xonix' ? INITIAL_BALLS_XONIX : INITIAL_BALLS_ISOLATE, numBalls);
for (let i = 0; i < numBalls; i++) {
let ballX, ballY;
let safe = false;
let attempts = 0;
do {
ballX = BALL_RADIUS + Math.random() * (CANVAS_WIDTH - 2 * BALL_RADIUS);
ballY = BALL_RADIUS + Math.random() * (CANVAS_HEIGHT - 2 * BALL_RADIUS);
let playerInitialEdge = player ? player.edge : 'top';
let playerInitialY = player ? player.y : 0;
let playerEffectiveY = (playerInitialEdge === 'top') ? playerInitialY + PLAYER_SIZE : CANVAS_HEIGHT;
if (ballY > playerEffectiveY + 20) {
safe = true;
}
attempts++;
} while (!safe && attempts < 100);
const angle = Math.random() * Math.PI * 2;
balls.push(new Ball(
ballX, ballY,
Math.cos(angle) * baseSpeed,
Math.sin(angle) * baseSpeed,
BALL_RADIUS,
BALL_COLORS[i % BALL_COLORS.length]
));
}
updateUI();
setGameState('READY');
}
function showModeSelectionScreen() {
modeSelectionOverlay.classList.remove('hidden');
messageOverlay.classList.add('hidden');
modeDisplay.textContent = '-';
levelDisplay.textContent = '1';
livesDisplay.textContent = String(INITIAL_LIVES);
areaDisplay.textContent = '0%';
gameState = 'MODE_SELECT';
draw();
}
function startGame() {
initLevel(1);
}
function setGameState(newState) {
gameState = newState;
if (messageOverlay) {
messageOverlay.classList.add('hidden');
}
if(modeSelectionOverlay && newState !== 'MODE_SELECT'){
modeSelectionOverlay.classList.add('hidden');
}
const numCurrentBalls = balls ? balls.length : (gameMode === 'xonix' ? INITIAL_BALLS_XONIX : INITIAL_BALLS_ISOLATE);
let readyMessage = gameMode === 'xonix' ?
`Nivel ${currentLevel}. Conquista el ${TARGET_AREA_PERCENTAGE}% del área. Bolas: ${numCurrentBalls}.` :
`Modo Aislar - Nivel ${currentLevel}. ¡Encierra ${numCurrentBalls} bolas en celdas separadas!`;
if (gameState === 'READY') {
showMessage(gameMode === 'xonix' ? `Nivel ${currentLevel}` : `Aislar Bolas - Nivel ${currentLevel}`, `${readyMessage} Presiona ESPACIO para empezar.`, "Empezar", () => setGameState('PLAYING'));
} else if (gameState === 'GAME_OVER') {
showMessage("¡Juego Terminado!", `Te quedaste sin vidas. Nivel alcanzado: ${currentLevel}. Presiona para elegir modo.`, "Elegir Modo", () => {
showModeSelectionScreen();
});
} else if (gameState === 'GAME_WON') {
let winMsg = "";
let nextAction = null;
let buttonText = "Elegir Modo";
if (gameMode === 'xonix') {
winMsg = `¡Nivel ${currentLevel} de Xonix Completado!`;
if (currentLevel >= MAX_LEVELS) {
winMsg = "¡Completaste todos los niveles de Xonix!";
nextAction = () => showModeSelectionScreen();
} else {
buttonText = "Siguiente Nivel (Xonix)";
nextAction = () => initLevel(currentLevel + 1);
}
} else {
winMsg = `¡Nivel ${currentLevel} de Aislar Bolas Completado!`;
if (currentLevel >= MAX_LEVELS) {
winMsg = "¡Completaste todos los niveles de Aislar Bolas!";
nextAction = () => showModeSelectionScreen();
} else {
buttonText = "Siguiente Nivel (Aislar)";
nextAction = () => initLevel(currentLevel + 1);
}
}
showMessage("¡Felicidades!", `${winMsg} Presiona para continuar.`, buttonText, nextAction);
}
}
function showMessage(title, text, buttonText, callback) {
messageTitle.textContent = title;
messageText.textContent = text;
const newButton = messageButton.cloneNode(true);
newButton.textContent = buttonText;
messageButton.parentNode.replaceChild(newButton, messageButton);
messageButton = newButton;
messageOverlay.classList.remove('hidden');
modeSelectionOverlay.classList.add('hidden');
messageButton.onclick = () => {
if (messageOverlay) messageOverlay.classList.add('hidden');
if (callback) callback();
};
}
function updatePlayerPosition(dx, dy) {
if (!player) return;
if (player.edge === 'top') {
player.x = Math.max(0, Math.min(player.x + dx, CANVAS_WIDTH - PLAYER_SIZE));
} else if (player.edge === 'bottom') {
player.x = Math.max(0, Math.min(player.x + dx, CANVAS_WIDTH - PLAYER_SIZE));
} else if (player.edge === 'left') {
player.y = Math.max(0, Math.min(player.y + dy, CANVAS_HEIGHT - PLAYER_SIZE));
} else if (player.edge === 'right') {
player.y = Math.max(0, Math.min(player.y + dy, CANVAS_HEIGHT - PLAYER_SIZE));
}
}
function switchPlayerEdge(key) {
if (!player) return;
if (player.edge === 'top') {
if (key === 'ArrowLeft' && player.x === 0) player.edge = 'left';
else if (key === 'ArrowRight' && player.x === CANVAS_WIDTH - PLAYER_SIZE) player.edge = 'right';
} else if (player.edge === 'bottom') {
if (key === 'ArrowLeft' && player.x === 0) { player.edge = 'left'; player.y = CANVAS_HEIGHT - PLAYER_SIZE; }
else if (key === 'ArrowRight' && player.x === CANVAS_WIDTH - PLAYER_SIZE) { player.edge = 'right'; player.y = CANVAS_HEIGHT - PLAYER_SIZE; }
} else if (player.edge === 'left') {
if (key === 'ArrowUp' && player.y === 0) player.edge = 'top';
else if (key === 'ArrowDown' && player.y === CANVAS_HEIGHT - PLAYER_SIZE) player.edge = 'bottom';
} else if (player.edge === 'right') {
if (key === 'ArrowUp' && player.y === 0) { player.edge = 'top'; player.x = CANVAS_WIDTH - PLAYER_SIZE; }
else if (key === 'ArrowDown' && player.y === CANVAS_HEIGHT - PLAYER_SIZE) { player.edge = 'bottom'; player.x = CANVAS_WIDTH - PLAYER_SIZE; }
}
}
function handleInput(e) {
if (!player) return;
if (gameState !== 'PLAYING' && gameState !== 'READY') return;
if (gameState === 'READY' && e.key === ' ') { e.preventDefault(); setGameState('PLAYING'); return; }
if (gameState !== 'PLAYING') return;
const prevX = player.x; const prevY = player.y;
if (e.key === 'ArrowLeft') {
e.preventDefault();
if (player.edge === 'top' || player.edge === 'bottom') {
updatePlayerPosition(-PLAYER_SPEED, 0);
if (player.x === prevX && player.x === 0) switchPlayerEdge(e.key);
}
} else if (e.key === 'ArrowRight') {
e.preventDefault();
if (player.edge === 'top' || player.edge === 'bottom') {
updatePlayerPosition(PLAYER_SPEED, 0);
if (player.x === prevX && player.x === CANVAS_WIDTH - PLAYER_SIZE) switchPlayerEdge(e.key);
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (player.edge === 'left' || player.edge === 'right') {
updatePlayerPosition(0, -PLAYER_SPEED);
if (player.y === prevY && player.y === 0) switchPlayerEdge(e.key);
}
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (player.edge === 'left' || player.edge === 'right') {
updatePlayerPosition(0, PLAYER_SPEED);
if (player.y === prevY && player.y === CANVAS_HEIGHT - PLAYER_SIZE) switchPlayerEdge(e.key);
}
} else if (e.key === ' ' && !player.drawingLine) { e.preventDefault(); startDrawingLine(); }
}
function startDrawingLine() {
if (!player) return;
setGameState('DRAWING');
let startX, startY, endX, endY, orientation, directionFactor;
const playerCenterX = player.x + PLAYER_SIZE / 2;
const playerCenterY = player.y + PLAYER_SIZE / 2;
let drawBounds = (gameMode === 'xonix') ? activeArea : {x:0, y:0, width: CANVAS_WIDTH, height: CANVAS_HEIGHT};
if (player.edge === 'top') {
startX = playerCenterX; startY = drawBounds.y;
endX = playerCenterX; endY = drawBounds.y + drawBounds.height;
orientation = 'vertical'; directionFactor = 1;
} else if (player.edge === 'bottom') {
startX = playerCenterX; startY = drawBounds.y + drawBounds.height;
endX = playerCenterX; endY = drawBounds.y;
orientation = 'vertical'; directionFactor = -1;
} else if (player.edge === 'left') {
startX = drawBounds.x; startY = playerCenterY;
endX = drawBounds.x + drawBounds.width; endY = playerCenterY;
orientation = 'horizontal'; directionFactor = 1;
} else {
startX = drawBounds.x + drawBounds.width; startY = playerCenterY;
endX = drawBounds.x; endY = playerCenterY;
orientation = 'horizontal'; directionFactor = -1;
}
player.drawingLine = {
startX: startX, startY: startY, currentX: startX, currentY: startY,
targetX: endX, targetY: endY, orientation: orientation,
directionFactor: directionFactor, color: LINE_COLOR
};
}
function updateDrawingLine() {
if (!player || !player.drawingLine) return;
const line = player.drawingLine;
const prevCurrentX = line.currentX;
const prevCurrentY = line.currentY;
if (line.orientation === 'vertical') {
line.currentY += LINE_DRAW_SPEED * line.directionFactor;
} else {
line.currentX += LINE_DRAW_SPEED * line.directionFactor;
}
if (gameMode === 'isolate') {
for (let wall of walls) {
let intersected = false;
if (line.orientation === 'vertical') {
if (wall.orientation === 'horizontal' &&
line.startX >= Math.min(wall.x1, wall.x2) && line.startX <= Math.max(wall.x1, wall.x2) &&
((prevCurrentY < wall.y1 && line.currentY >= wall.y1) ||
(prevCurrentY > wall.y1 && line.currentY <= wall.y1))) {
line.currentY = wall.y1;
intersected = true;
}
} else {
if (wall.orientation === 'vertical' &&
line.startY >= Math.min(wall.y1, wall.y2) && line.startY <= Math.max(wall.y1, wall.y2) &&
((prevCurrentX < wall.x1 && line.currentX >= wall.x1) ||
(prevCurrentX > wall.x1 && line.currentX <= wall.x1))) {
line.currentX = wall.x1;
intersected = true;
}
}
if (intersected) {
completeLine();
return;
}
}
}
if (line.orientation === 'vertical') {
if ((line.directionFactor > 0 && line.currentY >= line.targetY) || (line.directionFactor < 0 && line.currentY <= line.targetY)) {
line.currentY = line.targetY; completeLine();
}
} else {
if ((line.directionFactor > 0 && line.currentX >= line.targetX) || (line.directionFactor < 0 && line.currentX <= line.targetX)) {
line.currentX = line.targetX; completeLine();
}
}
checkLineBallCollision();
}
function checkLineBallCollision() {
if (!player || !player.drawingLine) return;
const line = player.drawingLine;
for (let ball of balls) {
let collision = false;
const lineThicknessBuffer = 2;
if (line.orientation === 'vertical') {
const x0 = line.startX;
const y1 = Math.min(line.startY, line.currentY);
const y2 = Math.max(line.startY, line.currentY);
if (Math.abs(ball.x - x0) < ball.radius + lineThicknessBuffer &&
ball.y + ball.radius > y1 && ball.y - ball.radius < y2) collision = true;
} else {
const y0 = line.startY;
const x1 = Math.min(line.startX, line.currentX);
const x2 = Math.max(line.startX, line.currentX);
if (Math.abs(ball.y - y0) < ball.radius + lineThicknessBuffer &&
ball.x + ball.radius > x1 && ball.x - ball.radius < x2) collision = true;
}
if (collision) { loseLife(); return; }
}
}
function loseLife() {
lives--;
if(player) player.drawingLine = null;
updateUI();
if (lives <= 0) setGameState('GAME_OVER');
else setGameState('PLAYING');
}
function completeLine() {
if (!player || !player.drawingLine) return;
const line = player.drawingLine;
player.drawingLine = null;
if (gameMode === 'xonix') {
completeLineXonix(line);
} else {
completeLineIsolate(line);
}
}
function completeLineXonix(line) {
let rect1, rect2;
if (line.orientation === 'vertical') {
const splitX = line.startX;
rect1 = { x: activeArea.x, y: activeArea.y, width: splitX - activeArea.x, height: activeArea.height };
rect2 = { x: splitX, y: activeArea.y, width: (activeArea.x + activeArea.width) - splitX, height: activeArea.height };
} else {
const splitY = line.startY;
rect1 = { x: activeArea.x, y: activeArea.y, width: activeArea.width, height: splitY - activeArea.y };
rect2 = { x: activeArea.x, y: splitY, width: activeArea.width, height: (activeArea.y + activeArea.height) - splitY };
}
const validRects = [rect1, rect2].filter(r => r && r.width > 0.1 && r.height > 0.1);
if (validRects.length < 2) { setGameState('PLAYING'); return; }
let ballsInRect1 = 0, ballsInRect2 = 0;
for (let ball of balls) {
if (rect1.width > 0.1 && rect1.height > 0.1 && isBallInRect(ball, rect1)) ballsInRect1++;
else if (rect2.width > 0.1 && rect2.height > 0.1 && isBallInRect(ball, rect2)) ballsInRect2++;
}
let filledNewArea = false, areaToKeep = null;
if (rect1.width > 0.1 && rect1.height > 0.1 && ballsInRect1 === 0) {
filledAreas.push(rect1); areaToKeep = rect2;
if (!(areaToKeep.width > 0.1 && areaToKeep.height > 0.1)) areaToKeep = null;
filledNewArea = true;
} else if (rect2.width > 0.1 && rect2.height > 0.1 && ballsInRect2 === 0) {
filledAreas.push(rect2); areaToKeep = rect1;
if (!(areaToKeep.width > 0.1 && areaToKeep.height > 0.1)) areaToKeep = null;
filledNewArea = true;
}
if (filledNewArea && areaToKeep) {
activeArea = areaToKeep; repositionPlayerToActiveAreaXonix(); calculateConqueredArea();
if (conqueredAreaPercentage >= TARGET_AREA_PERCENTAGE) {
setGameState('GAME_WON');
return;
}
}
setGameState('PLAYING');
}
function completeLineIsolate(line) {
const newWall = {
x1: line.startX, y1: line.startY, x2: line.currentX, y2: line.currentY,
orientation: line.orientation
};
if ((newWall.orientation === 'vertical' && Math.abs(newWall.y1 - newWall.y2) < PLAYER_SIZE / 2) ||
(newWall.orientation === 'horizontal' && Math.abs(newWall.x1 - newWall.x2) < PLAYER_SIZE / 2)) {
setGameState('PLAYING');
return;
}
walls.push(newWall);
checkWinConditionIsolate();
if (gameState !== 'GAME_WON' && gameState !== 'GAME_OVER') {
setGameState('PLAYING');
}
}
function checkWinConditionIsolate() {
if (balls.length === 0 && walls.length > 0) {
setGameState('GAME_WON'); return;
}
if (balls.length === 0 && walls.length === 0) return;
const ballCells = [];
for (let i = 0; i < balls.length; i++) {
const ball = balls[i];
let minX = 0, maxX = CANVAS_WIDTH;
let minY = 0, maxY = CANVAS_HEIGHT;
for (let wall of walls) {
if (wall.orientation === 'vertical') {
const wallX = wall.x1;
const wallSegmentMinY = Math.min(wall.y1, wall.y2);
const wallSegmentMaxY = Math.max(wall.y1, wall.y2);
if (ball.y >= wallSegmentMinY && ball.y <= wallSegmentMaxY) {
if (wallX < ball.x && wallX > minX) minX = wallX;
if (wallX > ball.x && wallX < maxX) maxX = wallX;
}
} else {
const wallY = wall.y1;
const wallSegmentMinX = Math.min(wall.x1, wall.x2);
const wallSegmentMaxX = Math.max(wall.x1, wall.x2);
if (ball.x >= wallSegmentMinX && ball.x <= wallSegmentMaxX) {
if (wallY < ball.y && wallY > minY) minY = wallY;
if (wallY > ball.y && wallY < maxY) maxY = wallY;
}
}
}
if (maxX - minX < BALL_RADIUS*1.5 || maxY - minY < BALL_RADIUS*1.5) {
}
const cellId = `${minX.toFixed(1)}-${minY.toFixed(1)}-${maxX.toFixed(1)}-${maxY.toFixed(1)}`;
ball.cell = cellId;
for (let j = 0; j < ballCells.length; j++) {
if (ballCells[j] === cellId) {
return;
}
}
ballCells.push(cellId);
}
setGameState('GAME_WON');
}
function repositionPlayerToActiveAreaXonix() {
if (!player) return;
player.edge = 'top';
player.x = activeArea.x + activeArea.width / 2 - PLAYER_SIZE / 2;
player.y = activeArea.y;
player.x = Math.max(activeArea.x, Math.min(player.x, activeArea.x + activeArea.width - PLAYER_SIZE));
player.y = Math.max(activeArea.y, Math.min(player.y, activeArea.y + activeArea.height - PLAYER_SIZE));
if (player.edge === 'top') player.y = activeArea.y;
else if (player.edge === 'bottom') player.y = activeArea.y + activeArea.height - PLAYER_SIZE;
else if (player.edge === 'left') player.x = activeArea.x;
else if (player.edge === 'right') player.x = activeArea.x + activeArea.width - PLAYER_SIZE;
}
function isBallInRect(ball, rect) {
if (!rect) return false;
return ball.x - ball.radius >= rect.x && ball.x + ball.radius <= rect.x + rect.width &&
ball.y - ball.radius >= rect.y && ball.y + ball.radius <= rect.y + rect.height;
}
function calculateConqueredArea() {
let currentFilledAreaSum = 0;
for (let area of filledAreas) { currentFilledAreaSum += area.width * area.height; }
conqueredAreaPercentage = (currentFilledAreaSum / totalCanvasArea) * 100;
updateUI();
}
function updateUI() {
modeDisplay.textContent = gameMode === 'xonix' ? 'Xonix' : 'Aislar';
levelDisplay.textContent = currentLevel || 1;
livesDisplay.textContent = (typeof lives === 'number') ? lives : INITIAL_LIVES;
areaDisplay.textContent = (gameMode === 'xonix') ? `${Math.floor(conqueredAreaPercentage || 0)}%` : '-';
}
function draw() {
ctx.fillStyle = ACTIVE_AREA_COLOR;
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
if (gameMode === 'xonix' && filledAreas) {
ctx.fillStyle = FILLED_AREA_COLOR;
for (let area of filledAreas) { ctx.fillRect(area.x, area.y, area.width, area.height); }
} else if (gameMode === 'isolate' && walls) {
ctx.strokeStyle = WALL_COLOR;
ctx.lineWidth = 2;
for (let wall of walls) {
ctx.beginPath();
ctx.moveTo(wall.x1, wall.y1);
ctx.lineTo(wall.x2, wall.y2);
ctx.stroke();
}
}
if ((gameState === 'PLAYING' || gameState === 'DRAWING' || gameState === 'READY') && balls) {
for (let ball of balls) { ball.draw(); }
}
if (player && (gameState === 'PLAYING' || gameState === 'DRAWING')) {
ctx.fillStyle = PLAYER_COLOR;
ctx.fillRect(player.x, player.y, PLAYER_SIZE, PLAYER_SIZE);
}
if (player && player.drawingLine) {
const line = player.drawingLine;
ctx.beginPath(); ctx.moveTo(line.startX, line.startY); ctx.lineTo(line.currentX, line.currentY);
ctx.strokeStyle = line.color; ctx.lineWidth = 2; ctx.stroke(); ctx.closePath();
}
}
function gameLoop() {
if ((gameState === 'PLAYING' || gameState === 'DRAWING') && balls) {
balls.forEach(ball => ball.update());
}
if (gameState === 'DRAWING') {
updateDrawingLine();
}
if (gameState !== 'MODE_SELECT' || (gameState === 'MODE_SELECT' && modeSelectionOverlay.classList.contains('hidden'))) {
draw();
} else if (gameState === 'MODE_SELECT') {
ctx.fillStyle = ACTIVE_AREA_COLOR;
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
}
requestAnimationFrame(gameLoop);
}
modeXonixButton.addEventListener('click', () => {
gameMode = 'xonix';
modeDisplay.textContent = 'Xonix';
modeSelectionOverlay.classList.add('hidden');
startGame();
});
modeIsolateButton.addEventListener('click', () => {
gameMode = 'isolate';
modeDisplay.textContent = 'Aislar';
modeSelectionOverlay.classList.add('hidden');
startGame();
});
document.addEventListener('keydown', handleInput);
showModeSelectionScreen();
requestAnimationFrame(gameLoop);
</script>
</body>
</html>