Mejoras y optimizaciones en general.
This commit is contained in:
@@ -3,18 +3,30 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Empareja la Bandera</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Empareja la Bandera</h1>
|
||||
<h1 id="title">Empareja la Bandera</h1>
|
||||
<p>Haz clic en una bandera y después en el país correspondiente. ¿Puedes emparejar todas?</p>
|
||||
|
||||
<div id="controls">
|
||||
<label for="pairs-count">Número de parejas:</label>
|
||||
<select id="pairs-count">
|
||||
<option value="6">6</option>
|
||||
<option value="8">8</option>
|
||||
<option value="12" selected>12</option>
|
||||
<option value="16">16</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="score">Aciertos: <span id="score-value"></span> / <span id="score-total"></span></div>
|
||||
<div id="game">
|
||||
<div class="flags" id="flags"></div>
|
||||
<div class="countries" id="countries"></div>
|
||||
<div id="game" role="group" aria-labelledby="title">
|
||||
<div class="flags" id="flags" aria-label="Banderas" role="list"></div>
|
||||
<div class="countries" id="countries" aria-label="Países" role="list"></div>
|
||||
</div>
|
||||
<button id="restart-btn">Reiniciar</button>
|
||||
<div id="status"></div>
|
||||
<div id="status" aria-live="polite"></div>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
@@ -1,3 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
// Lista completa con país y siglas según ISO 3166-1 alpha-2
|
||||
const countryList = [
|
||||
{ name: "España", code: "ES" }, { name: "Francia", code: "FR" }, { name: "Alemania", code: "DE" }, { name: "Italia", code: "IT" },
|
||||
@@ -13,88 +15,119 @@ const countryList = [
|
||||
function getFlagEmoji(code) {
|
||||
// Las banderas se generan a partir de letras usando unicode, no por siglas textuales
|
||||
// Solo funcionan para países con código alpha-2 y script latino
|
||||
if (!code || code.length !== 2) return "";
|
||||
if (!code || code.length !== 2) return '';
|
||||
return String.fromCodePoint(
|
||||
...code.toUpperCase().split("").map(c => 0x1F1E6 + c.charCodeAt(0) - 65)
|
||||
...code.toUpperCase().split('').map(c => 0x1F1E6 + c.charCodeAt(0) - 65)
|
||||
);
|
||||
}
|
||||
|
||||
// Para elegir N países aleatorios distintos con bandera
|
||||
// Elegir N países aleatorios distintos con bandera, sin mutar la lista original
|
||||
function pickRandomCountries(list, n) {
|
||||
const pool = list.slice(); // copia de seguridad
|
||||
const chosen = [];
|
||||
const used = {};
|
||||
while (chosen.length < n && list.length > 0) {
|
||||
let idx = Math.floor(Math.random() * list.length);
|
||||
let c = list[idx];
|
||||
let flag = getFlagEmoji(c.code);
|
||||
if (flag && !used[c.code]) {
|
||||
chosen.push({name: c.name, code: c.code, flag});
|
||||
used[c.code] = true;
|
||||
const used = new Set();
|
||||
while (chosen.length < n && pool.length > 0) {
|
||||
const idx = Math.floor(Math.random() * pool.length);
|
||||
const c = pool[idx];
|
||||
const flag = getFlagEmoji(c.code);
|
||||
if (flag && !used.has(c.code)) {
|
||||
chosen.push({ name: c.name, code: c.code, flag });
|
||||
used.add(c.code);
|
||||
}
|
||||
list.splice(idx,1);
|
||||
// Eliminar del pool para evitar repetir
|
||||
pool.splice(idx, 1);
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
|
||||
let flagsDiv = document.getElementById('flags');
|
||||
let countriesDiv = document.getElementById('countries');
|
||||
let statusDiv = document.getElementById('status');
|
||||
let scoreSpan = document.getElementById('score-value');
|
||||
let scoreTotal = document.getElementById('score-total');
|
||||
let restartBtn = document.getElementById('restart-btn');
|
||||
// Referencias DOM
|
||||
const flagsDiv = document.getElementById('flags');
|
||||
const countriesDiv = document.getElementById('countries');
|
||||
const statusDiv = document.getElementById('status');
|
||||
const scoreSpan = document.getElementById('score-value');
|
||||
const scoreTotal = document.getElementById('score-total');
|
||||
const restartBtn = document.getElementById('restart-btn');
|
||||
const pairsSelect = document.getElementById('pairs-count');
|
||||
|
||||
let pairs = [], flags = [], countries = [], selectedFlag = null, selectedCountry = null, score = 0, totalPairs = 12;
|
||||
// Estado
|
||||
let pairs = [];
|
||||
let flags = [];
|
||||
let countries = [];
|
||||
let selectedFlag = null;
|
||||
let selectedCountry = null;
|
||||
let score = 0;
|
||||
let totalPairs = 12;
|
||||
|
||||
// Utilidades
|
||||
function shuffle(arr) {
|
||||
for (let i = arr.length-1; i>0; i--) {
|
||||
const j = Math.floor(Math.random() * (i+1));
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||
}
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
let fullList = countryList.slice(); // copia
|
||||
pairs = pickRandomCountries(fullList, totalPairs);
|
||||
flags = pairs.map(o=>o);
|
||||
countries = pairs.map(o=>o);
|
||||
shuffle(flags); shuffle(countries);
|
||||
score = 0;
|
||||
scoreSpan.textContent = score;
|
||||
scoreTotal.textContent = pairs.length;
|
||||
selectedFlag = selectedCountry = null;
|
||||
renderFlags();
|
||||
renderCountries();
|
||||
statusDiv.textContent = 'Empareja todas las banderas con su país';
|
||||
function setStatus(msg) {
|
||||
statusDiv.textContent = msg;
|
||||
}
|
||||
|
||||
// Renderizado
|
||||
function renderFlags() {
|
||||
flagsDiv.innerHTML = '';
|
||||
// Limpieza segura
|
||||
flagsDiv.textContent = '';
|
||||
flags.forEach((p, i) => {
|
||||
const d = document.createElement('div');
|
||||
const d = document.createElement('button');
|
||||
d.type = 'button';
|
||||
d.className = 'flag';
|
||||
d.textContent = p.flag;
|
||||
d.setAttribute('tabindex', 0);
|
||||
d.onclick = () => selectFlag(i);
|
||||
d.textContent = p.flag; // seguro (caracter unicode)
|
||||
d.setAttribute('tabindex', '0');
|
||||
d.setAttribute('role', 'listitem');
|
||||
d.setAttribute('aria-label', `Bandera de ${p.name}`);
|
||||
d.setAttribute('aria-disabled', p.matched ? 'true' : 'false');
|
||||
d.setAttribute('aria-selected', selectedFlag === i ? 'true' : 'false');
|
||||
|
||||
if (p.matched) d.classList.add('matched');
|
||||
if (selectedFlag === i) d.classList.add('selected');
|
||||
|
||||
d.addEventListener('click', () => selectFlag(i));
|
||||
d.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
selectFlag(i);
|
||||
}
|
||||
});
|
||||
|
||||
flagsDiv.appendChild(d);
|
||||
});
|
||||
}
|
||||
|
||||
function renderCountries() {
|
||||
countriesDiv.innerHTML = '';
|
||||
countriesDiv.textContent = '';
|
||||
countries.forEach((p, i) => {
|
||||
const d = document.createElement('div');
|
||||
const d = document.createElement('button');
|
||||
d.type = 'button';
|
||||
d.className = 'country';
|
||||
d.textContent = p.name;
|
||||
d.setAttribute('tabindex', 0);
|
||||
d.onclick = () => selectCountry(i);
|
||||
d.textContent = p.name; // seguro
|
||||
d.setAttribute('tabindex', '0');
|
||||
d.setAttribute('role', 'listitem');
|
||||
d.setAttribute('aria-label', `País ${p.name}`);
|
||||
d.setAttribute('aria-disabled', p.matched ? 'true' : 'false');
|
||||
d.setAttribute('aria-selected', selectedCountry === i ? 'true' : 'false');
|
||||
|
||||
if (p.matched) d.classList.add('matched');
|
||||
if (selectedCountry === i) d.classList.add('selected');
|
||||
|
||||
d.addEventListener('click', () => selectCountry(i));
|
||||
d.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
selectCountry(i);
|
||||
}
|
||||
});
|
||||
|
||||
countriesDiv.appendChild(d);
|
||||
});
|
||||
}
|
||||
|
||||
// Lógica selección
|
||||
function selectFlag(i) {
|
||||
if (flags[i].matched) return;
|
||||
selectedFlag = i;
|
||||
@@ -110,32 +143,81 @@ function selectCountry(i) {
|
||||
}
|
||||
|
||||
function checkMatch() {
|
||||
if (selectedFlag === null || selectedCountry === null) return;
|
||||
|
||||
const flagObj = flags[selectedFlag];
|
||||
const countryObj = countries[selectedCountry];
|
||||
|
||||
if (!flagObj || !countryObj) return;
|
||||
|
||||
if (flagObj.code === countryObj.code) {
|
||||
flags[selectedFlag].matched = true;
|
||||
countries[selectedCountry].matched = true;
|
||||
score++;
|
||||
scoreSpan.textContent = score;
|
||||
statusDiv.textContent = '¡Correcto!';
|
||||
scoreSpan.textContent = String(score);
|
||||
setStatus('¡Correcto!');
|
||||
renderFlags();
|
||||
renderCountries();
|
||||
if (score === pairs.length) {
|
||||
statusDiv.textContent = '¡Has emparejado todas las banderas! 🎉';
|
||||
setStatus('¡Has emparejado todas las banderas! 🎉');
|
||||
}
|
||||
} else {
|
||||
statusDiv.textContent = 'No es correcto, intenta otra vez.';
|
||||
setStatus('No es correcto, intenta otra vez.');
|
||||
// Reset de selección tras breve pausa
|
||||
setTimeout(() => {
|
||||
statusDiv.textContent = '';
|
||||
selectedFlag = selectedCountry = null;
|
||||
setStatus('');
|
||||
selectedFlag = null;
|
||||
selectedCountry = null;
|
||||
renderFlags();
|
||||
renderCountries();
|
||||
}, 850);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset selección tras acierto
|
||||
selectedFlag = null;
|
||||
selectedCountry = null;
|
||||
}
|
||||
|
||||
restartBtn.onclick = startGame;
|
||||
// Inicio/reinicio
|
||||
function startGame() {
|
||||
// Leer número de parejas desde selector, con límites
|
||||
const desired = pairsSelect && Number(pairsSelect.value) ? Number(pairsSelect.value) : 12;
|
||||
totalPairs = Math.max(4, Math.min(desired, countryList.length));
|
||||
|
||||
const fullList = countryList.slice(); // copia
|
||||
pairs = pickRandomCountries(fullList, totalPairs);
|
||||
// Si no se pudieron escoger las deseadas (por restricciones), ajustar total
|
||||
if (pairs.length < totalPairs) {
|
||||
totalPairs = pairs.length;
|
||||
}
|
||||
|
||||
flags = pairs.map(o => ({ ...o }));
|
||||
countries = pairs.map(o => ({ ...o }));
|
||||
|
||||
shuffle(flags);
|
||||
shuffle(countries);
|
||||
|
||||
score = 0;
|
||||
scoreSpan.textContent = String(score);
|
||||
scoreTotal.textContent = String(pairs.length);
|
||||
selectedFlag = null;
|
||||
selectedCountry = null;
|
||||
|
||||
renderFlags();
|
||||
renderCountries();
|
||||
setStatus('Empareja todas las banderas con su país');
|
||||
|
||||
// Enfocar primera bandera para accesibilidad
|
||||
const firstFlag = flagsDiv.querySelector('.flag');
|
||||
if (firstFlag) firstFlag.focus();
|
||||
}
|
||||
|
||||
// Listeners (evitar handlers inline)
|
||||
restartBtn.addEventListener('click', startGame);
|
||||
if (pairsSelect) {
|
||||
pairsSelect.addEventListener('change', startGame);
|
||||
}
|
||||
|
||||
// Init
|
||||
startGame();
|
@@ -73,4 +73,68 @@ h1 {
|
||||
}
|
||||
@media (max-width: 520px) {
|
||||
.flags, .countries { grid-template-columns: 1fr;}
|
||||
}
|
||||
/* Controles y accesibilidad */
|
||||
#controls {
|
||||
margin: 1rem auto 0.5rem auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
#controls select#pairs-count {
|
||||
font-size: 1em;
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.flag, .country {
|
||||
outline: none;
|
||||
}
|
||||
.flag:focus-visible, .country:focus-visible {
|
||||
box-shadow: 0 0 0 3px rgba(35, 105, 143, 0.35);
|
||||
}
|
||||
.flag[aria-disabled="true"], .country[aria-disabled="true"] {
|
||||
cursor: default;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Modo oscuro básico */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #0f1222;
|
||||
color: #eaeaf0;
|
||||
}
|
||||
#score, #status {
|
||||
color: #a0a3b0;
|
||||
}
|
||||
.flag, .country {
|
||||
background: #20233a;
|
||||
color: #eaeaf0;
|
||||
border-color: #2c3252;
|
||||
box-shadow: 0 2px 10px rgba(61, 90, 254, 0.12);
|
||||
}
|
||||
.flag.selected, .country.selected {
|
||||
background: #1e3a8a;
|
||||
border-color: #3d5afe;
|
||||
box-shadow: 0 0 0 2px #3d5afe;
|
||||
}
|
||||
.flag.matched, .country.matched {
|
||||
background: #1b5e20;
|
||||
color: #d7ffd9;
|
||||
border-color: #66bb6a;
|
||||
box-shadow: 0 0 0 2px #66bb6a;
|
||||
}
|
||||
#restart-btn {
|
||||
background: #3d5afe;
|
||||
}
|
||||
#restart-btn:hover {
|
||||
background: #0a2459;
|
||||
}
|
||||
#controls select#pairs-count {
|
||||
background: #20233a;
|
||||
color: #eaeaf0;
|
||||
border-color: #2c3252;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user