Añadidos juegos
This commit is contained in:
15
3-en-raya-computer/index.html
Normal file
15
3-en-raya-computer/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Tres en Raya vs Máquina</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Tres en Raya vs Máquina</h1>
|
||||||
|
<div id="board"></div>
|
||||||
|
<div id="status"></div>
|
||||||
|
<button id="restart-btn">Reiniciar</button>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
122
3-en-raya-computer/script.js
Normal file
122
3-en-raya-computer/script.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
const boardDiv = document.getElementById('board');
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
const restartBtn = document.getElementById('restart-btn');
|
||||||
|
let board, gameOver;
|
||||||
|
|
||||||
|
function initGame() {
|
||||||
|
board = Array(9).fill('');
|
||||||
|
gameOver = false;
|
||||||
|
statusDiv.textContent = "Tu turno (X)";
|
||||||
|
renderBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBoard() {
|
||||||
|
boardDiv.innerHTML = '';
|
||||||
|
board.forEach((cell, idx) => {
|
||||||
|
const cellDiv = document.createElement('div');
|
||||||
|
cellDiv.className = 'cell';
|
||||||
|
cellDiv.textContent = cell;
|
||||||
|
cellDiv.onclick = () => handlePlayerMove(idx);
|
||||||
|
boardDiv.appendChild(cellDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePlayerMove(idx) {
|
||||||
|
if (gameOver || board[idx] !== '') return;
|
||||||
|
board[idx] = 'X';
|
||||||
|
renderBoard();
|
||||||
|
if (checkWinner('X')) {
|
||||||
|
statusDiv.textContent = `¡Tú ganas! 🎉`;
|
||||||
|
gameOver = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isDraw()) {
|
||||||
|
statusDiv.textContent = "¡Empate!";
|
||||||
|
gameOver = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
statusDiv.textContent = "Turno de la máquina (O)";
|
||||||
|
setTimeout(machineMove, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
function machineMove() {
|
||||||
|
if (gameOver) return;
|
||||||
|
let bestScore = -Infinity;
|
||||||
|
let move = null;
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
if (board[i] === '') {
|
||||||
|
board[i] = 'O';
|
||||||
|
let score = minimax(board, 0, false);
|
||||||
|
board[i] = '';
|
||||||
|
if (score > bestScore) {
|
||||||
|
bestScore = score;
|
||||||
|
move = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (move !== null) board[move] = 'O';
|
||||||
|
renderBoard();
|
||||||
|
if (checkWinner('O')) {
|
||||||
|
statusDiv.textContent = `¡La máquina gana! 🤖`;
|
||||||
|
gameOver = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isDraw()) {
|
||||||
|
statusDiv.textContent = "¡Empate!";
|
||||||
|
gameOver = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
statusDiv.textContent = "Tu turno (X)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Algoritmo Minimax
|
||||||
|
function minimax(newBoard, depth, isMaximizing) {
|
||||||
|
if (checkWinnerOnBoard(newBoard, 'O')) return 10 - depth;
|
||||||
|
if (checkWinnerOnBoard(newBoard, 'X')) return depth - 10;
|
||||||
|
if (newBoard.every(cell => cell !== '')) return 0;
|
||||||
|
|
||||||
|
if (isMaximizing) {
|
||||||
|
let bestScore = -Infinity;
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
if (newBoard[i] === '') {
|
||||||
|
newBoard[i] = 'O';
|
||||||
|
let score = minimax(newBoard, depth + 1, false);
|
||||||
|
newBoard[i] = '';
|
||||||
|
bestScore = Math.max(score, bestScore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestScore;
|
||||||
|
} else {
|
||||||
|
let bestScore = Infinity;
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
if (newBoard[i] === '') {
|
||||||
|
newBoard[i] = 'X';
|
||||||
|
let score = minimax(newBoard, depth + 1, true);
|
||||||
|
newBoard[i] = '';
|
||||||
|
bestScore = Math.min(score, bestScore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestScore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWinner(player) {
|
||||||
|
return checkWinnerOnBoard(board, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWinnerOnBoard(b, player) {
|
||||||
|
const wins = [
|
||||||
|
[0,1,2],[3,4,5],[6,7,8],
|
||||||
|
[0,3,6],[1,4,7],[2,5,8],
|
||||||
|
[0,4,8],[2,4,6]
|
||||||
|
];
|
||||||
|
return wins.some(combo => combo.every(i => b[i] === player));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDraw() {
|
||||||
|
return board.every(cell => cell !== '') && !checkWinner('X') && !checkWinner('O');
|
||||||
|
}
|
||||||
|
|
||||||
|
restartBtn.onclick = initGame;
|
||||||
|
|
||||||
|
initGame();
|
122
3-en-raya-computer/styles.css
Normal file
122
3-en-raya-computer/styles.css
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
body {
|
||||||
|
background: #eef2f3;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #222;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: #3498db;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(60px, 26vw));
|
||||||
|
grid-template-rows: repeat(3, minmax(60px, 26vw));
|
||||||
|
gap: 0.7em;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 2rem auto 1.2rem auto;
|
||||||
|
max-width: 95vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 8px #bbb;
|
||||||
|
font-size: 10em;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell:hover,
|
||||||
|
.cell:focus {
|
||||||
|
background: #f1fafe;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
font-size: 1.1em;
|
||||||
|
min-height: 2em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-btn {
|
||||||
|
background: #3498db;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 0.7em 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-btn:hover,
|
||||||
|
#restart-btn:focus {
|
||||||
|
background: #297fb8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Celulares */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(3, minmax(38px, 30vw));
|
||||||
|
grid-template-rows: repeat(3, minmax(38px, 30vw));
|
||||||
|
gap: 0.4em;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: 10em;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
#status {
|
||||||
|
font-size: 1em;
|
||||||
|
min-height: 1.2em;
|
||||||
|
margin-bottom: 0.7em;
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
font-size: 0.95em;
|
||||||
|
padding: 0.5em 1.1em;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablets */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(3, minmax(50px, 22vw));
|
||||||
|
grid-template-rows: repeat(3, minmax(50px, 22vw));
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pantallas grandes */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(3, 100px);
|
||||||
|
grid-template-rows: repeat(3, 100px);
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
}
|
15
3-en-raya/index.html
Normal file
15
3-en-raya/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Tres en Raya</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Tres en Raya (Tic Tac Toe)</h1>
|
||||||
|
<div id="board"></div>
|
||||||
|
<div id="status"></div>
|
||||||
|
<button id="restart-btn">Reiniciar</button>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
52
3-en-raya/script.js
Normal file
52
3-en-raya/script.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
const boardDiv = document.getElementById('board');
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
const restartBtn = document.getElementById('restart-btn');
|
||||||
|
let board, currentPlayer, gameOver;
|
||||||
|
|
||||||
|
function initGame() {
|
||||||
|
board = Array(9).fill('');
|
||||||
|
currentPlayer = 'X';
|
||||||
|
gameOver = false;
|
||||||
|
statusDiv.textContent = "Turno de " + currentPlayer;
|
||||||
|
renderBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBoard() {
|
||||||
|
boardDiv.innerHTML = '';
|
||||||
|
board.forEach((cell, idx) => {
|
||||||
|
const cellDiv = document.createElement('div');
|
||||||
|
cellDiv.className = 'cell';
|
||||||
|
cellDiv.textContent = cell;
|
||||||
|
cellDiv.onclick = () => handleCellClick(idx);
|
||||||
|
boardDiv.appendChild(cellDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCellClick(idx) {
|
||||||
|
if (gameOver || board[idx] !== '') return;
|
||||||
|
board[idx] = currentPlayer;
|
||||||
|
renderBoard();
|
||||||
|
if (checkWinner(currentPlayer)) {
|
||||||
|
statusDiv.textContent = `¡${currentPlayer} gana! 🎉`;
|
||||||
|
gameOver = true;
|
||||||
|
} else if (board.every(cell => cell !== '')) {
|
||||||
|
statusDiv.textContent = "¡Empate!";
|
||||||
|
gameOver = true;
|
||||||
|
} else {
|
||||||
|
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
|
||||||
|
statusDiv.textContent = "Turno de " + currentPlayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWinner(player) {
|
||||||
|
const winCases = [
|
||||||
|
[0,1,2],[3,4,5],[6,7,8], // Filas
|
||||||
|
[0,3,6],[1,4,7],[2,5,8], // Columnas
|
||||||
|
[0,4,8],[2,4,6] // Diagonales
|
||||||
|
];
|
||||||
|
return winCases.some(combo => combo.every(i => board[i] === player));
|
||||||
|
}
|
||||||
|
|
||||||
|
restartBtn.onclick = initGame;
|
||||||
|
|
||||||
|
initGame();
|
121
3-en-raya/styles.css
Normal file
121
3-en-raya/styles.css
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
body {
|
||||||
|
background: #eef2f3;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #222;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: #3498db;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Board responsive styles */
|
||||||
|
#board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(60px, 22vw));
|
||||||
|
grid-template-rows: repeat(3, minmax(60px, 22vw));
|
||||||
|
gap: 0.8em;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 2rem auto 1.5rem auto;
|
||||||
|
max-width: 95vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cell responsive styles */
|
||||||
|
.cell {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
box-shadow: 0 2px 8px #bbb;
|
||||||
|
font-size: 8em;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell:hover, .cell:focus {
|
||||||
|
background: #f1fafe;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
font-size: 1.15em;
|
||||||
|
min-height: 2em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-btn {
|
||||||
|
background: #3498db;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
padding: 0.7em 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-btn:hover,
|
||||||
|
#restart-btn:focus {
|
||||||
|
background: #297fb8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SMALL DEVICES (phones) */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(3, minmax(40px, 28vw));
|
||||||
|
grid-template-rows: repeat(3, minmax(40px, 28vw));
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: 10em;
|
||||||
|
border-radius: 0.35em;
|
||||||
|
}
|
||||||
|
#status {
|
||||||
|
font-size: 1em;
|
||||||
|
min-height: 1.2em;
|
||||||
|
margin-bottom: 0.7em;
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
font-size: 0.95em;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TABLETS */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(3, minmax(50px, 22vw));
|
||||||
|
grid-template-rows: repeat(3, minmax(50px, 22vw));
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BIG SCREENS */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(3, 110px);
|
||||||
|
grid-template-rows: repeat(3, 110px);
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
}
|
15
4-en-raya/index.html
Normal file
15
4-en-raya/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>4 en Raya vs Máquina</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>4 en Raya vs Máquina</h1>
|
||||||
|
<div id="board"></div>
|
||||||
|
<div id="status"></div>
|
||||||
|
<button id="restart-btn">Reiniciar</button>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
151
4-en-raya/script.js
Normal file
151
4-en-raya/script.js
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
const ROWS = 6;
|
||||||
|
const COLS = 7;
|
||||||
|
const HUMAN = '🟡';
|
||||||
|
const AI = '❌';
|
||||||
|
const boardDiv = document.getElementById('board');
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
const restartBtn = document.getElementById('restart-btn');
|
||||||
|
let board, gameOver, currentPlayer;
|
||||||
|
|
||||||
|
function initGame() {
|
||||||
|
board = Array.from({length: ROWS}, () => Array(COLS).fill(''));
|
||||||
|
gameOver = false;
|
||||||
|
currentPlayer = HUMAN;
|
||||||
|
statusDiv.textContent = "Tu turno (🟡)";
|
||||||
|
renderBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBoard() {
|
||||||
|
boardDiv.innerHTML = '';
|
||||||
|
// Creamos todas las celdas primero
|
||||||
|
for (let row = 0; row < ROWS; row++) {
|
||||||
|
for (let col = 0; col < COLS; col++) {
|
||||||
|
const cellDiv = document.createElement('div');
|
||||||
|
cellDiv.className = 'cell';
|
||||||
|
cellDiv.textContent = board[row][col];
|
||||||
|
// Si es turno humano y no está terminado el juego
|
||||||
|
if (!gameOver && currentPlayer === HUMAN && board[row][col] === '') {
|
||||||
|
let lowestRow = getLowestEmptyRow(col);
|
||||||
|
if (lowestRow === row) {
|
||||||
|
cellDiv.style.cursor = "pointer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// El evento click siempre llama para la columna
|
||||||
|
cellDiv.onclick = () => {
|
||||||
|
if (!gameOver && currentPlayer === HUMAN) {
|
||||||
|
playerMove(col);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
boardDiv.appendChild(cellDiv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLowestEmptyRow(col) {
|
||||||
|
for (let r = ROWS - 1; r >= 0; r--) {
|
||||||
|
if (board[r][col] === '') return r;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerMove(col) {
|
||||||
|
if (gameOver || currentPlayer !== HUMAN) return;
|
||||||
|
let row = getLowestEmptyRow(col);
|
||||||
|
if (row === -1) return; // columna llena
|
||||||
|
board[row][col] = HUMAN;
|
||||||
|
renderBoard();
|
||||||
|
if (checkWinner(HUMAN)) {
|
||||||
|
statusDiv.textContent = "¡Has ganado! 🎉";
|
||||||
|
gameOver = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isFull()) {
|
||||||
|
statusDiv.textContent = "¡Empate!";
|
||||||
|
gameOver = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentPlayer = AI;
|
||||||
|
statusDiv.textContent = "Turno de la máquina (❌)";
|
||||||
|
setTimeout(machineMove, 450);
|
||||||
|
}
|
||||||
|
|
||||||
|
function machineMove() {
|
||||||
|
if (gameOver || currentPlayer !== AI) return;
|
||||||
|
// IA sencilla: elige columna aleatoria libre
|
||||||
|
let validCols = [];
|
||||||
|
for (let c = 0; c < COLS; c++) {
|
||||||
|
if (board[0][c] === '') validCols.push(c);
|
||||||
|
}
|
||||||
|
if (validCols.length === 0) return;
|
||||||
|
let col = validCols[Math.floor(Math.random() * validCols.length)];
|
||||||
|
let row = getLowestEmptyRow(col);
|
||||||
|
board[row][col] = AI;
|
||||||
|
renderBoard();
|
||||||
|
if (checkWinner(AI)) {
|
||||||
|
statusDiv.textContent = "¡La máquina gana! 🤖";
|
||||||
|
gameOver = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isFull()) {
|
||||||
|
statusDiv.textContent = "¡Empate!";
|
||||||
|
gameOver = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentPlayer = HUMAN;
|
||||||
|
statusDiv.textContent = "Tu turno (🟡)";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFull() {
|
||||||
|
return board[0].every(cell => cell !== '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWinner(player) {
|
||||||
|
// Horizontal
|
||||||
|
for (let r = 0; r < ROWS; r++) {
|
||||||
|
for (let c = 0; c < COLS - 3; c++) {
|
||||||
|
if (
|
||||||
|
board[r][c] === player &&
|
||||||
|
board[r][c + 1] === player &&
|
||||||
|
board[r][c + 2] === player &&
|
||||||
|
board[r][c + 3] === player
|
||||||
|
) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Vertical
|
||||||
|
for (let c = 0; c < COLS; c++) {
|
||||||
|
for (let r = 0; r < ROWS - 3; r++) {
|
||||||
|
if (
|
||||||
|
board[r][c] === player &&
|
||||||
|
board[r + 1][c] === player &&
|
||||||
|
board[r + 2][c] === player &&
|
||||||
|
board[r + 3][c] === player
|
||||||
|
) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Diagonal descendente \
|
||||||
|
for (let r = 0; r < ROWS - 3; r++) {
|
||||||
|
for (let c = 0; c < COLS - 3; c++) {
|
||||||
|
if (
|
||||||
|
board[r][c] === player &&
|
||||||
|
board[r + 1][c + 1] === player &&
|
||||||
|
board[r + 2][c + 2] === player &&
|
||||||
|
board[r + 3][c + 3] === player
|
||||||
|
) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Diagonal ascendente /
|
||||||
|
for (let r = 3; r < ROWS; r++) {
|
||||||
|
for (let c = 0; c < COLS - 3; c++) {
|
||||||
|
if (
|
||||||
|
board[r][c] === player &&
|
||||||
|
board[r - 1][c + 1] === player &&
|
||||||
|
board[r - 2][c + 2] === player &&
|
||||||
|
board[r - 3][c + 3] === player
|
||||||
|
) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
restartBtn.onclick = initGame;
|
||||||
|
initGame();
|
132
4-en-raya/styles.css
Normal file
132
4-en-raya/styles.css
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
body {
|
||||||
|
background: #eef2f3;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #222;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: #d7263d;
|
||||||
|
font-size: 2.1rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablero responsive */
|
||||||
|
#board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, minmax(35px, 11vw));
|
||||||
|
grid-template-rows: repeat(6, minmax(35px, 11vw));
|
||||||
|
gap: 0.6em;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 2rem auto 1.3rem auto;
|
||||||
|
background: #1565c0;
|
||||||
|
padding: 1.2em 0;
|
||||||
|
border-radius: 15px;
|
||||||
|
max-width: 100vw;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Celda responsive */
|
||||||
|
.cell {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 2px 8px #bbb;
|
||||||
|
font-size: 4.2em;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell:hover,
|
||||||
|
.cell:focus {
|
||||||
|
background: #ffe082;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
font-size: 1.1em;
|
||||||
|
min-height: 2em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-btn {
|
||||||
|
background: #d7263d;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 0.7em 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-btn:hover,
|
||||||
|
#restart-btn:focus {
|
||||||
|
background: #920c22;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Celulares */
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(7, minmax(22px, 12vw));
|
||||||
|
grid-template-rows: repeat(6, minmax(22px, 12vw));
|
||||||
|
gap: 0.25em;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 1rem auto 1rem auto;
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: 4.2em;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
#status {
|
||||||
|
font-size: 0.97em;
|
||||||
|
min-height: 1.2em;
|
||||||
|
margin-bottom: 0.8em;
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding: 0.45em 1em;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablets */
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(7, minmax(28px, 11vw));
|
||||||
|
grid-template-rows: repeat(6, minmax(28px, 11vw));
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: 4.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pantallas grandes */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(7, 64px);
|
||||||
|
grid-template-rows: repeat(6, 64px);
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
}
|
21
adivina/index.html
Normal file
21
adivina/index.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Adivina el Número</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>¡Adivina el número!</h1>
|
||||||
|
<div id="game-box">
|
||||||
|
<p>Estoy pensando en un número entre 1 y 100.</p>
|
||||||
|
<p>¿Puedes adivinarlo en 7 intentos?</p>
|
||||||
|
<input id="guess-input" type="number" min="1" max="100" placeholder="Tu número" />
|
||||||
|
<button id="guess-btn">Adivinar</button>
|
||||||
|
<button id="restart-btn" class="hidden">Jugar de nuevo</button>
|
||||||
|
<div id="info"></div>
|
||||||
|
<div id="attempts">Intentos restantes: <span id="attempts-left">7</span></div>
|
||||||
|
</div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
55
adivina/script.js
Normal file
55
adivina/script.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
let randomNumber;
|
||||||
|
let attemptsLeft = 7;
|
||||||
|
|
||||||
|
const guessInput = document.getElementById('guess-input');
|
||||||
|
const guessBtn = document.getElementById('guess-btn');
|
||||||
|
const restartBtn = document.getElementById('restart-btn');
|
||||||
|
const infoDiv = document.getElementById('info');
|
||||||
|
const attemptsSpan = document.getElementById('attempts-left');
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
randomNumber = Math.floor(Math.random() * 100) + 1;
|
||||||
|
attemptsLeft = 7;
|
||||||
|
attemptsSpan.textContent = attemptsLeft;
|
||||||
|
infoDiv.textContent = '';
|
||||||
|
guessInput.value = '';
|
||||||
|
guessInput.disabled = false;
|
||||||
|
guessBtn.disabled = false;
|
||||||
|
restartBtn.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkGuess() {
|
||||||
|
const guess = Number(guessInput.value);
|
||||||
|
|
||||||
|
if (guess < 1 || guess > 100 || isNaN(guess)) {
|
||||||
|
infoDiv.textContent = "Por favor ingresa un número válido entre 1 y 100.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
attemptsLeft--;
|
||||||
|
attemptsSpan.textContent = attemptsLeft;
|
||||||
|
|
||||||
|
if (guess === randomNumber) {
|
||||||
|
infoDiv.textContent = "¡Correcto! 🎉 Has adivinado el número.";
|
||||||
|
endGame(true);
|
||||||
|
} else if (attemptsLeft === 0) {
|
||||||
|
infoDiv.textContent = `¡Oh no! Te quedaste sin intentos. El número era ${randomNumber}.`;
|
||||||
|
endGame(false);
|
||||||
|
} else if (guess < randomNumber) {
|
||||||
|
infoDiv.textContent = "Demasiado bajo. Intenta nuevamente.";
|
||||||
|
} else {
|
||||||
|
infoDiv.textContent = "Demasiado alto. Intenta nuevamente.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function endGame(won) {
|
||||||
|
guessInput.disabled = true;
|
||||||
|
guessBtn.disabled = true;
|
||||||
|
restartBtn.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
guessBtn.onclick = checkGuess;
|
||||||
|
restartBtn.onclick = startGame;
|
||||||
|
guessInput.onkeydown = (e) => { if (e.key === "Enter") checkGuess(); };
|
||||||
|
|
||||||
|
startGame();
|
68
adivina/styles.css
Normal file
68
adivina/styles.css
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
body {
|
||||||
|
background: #f7f7f7;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 40px;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
#game-box {
|
||||||
|
background: white;
|
||||||
|
width: 350px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 30px 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 0 8px #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
width: 100px;
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding: 6px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 7px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #2196f3;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #1769aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin-top: 14px;
|
||||||
|
min-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#attempts {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 728px) {
|
||||||
|
body,
|
||||||
|
#game-box,
|
||||||
|
input[type="number"],
|
||||||
|
button,
|
||||||
|
#info,
|
||||||
|
#attempts {
|
||||||
|
width: 95%;
|
||||||
|
font-size: 130%;
|
||||||
|
}
|
||||||
|
}
|
15
buscaminas/index.html
Normal file
15
buscaminas/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Buscaminas</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Buscaminas</h1>
|
||||||
|
<div id="status"></div>
|
||||||
|
<div id="board"></div>
|
||||||
|
<button id="restart-btn">Reiniciar</button>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
134
buscaminas/script.js
Normal file
134
buscaminas/script.js
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
const SIZE = 10;
|
||||||
|
const MINES = 15;
|
||||||
|
const boardDiv = document.getElementById('board');
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
const restartBtn = document.getElementById('restart-btn');
|
||||||
|
let board, revealed, flagged, gameOver, cellsRevealed;
|
||||||
|
|
||||||
|
function initGame() {
|
||||||
|
board = Array.from({length: SIZE}, () => Array(SIZE).fill(0));
|
||||||
|
revealed = Array.from({length: SIZE}, () => Array(SIZE).fill(false));
|
||||||
|
flagged = Array.from({length: SIZE}, () => Array(SIZE).fill(false));
|
||||||
|
gameOver = false;
|
||||||
|
cellsRevealed = 0;
|
||||||
|
statusDiv.textContent = 'Haz clic izquierdo para revelar, clic derecho para marcar.';
|
||||||
|
placeMines();
|
||||||
|
calculateNumbers();
|
||||||
|
renderBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeMines() {
|
||||||
|
let placed = 0;
|
||||||
|
while (placed < MINES) {
|
||||||
|
const r = Math.floor(Math.random() * SIZE);
|
||||||
|
const c = Math.floor(Math.random() * SIZE);
|
||||||
|
if (board[r][c] !== 'M') {
|
||||||
|
board[r][c] = 'M';
|
||||||
|
placed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateNumbers() {
|
||||||
|
for (let r = 0; r < SIZE; r++) {
|
||||||
|
for (let c = 0; c < SIZE; c++) {
|
||||||
|
if (board[r][c] === 'M') continue;
|
||||||
|
let count = 0;
|
||||||
|
for (let i = -1; i <= 1; i++) {
|
||||||
|
for (let j = -1; j <= 1; j++) {
|
||||||
|
let nr = r+i, nc = c+j;
|
||||||
|
if (nr >= 0 && nr < SIZE && nc >=0 && nc < SIZE && board[nr][nc] === 'M') count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
board[r][c] = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBoard() {
|
||||||
|
boardDiv.innerHTML = '';
|
||||||
|
for (let r = 0; r < SIZE; r++) {
|
||||||
|
for (let c = 0; c < SIZE; c++) {
|
||||||
|
const cell = document.createElement('div');
|
||||||
|
cell.classList.add('cell');
|
||||||
|
if (revealed[r][c]) {
|
||||||
|
cell.classList.add('revealed');
|
||||||
|
if (board[r][c] === 'M') {
|
||||||
|
cell.classList.add('mine');
|
||||||
|
cell.textContent = '💣';
|
||||||
|
} else if (board[r][c] > 0) {
|
||||||
|
cell.textContent = board[r][c];
|
||||||
|
} else {
|
||||||
|
cell.textContent = '';
|
||||||
|
}
|
||||||
|
} else if (flagged[r][c]) {
|
||||||
|
cell.classList.add('flag');
|
||||||
|
cell.textContent = '🚩';
|
||||||
|
} else {
|
||||||
|
cell.textContent = '';
|
||||||
|
}
|
||||||
|
// Click izquierdo
|
||||||
|
cell.onmousedown = (e) => {
|
||||||
|
if (gameOver) return;
|
||||||
|
if (e.button === 0) revealCell(r, c);
|
||||||
|
else if (e.button === 2) toggleFlag(r, c);
|
||||||
|
};
|
||||||
|
cell.oncontextmenu = (e) => e.preventDefault();
|
||||||
|
boardDiv.appendChild(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function revealCell(r, c) {
|
||||||
|
if (revealed[r][c] || flagged[r][c]) return;
|
||||||
|
revealed[r][c] = true;
|
||||||
|
cellsRevealed++;
|
||||||
|
if (board[r][c] === 'M') {
|
||||||
|
endGame(false);
|
||||||
|
renderBoard();
|
||||||
|
return;
|
||||||
|
} else if (board[r][c] === 0) {
|
||||||
|
// Revela recursivo
|
||||||
|
for (let i = -1; i <= 1; i++) {
|
||||||
|
for (let j = -1; j <= 1; j++) {
|
||||||
|
let nr = r+i, nc = c+j;
|
||||||
|
if (nr >= 0 && nr < SIZE && nc >= 0 && nc < SIZE) {
|
||||||
|
if (!revealed[nr][nc]) revealCell(nr, nc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkWin();
|
||||||
|
renderBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFlag(r, c) {
|
||||||
|
if (revealed[r][c]) return;
|
||||||
|
flagged[r][c] = !flagged[r][c];
|
||||||
|
renderBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWin() {
|
||||||
|
if (cellsRevealed === SIZE*SIZE - MINES) {
|
||||||
|
endGame(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function endGame(won) {
|
||||||
|
gameOver = true;
|
||||||
|
// Revela todas las minas si perdiste
|
||||||
|
if (!won) {
|
||||||
|
for (let r = 0; r < SIZE; r++) {
|
||||||
|
for (let c = 0; c < SIZE; c++) {
|
||||||
|
if (board[r][c] === 'M') revealed[r][c] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statusDiv.textContent = "¡BOOM! Has perdido 💣";
|
||||||
|
} else {
|
||||||
|
statusDiv.textContent = "¡Felicidades! Has ganado 🎉";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restartBtn.onclick = initGame;
|
||||||
|
|
||||||
|
initGame();
|
149
buscaminas/styles.css
Normal file
149
buscaminas/styles.css
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
/* ===== Diseño base ===== */
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f2f2f2;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
color: #222;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Encabezado ===== */
|
||||||
|
h1 {
|
||||||
|
color: #d7263d;
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-size: clamp(2rem, 4vw, 2.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Tablero ===== */
|
||||||
|
#board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(10, 1fr);
|
||||||
|
gap: 0.7vw;
|
||||||
|
margin: 2.5rem auto 1.7rem auto;
|
||||||
|
background: #1565c0;
|
||||||
|
padding: 2vw;
|
||||||
|
border-radius: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 98vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Una celda */
|
||||||
|
.cell {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 0.7rem;
|
||||||
|
box-shadow: 0 2px 8px #bbb;
|
||||||
|
font-size: clamp(1rem, 2.5vw, 1.4rem);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.15s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.cell.revealed { background: #bbdefb; cursor: default; }
|
||||||
|
.cell.mine { background: #d7263d; color: #fff; }
|
||||||
|
.cell.flag { background: #ffe082; color: #d7263d; }
|
||||||
|
|
||||||
|
/* ===== Estado y botón ===== */
|
||||||
|
#status {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
min-height: 2em;
|
||||||
|
margin: 0.5em 0 0.7em 0;
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
background: #d7263d;
|
||||||
|
color: #fff;
|
||||||
|
font-size: clamp(1rem, 2vw, 1.3rem);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.7rem;
|
||||||
|
padding: 0.5em 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
margin-bottom: 1.2em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
#restart-btn:hover { background: #920c22; }
|
||||||
|
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
===== Pantallas pequeñas (<720px) ====
|
||||||
|
======================================== */
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
h1 {
|
||||||
|
margin-top: 3vw;
|
||||||
|
font-size: clamp(1.3rem, 6vw, 2.1rem);
|
||||||
|
}
|
||||||
|
#board {
|
||||||
|
/* Cambia columnas si el tablero lo permite */
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
padding: 4vw;
|
||||||
|
border-radius: 4vw;
|
||||||
|
gap: 2.8vw;
|
||||||
|
margin-top: 4vw;
|
||||||
|
margin-bottom: 6vw;
|
||||||
|
max-width: 99vw;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: clamp(1.2rem, 8vw, 2.5rem);
|
||||||
|
border-radius: 3vw;
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
font-size: clamp(1rem, 8vw, 2rem);
|
||||||
|
padding: 3vw 12vw;
|
||||||
|
border-radius: 3vw;
|
||||||
|
margin-bottom: 6vw;
|
||||||
|
margin-top: 3vw;
|
||||||
|
}
|
||||||
|
#status {
|
||||||
|
font-size: clamp(1rem, 7vw, 1.7rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
===== Pantallas muy pequeñas (<400px) ==
|
||||||
|
======================================== */
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
#board {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
padding: 2vw;
|
||||||
|
gap: 2vw;
|
||||||
|
border-radius: 5vw;
|
||||||
|
}
|
||||||
|
.cell { font-size: clamp(1rem, 10vw, 2.2rem); }
|
||||||
|
#restart-btn {
|
||||||
|
padding: 3vw 4vw;
|
||||||
|
font-size: clamp(0.9rem, 7vw, 1.5rem);
|
||||||
|
border-radius: 5vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
===== Pantallas grandes (>1200px) ====
|
||||||
|
======================================== */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
#board {
|
||||||
|
max-width: 700px;
|
||||||
|
padding: 2rem;
|
||||||
|
gap: 0.7rem;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(2.5rem, 3vw, 3.5rem);
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: clamp(1.3rem, 2vw, 2rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* :::::::::::::::::::::::::::::: */
|
269
index.html
Normal file
269
index.html
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>FerMdez - Games</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:700,400&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(120deg, #9cebfc 0%, #e9defa 100%);
|
||||||
|
font-family: 'Montserrat', Arial, sans-serif;
|
||||||
|
color: #242424;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 55px 0 22px 0;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2.4em;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
color: #3f3d56;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-shadow: 0 1px 11px #b7d4ed73;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
color: #444;
|
||||||
|
font-size: 1.15em;
|
||||||
|
margin: 0 0 28px 0;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px 16px 60px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(255px, 1fr));
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card {
|
||||||
|
background: rgba(255,255,255,0.85);
|
||||||
|
border-radius: 18px;
|
||||||
|
box-shadow: 0 6px 32px rgba(51, 101, 160, 0.11), 0 0 0 1.5px #e6e6f9;
|
||||||
|
padding: 0 0 20px 0;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
transition: box-shadow 0.22s, transform 0.16s;
|
||||||
|
will-change: transform;
|
||||||
|
min-height: 290px;
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
border: 1.5px solid #f4f1fe;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.game-card:hover {
|
||||||
|
box-shadow: 0 12px 32px 0 rgba(39,85,151,0.13), 0 0 0 2.5px #6cb2ed;
|
||||||
|
transform: translateY(-7px) scale(1.03);
|
||||||
|
z-index: 2;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-icon {
|
||||||
|
font-size: 2.7em;
|
||||||
|
margin-top: 23px;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
filter: grayscale(0.2) drop-shadow(0 3px 6px #caf0f8c6);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.game-title {
|
||||||
|
font-size: 1.15em;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #234075;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.game-desc {
|
||||||
|
font-size: 1em;
|
||||||
|
color: #454545;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
margin-top: 0;
|
||||||
|
text-align: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn {
|
||||||
|
background: linear-gradient(108deg,#3d5afe 55%,#228be6 110%);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 44px;
|
||||||
|
padding: 13px 0;
|
||||||
|
font-size: 1.05em;
|
||||||
|
font-weight: 700;
|
||||||
|
width: 88%;
|
||||||
|
box-shadow: 0 2px 12px #b7d4ed99;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.22s, box-shadow 0.16s, transform 0.13s;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1.1px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: auto;
|
||||||
|
outline: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
border: 2px solid #dbeafe;
|
||||||
|
animation: btn-pop 1.1s cubic-bezier(.4,.28,.32,.99) both;
|
||||||
|
}
|
||||||
|
.play-btn:hover, .play-btn:focus {
|
||||||
|
background: linear-gradient(108deg,#0a2459 55%,#195987 110%);
|
||||||
|
transform: scale(1.055);
|
||||||
|
box-shadow: 0 7px 22px #3d5afe1a;
|
||||||
|
border-color: #3d5afe;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.play-btn svg {
|
||||||
|
margin-left: 5px;
|
||||||
|
width: 1.4em;
|
||||||
|
height: 1.3em;
|
||||||
|
vertical-align: middle;
|
||||||
|
fill: #fff;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.grid { gap: 23px; }
|
||||||
|
.game-card { min-height: 240px; }
|
||||||
|
header { padding-bottom: 6px; }
|
||||||
|
main { padding: 10px 3vw 35px 3vw;}
|
||||||
|
}
|
||||||
|
@media (max-width: 660px) {
|
||||||
|
h1 { font-size: 1.45em; }
|
||||||
|
.game-title { font-size: 1em;}
|
||||||
|
.game-desc { font-size: 0.98em;}
|
||||||
|
.game-card { min-height: 180px;}
|
||||||
|
}
|
||||||
|
@keyframes btn-pop { 0%{transform:scale(.98);} 100%{transform:scale(1);} }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Fermdez - Juegos</h1>
|
||||||
|
<p>¡Prueba estos mini juegos creados por <a href="https://fermdez.net/" target="_blank">Fernando Méndez</a>!<br />Elige, juega y supera tus récords.</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🏓</div>
|
||||||
|
<div class="game-title">Pong Clásico</div>
|
||||||
|
<div class="game-desc">Juega al clásico Pong contra la máquina usando las flechas.</div>
|
||||||
|
<a class="play-btn" href="pong/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🔲</div>
|
||||||
|
<div class="game-title">Simon Dice</div>
|
||||||
|
<div class="game-desc">Recuerda y repite la secuencia de colores para avanzar de nivel.</div>
|
||||||
|
<a class="play-btn" href="simon-dice/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🧮</div>
|
||||||
|
<div class="game-title">Buscaminas</div>
|
||||||
|
<div class="game-desc">Marca las minas y evita explotarlas en el tablero.</div>
|
||||||
|
<a class="play-btn" href="buscaminas/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🃏</div>
|
||||||
|
<div class="game-title">Juego de Memoria</div>
|
||||||
|
<div class="game-desc">Descubre todas las parejas de cartas y ejercita tu memoria.</div>
|
||||||
|
<a class="play-btn" href="memoria/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🃏</div>
|
||||||
|
<div class="game-title">Juego de Memoria Avanzado</div>
|
||||||
|
<div class="game-desc">Descubre todas las parejas de cartas y ejercita tu memoria.</div>
|
||||||
|
<a class="play-btn" href="memoria-v2/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">⭕❌</div>
|
||||||
|
<div class="game-title">3 en Raya</div>
|
||||||
|
<div class="game-desc">Tres en línea clásico: reta a un amigo.</div>
|
||||||
|
<a class="play-btn" href="3-en-raya/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">⭕❌</div>
|
||||||
|
<div class="game-title">3 en Raya vs Máquina</div>
|
||||||
|
<div class="game-desc">Tres en línea clásico: reta a una IA invencible.</div>
|
||||||
|
<a class="play-btn" href="3-en-raya-computer/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🔵🔴🔵🔴</div>
|
||||||
|
<div class="game-title">4 en Raya vs Máquina</div>
|
||||||
|
<div class="game-desc">Conecta 4 fichas antes que la máquina en este clásico de estrategia.</div>
|
||||||
|
<a class="play-btn" href="4-en-raya/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🧩</div>
|
||||||
|
<div class="game-title">Puzzle de Números</div>
|
||||||
|
<div class="game-desc">Resuelve el clásico puzzle de 15 piezas deslizantes.</div>
|
||||||
|
<a class="play-btn" href="puzle-numeros/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🔢</div>
|
||||||
|
<div class="game-title">Adivina el Número</div>
|
||||||
|
<div class="game-desc">Resuelve el número que ha pensado la máquina en menos de 7 intentos.</div>
|
||||||
|
<a class="play-btn" href="puzle-numeros/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🐍</div>
|
||||||
|
<div class="game-title">Snake Game</div>
|
||||||
|
<div class="game-desc">Haz crecer la serpiente comiendo puntos, ¡no choques con la pared!</div>
|
||||||
|
<a class="play-btn" href="snake/index.html" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🧱</div>
|
||||||
|
<div class="game-title">Rompe Ladrillos</div>
|
||||||
|
<div class="game-desc">Rompe todos los ladrillos controlando la pala y la bola.</div>
|
||||||
|
<a class="play-btn" href="ladrillos" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">⏱️</div>
|
||||||
|
<div class="game-title">Carrera de Reacción</div>
|
||||||
|
<div class="game-desc">Haz clic cuando la pantalla se ponga verde, ¡mide tu tiempo de reacción!</div>
|
||||||
|
<a class="play-btn" href="reflejos/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="game-card">
|
||||||
|
<div class="game-icon">🦦</div>
|
||||||
|
<div class="game-title">Atrapa el Topo</div>
|
||||||
|
<div class="game-desc">Haz clic en el topo cuando aparezca y suma puntos.</div>
|
||||||
|
<a class="play-btn" href="topo/" target="_blank">Jugar
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
17
ladrillos/index.html
Normal file
17
ladrillos/index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Breakout</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Rompe Ladrillos</h1>
|
||||||
|
<p>Usa el ratón para mover el bloque. Rompe todos los ladrillos para ganar.</p>
|
||||||
|
<canvas id="gameCanvas" width="540" height="480"></canvas>
|
||||||
|
<div id="score">Puntuación: <span id="score-value">0</span></div>
|
||||||
|
<button id="restart-btn">Reiniciar</button>
|
||||||
|
<div id="game-over-message"></div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
147
ladrillos/script.js
Normal file
147
ladrillos/script.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
const canvas = document.getElementById('gameCanvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const scoreSpan = document.getElementById('score-value');
|
||||||
|
const restartBtn = document.getElementById('restart-btn');
|
||||||
|
const gameOverDiv = document.getElementById('game-over-message');
|
||||||
|
|
||||||
|
const ballRadius = 8;
|
||||||
|
let x, y, dx, dy;
|
||||||
|
const paddleHeight = 12, paddleWidth = 75;
|
||||||
|
let paddleX;
|
||||||
|
const brickRowCount = 5, brickColumnCount = 7;
|
||||||
|
const brickWidth = 60, brickHeight = 20, brickPadding = 10, brickOffsetTop = 30, brickOffsetLeft = 30;
|
||||||
|
let bricks = [];
|
||||||
|
let score, gameOver;
|
||||||
|
|
||||||
|
function setupBricks() {
|
||||||
|
bricks = [];
|
||||||
|
for(let c=0; c<brickColumnCount; c++) {
|
||||||
|
bricks[c] = [];
|
||||||
|
for(let r=0; r<brickRowCount; r++) {
|
||||||
|
bricks[c][r] = { x:0, y:0, status:1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
x = canvas.width/2;
|
||||||
|
y = canvas.height-30;
|
||||||
|
dx = 3;
|
||||||
|
dy = -3;
|
||||||
|
paddleX = (canvas.width-paddleWidth)/2;
|
||||||
|
score = 0;
|
||||||
|
scoreSpan.textContent = score;
|
||||||
|
gameOver = false;
|
||||||
|
gameOverDiv.textContent = '';
|
||||||
|
setupBricks();
|
||||||
|
document.addEventListener("mousemove", mouseMoveHandler);
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseMoveHandler(e) {
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
let relativeX = e.clientX - rect.left;
|
||||||
|
if(relativeX > 0 && relativeX < canvas.width) {
|
||||||
|
paddleX = relativeX - paddleWidth/2;
|
||||||
|
if(paddleX < 0) paddleX = 0;
|
||||||
|
if(paddleX + paddleWidth > canvas.width) paddleX = canvas.width - paddleWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function collisionDetection() {
|
||||||
|
for(let c=0; c<brickColumnCount; c++) {
|
||||||
|
for(let r=0; r<brickRowCount; r++) {
|
||||||
|
const b = bricks[c][r];
|
||||||
|
if(b.status == 1) {
|
||||||
|
if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHeight) {
|
||||||
|
dy = -dy;
|
||||||
|
b.status = 0;
|
||||||
|
score++;
|
||||||
|
scoreSpan.textContent = score;
|
||||||
|
if(score == brickRowCount*brickColumnCount) {
|
||||||
|
gameOver = true;
|
||||||
|
gameOverDiv.textContent = "¡Ganaste! 🏆";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBall() {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
|
||||||
|
ctx.fillStyle = "#f9d923";
|
||||||
|
ctx.fill();
|
||||||
|
ctx.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawPaddle() {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(paddleX, canvas.height-paddleHeight-5, paddleWidth, paddleHeight);
|
||||||
|
ctx.fillStyle = "#e94560";
|
||||||
|
ctx.fill();
|
||||||
|
ctx.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBricks() {
|
||||||
|
for(let c=0; c<brickColumnCount; c++) {
|
||||||
|
for(let r=0; r<brickRowCount; r++) {
|
||||||
|
if(bricks[c][r].status == 1) {
|
||||||
|
const brickX = (c*(brickWidth+brickPadding))+brickOffsetLeft;
|
||||||
|
const brickY = (r*(brickHeight+brickPadding))+brickOffsetTop;
|
||||||
|
bricks[c][r].x = brickX;
|
||||||
|
bricks[c][r].y = brickY;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(brickX, brickY, brickWidth, brickHeight);
|
||||||
|
ctx.fillStyle = "#393e46";
|
||||||
|
ctx.strokeStyle = "#f9d923";
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.closePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
drawBricks();
|
||||||
|
drawBall();
|
||||||
|
drawPaddle();
|
||||||
|
collisionDetection();
|
||||||
|
|
||||||
|
// Rebote en las paredes
|
||||||
|
if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
|
||||||
|
dx = -dx;
|
||||||
|
}
|
||||||
|
if(y + dy < ballRadius) {
|
||||||
|
dy = -dy;
|
||||||
|
} else if(y + dy > canvas.height-ballRadius-paddleHeight-5) {
|
||||||
|
// Rebote en la paleta
|
||||||
|
if(x > paddleX && x < paddleX + paddleWidth) {
|
||||||
|
dy = -dy;
|
||||||
|
} else if (y + dy > canvas.height-ballRadius) {
|
||||||
|
// GAME OVER
|
||||||
|
gameOver = true;
|
||||||
|
gameOverDiv.textContent = "¡Game Over! 😢";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x += dx;
|
||||||
|
y += dy;
|
||||||
|
|
||||||
|
if(!gameOver) {
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restartBtn.onclick = function() {
|
||||||
|
document.removeEventListener("mousemove", mouseMoveHandler);
|
||||||
|
startGame();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inicio
|
||||||
|
startGame();
|
115
ladrillos/styles.css
Normal file
115
ladrillos/styles.css
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/* Reset y box-sizing global */
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variables de colores y métricas */
|
||||||
|
:root {
|
||||||
|
/* Colores */
|
||||||
|
--color-bg: #1a1a2e;
|
||||||
|
--color-text: #e7e7de;
|
||||||
|
--color-accent: #e94560;
|
||||||
|
--color-highlight: #f9d923;
|
||||||
|
--color-canvas-bg: #0f3460;
|
||||||
|
--color-shadow: rgba(37, 34, 43, 0.8);
|
||||||
|
|
||||||
|
/* Sombras y radios */
|
||||||
|
--radius: 8px;
|
||||||
|
--shadow: 0 0 16px var(--color-shadow);
|
||||||
|
|
||||||
|
/* Tipografía */
|
||||||
|
--font: 'Arial', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BODY: centrar y padding responsive */
|
||||||
|
body {
|
||||||
|
background: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-family: var(--font);
|
||||||
|
font-size: 100%; /* 1rem = 16px base */
|
||||||
|
line-height: 1.4;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: clamp(1rem, 4vw, 2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TITULAR */
|
||||||
|
h1 {
|
||||||
|
margin-top: clamp(1.5rem, 6vw, 3rem);
|
||||||
|
color: var(--color-accent);
|
||||||
|
font-size: clamp(1.75rem, 8vw, 2.75rem);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PÁRRAFOS */
|
||||||
|
p {
|
||||||
|
color: #fff;
|
||||||
|
font-size: clamp(0.9rem, 2.5vw, 1.2rem);
|
||||||
|
margin: clamp(0.5rem, 2vw, 1rem) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CANVAS: ancho fluido, altura automática para mantener proporción */
|
||||||
|
canvas {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 640px;
|
||||||
|
aspect-ratio: 640 / 480;
|
||||||
|
height: auto;
|
||||||
|
background: var(--color-canvas-bg);
|
||||||
|
display: block;
|
||||||
|
margin: clamp(1rem, 4vw, 2rem) auto;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PUNTUACIÓN */
|
||||||
|
#score {
|
||||||
|
color: var(--color-highlight);
|
||||||
|
font-size: clamp(1rem, 2.5vw, 1.5rem);
|
||||||
|
margin-top: clamp(0.5rem, 2vw, 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MENSAJE DE GAME OVER */
|
||||||
|
#game-over-message {
|
||||||
|
color: var(--color-accent);
|
||||||
|
font-size: clamp(1.2rem, 4vw, 1.8rem);
|
||||||
|
margin-top: clamp(0.75rem, 3vw, 1.5rem);
|
||||||
|
min-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BOTÓN REINICIAR */
|
||||||
|
#restart-btn {
|
||||||
|
margin-top: clamp(0.75rem, 3vw, 1.5rem);
|
||||||
|
font-size: clamp(0.9rem, 2.5vw, 1.2rem);
|
||||||
|
padding: clamp(0.5rem, 2vw, 1rem) clamp(1rem, 4vw, 2rem);
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: #fafafa;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-btn:hover {
|
||||||
|
background: #f0134d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MEDIA QUERY (opcional) para pantallas muy pequeñas */
|
||||||
|
@media (max-width: 320px) {
|
||||||
|
body {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
20
memoria-v2/index.html
Normal file
20
memoria-v2/index.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Juego de Memoria</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Juego de Memoria Avanzado</h1>
|
||||||
|
<p>Haz clic sobre las cartas para descubrir y encontrar las parejas.</p>
|
||||||
|
<div id="hud">
|
||||||
|
<span id="moves"></span>
|
||||||
|
<span id="timer"></span>
|
||||||
|
</div>
|
||||||
|
<div id="game-board"></div>
|
||||||
|
<div id="status"></div>
|
||||||
|
<button id="restart-btn">Reiniciar</button>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
131
memoria-v2/script.js
Normal file
131
memoria-v2/script.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
const symbols = [
|
||||||
|
'🐶','🌸','⚽','🍕','🎲','🌞','🚗','🍩',
|
||||||
|
'⭐','🚀','🎮','💎'
|
||||||
|
];
|
||||||
|
let cards = [];
|
||||||
|
let firstCard = null;
|
||||||
|
let secondCard = null;
|
||||||
|
let lockBoard = false;
|
||||||
|
let matches = 0;
|
||||||
|
let moves = 0;
|
||||||
|
const maxMoves = 45;
|
||||||
|
let timer = 120; // segundos
|
||||||
|
let timerInterval = null;
|
||||||
|
const boardDiv = document.getElementById("game-board");
|
||||||
|
const statusDiv = document.getElementById("status");
|
||||||
|
const restartBtn = document.getElementById("restart-btn");
|
||||||
|
const movesSpan = document.getElementById("moves");
|
||||||
|
const timerSpan = document.getElementById("timer");
|
||||||
|
|
||||||
|
function shuffle(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]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
matches = 0;
|
||||||
|
moves = 0;
|
||||||
|
timer = 120;
|
||||||
|
firstCard = null;
|
||||||
|
secondCard = null;
|
||||||
|
lockBoard = false;
|
||||||
|
statusDiv.textContent = '';
|
||||||
|
updateHUD();
|
||||||
|
clearInterval(timerInterval);
|
||||||
|
timerInterval = setInterval(updateTimer, 1000);
|
||||||
|
cards = [...symbols, ...symbols];
|
||||||
|
shuffle(cards);
|
||||||
|
boardDiv.innerHTML = '';
|
||||||
|
cards.forEach((symbol, idx) => {
|
||||||
|
const card = document.createElement("div");
|
||||||
|
card.className = "card";
|
||||||
|
card.dataset.index = idx;
|
||||||
|
card.dataset.symbol = symbol;
|
||||||
|
card.textContent = '';
|
||||||
|
card.onclick = () => flipCard(card);
|
||||||
|
boardDiv.appendChild(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function flipCard(card) {
|
||||||
|
if (lockBoard) return;
|
||||||
|
if (card.classList.contains('flipped') || card.classList.contains('matched')) return;
|
||||||
|
|
||||||
|
card.classList.add('flipped');
|
||||||
|
card.textContent = card.dataset.symbol;
|
||||||
|
|
||||||
|
if (!firstCard) {
|
||||||
|
firstCard = card;
|
||||||
|
return; // ¡Esperamos por la segunda carta!
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card === firstCard) return;
|
||||||
|
secondCard = card;
|
||||||
|
lockBoard = true;
|
||||||
|
moves++;
|
||||||
|
updateHUD();
|
||||||
|
|
||||||
|
if (firstCard.dataset.symbol === secondCard.dataset.symbol) {
|
||||||
|
// Es PAR
|
||||||
|
firstCard.classList.add('matched');
|
||||||
|
secondCard.classList.add('matched');
|
||||||
|
matches++;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
firstCard.classList.add('hide');
|
||||||
|
secondCard.classList.add('hide');
|
||||||
|
resetTurn();
|
||||||
|
// Verifica victoria DESPUÉS de ocultar
|
||||||
|
if (matches === symbols.length) {
|
||||||
|
clearInterval(timerInterval);
|
||||||
|
statusDiv.textContent = `¡Felicidades! Lo lograste en ${moves} movimientos y te sobraron ${timer} segs 🎉`;
|
||||||
|
lockBoard = true; // Deshabilita el tablero tras terminar
|
||||||
|
}
|
||||||
|
}, 800);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// No es PAR
|
||||||
|
setTimeout(() => {
|
||||||
|
firstCard.classList.remove('flipped');
|
||||||
|
secondCard.classList.remove('flipped');
|
||||||
|
firstCard.textContent = '';
|
||||||
|
secondCard.textContent = '';
|
||||||
|
resetTurn();
|
||||||
|
}, 900);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DERROTA: por movimientos
|
||||||
|
if (moves >= maxMoves) {
|
||||||
|
endGame(false, "Has alcanzado el límite de movimientos. ¡Inténtalo otra vez!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHUD() {
|
||||||
|
movesSpan.textContent = `Movimientos: ${moves} / ${maxMoves}`;
|
||||||
|
timerSpan.textContent = `Tiempo: ${timer}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTimer() {
|
||||||
|
timer--;
|
||||||
|
updateHUD();
|
||||||
|
if (timer <= 0) {
|
||||||
|
endGame(false, "¡Se acabó el tiempo!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetTurn() {
|
||||||
|
firstCard = null;
|
||||||
|
secondCard = null;
|
||||||
|
lockBoard = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function endGame(win, msg) {
|
||||||
|
lockBoard = true;
|
||||||
|
clearInterval(timerInterval);
|
||||||
|
statusDiv.textContent = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
restartBtn.onclick = startGame;
|
||||||
|
startGame();
|
179
memoria-v2/styles.css
Normal file
179
memoria-v2/styles.css
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f0f2f6;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #222;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 2.2rem;
|
||||||
|
color: #3d5a80;
|
||||||
|
font-size: clamp(1.6rem, 4vw, 2.7rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hud {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1.2rem;
|
||||||
|
margin: 0.5rem auto;
|
||||||
|
font-size: clamp(1em, 2vw, 1.15em);
|
||||||
|
color: #223757;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 650px;
|
||||||
|
padding: 0 1vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Tablero responsive ==== */
|
||||||
|
#game-board {
|
||||||
|
margin: 2.1rem auto 1rem auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, minmax(45px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 98vw;
|
||||||
|
padding: 0 1vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
width: 100%;
|
||||||
|
background: #98c1d9;
|
||||||
|
color: #293241;
|
||||||
|
font-size: clamp(1.2rem, 4vw, 2.3rem);
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
box-shadow: 0 2px 8px #bbb;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: background 0.2s, opacity 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.flipped {
|
||||||
|
background: #e0fbfc;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.matched {
|
||||||
|
background: #3d5a80;
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.hide {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.45s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
font-size: clamp(1em, 2vw, 1.2em);
|
||||||
|
min-height: 2em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
color: #3d5a80;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-btn {
|
||||||
|
background: #3d5a80;
|
||||||
|
color: #fff;
|
||||||
|
font-size: clamp(1em, 2vw, 1.16em);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
padding: 0.5em 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
margin-bottom: 1.3em;
|
||||||
|
}
|
||||||
|
#restart-btn:hover {
|
||||||
|
background: #223757;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas móviles ==== */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
h1 {
|
||||||
|
margin-top: 5vw;
|
||||||
|
font-size: clamp(1.3rem, 7vw, 2.1rem);
|
||||||
|
}
|
||||||
|
#hud {
|
||||||
|
font-size: clamp(1em, 5vw, 1.25em);
|
||||||
|
gap: 4vw;
|
||||||
|
max-width: 98vw;
|
||||||
|
padding: 0 2vw;
|
||||||
|
}
|
||||||
|
#game-board {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 5vw;
|
||||||
|
padding: 2vw;
|
||||||
|
margin-top: 5vw;
|
||||||
|
margin-bottom: 5vw;
|
||||||
|
max-width: 98vw;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
border-radius: 5vw;
|
||||||
|
font-size: clamp(1.2rem, 11vw, 3rem);
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
font-size: clamp(1em, 6vw, 1.3em);
|
||||||
|
border-radius: 5vw;
|
||||||
|
padding: 3vw 10vw;
|
||||||
|
margin-bottom: 5vw;
|
||||||
|
}
|
||||||
|
#status {
|
||||||
|
font-size: clamp(1em, 7vw, 1.3em);
|
||||||
|
margin-bottom: 4vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Tablets o pantallas intermedias ==== */
|
||||||
|
@media (min-width: 601px) and (max-width: 900px) {
|
||||||
|
#game-board {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 2vw;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
font-size: clamp(1.3em, 6vw, 2.6rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas muy grandes ==== */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
#hud {
|
||||||
|
font-size: clamp(1.1em, 1vw, 1.3em);
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
#game-board {
|
||||||
|
max-width: 700px;
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
font-size: clamp(2rem, 2vw, 3rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Extra pequeñas ==== */
|
||||||
|
@media (max-width: 350px) {
|
||||||
|
#game-board {
|
||||||
|
grid-template-columns: repeat(1, 1fr);
|
||||||
|
gap: 2vw;
|
||||||
|
padding: 1vw;
|
||||||
|
}
|
||||||
|
.card { font-size: clamp(1rem, 23vw, 1.8rem); }
|
||||||
|
#hud {
|
||||||
|
font-size: clamp(1em, 9vw, 1.1em);
|
||||||
|
gap: 2vw;
|
||||||
|
}
|
||||||
|
}
|
16
memoria/index.html
Normal file
16
memoria/index.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Juego de Memoria</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Juego de Memoria</h1>
|
||||||
|
<p>Haz clic en las cartas para darles la vuelta y encuentra las parejas.</p>
|
||||||
|
<div id="game-board"></div>
|
||||||
|
<div id="info"></div>
|
||||||
|
<button id="reset-btn">Reiniciar</button>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
85
memoria/script.js
Normal file
85
memoria/script.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
const symbols = ['🍎','🍌','🍒','🍇','🍉','🍑','🍊','🍓'];
|
||||||
|
let cards = [];
|
||||||
|
let firstCard = null;
|
||||||
|
let secondCard = null;
|
||||||
|
let lockBoard = false;
|
||||||
|
let matches = 0;
|
||||||
|
const gameBoard = document.getElementById('game-board');
|
||||||
|
const infoDiv = document.getElementById('info');
|
||||||
|
const resetBtn = document.getElementById('reset-btn');
|
||||||
|
|
||||||
|
function shuffle(array) {
|
||||||
|
// Fisher-Yates shuffle
|
||||||
|
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]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupBoard() {
|
||||||
|
matches = 0;
|
||||||
|
firstCard = null;
|
||||||
|
secondCard = null;
|
||||||
|
lockBoard = false;
|
||||||
|
infoDiv.textContent = '';
|
||||||
|
// Duplica los símbolos y los mezcla
|
||||||
|
cards = [...symbols, ...symbols];
|
||||||
|
shuffle(cards);
|
||||||
|
gameBoard.innerHTML = '';
|
||||||
|
cards.forEach((symbol, idx) => {
|
||||||
|
const cardEl = document.createElement('div');
|
||||||
|
cardEl.className = 'card';
|
||||||
|
cardEl.dataset.index = idx;
|
||||||
|
cardEl.dataset.symbol = symbol;
|
||||||
|
cardEl.textContent = '';
|
||||||
|
cardEl.onclick = () => flipCard(cardEl);
|
||||||
|
gameBoard.appendChild(cardEl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function flipCard(cardEl) {
|
||||||
|
if (lockBoard) return;
|
||||||
|
if (cardEl.classList.contains('flipped') || cardEl.classList.contains('matched')) return;
|
||||||
|
|
||||||
|
cardEl.classList.add('flipped');
|
||||||
|
cardEl.textContent = cardEl.dataset.symbol;
|
||||||
|
|
||||||
|
if (!firstCard) {
|
||||||
|
firstCard = cardEl;
|
||||||
|
} else if(!secondCard && cardEl !== firstCard) {
|
||||||
|
secondCard = cardEl;
|
||||||
|
lockBoard = true;
|
||||||
|
|
||||||
|
if (firstCard.dataset.symbol === secondCard.dataset.symbol) {
|
||||||
|
// ¡Es pareja!
|
||||||
|
firstCard.classList.add('matched');
|
||||||
|
secondCard.classList.add('matched');
|
||||||
|
matches++;
|
||||||
|
resetFlipped(700);
|
||||||
|
if (matches === symbols.length) {
|
||||||
|
infoDiv.textContent = '¡Felicidades! Has encontrado todas las parejas 🎉';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No es pareja, voltea las cartas después de un momento
|
||||||
|
setTimeout(() => {
|
||||||
|
firstCard.classList.remove('flipped');
|
||||||
|
secondCard.classList.remove('flipped');
|
||||||
|
firstCard.textContent = '';
|
||||||
|
secondCard.textContent = '';
|
||||||
|
resetFlipped(0);
|
||||||
|
}, 900);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFlipped(delay) {
|
||||||
|
setTimeout(() => {
|
||||||
|
firstCard = null;
|
||||||
|
secondCard = null;
|
||||||
|
lockBoard = false;
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetBtn.onclick = setupBoard;
|
||||||
|
|
||||||
|
setupBoard();
|
146
memoria/styles.css
Normal file
146
memoria/styles.css
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
/* ==== Reset y base ==== */
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #eef2f3;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
color: #364f6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Encabezado ===== */
|
||||||
|
h1 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: #364f6b;
|
||||||
|
font-size: clamp(1.8rem, 4vw, 2.7rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Info & botón ==== */
|
||||||
|
#info {
|
||||||
|
font-size: clamp(1rem, 2.2vw, 1.35em);
|
||||||
|
min-height: 2em;
|
||||||
|
margin-bottom: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reset-btn {
|
||||||
|
background: #fc5185;
|
||||||
|
color: #fff;
|
||||||
|
font-size: clamp(1em, 2vw, 1.2em);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.7rem;
|
||||||
|
padding: 0.5em 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
#reset-btn:hover { background: #f31349; }
|
||||||
|
|
||||||
|
/* ==== Tablero ==== */
|
||||||
|
#game-board {
|
||||||
|
margin: 2.2rem auto 1.2rem auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(60px,1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 98vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Tarjetas ==== */
|
||||||
|
.card {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
width: 100%; /* para que rellenen su columna */
|
||||||
|
background: #3fc1c9;
|
||||||
|
color: #364f6b;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
box-shadow: 0 2px 8px #bbb;
|
||||||
|
font-size: clamp(1.5rem, 4vw, 2.5rem);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: background 0.2s, color 0.2s, opacity 0.2s;
|
||||||
|
}
|
||||||
|
.card.flipped {
|
||||||
|
background: #fc5185;
|
||||||
|
color: #fff;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.card.matched {
|
||||||
|
background: #364f6b;
|
||||||
|
color: #fff;
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== MÓVILES: ancho máximo y menos columnas ==== */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
h1 {
|
||||||
|
margin-top: 3vw;
|
||||||
|
font-size: clamp(1.3rem, 7vw, 2.2rem);
|
||||||
|
}
|
||||||
|
#game-board {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 4vw;
|
||||||
|
margin-top: 4vw;
|
||||||
|
margin-bottom: 6vw;
|
||||||
|
padding: 2vw;
|
||||||
|
max-width: 98vw;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
border-radius: 5vw;
|
||||||
|
font-size: clamp(1.2rem, 12vw, 2.7rem);
|
||||||
|
}
|
||||||
|
#reset-btn {
|
||||||
|
font-size: clamp(1rem, 7vw, 1.7em);
|
||||||
|
border-radius: 5vw;
|
||||||
|
padding: 3vw 12vw;
|
||||||
|
margin-bottom: 4vw;
|
||||||
|
}
|
||||||
|
#info {
|
||||||
|
font-size: clamp(1rem, 6vw, 1.4em);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Phablets/tablets: más columnas ==== */
|
||||||
|
@media (min-width: 601px) and (max-width: 900px) {
|
||||||
|
#game-board {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 2vw;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
font-size: clamp(1.3rem, 5vw, 2.2rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas grandes (>1200px): tablero más ancho ==== */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
#game-board {
|
||||||
|
max-width: 500px;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
font-size: clamp(2rem, 2vw, 3rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Extra pequeñas (<350px): modo apilado ==== */
|
||||||
|
@media (max-width: 350px) {
|
||||||
|
#game-board {
|
||||||
|
grid-template-columns: repeat(1, 1fr);
|
||||||
|
gap: 2vw;
|
||||||
|
padding: 1vw;
|
||||||
|
}
|
||||||
|
.card { font-size: clamp(1rem, 20vw, 2rem); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::::::::::::::::::::::::::: */
|
23
piedra-papel-tijera/index.html
Normal file
23
piedra-papel-tijera/index.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Piedra, Papel o Tijera</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Piedra, Papel o Tijera</h1>
|
||||||
|
<div id="scoreboard">
|
||||||
|
Tú: <span id="user-score">0</span> |
|
||||||
|
Máquina: <span id="computer-score">0</span>
|
||||||
|
</div>
|
||||||
|
<div id="choices">
|
||||||
|
<button data-choice="piedra">🪨 Piedra</button>
|
||||||
|
<button data-choice="papel">📄 Papel</button>
|
||||||
|
<button data-choice="tijera">✂️ Tijera</button>
|
||||||
|
</div>
|
||||||
|
<div id="result"></div>
|
||||||
|
<button id="reset-btn">Reiniciar</button>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
62
piedra-papel-tijera/script.js
Normal file
62
piedra-papel-tijera/script.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
const userScoreSpan = document.getElementById('user-score');
|
||||||
|
const computerScoreSpan = document.getElementById('computer-score');
|
||||||
|
const resultDiv = document.getElementById('result');
|
||||||
|
const choiceButtons = document.querySelectorAll('#choices button');
|
||||||
|
const resetBtn = document.getElementById('reset-btn');
|
||||||
|
|
||||||
|
let userScore = 0;
|
||||||
|
let computerScore = 0;
|
||||||
|
|
||||||
|
const choices = ['piedra', 'papel', 'tijera'];
|
||||||
|
|
||||||
|
function computerPlay() {
|
||||||
|
const idx = Math.floor(Math.random() * 3);
|
||||||
|
return choices[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
function playRound(userChoice) {
|
||||||
|
const computerChoice = computerPlay();
|
||||||
|
|
||||||
|
let resultMsg = `Tu elección: ${emoji(userChoice)} ${capitalize(userChoice)}<br>
|
||||||
|
Computadora: ${emoji(computerChoice)} ${capitalize(computerChoice)}<br>`;
|
||||||
|
|
||||||
|
if (userChoice === computerChoice) {
|
||||||
|
resultMsg += "<strong>¡Empate!</strong>";
|
||||||
|
} else if (
|
||||||
|
(userChoice === 'piedra' && computerChoice === 'tijera') ||
|
||||||
|
(userChoice === 'papel' && computerChoice === 'piedra') ||
|
||||||
|
(userChoice === 'tijera' && computerChoice === 'papel')
|
||||||
|
) {
|
||||||
|
userScore++;
|
||||||
|
userScoreSpan.textContent = userScore;
|
||||||
|
resultMsg += "<strong>¡Ganaste esta ronda! 🎉</strong>";
|
||||||
|
} else {
|
||||||
|
computerScore++;
|
||||||
|
computerScoreSpan.textContent = computerScore;
|
||||||
|
resultMsg += "<strong>La computadora gana esta ronda.</strong>";
|
||||||
|
}
|
||||||
|
|
||||||
|
resultDiv.innerHTML = resultMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function emoji(choice) {
|
||||||
|
if (choice === 'piedra') return '🪨';
|
||||||
|
if (choice === 'papel') return '📄';
|
||||||
|
if (choice === 'tijera') return '✂️';
|
||||||
|
}
|
||||||
|
|
||||||
|
function capitalize(word) {
|
||||||
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
choiceButtons.forEach(btn => {
|
||||||
|
btn.onclick = () => playRound(btn.getAttribute('data-choice'));
|
||||||
|
});
|
||||||
|
|
||||||
|
resetBtn.onclick = () => {
|
||||||
|
userScore = 0;
|
||||||
|
computerScore = 0;
|
||||||
|
userScoreSpan.textContent = userScore;
|
||||||
|
computerScoreSpan.textContent = computerScore;
|
||||||
|
resultDiv.textContent = '';
|
||||||
|
};
|
152
piedra-papel-tijera/styles.css
Normal file
152
piedra-papel-tijera/styles.css
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #fdf6e3;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #222;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Encabezado */
|
||||||
|
h1 {
|
||||||
|
margin-top: 2.7rem;
|
||||||
|
color: #222;
|
||||||
|
font-size: clamp(1.5rem, 4vw, 2.7rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Marcador */
|
||||||
|
#scoreboard {
|
||||||
|
font-size: clamp(1.05em, 2.5vw, 1.2em);
|
||||||
|
margin: 0.9em 0 1.8em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botones de elección */
|
||||||
|
#choices {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1em;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#choices button {
|
||||||
|
font-size: clamp(1em, 3vw, 1.3em);
|
||||||
|
margin: 0 0.4em;
|
||||||
|
padding: 0.9em 2em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
background: #6ab04c;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
min-width: 100px;
|
||||||
|
max-width: 90vw;
|
||||||
|
box-shadow: 0 2px 6px #eaeaea;
|
||||||
|
}
|
||||||
|
|
||||||
|
#choices button:hover {
|
||||||
|
background: #218c5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resultado */
|
||||||
|
#result {
|
||||||
|
font-size: clamp(1.05em, 2vw, 1.2em);
|
||||||
|
margin: 1.1em 0 1.7em 0;
|
||||||
|
min-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset */
|
||||||
|
#reset-btn {
|
||||||
|
font-size: clamp(1em, 2vw, 1.15em);
|
||||||
|
padding: 0.6em 2em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
background: #f76707;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
margin-top: 1.1em;
|
||||||
|
box-shadow: 0 2px 6px #eaeaea;
|
||||||
|
}
|
||||||
|
#reset-btn:hover {
|
||||||
|
background: #c44210;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======= Pantallas pequeñas (móviles) ======= */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
h1 {
|
||||||
|
margin-top: 5vw;
|
||||||
|
font-size: clamp(1.2rem, 7vw, 2rem);
|
||||||
|
}
|
||||||
|
#scoreboard {
|
||||||
|
font-size: clamp(1em, 6vw, 1.35em);
|
||||||
|
margin-top: 2vw;
|
||||||
|
margin-bottom: 5vw;
|
||||||
|
}
|
||||||
|
#choices {
|
||||||
|
gap: 3vw;
|
||||||
|
margin-bottom: 5vw;
|
||||||
|
}
|
||||||
|
#choices button {
|
||||||
|
font-size: clamp(1em, 6vw, 2em);
|
||||||
|
padding: 1.2em 6vw;
|
||||||
|
border-radius: 7vw;
|
||||||
|
min-width: 48vw;
|
||||||
|
}
|
||||||
|
#result {
|
||||||
|
font-size: clamp(1em, 7vw, 1.5em);
|
||||||
|
margin-bottom: 6vw;
|
||||||
|
}
|
||||||
|
#reset-btn {
|
||||||
|
font-size: clamp(1em, 8vw, 2em);
|
||||||
|
padding: 1em 15vw;
|
||||||
|
border-radius: 8vw;
|
||||||
|
margin-top: 5vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Pantallas muy grandes ===== */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(2rem, 2vw, 3rem);
|
||||||
|
}
|
||||||
|
#scoreboard {
|
||||||
|
font-size: clamp(1.2em, 2vw, 1.5em);
|
||||||
|
margin-bottom: 2.5em;
|
||||||
|
}
|
||||||
|
#choices button {
|
||||||
|
font-size: clamp(1.2em, 2vw, 1.5em);
|
||||||
|
padding: 1em 3em;
|
||||||
|
border-radius: 1em;
|
||||||
|
min-width: 120px;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
#reset-btn {
|
||||||
|
font-size: clamp(1.12em, 2vw, 1.25em);
|
||||||
|
padding: 0.8em 2.5em;
|
||||||
|
border-radius: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Pantallas muy pequeñas (<350px) ===== */
|
||||||
|
@media (max-width: 350px) {
|
||||||
|
#choices {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5vw;
|
||||||
|
}
|
||||||
|
#choices button {
|
||||||
|
min-width: 60vw;
|
||||||
|
padding: 1em 7vw;
|
||||||
|
}
|
||||||
|
#reset-btn {
|
||||||
|
padding: 1em 8vw;
|
||||||
|
}
|
||||||
|
}
|
16
pong/index.html
Normal file
16
pong/index.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Pong Game</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Pong Clásico</h1>
|
||||||
|
<p>Usa las flechas arriba/abajo para mover tu pala. ¡No dejes que la bola pase!</p>
|
||||||
|
<canvas id="pong" width="600" height="400"></canvas>
|
||||||
|
<div id="score"></div>
|
||||||
|
<button id="restart-btn">Reiniciar</button>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
125
pong/script.js
Normal file
125
pong/script.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
const canvas = document.getElementById('pong');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const scoreDiv = document.getElementById('score');
|
||||||
|
const restartBtn = document.getElementById('restart-btn');
|
||||||
|
|
||||||
|
const w = canvas.width, h = canvas.height;
|
||||||
|
const paddleHeight = 80, paddleWidth = 14;
|
||||||
|
let playerY = h/2 - paddleHeight/2, aiY = h/2 - paddleHeight/2;
|
||||||
|
let playerScore = 0, aiScore = 0;
|
||||||
|
|
||||||
|
let ball = {
|
||||||
|
x: w/2, y: h/2,
|
||||||
|
size: 12,
|
||||||
|
speed: 5,
|
||||||
|
velX: 5,
|
||||||
|
velY: -4
|
||||||
|
};
|
||||||
|
|
||||||
|
let up = false, down = false;
|
||||||
|
let running = true;
|
||||||
|
|
||||||
|
// Dibujo de todo (juego, paletas, bola, marcador)
|
||||||
|
function draw() {
|
||||||
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
ctx.fillStyle = "#f2e9e4";
|
||||||
|
// Jugador (izq)
|
||||||
|
ctx.fillRect(16, playerY, paddleWidth, paddleHeight);
|
||||||
|
// AI (der)
|
||||||
|
ctx.fillRect(w-16-paddleWidth, aiY, paddleWidth, paddleHeight);
|
||||||
|
// Bola
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(ball.x, ball.y, ball.size, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = "#f6c90e";
|
||||||
|
ctx.fill();
|
||||||
|
// Red central
|
||||||
|
for(let i=15;i<h;i+=30){
|
||||||
|
ctx.fillRect(w/2-2,i,4,14);
|
||||||
|
}
|
||||||
|
// Score
|
||||||
|
scoreDiv.innerHTML = `<b>Tú:</b> ${playerScore} | <b>Máquina:</b> ${aiScore}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameLoop() {
|
||||||
|
if (!running) return;
|
||||||
|
|
||||||
|
// Jugador
|
||||||
|
if (up) playerY -= 7;
|
||||||
|
if (down) playerY += 7;
|
||||||
|
playerY = Math.max(0, Math.min(h-paddleHeight, playerY));
|
||||||
|
// AI: sigue la bola
|
||||||
|
if (aiY + paddleHeight/2 < ball.y - 12) aiY += 5;
|
||||||
|
else if (aiY + paddleHeight/2 > ball.y + 12) aiY -= 5;
|
||||||
|
aiY = Math.max(0, Math.min(h-paddleHeight, aiY));
|
||||||
|
|
||||||
|
// Bola
|
||||||
|
ball.x += ball.velX;
|
||||||
|
ball.y += ball.velY;
|
||||||
|
|
||||||
|
// Colisión con pared arriba/abajo
|
||||||
|
if (ball.y - ball.size < 0 || ball.y + ball.size > h) ball.velY *= -1;
|
||||||
|
|
||||||
|
// Colisión con paleta jugador
|
||||||
|
if (
|
||||||
|
ball.x - ball.size < 16 + paddleWidth &&
|
||||||
|
ball.y > playerY && ball.y < playerY + paddleHeight
|
||||||
|
) {
|
||||||
|
ball.velX = Math.abs(ball.velX);
|
||||||
|
ball.velY += (Math.random() - 0.5) * 2.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colisión con paleta AI
|
||||||
|
if (
|
||||||
|
ball.x + ball.size > w-16-paddleWidth &&
|
||||||
|
ball.y > aiY && ball.y < aiY + paddleHeight
|
||||||
|
) {
|
||||||
|
ball.velX = -Math.abs(ball.velX);
|
||||||
|
ball.velY += (Math.random() - 0.5) * 2.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gol jugador
|
||||||
|
if (ball.x - ball.size < 0) {
|
||||||
|
aiScore++;
|
||||||
|
resetBall();
|
||||||
|
}
|
||||||
|
// Gol máquina
|
||||||
|
if (ball.x + ball.size > w) {
|
||||||
|
playerScore++;
|
||||||
|
resetBall();
|
||||||
|
}
|
||||||
|
|
||||||
|
draw();
|
||||||
|
requestAnimationFrame(gameLoop);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetBall() {
|
||||||
|
ball.x = w/2; ball.y = h/2;
|
||||||
|
ball.velX = (Math.random() > 0.5 ? 5 : -5);
|
||||||
|
ball.velY = (Math.random() - 0.5) * 7;
|
||||||
|
running = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
running = true;
|
||||||
|
requestAnimationFrame(gameLoop);
|
||||||
|
}, 900);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'ArrowUp') up = true;
|
||||||
|
if (e.key === 'ArrowDown') down = true;
|
||||||
|
});
|
||||||
|
document.addEventListener('keyup', e => {
|
||||||
|
if (e.key === 'ArrowUp') up = false;
|
||||||
|
if (e.key === 'ArrowDown') down = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
restartBtn.onclick = () => {
|
||||||
|
playerScore = aiScore = 0;
|
||||||
|
playerY = h/2 - paddleHeight/2;
|
||||||
|
aiY = h/2 - paddleHeight/2;
|
||||||
|
resetBall();
|
||||||
|
running = true;
|
||||||
|
requestAnimationFrame(gameLoop);
|
||||||
|
};
|
||||||
|
|
||||||
|
draw();
|
||||||
|
gameLoop();
|
36
pong/styles.css
Normal file
36
pong/styles.css
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
body {
|
||||||
|
background: #22223b;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
color: #f2e9e4;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-top: 32px;
|
||||||
|
color: #9599b2;
|
||||||
|
}
|
||||||
|
#pong {
|
||||||
|
background: #232946;
|
||||||
|
display: block;
|
||||||
|
margin: 28px auto 12px auto;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 12px #bbb;
|
||||||
|
}
|
||||||
|
#score {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #f6c90e;
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
background: #4a4e69;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 7px 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
#restart-btn:hover {
|
||||||
|
background: #232946;
|
||||||
|
}
|
15
puzle-numeros/index.html
Normal file
15
puzle-numeros/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Puzzle de Números</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Puzzle de Números</h1>
|
||||||
|
<div id="board"></div>
|
||||||
|
<div id="status"></div>
|
||||||
|
<button id="restart-btn">Reiniciar</button>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
97
puzle-numeros/script.js
Normal file
97
puzle-numeros/script.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
const SIZE = 4;
|
||||||
|
const boardDiv = document.getElementById('board');
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
const restartBtn = document.getElementById('restart-btn');
|
||||||
|
let board, won;
|
||||||
|
|
||||||
|
function initGame() {
|
||||||
|
board = [];
|
||||||
|
won = false;
|
||||||
|
let numbers = Array.from({length: SIZE*SIZE}, (_, i) => i);
|
||||||
|
do {
|
||||||
|
shuffle(numbers);
|
||||||
|
} while(!isSolvable(numbers));
|
||||||
|
// Llenar el tablero
|
||||||
|
for (let r=0; r<SIZE; r++) {
|
||||||
|
let row = [];
|
||||||
|
for (let c=0; c<SIZE; c++)
|
||||||
|
row.push(numbers[r*SIZE + c]);
|
||||||
|
board.push(row);
|
||||||
|
}
|
||||||
|
statusDiv.textContent = "Ordena las fichas del 1 al 15";
|
||||||
|
renderBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
function shuffle(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]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica si el puzzle es resoluble (paridad)
|
||||||
|
function isSolvable(arr) {
|
||||||
|
let invCount = 0;
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i] === 0) continue;
|
||||||
|
for (let j = i+1; j < arr.length; j++) {
|
||||||
|
if (arr[j] !== 0 && arr[i] > arr[j]) invCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let emptyRow = Math.floor(arr.indexOf(0) / SIZE);
|
||||||
|
return (invCount + emptyRow) % 2 === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBoard() {
|
||||||
|
boardDiv.innerHTML = '';
|
||||||
|
for (let r = 0; r < SIZE; r++) {
|
||||||
|
for (let c = 0; c < SIZE; c++) {
|
||||||
|
const val = board[r][c];
|
||||||
|
const tile = document.createElement('div');
|
||||||
|
tile.className = 'tile' + (val === 0 ? ' empty' : '');
|
||||||
|
tile.textContent = val === 0 ? '' : val;
|
||||||
|
if (val !== 0 && !won) {
|
||||||
|
tile.onclick = () => moveTile(r, c);
|
||||||
|
}
|
||||||
|
boardDiv.appendChild(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (checkWin() && !won) {
|
||||||
|
won = true;
|
||||||
|
statusDiv.textContent = "¡Felicidades! Puzzle resuelto 🎉";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveTile(r, c) {
|
||||||
|
let neighbours = [
|
||||||
|
[r-1, c],
|
||||||
|
[r+1, c],
|
||||||
|
[r, c-1],
|
||||||
|
[r, c+1]
|
||||||
|
];
|
||||||
|
for (let [nr, nc] of neighbours) {
|
||||||
|
if (nr >=0 && nr < SIZE && nc >=0 && nc < SIZE && board[nr][nc] === 0) {
|
||||||
|
// Intercambia
|
||||||
|
[board[nr][nc], board[r][c]] = [board[r][c], board[nr][nc]];
|
||||||
|
renderBoard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWin() {
|
||||||
|
let n = 1;
|
||||||
|
for (let r = 0; r < SIZE; r++) {
|
||||||
|
for (let c = 0; c < SIZE; c++) {
|
||||||
|
if (r === SIZE-1 && c === SIZE-1) {
|
||||||
|
if (board[r][c] !== 0) return false;
|
||||||
|
} else {
|
||||||
|
if (board[r][c] !== n++) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
restartBtn.onclick = initGame;
|
||||||
|
initGame();
|
133
puzle-numeros/styles.css
Normal file
133
puzle-numeros/styles.css
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f4f4f4;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #222;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Encabezado */
|
||||||
|
h1 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: #3498db;
|
||||||
|
font-size: clamp(1.6rem, 4vw, 2.7rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablero responsive */
|
||||||
|
#board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-template-rows: repeat(4, 1fr);
|
||||||
|
gap: 0.7rem;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 2.2rem auto 1.3rem auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 410px;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
background: #e9ecef;
|
||||||
|
border-radius: 1.2rem;
|
||||||
|
padding: 1.2rem;
|
||||||
|
box-shadow: 0 2px 14px #bbb4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ficha */
|
||||||
|
.tile {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 0.6em;
|
||||||
|
box-shadow: 0 2px 8px #bbb;
|
||||||
|
font-size: clamp(1.1em, 5vw, 2em);
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.15s;
|
||||||
|
user-select: none;
|
||||||
|
min-width: 0; /* Para que flex funcionen perfecto en grid */
|
||||||
|
min-height: 0;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
/* width y height no son necesarias, grid y aspect-ratio lo ajustan */
|
||||||
|
}
|
||||||
|
.tile.empty {
|
||||||
|
background: #bdd4ea;
|
||||||
|
cursor: default;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estado y botón */
|
||||||
|
#status {
|
||||||
|
font-size: clamp(1em, 2vw, 1.15em);
|
||||||
|
min-height: 2em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-btn {
|
||||||
|
background: #3498db;
|
||||||
|
color: #fff;
|
||||||
|
font-size: clamp(1em, 2vw, 1.15em);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
padding: 0.5em 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
margin-bottom: 1.4em;
|
||||||
|
box-shadow: 0 2px 8px #3498db33;
|
||||||
|
}
|
||||||
|
#restart-btn:hover {
|
||||||
|
background: #217dbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas pequeñas/móviles ==== */
|
||||||
|
@media (max-width: 728px) {
|
||||||
|
h1 {
|
||||||
|
margin-top: 6vw;
|
||||||
|
font-size: clamp(1.1em, 7vw, 2.1em);
|
||||||
|
}
|
||||||
|
#board {
|
||||||
|
max-width: 96vw;
|
||||||
|
padding: 2vw;
|
||||||
|
gap: 3vw;
|
||||||
|
border-radius: 6vw;
|
||||||
|
margin-top: 6vw;
|
||||||
|
margin-bottom: 6vw;
|
||||||
|
}
|
||||||
|
.tile {
|
||||||
|
font-size: clamp(1em, 10vw, 2.6em);
|
||||||
|
border-radius: 5vw;
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
font-size: clamp(1em, 8vw, 1.5em);
|
||||||
|
padding: 1em 13vw;
|
||||||
|
border-radius: 5vw;
|
||||||
|
margin-bottom: 5vw;
|
||||||
|
}
|
||||||
|
#status {
|
||||||
|
font-size: clamp(1em, 7vw, 1.3em);
|
||||||
|
margin-bottom: 4vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas muy pequeñas ==== */
|
||||||
|
@media (max-width: 350px) {
|
||||||
|
#board {
|
||||||
|
padding: 1vw;
|
||||||
|
gap: 2vw;
|
||||||
|
border-radius: 9vw;
|
||||||
|
}
|
||||||
|
.tile { font-size: clamp(0.9em, 15vw, 1.6em); }
|
||||||
|
#restart-btn {
|
||||||
|
padding: 0.7em 3vw;
|
||||||
|
border-radius: 8vw;
|
||||||
|
font-size: clamp(0.9em, 13vw, 1.1em);
|
||||||
|
}
|
||||||
|
}
|
25
reflejos/index.html
Normal file
25
reflejos/index.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Juego: ¡Haz click al Círculo!</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>¡Haz click al Círculo!</h1>
|
||||||
|
<div id="game-info">
|
||||||
|
<span>Puntuación: <span id="score">0</span></span> |
|
||||||
|
<span>Tiempo: <span id="timer">20</span> s</span>
|
||||||
|
</div>
|
||||||
|
<div id="game-area"></div>
|
||||||
|
<button id="start-btn">Empezar Juego</button>
|
||||||
|
|
||||||
|
<div id="game-over" class="hidden">
|
||||||
|
<h2>¡Juego Terminado!</h2>
|
||||||
|
<p>Tu puntuación fue: <span id="final-score"></span></p>
|
||||||
|
<button onclick="startGame()">Jugar de nuevo</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
84
reflejos/script.js
Normal file
84
reflejos/script.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
const gameArea = document.getElementById('game-area');
|
||||||
|
const scoreSpan = document.getElementById('score');
|
||||||
|
const timerSpan = document.getElementById('timer');
|
||||||
|
const startBtn = document.getElementById('start-btn');
|
||||||
|
const gameOverDiv = document.getElementById('game-over');
|
||||||
|
const finalScoreSpan = document.getElementById('final-score');
|
||||||
|
|
||||||
|
let score = 0;
|
||||||
|
let timer = 20; // duración del juego en segundos
|
||||||
|
let interval, timeout;
|
||||||
|
let playing = false;
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
score = 0;
|
||||||
|
timer = 20;
|
||||||
|
scoreSpan.textContent = score;
|
||||||
|
timerSpan.textContent = timer;
|
||||||
|
gameArea.innerHTML = '';
|
||||||
|
gameOverDiv.classList.add('hidden');
|
||||||
|
startBtn.disabled = true;
|
||||||
|
playing = true;
|
||||||
|
|
||||||
|
// Iniciar contador regresivo
|
||||||
|
interval = setInterval(() => {
|
||||||
|
timer--;
|
||||||
|
timerSpan.textContent = timer;
|
||||||
|
if (timer <= 0) {
|
||||||
|
endGame();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
spawnCircle();
|
||||||
|
}
|
||||||
|
|
||||||
|
function endGame() {
|
||||||
|
playing = false;
|
||||||
|
clearInterval(interval);
|
||||||
|
clearTimeout(timeout);
|
||||||
|
gameArea.innerHTML = '';
|
||||||
|
startBtn.disabled = false;
|
||||||
|
|
||||||
|
// Mostrar resultado final
|
||||||
|
finalScoreSpan.textContent = score;
|
||||||
|
gameOverDiv.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawnCircle() {
|
||||||
|
if (!playing || timer <= 0) return;
|
||||||
|
|
||||||
|
// Crear círculo y posicionarlo aleatoriamente
|
||||||
|
const circle = document.createElement('div');
|
||||||
|
circle.className = 'circle';
|
||||||
|
|
||||||
|
// Cálculo de posición dentro de gameArea
|
||||||
|
const areaRect = gameArea.getBoundingClientRect();
|
||||||
|
const radius = 25; // la mitad de 50px
|
||||||
|
const maxLeft = gameArea.clientWidth - circle.offsetWidth;
|
||||||
|
const maxTop = gameArea.clientHeight - circle.offsetHeight;
|
||||||
|
const left = Math.floor(Math.random() * maxLeft);
|
||||||
|
const top = Math.floor(Math.random() * maxTop);
|
||||||
|
|
||||||
|
circle.style.left = left + 'px';
|
||||||
|
circle.style.top = top + 'px';
|
||||||
|
|
||||||
|
// Evento de clic
|
||||||
|
circle.onclick = function() {
|
||||||
|
score++;
|
||||||
|
scoreSpan.textContent = score;
|
||||||
|
gameArea.removeChild(circle);
|
||||||
|
spawnCircle();
|
||||||
|
};
|
||||||
|
|
||||||
|
gameArea.appendChild(circle);
|
||||||
|
|
||||||
|
// Si no lo clickean en 1.2s, desaparece y aparece otro
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
if (playing && gameArea.contains(circle)) {
|
||||||
|
gameArea.removeChild(circle);
|
||||||
|
spawnCircle();
|
||||||
|
}
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
startBtn.onclick = startGame;
|
129
reflejos/styles.css
Normal file
129
reflejos/styles.css
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
background: #f2f2f2;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info del juego */
|
||||||
|
#game-info {
|
||||||
|
font-size: clamp(1em, 2vw, 1.2em);
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Área de juego */
|
||||||
|
#game-area {
|
||||||
|
width: 100vw;
|
||||||
|
max-width: 420px;
|
||||||
|
height: 100vw;
|
||||||
|
max-height: 420px;
|
||||||
|
margin: 2.2rem auto 1.3rem auto;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #2196f3;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0.9em;
|
||||||
|
box-shadow: 0 2px 14px #bbb4;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Círculo responsive */
|
||||||
|
.circle {
|
||||||
|
width: clamp(38px, 12vw, 54px);
|
||||||
|
height: clamp(38px, 12vw, 54px);
|
||||||
|
background: #4caf50;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 3px 10px #999;
|
||||||
|
transition: background 0.2s;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
.circle:active {
|
||||||
|
background: #388e3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botón Start */
|
||||||
|
#start-btn {
|
||||||
|
font-size: clamp(1em, 3vw, 1.15em);
|
||||||
|
padding: 0.7em 2em;
|
||||||
|
margin-top: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #2196f3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.45em;
|
||||||
|
box-shadow: 0 2px 8px #2196f366;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
#start-btn:hover,
|
||||||
|
#start-btn:active {
|
||||||
|
background: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Game Over */
|
||||||
|
#game-over {
|
||||||
|
margin-top: 1.3em;
|
||||||
|
font-size: clamp(1em, 2vw, 1.15em);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Oculto */
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas móviles ==== */
|
||||||
|
@media (max-width: 728px) {
|
||||||
|
#game-area {
|
||||||
|
max-width: 98vw;
|
||||||
|
max-height: 98vw;
|
||||||
|
border-radius: 6vw;
|
||||||
|
box-shadow: 0 2px 8px #2196f350;
|
||||||
|
margin-top: 5vw;
|
||||||
|
margin-bottom: 4vw;
|
||||||
|
}
|
||||||
|
.circle {
|
||||||
|
width: clamp(33px, 20vw, 54px);
|
||||||
|
height: clamp(33px, 20vw, 54px);
|
||||||
|
}
|
||||||
|
#start-btn {
|
||||||
|
font-size: clamp(1em, 8vw, 1.5em);
|
||||||
|
padding: 1em 14vw;
|
||||||
|
border-radius: 5vw;
|
||||||
|
margin-top: 4vw;
|
||||||
|
}
|
||||||
|
#game-info {
|
||||||
|
font-size: clamp(1em, 6vw, 1.25em);
|
||||||
|
margin-bottom: 4vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas muy pequeñas ==== */
|
||||||
|
@media (max-width: 340px) {
|
||||||
|
#game-area {
|
||||||
|
max-width: 94vw;
|
||||||
|
max-height: 94vw;
|
||||||
|
border-radius: 13vw;
|
||||||
|
}
|
||||||
|
.circle {
|
||||||
|
width: clamp(22px, 33vw, 38px);
|
||||||
|
height: clamp(22px, 33vw, 38px);
|
||||||
|
}
|
||||||
|
#start-btn {
|
||||||
|
font-size: clamp(0.9em, 10vw, 1.2em);
|
||||||
|
padding: 0.5em 8vw;
|
||||||
|
border-radius: 8vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ::::::::::::::::::::::::::::::::: */
|
19
serpiente/index.html
Normal file
19
serpiente/index.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Snake Game</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Juego de la Serpiente</h1>
|
||||||
|
<p>Usa las flechas del teclado para mover la serpiente. ¡Come y crece!</p>
|
||||||
|
<div id="game-container">
|
||||||
|
<canvas id="gameCanvas" width="400" height="400"></canvas>
|
||||||
|
</div>
|
||||||
|
<div id="score">Puntaje: <span id="score-value">0</span></div>
|
||||||
|
<button id="restart-btn">Reiniciar</button>
|
||||||
|
<div id="game-over-message"></div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
116
serpiente/script.js
Normal file
116
serpiente/script.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
const canvas = document.getElementById('gameCanvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const scoreSpan = document.getElementById('score-value');
|
||||||
|
const restartBtn = document.getElementById('restart-btn');
|
||||||
|
const gameOverDiv = document.getElementById('game-over-message');
|
||||||
|
|
||||||
|
// Parámetros del juego
|
||||||
|
const gridSize = 20; // Tamaño de cada celda
|
||||||
|
const tileCount = 20; // El área es de 20x20 celdas
|
||||||
|
let snake, direction, food, score, gameInterval, gameOver;
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
snake = [
|
||||||
|
{x: 10, y: 10},
|
||||||
|
{x: 9, y: 10},
|
||||||
|
{x: 8, y: 10}
|
||||||
|
];
|
||||||
|
direction = 'right';
|
||||||
|
score = 0;
|
||||||
|
gameOver = false;
|
||||||
|
scoreSpan.textContent = score;
|
||||||
|
gameOverDiv.textContent = '';
|
||||||
|
placeFood();
|
||||||
|
clearInterval(gameInterval);
|
||||||
|
gameInterval = setInterval(gameLoop, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeFood() {
|
||||||
|
food = {
|
||||||
|
x: Math.floor(Math.random() * tileCount),
|
||||||
|
y: Math.floor(Math.random() * tileCount)
|
||||||
|
};
|
||||||
|
// Asegúrate que la comida no aparece en la serpiente
|
||||||
|
if (snake.some(segment => segment.x === food.x && segment.y === food.y)) {
|
||||||
|
placeFood();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameLoop() {
|
||||||
|
// Mueve la serpiente
|
||||||
|
const head = {x: snake[0].x, y: snake[0].y};
|
||||||
|
switch (direction) {
|
||||||
|
case 'left': head.x--; break;
|
||||||
|
case 'up': head.y--; break;
|
||||||
|
case 'right': head.x++; break;
|
||||||
|
case 'down': head.y++; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choca con pared
|
||||||
|
if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) {
|
||||||
|
endGame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Choca consigo mismo
|
||||||
|
if (snake.some(seg => seg.x === head.x && seg.y === head.y)) {
|
||||||
|
endGame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
snake.unshift(head);
|
||||||
|
|
||||||
|
// Come la comida
|
||||||
|
if (head.x === food.x && head.y === food.y) {
|
||||||
|
score++;
|
||||||
|
scoreSpan.textContent = score;
|
||||||
|
placeFood();
|
||||||
|
} else {
|
||||||
|
snake.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawGame() {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Dibuja la comida
|
||||||
|
ctx.fillStyle = "#fc5185";
|
||||||
|
ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);
|
||||||
|
|
||||||
|
// Dibuja la serpiente
|
||||||
|
for (let i = 0; i < snake.length; i++) {
|
||||||
|
ctx.fillStyle = i === 0 ? "#ffd700" : "#145991";
|
||||||
|
ctx.fillRect(snake[i].x * gridSize, snake[i].y * gridSize, gridSize, gridSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeDirection(e) {
|
||||||
|
if (gameOver) return;
|
||||||
|
switch (e.key) {
|
||||||
|
case "ArrowLeft":
|
||||||
|
if (direction !== "right") direction = "left";
|
||||||
|
break;
|
||||||
|
case "ArrowUp":
|
||||||
|
if (direction !== "down") direction = "up";
|
||||||
|
break;
|
||||||
|
case "ArrowRight":
|
||||||
|
if (direction !== "left") direction = "right";
|
||||||
|
break;
|
||||||
|
case "ArrowDown":
|
||||||
|
if (direction !== "up") direction = "down";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function endGame() {
|
||||||
|
clearInterval(gameInterval);
|
||||||
|
gameOver = true;
|
||||||
|
gameOverDiv.textContent = "¡Fin del juego! Puntaje final: " + score;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', changeDirection);
|
||||||
|
restartBtn.onclick = startGame;
|
||||||
|
|
||||||
|
// Inicia el juego
|
||||||
|
startGame();
|
130
serpiente/styles.css
Normal file
130
serpiente/styles.css
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #142850;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
color: #fafafa;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Encabezado */
|
||||||
|
h1 {
|
||||||
|
margin-top: 2.2rem;
|
||||||
|
color: #00a8cc;
|
||||||
|
font-size: clamp(1.5rem, 4vw, 2.7rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contenedor del juego */
|
||||||
|
#game-container {
|
||||||
|
margin: 2rem auto;
|
||||||
|
display: block;
|
||||||
|
background: #27496d;
|
||||||
|
border-radius: 1em;
|
||||||
|
box-shadow: 0 2px 10px #1119;
|
||||||
|
padding: 1.1em;
|
||||||
|
max-width: 98vw;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Canvas responsive */
|
||||||
|
canvas {
|
||||||
|
background: #dae1e7;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 0.65em;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 420px;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
height: auto;
|
||||||
|
box-shadow: 0 2px 10px #27496d44;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Score */
|
||||||
|
#score {
|
||||||
|
font-size: clamp(1.1em, 2vw, 1.4em);
|
||||||
|
margin-top: 1em;
|
||||||
|
color: #ffd700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Game over mensaje */
|
||||||
|
#game-over-message {
|
||||||
|
font-size: clamp(1.1em, 3vw, 1.5em);
|
||||||
|
color: #fc5185;
|
||||||
|
margin-top: 1em;
|
||||||
|
min-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botón restart */
|
||||||
|
#restart-btn {
|
||||||
|
margin-top: 1.1em;
|
||||||
|
font-size: clamp(1em, 2vw, 1.18em);
|
||||||
|
padding: 0.6em 2em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.6em;
|
||||||
|
background: #00a8cc;
|
||||||
|
color: #fafafa;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
box-shadow: 0 2px 8px #00a8cc44;
|
||||||
|
}
|
||||||
|
#restart-btn:hover,
|
||||||
|
#restart-btn:active {
|
||||||
|
background: #145991;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas móviles ==== */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
h1 {
|
||||||
|
margin-top: 5vw;
|
||||||
|
font-size: clamp(1.2rem, 7vw, 2em);
|
||||||
|
}
|
||||||
|
#game-container {
|
||||||
|
border-radius: 6vw;
|
||||||
|
padding: 5vw;
|
||||||
|
margin-top: 6vw;
|
||||||
|
margin-bottom: 8vw;
|
||||||
|
box-shadow: 0 2px 10px #1c254080;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
max-width: 99vw;
|
||||||
|
border-radius: 5vw;
|
||||||
|
}
|
||||||
|
#score {
|
||||||
|
font-size: clamp(1em, 8vw, 1.6em);
|
||||||
|
margin-top: 4vw;
|
||||||
|
}
|
||||||
|
#game-over-message {
|
||||||
|
font-size: clamp(1em, 8vw, 1.3em);
|
||||||
|
margin-top: 4vw;
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
font-size: clamp(1em, 8vw, 1.45em);
|
||||||
|
padding: 1em 12vw;
|
||||||
|
border-radius: 6vw;
|
||||||
|
margin-top: 4vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas muy pequeñas ==== */
|
||||||
|
@media (max-width: 340px) {
|
||||||
|
#game-container {
|
||||||
|
padding: 2vw;
|
||||||
|
border-radius: 11vw;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
border-radius: 10vw;
|
||||||
|
}
|
||||||
|
#restart-btn {
|
||||||
|
font-size: clamp(0.9em, 8vw, 1.14em);
|
||||||
|
padding: 0.9em 3vw;
|
||||||
|
border-radius: 10vw;
|
||||||
|
}
|
||||||
|
}
|
20
simon-dice/index.html
Normal file
20
simon-dice/index.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Simon Dice</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Simon Dice</h1>
|
||||||
|
<div id="game-container">
|
||||||
|
<div class="color-btn" id="green"></div>
|
||||||
|
<div class="color-btn" id="red"></div>
|
||||||
|
<div class="color-btn" id="yellow"></div>
|
||||||
|
<div class="color-btn" id="blue"></div>
|
||||||
|
</div>
|
||||||
|
<div id="status"></div>
|
||||||
|
<button id="start-btn">Empezar</button>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
66
simon-dice/script.js
Normal file
66
simon-dice/script.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
const colors = ['green', 'red', 'yellow', 'blue'];
|
||||||
|
let sequence = [];
|
||||||
|
let playerSequence = [];
|
||||||
|
let level = 0;
|
||||||
|
let waitingInput = false;
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
const startBtn = document.getElementById('start-btn');
|
||||||
|
|
||||||
|
colors.forEach(color => {
|
||||||
|
document.getElementById(color).onclick = () => handleColorClick(color);
|
||||||
|
});
|
||||||
|
|
||||||
|
startBtn.onclick = startGame;
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
sequence = [];
|
||||||
|
playerSequence = [];
|
||||||
|
level = 0;
|
||||||
|
statusDiv.textContent = '';
|
||||||
|
nextLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextLevel() {
|
||||||
|
level++;
|
||||||
|
statusDiv.textContent = `Nivel ${level}`;
|
||||||
|
playerSequence = [];
|
||||||
|
sequence.push(colors[Math.floor(Math.random() * 4)]);
|
||||||
|
showSequence(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSequence(idx) {
|
||||||
|
waitingInput = false;
|
||||||
|
if (idx < sequence.length) {
|
||||||
|
const color = sequence[idx];
|
||||||
|
flashColor(color);
|
||||||
|
setTimeout(() => showSequence(idx + 1), 700);
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
waitingInput = true;
|
||||||
|
statusDiv.textContent = `Tu turno (Nivel ${level})`;
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flashColor(color) {
|
||||||
|
const el = document.getElementById(color);
|
||||||
|
el.classList.add('active');
|
||||||
|
setTimeout(() => el.classList.remove('active'), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleColorClick(color) {
|
||||||
|
if (!waitingInput) return;
|
||||||
|
playerSequence.push(color);
|
||||||
|
flashColor(color);
|
||||||
|
|
||||||
|
const idx = playerSequence.length - 1;
|
||||||
|
if (color !== sequence[idx]) {
|
||||||
|
statusDiv.textContent = `¡Fallaste! Llegaste al nivel ${level}.`;
|
||||||
|
waitingInput = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (playerSequence.length === sequence.length) {
|
||||||
|
waitingInput = false;
|
||||||
|
setTimeout(nextLevel, 900);
|
||||||
|
}
|
||||||
|
}
|
136
simon-dice/styles.css
Normal file
136
simon-dice/styles.css
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #e1f5fe;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #222;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Encabezado */
|
||||||
|
h1 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: #01579b;
|
||||||
|
font-size: clamp(1.3rem, 5vw, 2.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panel de botones Simon */
|
||||||
|
#game-container {
|
||||||
|
margin: 2.2rem auto 1rem auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 340px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-template-rows: repeat(2, 1fr);
|
||||||
|
gap: 1.2em;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.8em;
|
||||||
|
border-radius: 1.2em;
|
||||||
|
background: #ffffffdd;
|
||||||
|
box-shadow: 0 2px 14px #bbbe;
|
||||||
|
aspect-ratio: 1 / 1; /* el panel siempre es cuadrado */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botón Simon */
|
||||||
|
.color-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 1.1em;
|
||||||
|
box-shadow: 0 2px 8px #bbb;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: filter 0.2s, border 0.2s;
|
||||||
|
filter: brightness(0.85);
|
||||||
|
border: 3px solid #fafafa;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
/* background colors asignados por id */
|
||||||
|
}
|
||||||
|
#green { background: #43a047;}
|
||||||
|
#red { background: #e53935;}
|
||||||
|
#yellow { background: #fbc02d;}
|
||||||
|
#blue { background: #1e88e5;}
|
||||||
|
.color-btn.active {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
border: 3px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estado y botón inicio */
|
||||||
|
#status {
|
||||||
|
font-size: clamp(1em, 2vw, 1.15em);
|
||||||
|
min-height: 2em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
color: #01579b;
|
||||||
|
}
|
||||||
|
|
||||||
|
#start-btn {
|
||||||
|
background: #01579b;
|
||||||
|
color: #fff;
|
||||||
|
font-size: clamp(1em, 2vw, 1.13em);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.6em;
|
||||||
|
padding: 0.5em 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
box-shadow: 0 2px 8px #01579b33;
|
||||||
|
margin-bottom: 1.4em;
|
||||||
|
}
|
||||||
|
#start-btn:hover,
|
||||||
|
#start-btn:active {
|
||||||
|
background: #263238;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas pequeñas/móviles ==== */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
h1 {
|
||||||
|
margin-top: 6vw;
|
||||||
|
font-size: clamp(1.1em, 7vw, 2em);
|
||||||
|
}
|
||||||
|
#game-container {
|
||||||
|
max-width: 96vw;
|
||||||
|
gap: 6vw;
|
||||||
|
border-radius: 6vw;
|
||||||
|
padding: 2vw;
|
||||||
|
}
|
||||||
|
.color-btn {
|
||||||
|
border-radius: 5vw;
|
||||||
|
box-shadow: 0 2px 6px #bbb;
|
||||||
|
}
|
||||||
|
#start-btn {
|
||||||
|
font-size: clamp(1em, 8vw, 1.4em);
|
||||||
|
padding: 1em 13vw;
|
||||||
|
border-radius: 5vw;
|
||||||
|
margin-bottom: 6vw;
|
||||||
|
}
|
||||||
|
#status {
|
||||||
|
font-size: clamp(1em, 7vw, 1.3em);
|
||||||
|
margin-bottom: 5vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas muy pequeñas ==== */
|
||||||
|
@media (max-width: 350px) {
|
||||||
|
#game-container {
|
||||||
|
max-width: 93vw;
|
||||||
|
gap: 3vw;
|
||||||
|
padding: 1vw;
|
||||||
|
border-radius: 10vw;
|
||||||
|
}
|
||||||
|
.color-btn {
|
||||||
|
border-radius: 8vw;
|
||||||
|
}
|
||||||
|
#start-btn {
|
||||||
|
padding: 0.8em 3vw;
|
||||||
|
border-radius: 8vw;
|
||||||
|
font-size: clamp(0.9em, 13vw, 1.1em);
|
||||||
|
}
|
||||||
|
}
|
18
topo/index.html
Normal file
18
topo/index.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Atrapa el Topo</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Atrapa el Topo</h1>
|
||||||
|
<p>Haz clic en el topo cuando aparezca. ¡Consigue la mejor puntuación en 30 segundos!</p>
|
||||||
|
<div id="score">Puntaje: <span id="score-value">0</span></div>
|
||||||
|
<div id="timer">Tiempo: <span id="timer-value">30</span>s</div>
|
||||||
|
<div id="grid"></div>
|
||||||
|
<button id="start-btn">¡Empezar!</button>
|
||||||
|
<div id="end-message"></div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
79
topo/script.js
Normal file
79
topo/script.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
const gridDiv = document.getElementById('grid');
|
||||||
|
const scoreSpan = document.getElementById('score-value');
|
||||||
|
const timerSpan = document.getElementById('timer-value');
|
||||||
|
const startBtn = document.getElementById('start-btn');
|
||||||
|
const endMessageDiv = document.getElementById('end-message');
|
||||||
|
|
||||||
|
const TOTAL_TIME = 30;
|
||||||
|
const GRID_SIZE = 16;
|
||||||
|
const MOLE_SYMBOL = '🦦';
|
||||||
|
|
||||||
|
let score = 0, moleIdx = null, timer = TOTAL_TIME, intervalId, moleTimeoutId, playing = false;
|
||||||
|
|
||||||
|
function createGrid() {
|
||||||
|
gridDiv.innerHTML = '';
|
||||||
|
for (let i = 0; i < GRID_SIZE; i++) {
|
||||||
|
const cell = document.createElement('div');
|
||||||
|
cell.className = 'cell';
|
||||||
|
cell.dataset.idx = i;
|
||||||
|
cell.onclick = () => hitCell(i);
|
||||||
|
gridDiv.appendChild(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
score = 0;
|
||||||
|
timer = TOTAL_TIME;
|
||||||
|
scoreSpan.textContent = score;
|
||||||
|
timerSpan.textContent = timer;
|
||||||
|
endMessageDiv.textContent = '';
|
||||||
|
startBtn.disabled = true;
|
||||||
|
playing = true;
|
||||||
|
createGrid();
|
||||||
|
spawnMole();
|
||||||
|
intervalId = setInterval(() => {
|
||||||
|
timer--;
|
||||||
|
timerSpan.textContent = timer;
|
||||||
|
if (timer <= 0) endGame();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function endGame() {
|
||||||
|
playing = false;
|
||||||
|
clearInterval(intervalId);
|
||||||
|
clearTimeout(moleTimeoutId);
|
||||||
|
startBtn.disabled = false;
|
||||||
|
removeMole();
|
||||||
|
endMessageDiv.innerHTML = `¡Fin del juego! Tu puntaje: <b>${score}</b>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawnMole() {
|
||||||
|
removeMole();
|
||||||
|
if (!playing) return;
|
||||||
|
moleIdx = Math.floor(Math.random() * GRID_SIZE);
|
||||||
|
const cell = gridDiv.children[moleIdx];
|
||||||
|
cell.classList.add('mole');
|
||||||
|
cell.textContent = MOLE_SYMBOL;
|
||||||
|
moleTimeoutId = setTimeout(spawnMole, Math.floor(Math.random() * 700) + 600); // aparecer entre 0.6-1.3s
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeMole() {
|
||||||
|
if (moleIdx !== null) {
|
||||||
|
const cell = gridDiv.children[moleIdx];
|
||||||
|
cell.classList.remove('mole');
|
||||||
|
cell.textContent = '';
|
||||||
|
moleIdx = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hitCell(idx) {
|
||||||
|
if (!playing) return;
|
||||||
|
if (idx === moleIdx) {
|
||||||
|
score++;
|
||||||
|
scoreSpan.textContent = score;
|
||||||
|
removeMole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startBtn.onclick = startGame;
|
||||||
|
createGrid();
|
143
topo/styles.css
Normal file
143
topo/styles.css
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #fae3d9;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #22223b;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Encabezado */
|
||||||
|
h1 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: #9a8c98;
|
||||||
|
font-size: clamp(1.5rem, 5vw, 2.7rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Score y timer flexibles */
|
||||||
|
#score, #timer {
|
||||||
|
font-size: clamp(1em, 2vw, 1.18em);
|
||||||
|
margin-bottom: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablero de celdas adaptativo y cuadrado */
|
||||||
|
#grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-template-rows: repeat(4, 1fr);
|
||||||
|
gap: 1em;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 1.6em auto 0.8em auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 360px;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
background: #f5ebe3;
|
||||||
|
border-radius: 1.2em;
|
||||||
|
padding: 1em;
|
||||||
|
box-shadow: 0 2px 10px #bbb7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Celdas circulares */
|
||||||
|
.cell {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 2px 10px #bbb;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: clamp(1.5em, 6vw, 2.4em);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
}
|
||||||
|
.cell.mole {
|
||||||
|
background: #b5838d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botón start flexible */
|
||||||
|
#start-btn {
|
||||||
|
background: #9a8c98;
|
||||||
|
color: #fff;
|
||||||
|
font-size: clamp(1em, 2vw, 1.18em);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.6em;
|
||||||
|
padding: 0.6em 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 2px 8px #b5838d44;
|
||||||
|
transition: background 0.2s;
|
||||||
|
margin-bottom: 1.3em;
|
||||||
|
}
|
||||||
|
#start-btn:hover, #start-btn:active {
|
||||||
|
background: #22223b;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mensaje final */
|
||||||
|
#end-message {
|
||||||
|
font-size: clamp(1em, 2vw, 1.15em);
|
||||||
|
margin-top: 1.1em;
|
||||||
|
min-height: 2em;
|
||||||
|
color: #b5838d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas móviles ==== */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
h1 {
|
||||||
|
margin-top: 6vw;
|
||||||
|
font-size: clamp(1.1em, 7vw, 2.1em);
|
||||||
|
}
|
||||||
|
#grid {
|
||||||
|
max-width: 97vw;
|
||||||
|
gap: 4vw;
|
||||||
|
padding: 2vw;
|
||||||
|
border-radius: 7vw;
|
||||||
|
margin-top: 6vw;
|
||||||
|
margin-bottom: 6vw;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: clamp(1.1em, 10vw, 2.8em);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
#start-btn {
|
||||||
|
font-size: clamp(1em, 8vw, 1.5em);
|
||||||
|
padding: 1em 13vw;
|
||||||
|
border-radius: 5vw;
|
||||||
|
margin-bottom: 6vw;
|
||||||
|
}
|
||||||
|
#score, #timer {
|
||||||
|
font-size: clamp(1em, 7vw, 1.3em);
|
||||||
|
margin-bottom: 3vw;
|
||||||
|
}
|
||||||
|
#end-message {
|
||||||
|
font-size: clamp(1em, 7vw, 1.3em);
|
||||||
|
margin-top: 5vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== Pantallas muy pequeñas ==== */
|
||||||
|
@media (max-width: 350px) {
|
||||||
|
#grid {
|
||||||
|
max-width: 92vw;
|
||||||
|
gap: 2vw;
|
||||||
|
border-radius: 13vw;
|
||||||
|
padding: 1vw;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
font-size: clamp(0.8em, 12vw, 1.2em);
|
||||||
|
}
|
||||||
|
#start-btn {
|
||||||
|
font-size: clamp(0.9em, 10vw, 1.1em);
|
||||||
|
padding: 0.7em 3vw;
|
||||||
|
border-radius: 8vw;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user