Skip to content

Commit 2fbec1f

Browse files
committed
Feat: logica del juego. Style: cambios en html y css
1 parent 5544ef9 commit 2fbec1f

7 files changed

Lines changed: 551 additions & 10 deletions

File tree

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ <h2>Flota Enemiga</h2>
6262

6363
<button id="btn-restart" style="margin-top: 2rem; display: block;">🔄 Nueva Partida</button>
6464

65-
<script type="module" src="./src/index.js"></script>
65+
<script type="module" src="./src/Index.js"></script>
6666
</body>
6767

6868
</html>

src/Gameboard.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { Ship } from './Ship.js';
2+
3+
export class Gameboard {
4+
constructor() {
5+
this.size = 10;
6+
// grid[row][col] = null | { ship, index }
7+
this.grid = Array.from({ length: this.size }, () => Array(this.size).fill(null));
8+
this.ships = []; // { ship, coords }
9+
this.missedAttacks = new Set();
10+
this.hitAttacks = new Set();
11+
}
12+
13+
_key(row, col) {
14+
return `${row},${col}`;
15+
}
16+
17+
// Returns true if a ship of `length` can be placed at (row, col) in the given direction
18+
canPlace(row, col, length, vertical) {
19+
for (let i = 0; i < length; i++) {
20+
const r = vertical ? row + i : row;
21+
const c = vertical ? col : col + i;
22+
if (r >= this.size || c >= this.size) return false;
23+
if (this.grid[r][c] !== null) return false;
24+
}
25+
return true;
26+
}
27+
28+
placeShip(row, col, length, vertical) {
29+
if (!this.canPlace(row, col, length, vertical)) return false;
30+
const ship = new Ship(length);
31+
const coords = [];
32+
33+
for (let i = 0; i < length; i++) {
34+
const r = vertical ? row + i : row;
35+
const c = vertical ? col : col + i;
36+
this.grid[r][c] = { ship, index: i };
37+
coords.push([r, c]);
38+
}
39+
40+
this.ships.push({ ship, coords });
41+
return true;
42+
}
43+
44+
receiveAttack(row, col) {
45+
const key = this._key(row, col);
46+
if (this.missedAttacks.has(key) || this.hitAttacks.has(key)) return false;
47+
48+
const cell = this.grid[row][col];
49+
if (cell) {
50+
cell.ship.hit();
51+
this.hitAttacks.add(key);
52+
} else {
53+
this.missedAttacks.add(key);
54+
}
55+
return true;
56+
}
57+
58+
isAttacked(row, col) {
59+
const key = this._key(row, col);
60+
return this.missedAttacks.has(key) || this.hitAttacks.has(key);
61+
}
62+
63+
isHit(row, col) {
64+
return this.hitAttacks.has(this._key(row, col));
65+
}
66+
67+
allSunk() {
68+
return this.ships.every(({ ship }) => ship.isSunk());
69+
}
70+
71+
// For smart computer AI: returns list of unattacked coords adjacent to hits
72+
getAdjacentToHits() {
73+
const candidates = new Set();
74+
const dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]];
75+
76+
for (const key of this.hitAttacks) {
77+
const [r, c] = key.split(',').map(Number);
78+
// Only add adjacents to hits that are part of ships not yet sunk
79+
const cell = this.grid[r][c];
80+
if (cell && !cell.ship.isSunk()) {
81+
for (const [dr, dc] of dirs) {
82+
const nr = r + dr;
83+
const nc = c + dc;
84+
if (nr >= 0 && nr < this.size && nc >= 0 && nc < this.size && !this.isAttacked(nr, nc)) {
85+
candidates.add(this._key(nr, nc));
86+
}
87+
}
88+
}
89+
}
90+
91+
return [...candidates].map(k => k.split(',').map(Number));
92+
}
93+
}

src/Index.js

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { Player } from './Player.js';
2+
import {
3+
FLEET,
4+
renderBoard,
5+
renderTracker,
6+
renderSetupBoard,
7+
renderFleet,
8+
setStatus,
9+
setDragState,
10+
} from './UI.js';
11+
12+
// ─── DOM References ───────────────────────────────────────────────────────────
13+
const setupPanel = document.getElementById('setup-panel');
14+
const gameArea = document.getElementById('game-area');
15+
const statusText = document.getElementById('status-text');
16+
17+
const setupBoardEl = document.getElementById('setup-board');
18+
const fleetEl = document.getElementById('fleet');
19+
const rotateBtn = document.getElementById('btn-rotate');
20+
const randomBtn = document.getElementById('btn-random');
21+
const startBtn = document.getElementById('btn-start');
22+
23+
const playerBoardEl = document.getElementById('player-board');
24+
const enemyBoardEl = document.getElementById('enemy-board');
25+
const playerTracker = document.getElementById('player-tracker');
26+
const enemyTracker = document.getElementById('enemy-tracker');
27+
const restartBtn = document.getElementById('btn-restart');
28+
29+
// ─── State ────────────────────────────────────────────────────────────────────
30+
let human, computer;
31+
let isVertical = false;
32+
let remainingFleet = [];
33+
let gameActive = false;
34+
35+
// ─── Setup ────────────────────────────────────────────────────────────────────
36+
function initSetup() {
37+
human = new Player('human');
38+
computer = new Player('computer');
39+
isVertical = false;
40+
remainingFleet = [...FLEET];
41+
gameActive = false;
42+
43+
rotateBtn.textContent = '↔ Horizontal';
44+
setupPanel.classList.remove('hidden');
45+
gameArea.classList.add('hidden');
46+
47+
setStatus(statusText, 'Colocá tus barcos arrastrándolos al tablero');
48+
refreshSetup();
49+
computer.placeShipsRandomly(FLEET.map(([l]) => l));
50+
}
51+
52+
function refreshSetup() {
53+
renderSetupBoard(setupBoardEl, human.gameboard, (row, col, length, vertical) => {
54+
const placed = human.gameboard.placeShip(row, col, length, vertical);
55+
if (placed) {
56+
const idx = remainingFleet.findIndex(([l]) => l === length);
57+
if (idx !== -1) remainingFleet.splice(idx, 1);
58+
refreshSetup();
59+
}
60+
});
61+
62+
renderFleet(fleetEl, remainingFleet, (length, name) => {
63+
setStatus(statusText, `Arrastrando: ${name} (${length} casillas)`);
64+
}, isVertical);
65+
66+
const allPlaced = remainingFleet.length === 0;
67+
startBtn.disabled = !allPlaced;
68+
startBtn.style.opacity = allPlaced ? '1' : '0.5';
69+
70+
if (allPlaced) {
71+
setStatus(statusText, '¡Todo listo! Presioná "Iniciar Batalla"');
72+
}
73+
}
74+
75+
// Rotate: toggle isVertical y re-renderizar la flota
76+
rotateBtn.addEventListener('click', () => {
77+
isVertical = !isVertical;
78+
rotateBtn.textContent = isVertical ? '↕ Vertical' : '↔ Horizontal';
79+
refreshSetup();
80+
});
81+
82+
randomBtn.addEventListener('click', () => {
83+
human.placeShipsRandomly(FLEET.map(([l]) => l));
84+
remainingFleet = [];
85+
refreshSetup();
86+
});
87+
88+
startBtn.addEventListener('click', () => {
89+
if (remainingFleet.length > 0) return;
90+
startGame();
91+
});
92+
93+
// ─── Game ─────────────────────────────────────────────────────────────────────
94+
function startGame() {
95+
setupPanel.classList.add('hidden');
96+
gameArea.classList.remove('hidden');
97+
gameActive = true;
98+
setStatus(statusText, '¡Tu turno! Atacá el tablero enemigo');
99+
refreshGame();
100+
}
101+
102+
function refreshGame() {
103+
renderBoard(playerBoardEl, human.gameboard, { isEnemy: false });
104+
renderBoard(enemyBoardEl, computer.gameboard, {
105+
isEnemy: true,
106+
onAttack: handlePlayerAttack,
107+
});
108+
renderTracker(playerTracker, human.gameboard.ships);
109+
renderTracker(enemyTracker, computer.gameboard.ships);
110+
}
111+
112+
function handlePlayerAttack(row, col) {
113+
if (!gameActive) return;
114+
if (computer.gameboard.isAttacked(row, col)) return;
115+
116+
computer.gameboard.receiveAttack(row, col);
117+
118+
if (computer.gameboard.allSunk()) {
119+
gameActive = false;
120+
refreshGame();
121+
setStatus(statusText, '🎉 ¡Ganaste! Hundiste toda la flota enemiga');
122+
return;
123+
}
124+
125+
const hitShip = computer.gameboard.grid[row][col]?.ship;
126+
if (hitShip?.isSunk()) {
127+
setStatus(statusText, '💥 ¡Hundiste un barco enemigo! Tu turno de nuevo');
128+
} else if (computer.gameboard.isHit(row, col)) {
129+
setStatus(statusText, '🔥 ¡Impacto! Seguí atacando');
130+
} else {
131+
setStatus(statusText, '💧 Agua... turno del enemigo');
132+
}
133+
134+
refreshGame();
135+
setTimeout(computerTurn, 750);
136+
}
137+
138+
function computerTurn() {
139+
if (!gameActive) return;
140+
141+
const move = computer.makeComputerMove(human.gameboard);
142+
if (!move) return;
143+
144+
const [row, col] = move;
145+
146+
if (human.gameboard.allSunk()) {
147+
gameActive = false;
148+
refreshGame();
149+
setStatus(statusText, '💀 ¡Perdiste! El enemigo hundió toda tu flota');
150+
return;
151+
}
152+
153+
const hitShip = human.gameboard.grid[row][col]?.ship;
154+
if (hitShip?.isSunk()) {
155+
setStatus(statusText, '😱 ¡El enemigo hundió uno de tus barcos! Tu turno');
156+
} else if (human.gameboard.isHit(row, col)) {
157+
setStatus(statusText, '😬 ¡Te impactaron! Tu turno');
158+
} else {
159+
setStatus(statusText, '😮‍💨 El enemigo falló. ¡Tu turno!');
160+
}
161+
162+
refreshGame();
163+
}
164+
165+
restartBtn.addEventListener('click', initSetup);
166+
167+
// ─── Boot ─────────────────────────────────────────────────────────────────────
168+
initSetup();

src/Player.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Gameboard } from './Gameboard.js';
2+
3+
export class Player {
4+
constructor(type = 'human') {
5+
this.type = type; // 'human' | 'computer'
6+
this.gameboard = new Gameboard();
7+
}
8+
9+
// Computer makes a smart move: attack adjacent cells after a hit, else random
10+
makeComputerMove(enemyBoard) {
11+
// Try adjacent cells to existing hits first (smart targeting)
12+
const adjacent = enemyBoard.getAdjacentToHits();
13+
if (adjacent.length > 0) {
14+
const [row, col] = adjacent[Math.floor(Math.random() * adjacent.length)];
15+
return enemyBoard.receiveAttack(row, col) ? [row, col] : this._randomMove(enemyBoard);
16+
}
17+
return this._randomMove(enemyBoard);
18+
}
19+
20+
_randomMove(enemyBoard) {
21+
const size = enemyBoard.size;
22+
let row, col;
23+
let attempts = 0;
24+
do {
25+
row = Math.floor(Math.random() * size);
26+
col = Math.floor(Math.random() * size);
27+
attempts++;
28+
if (attempts > 200) return null; // safety valve
29+
} while (enemyBoard.isAttacked(row, col));
30+
31+
enemyBoard.receiveAttack(row, col);
32+
return [row, col];
33+
}
34+
35+
// Place ships randomly on own board
36+
placeShipsRandomly(shipLengths) {
37+
this.gameboard = new Gameboard();
38+
for (const length of shipLengths) {
39+
let placed = false;
40+
while (!placed) {
41+
const row = Math.floor(Math.random() * 10);
42+
const col = Math.floor(Math.random() * 10);
43+
const vertical = Math.random() < 0.5;
44+
placed = this.gameboard.placeShip(row, col, length, vertical);
45+
}
46+
}
47+
}
48+
}

src/Ship.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export class Ship {
2+
constructor(length) {
3+
this.length = length;
4+
this.hits = 0;
5+
}
6+
7+
hit() {
8+
if (!this.isSunk()) {
9+
this.hits++;
10+
}
11+
}
12+
13+
isSunk() {
14+
return this.hits >= this.length;
15+
}
16+
}

0 commit comments

Comments
 (0)