v15 — игры в чате: крестики-нолики, угадай число, реакция, спорт-викторина

This commit is contained in:
Dauren777 2026-06-01 11:16:43 +00:00
parent 0b1b216715
commit 6e31752125

View File

@ -105,6 +105,19 @@ th{color:var(--gray-500);font-size:11px;text-transform:uppercase}
.chat-tab{flex:1;padding:8px;border-radius:10px;border:none;font-size:13px;font-weight:600;cursor:pointer;background:#1a2332;color:var(--gray-500);transition:all .2s}
.chat-tab.active{background:var(--cyan);color:var(--ink)}
.game-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;width:180px;margin:12px auto}
.game-cell{aspect-ratio:1;background:#1a2332;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:32px;font-weight:800;cursor:pointer;transition:background .2s;border:2px solid #2a3342}
.game-cell:active{background:#2a3342}
.game-cell.x{color:var(--cyan)}
.game-cell.o{color:var(--red)}
.game-cell.disabled{pointer-events:none}
.game-status{text-align:center;font-size:14px;font-weight:600;margin:8px 0}
.quiz-option{display:block;width:100%;padding:12px;margin:6px 0;background:#1a2332;border:2px solid #2a3342;border-radius:10px;color:var(--white);font-size:14px;cursor:pointer;text-align:left;transition:all .2s}
.quiz-option:active{background:#2a3342}
.quiz-option.right{background:var(--green);border-color:var(--green);color:#fff}
.quiz-option.wrong{background:var(--red);border-color:var(--red);color:#fff}
.empty-state{text-align:center;padding:40px 20px;color:var(--gray-500)}
.empty-state .big{font-size:48px;margin-bottom:8px}
@ -476,7 +489,10 @@ function renderChatList(){
const other=users.filter(u=>u.id!==uid());
const msgs=LS('messages')||{};
let h='<div class="card"><div class="chat-tabs"><button class="chat-tab active" onclick="chatFilter=\'all\';renderPage()">&#x1F4AC; Все</button><button class="chat-tab" onclick="chatFilter=\'athlete\';renderPage()">&#x1F3CA; Спортсмены</button><button class="chat-tab" onclick="chatFilter=\'coach\';renderPage()">&#x1F3CB; Тренеры</button><button class="chat-tab" onclick="chatFilter=\'parent\';renderPage()">&#x1F468;&#x200D;&#x1F466; Родители</button></div>';
// Show create group chat button
// Games button
h+='<button class="btn outline" onclick="renderGames()" style="width:100%;margin-bottom:8px">&#x1F3AE; Игры</button>';
Show create group chat button
h+='<button class="btn small outline" onclick="createGroupChat()" style="width:100%;margin-bottom:8px">+ Создать групповой чат</button>';
if(!other.length){h+='<div class="empty-state"><div class="big">&#x1F4AC;</div>Нет других пользователей</div></div>';return h}
h+='<div style="font-size:13px;color:var(--gray-500);margin-bottom:6px">Выбери с кем общаться:</div>';
@ -529,6 +545,178 @@ function sendMsg(){
// scroll to bottom
setTimeout(()=>{const el=document.getElementById('chatMsgs');if(el)el.scrollTop=el.scrollHeight},100);
}
let gameState = null;
function renderGames(){
const c = document.getElementById('mainContent');
c.innerHTML = `
<div class="card" style="text-align:center">
<h3>&#x1F3AE; Игры в чате</h3>
<p class="muted">Выбери игру и играй с друзьями!</p>
<button class="quiz-option" onclick="startTicTacToe()">&#x274C;&#x2B55; Крестики-нолики</button>
<button class="quiz-option" onclick="startGuessNumber()">&#x1F522; Угадай число (1-100)</button>
<button class="quiz-option" onclick="startReaction()">&#x26A1; Реакция — кто быстрее</button>
<button class="quiz-option" onclick="startQuiz()">&#x1F3C6; Спорт-викторина</button>
<button class="btn small outline" onclick="showPage('chat')" style="margin-top:8px">&#x2190; Назад в чаты</button>
</div>
<div id="gameArea"></div>
`;
}
function startTicTacToe(){
gameState = { type: 'tictactoe', board: Array(9).fill(''), turn: 'X', over: false };
renderTicTacToe();
}
function renderTicTacToe(){
const g = gameState;
let h = '<div class="card"><h3 style="text-align:center">&#x274C;&#x2B55; Крестики-нолики</h3><div class="game-status">';
if(g.over){
h += g.winner === 'draw' ? 'Ничья!' : g.winner + ' победил!';
h += '<br><button class="btn small outline" onclick="startTicTacToe()" style="margin-top:8px">Играть ещё</button>';
} else {
h += 'Ход: ' + (g.turn === 'X' ? '&#x274C; Крестики' : '&#x2B55; Нолики');
}
h += '</div><div class="game-grid">';
g.board.forEach((cell, i) => {
h += `<div class="game-cell${cell?' '+ (cell==='X'?'x':'o')+(g.over||cell?' disabled':'') :''}" onclick="ticMove(${i})">${cell}</div>`;
});
h += '</div><button class="btn small outline" onclick="startTicTacToe()" style="width:100%;margin-top:8px">Новая игра</button></div>';
document.getElementById('gameArea').innerHTML = h;
}
function ticMove(i){
if(!gameState||gameState.over||gameState.board[i])return;
gameState.board[i] = gameState.turn;
const lines = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
for(const [a,b,c] of lines){
if(gameState.board[a]&&gameState.board[a]===gameState.board[b]&&gameState.board[a]===gameState.board[c]){
gameState.over = true; gameState.winner = gameState.board[a]; break;
}
}
if(!gameState.over && gameState.board.every(c=>c)){ gameState.over = true; gameState.winner = 'draw'; }
if(!gameState.over) gameState.turn = gameState.turn === 'X' ? 'O' : 'X';
renderTicTacToe();
}
function startGuessNumber(){
gameState = { type: 'guess', number: Math.floor(Math.random()*100)+1, attempts: 0, max: 7 };
renderGuessNumber();
}
function renderGuessNumber(){
const g = gameState;
let h = `<div class="card" style="text-align:center"><h3>&#x1F522; Угадай число (1-100)</h3>
<p class="muted">Осталось попыток: <strong>${g.max - g.attempts}</strong></p>`;
if(g.won){
h += `<p style="color:var(--green);font-size:20px;font-weight:800">&#x1F389; Угадал! Это ${g.number}!</p><p class="muted">За ${g.attempts} попыток</p>`;
h += '<button class="btn small outline" onclick="startGuessNumber()">Играть ещё</button>';
} else if(g.attempts >= g.max){
h += `<p style="color:var(--red);font-size:18px;font-weight:800">Не угадал! Это было ${g.number}</p>`;
h += '<button class="btn small outline" onclick="startGuessNumber()">Играть ещё</button>';
} else {
h += '<input type="number" id="guessInput" placeholder="Твоё число" min="1" max="100" onkeydown="if(event.key==='Enter')doGuess()"><button class="btn" onclick="doGuess()" style="margin-top:8px">Проверить</button>';
}
h += '</div><button class="btn small outline" onclick="renderGames()" style="width:100%">&#x2190; К играм</button>';
document.getElementById('gameArea').innerHTML = h;
}
function doGuess(){
if(!gameState||gameState.won||gameState.attempts>=gameState.max)return;
const n = +document.getElementById('guessInput').value;
if(!n||n<1||n>100)return;
gameState.attempts++;
if(n === gameState.number){ gameState.won = true; renderGuessNumber(); return }
const hint = n < gameState.number ? '&#x1F4C8; Больше!' : '&#x1F4C9; Меньше!';
const tempDiv = document.createElement('div'); tempDiv.innerHTML = `<div class="card" style="text-align:center;padding:12px;margin-bottom:8px"><p>${n} &mdash; ${hint}</p></div>`;
document.getElementById('gameArea').insertBefore(tempDiv, document.getElementById('gameArea').firstChild);
if(gameState.attempts >= gameState.max) renderGuessNumber();
else { document.getElementById('guessInput').value=''; document.getElementById('guessInput').focus(); }
}
function startReaction(){
gameState = { type: 'reaction', state: 'waiting', startTime: 0, best: getMy('reactionBest') || 9999 };
renderReaction();
}
function renderReaction(){
const g = gameState;
let bg = '#151c28', txt = '';
if(g.state === 'waiting'){ bg = '#FF6B6B'; txt = '&#x1F534; Жди зелёный!'; }
else if(g.state === 'ready'){ bg = '#4CAF50'; txt = '&#x1F7E2; ЖМИ СЕЙЧАС!'; }
else if(g.state === 'early'){ bg = '#FFD700'; txt = '&#x26A0; Слишком рано! Жми "Начать"'; }
else if(g.state === 'done'){ txt = `&#x23F1; Твоё время: <strong>${g.reaction} мс</strong><br>Рекорд: ${g.best !== 9999 ? g.best+' мс' : '—'}`; }
let h = `<div class="card" style="text-align:center">
<h3>&#x26A1; Реакция</h3>
<div style="width:200px;height:200px;border-radius:50%;background:${bg};display:flex;align-items:center;justify-content:center;margin:16px auto;cursor:${g.state==='ready'?'pointer':'default'};font-size:18px;font-weight:800;transition:background .1s" onclick="reactClick()">${txt||'&#x1F3AF; Нажми "Начать"'}</div>`;
if(g.state === 'done'){
h += '<button class="btn small outline" onclick="startReaction()">Ещё раз</button>';
} else if(g.state === 'waiting' || g.state === 'early'){
h += '<button class="btn" onclick="reactStart()">Начать</button>';
}
h += '</div><button class="btn small outline" onclick="renderGames()" style="width:100%">&#x2190; К играм</button>';
document.getElementById('gameArea').innerHTML = h;
}
function reactStart(){
if(!gameState||gameState.state==='ready')return;
gameState.state = 'waiting'; renderReaction();
const delay = 1500 + Math.random() * 3000;
setTimeout(() => {
if(gameState.state !== 'waiting') return;
gameState.state = 'ready'; gameState.startTime = Date.now(); renderReaction();
}, delay);
}
function reactClick(){
if(!gameState)return;
if(gameState.state === 'waiting'){ gameState.state = 'early'; renderReaction(); return; }
if(gameState.state === 'ready'){ gameState.reaction = Date.now() - gameState.startTime; gameState.state = 'done';
if(gameState.reaction < gameState.best){ gameState.best = gameState.reaction; setMy('reactionBest', gameState.reaction); }
renderReaction(); }
}
function startQuiz(){
const questions = [
{q:'Сколько метров в олимпийском бассейне?',a:['25 м','50 м','100 м','33 м'],r:1},
{q:'Какой стиль плавания самый быстрый?',a:['Брасс','Баттерфляй','Кроль','На спине'],r:2},
{q:'Сколько золотых медалей у Майкла Фелпса?',a:['8','15','23','28'],r:2},
{q:'Как зовут лучшего спринтера мира (50 м в/с)?',a:['Майкл Фелпс','Калеб Дрессел','Райан Лохте','Адам Пити'],r:1},
{q:'Где пройдёт Олимпиада-2032?',a:['Лос-Анджелес','Париж','Брисбен','Токио'],r:2},
{q:'Сколько длится олимпийский цикл?',a:['2 года','3 года','4 года','5 лет'],r:2},
{q:'Какая страна выиграла больше всех медалей в плавании?',a:['Китай','Австралия','Россия','США'],r:3},
{q:'Как называется поворот в кроле?',a:['Сальто','Кувырок','Разворот','Маятник'],r:1}
];
gameState = { type: 'quiz', questions, current: 0, score: 0, answered: false };
renderQuiz();
}
function renderQuiz(){
const g = gameState;
if(g.current >= g.questions.length){
document.getElementById('gameArea').innerHTML = `<div class="card" style="text-align:center"><h3>&#x1F3C6; Викторина завершена!</h3><p style="font-size:24px;font-weight:800;color:var(--cyan)">${g.score} / ${g.questions.length}</p><p class="muted">${g.score>=6?'&#x1F451; Ты знаток спорта!':g.score>=4?'&#x1F44D; Неплохо!':'&#x1F4DA; Учи матчасть!'}</p><button class="btn" onclick="startQuiz()">Ещё раз</button></div>`;
return;
}
const q = g.questions[g.current];
let h = `<div class="card"><h3>&#x2753; Вопрос ${g.current+1}/${g.questions.length}</h3><p style="font-size:17px;font-weight:600;margin:12px 0">${q.q}</p>`;
q.a.forEach((ans,i)=>{
let cls = 'quiz-option';
if(g.answered){
if(i === q.r) cls += ' right';
else if(g.selected === i) cls += ' wrong';
}
h += `<button class="${cls}" onclick="answerQuiz(${i})" ${g.answered?'disabled':''}>${ans}</button>`;
});
h += `<div style="margin-top:8px;font-size:13px;color:var(--gray-500)">Счёт: ${g.score}</div>`;
if(g.answered){
h += '<button class="btn small" onclick="nextQuiz()" style="margin-top:8px">Дальше &#x2192;</button>';
}
h += '</div>';
document.getElementById('gameArea').innerHTML = h;
}
function answerQuiz(i){
if(!gameState||gameState.answered)return;
gameState.answered = true; gameState.selected = i;
if(i === gameState.questions[gameState.current].r) gameState.score++;
renderQuiz();
}
function nextQuiz(){
gameState.current++; gameState.answered = false; gameState.selected = null; renderQuiz();
}
function createGroupChat(){
const name=prompt('Название группового чата:');
if(!name)return;