Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions js/solo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Соло-режим: игра без Twitch. Открывайте solo.html.
// Вместо init.js: сам инициализирует игру и принимает слова из поля ввода.

const SOLO_USER = { username: 'solo', 'display-name': 'Я' };
const SOLO_COLOR = '#00FF00';

const soloInput = document.getElementById('solo-input');
const soloSubmit = document.getElementById('solo-submit');

let solo_ready = false; // игра инициализирована: секретное слово получено

function setSoloControlsEnabled(enabled) {
if (soloInput) soloInput.disabled = !enabled;
if (soloSubmit) soloSubmit.disabled = !enabled;
}

async function runQueue() {
// Always shift the processed item, even if process_message throws.
// Otherwise the queue stalls forever and guesses stop being handled.
while (wordQueue.length > 0) {
const { user, color, msg } = wordQueue[0];
try {
await process_message(user, color, msg);
} catch (e) {
console.error('process_message failed:', e);
} finally {
wordQueue.shift();
}
}
}

// Своя версия app(): те же шаги, что в init.js, но без подключения к Twitch
// и без аналитики. init.js на этой странице не подключён, поэтому его задачу
// выполняет этот файл.
async function app() {
try {
loadSettings(); // канал Twitch для соло не обязателен, возвращаемое значение игнорируем

reset_round();

// Блокируем ввод, пока не получено секретное слово: иначе угадывание
// уйдёт в API с пустым challenge_id и слово потеряется.
setSoloControlsEnabled(false);

// получение секретного слова для отгадывания
secret_word_id = await generate_secret_word();
console.log('Ключ игры: ', secret_word_id);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

solo_ready = true;
setSoloControlsEnabled(true);
addTextToLastWords('🎯 Слово загадано! Введите свой вариант ниже');
Comment on lines +45 to +51

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 6 '\bcreateGame\s*\(|\bgameId\b' --glob '*.js'

Repository: AnnaCodit/Slovotron

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Tracked JavaScript files:\n'
git ls-files '*.js' | sed -n '1,120p'

printf '\nAPI-related identifiers:\n'
rg -n -C 4 'generate_secret_word|create_game|gameId|solo_ready|solo_guess|secret_word_id' -g '*.js' . || true

printf '\nFile sizes:\n'
wc -l js/*.js 2>/dev/null || true

Repository: AnnaCodit/Slovotron

Length of output: 13510


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'js/api.js createGame implementations and generate_secret_word:\n'
sed -n '130,275p' js/api.js | cat -n
printf '\n'
sed -n '285,365p' js/api.js | cat -n

printf '\njs/solo.js initialization path:\n'
sed -n '35,70p' js/solo.js | cat -n

Repository: AnnaCodit/Slovotron

Length of output: 12209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '150,200p' js/api.js | cat -n

printf '\nBehavioral probe for solo initialization with empty game IDs:\n'
node - <<'JS'
const initialValues = {
  secret_word_id: '',
  solo_ready: false
};
function generate_secret_word(payloadId = '') {
  return payloadId; // matches game.gameId passthrough
}
function setSoloControlsEnabled(enabled) {}
const result = {
  secret_word_id: generate_secret_word(''),
  console_log: 'Ключ игры: ',
  solo_ready: true,
  controls_enabled: true,
  message: '🎯 Слово загадано! Введите свой вариант ниже'
};
const canGuess = (message2) => result.solo_ready && !!(message2 || '').trim() !== '' && result.secret_word_id;
console.log(JSON.stringify({ result, guess_rejected_when_submitted: !canGuess('пример') }, null, 2));
JS

Repository: AnnaCodit/Slovotron

Length of output: 2585


Validate game.gameId before starting the solo round.

The current backends accept room_id even when it is empty, and generate_secret_word() then passes it to js/solo.js. This leaves solo_ready = true and input enabled, but solo_guess() rejects the submission because secret_word_id is falsy. Fail the initialization before enabling the controls when game.gameId is missing or empty.

🧰 Tools
🪛 ast-grep (0.45.0)

[error] 49-49: React's useState should not be directly called
Context: setSoloControlsEnabled(true)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/solo.js` around lines 45 - 51, Validate that game.gameId is present and
non-empty before calling generate_secret_word in the solo-round initialization
flow. If it is missing, fail initialization and return without setting
solo_ready, enabling controls, or displaying the ready message; preserve the
existing setup path when a valid gameId is available.

} catch (error) {
console.error(error);
// Управление остаётся заблокированным: раунд не запущен.
addTextToLastWords('⚠️ Не удалось начать игру. Проверьте интернет и настройки бэкенда (⚙️), затем нажмите 🔄');
}
}

function solo_guess() {
if (is_game_finished) return;

let message = (soloInput.value || '').trim();

// Слово ещё загадывается (или инициализация не удалась) — не отправляем
// запрос без валидного ключа игры, текст в поле сохраняем.
if (!solo_ready || !secret_word_id) {
if (message) addTextToLastWords('Подождите, слово ещё загадывается...');
return;
}

soloInput.value = '';

if (!message) return;

// проверка на подсказку, дальше не идем
if (message.toLowerCase().startsWith('!подска')) {
if (backend_supports_tips()) use_tip('', true);
return;
}

// Проверяем пасхалки
if (typeof check_easter_egg === 'function' && check_easter_egg(message)) {
return;
}

// если больше одного слова, слишком короткое или длинное, или число — игнорируем
if (message.split(' ').length > 1 || message.length > 20 || message.length <= 1 || !isNaN(message)) {
addTextToLastWords('Введите одно слово (2–20 букв)');
return;
}

// Приводим ЛЕД и ЛЁД к одному виду
message = message.replace(/ё/gi, 'е');

// числа и прочие символы убираем тоже (как в init.js)
message = message.replace(/[^a-zA-Zа-яА-Я]/g, '');

if (message.length < 2) {
addTextToLastWords('Введите одно слово (2–20 букв)');
return;
}

if (words_count === 0) {
document.getElementById('info').style.display = 'none';
document.getElementById('settings').style.display = 'none';
}
words_count++;
wordQueue.push({ 'user': SOLO_USER, 'color': SOLO_COLOR, 'msg': message });
if (wordQueue.length === 1) {
runQueue();
}
}

if (soloSubmit) {
soloSubmit.addEventListener('click', solo_guess);
}

if (soloInput) {
soloInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
solo_guess();
}
});
soloInput.focus();
}

app();
Loading