Aleksmorshen commited on
Commit
60ef9b3
·
verified ·
1 Parent(s): 3079d69

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +789 -335
app.py CHANGED
@@ -12,7 +12,7 @@ socketio = SocketIO(app)
12
 
13
  ROOMS_DB = os.path.join(app.root_path, 'rooms.json')
14
  USERS_DB = os.path.join(app.root_path, 'users.json')
15
- GAMES_DB = os.path.join(app.root_path, 'games.json') # Добавляем файл для хранения данных об играх
16
 
17
 
18
  def load_json(file_path, default={}):
@@ -37,26 +37,33 @@ def save_json(file_path, data):
37
  rooms = load_json(ROOMS_DB)
38
  users = load_json(USERS_DB)
39
  games_data = load_json(GAMES_DB, default={
40
- "game1": {
41
- "name": "Угадай число",
42
- "description": "Админ загадывает число от 1 до 100. Участники по очереди пытаются угадать число. Админ даёт подсказки 'больше' или 'меньше'.",
43
- "min_players": 1,
44
- "max_players": 5,
45
- "state": {} # Состояние игры для каждой комнаты
46
- },
47
- "game2": {
48
  "name": "Крокодил",
49
  "description": "Один игрок (ведущий) получает слово и должен показать его жестами остальным игрокам, не произнося ни слова. Остальные игроки пытаются угадать слово.",
50
  "min_players": 2,
51
  "max_players": 5,
52
- "state": {}
53
  },
54
- "game3":{
55
- "name": "Викторина",
56
- "description": "Администратор задает вопросы , а участники по очереди отвечают на них ",
57
- "min_players": 1,
58
  "max_players": 5,
59
  "state": {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  }
61
  })
62
  save_json(GAMES_DB, games_data)
@@ -100,7 +107,7 @@ def index():
100
  <meta charset="UTF-8">
101
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
102
  <title>Видеоконференция</title>
103
- <style>
104
  :root {
105
  --primary-color: #6200ee;
106
  --secondary-color: #3700b3;
@@ -218,7 +225,7 @@ def dashboard():
218
  <meta charset="UTF-8">
219
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
220
  <title>Панель управления</title>
221
- <style>
222
  :root {
223
  --primary-color: #6200ee;
224
  --secondary-color: #3700b3;
@@ -346,7 +353,7 @@ def room(token):
346
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
347
  <title>Комната {{ token }}</title>
348
  <style>
349
- :root {
350
  --primary-color: #4CAF50;
351
  --secondary-color: #388E3C;
352
  --background-color: #f0f0f0;
@@ -554,39 +561,127 @@ def room(token):
554
  background-color: var(--secondary-color);
555
  }
556
 
557
- #game-display {
558
- margin-top: 20px;
559
- padding: 15px;
560
- background-color: var(--surface-color);
561
- border-radius: var(--border-radius);
562
- box-shadow: var(--box-shadow);
563
- text-align: center;
564
- display: none; /* Скрыто по умолчанию */
565
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
 
567
- #game-display h2 {
568
- color: var(--primary-color);
569
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
 
571
- #game-display p {
572
- margin-bottom: 10px;
573
- }
574
 
575
- #game-display button {
576
- background-color: var(--primary-color);
577
- color: white;
578
- border: none;
579
- border-radius: var(--border-radius);
580
- padding: 5px 10px;
581
- cursor: pointer;
582
- transition: background-color 0.2s;
583
- margin-top: 10px;
584
- }
585
- #game-display button:hover{
586
- background-color: var(--secondary-color);
587
- }
588
-
589
-
590
  @media (max-width: 768px) {
591
  .main-container {
592
  flex-direction: column;
@@ -943,132 +1038,36 @@ def room(token):
943
  gameContent.innerHTML = '';
944
 
945
  // Инициализация игры в зависимости от gameId
946
- if(gameId === 'game1'){
947
- initGuessNumber(gameId, gameInfo, gameContent);
948
- } else if (gameId === 'game2'){
949
  initCrocodile(gameId, gameInfo, gameContent);
950
- }else if (gameId === 'game3'){
951
- initQuiz(gameId, gameInfo, gameContent)
952
- }
953
- });
954
-
955
- // Игра "Угадай число"
956
- function initGuessNumber(gameId, gameInfo, gameContent) {
957
- if (isAdmin) {
958
- const input = document.createElement('input');
959
- input.type = 'number';
960
- input.id = 'admin-guess-number';
961
- input.placeholder = 'Загадайте число (1-100)';
962
- gameContent.appendChild(input);
963
-
964
- const button = document.createElement('button');
965
- button.textContent = 'Загадать';
966
- button.onclick = () => {
967
- const number = parseInt(input.value, 10);
968
- if (number >= 1 && number <= 100) {
969
- socket.emit('set_game_state', { token, game_id: gameId, state: { number: number, guesses: [] } });
970
- } else {
971
- alert('Пожалуйста, введите число от 1 до 100.');
972
- }
973
- };
974
- gameContent.appendChild(button);
975
- }
976
-
977
- const guessInput = document.createElement('input');
978
- guessInput.type = 'number';
979
- guessInput.id = 'user-guess-input';
980
- guessInput.placeholder = 'Ваша догадка';
981
- guessInput.disabled = !isAdmin; // Изначально отключено для всех, кроме админа
982
- gameContent.appendChild(guessInput);
983
-
984
- const guessButton = document.createElement('button');
985
- guessButton.textContent = 'Угадать';
986
- guessButton.id = 'user-guess-button';
987
- guessButton.disabled = !isAdmin;
988
- guessButton.onclick = () => {
989
- const guess = parseInt(guessInput.value, 10);
990
- if (!isNaN(guess)) {
991
- socket.emit('game_action', { token, game_id: gameId, action: 'guess', value: guess, user: username });
992
- guessInput.value = ''; //очистка
993
- }
994
- };
995
- gameContent.appendChild(guessButton);
996
-
997
- const resultDiv = document.createElement('div');
998
- resultDiv.id = 'guess-result';
999
- gameContent.appendChild(resultDiv);
1000
- }
1001
-
1002
-
1003
- socket.on('update_game_state', (data) => {
1004
- const gameId = data.game_id;
1005
- const gameState = data.state;
1006
-
1007
- if(gameId === 'game1'){
1008
- updateGuessNumberState(gameId, gameState);
1009
- }else if (gameId === 'game2'){
1010
- updateCrocodileState(gameId,gameState);
1011
- }else if (gameId === 'game3'){
1012
- updateQuizState(gameId, gameState);
1013
- }
1014
-
1015
- });
1016
-
1017
- function updateGuessNumberState(gameId, gameState){
1018
- const resultDiv = document.getElementById('guess-result');
1019
- if (!resultDiv) return; // Если элемента нет (игра не "Угадай число")
1020
-
1021
- resultDiv.innerHTML = ''; // Очищаем предыдущие результаты
1022
-
1023
- // Включаем ввод для всех, когда число загадано
1024
- if (gameState.number) {
1025
- document.getElementById('user-guess-input').disabled = false;
1026
- document.getElementById('user-guess-button').disabled = false;
1027
-
1028
- }
1029
- if(gameState.guesses && gameState.guesses.length > 0){
1030
- const lastGuess = gameState.guesses[gameState.guesses.length - 1];
1031
- const p = document.createElement('p');
1032
- p.textContent = `${lastGuess.user}: ${lastGuess.value} - ${lastGuess.result}`;
1033
- resultDiv.appendChild(p);
1034
-
1035
- if (lastGuess.result === 'Угадано!') {
1036
- const winMessage = document.createElement('p');
1037
- winMessage.textContent = `Победил ${lastGuess.user}!`;
1038
- resultDiv.appendChild(winMessage);
1039
- document.getElementById('user-guess-input').disabled = true; // Отключаем после победы
1040
- document.getElementById('user-guess-button').disabled = true;
1041
-
1042
- }
1043
- }
1044
- }
1045
-
1046
- socket.on('game_action_result', (data) => {
1047
- const gameId = data.game_id;
1048
- const action = data.action;
1049
-
1050
- if (gameId === 'game1' && action === 'guess') {
1051
- // Обработка результата уже происходит в update_game_state
1052
  }
1053
  });
1054
 
1055
-
1056
- // Игра "Крокодил"
1057
  function initCrocodile(gameId, gameInfo, gameContent){
1058
  const wordInput = document.createElement('input');
1059
  wordInput.type = 'text';
1060
  wordInput.id = 'crocodile-word-input';
1061
  wordInput.placeholder = 'Введите слово';
 
1062
  wordInput.disabled = true; // По умолчанию отключен
1063
  gameContent.appendChild(wordInput);
1064
 
1065
  const startTurnButton = document.createElement('button');
1066
  startTurnButton.textContent = 'Начать ход';
 
1067
  startTurnButton.id = 'start-turn-button';
1068
  gameContent.appendChild(startTurnButton);
1069
 
1070
  const guessInput = document.createElement('input');
1071
  guessInput.type = 'text';
 
1072
  guessInput.id = 'crocodile-guess-input';
1073
  guessInput.placeholder = 'Ваша догадка';
1074
  guessInput.disabled = true;
@@ -1076,6 +1075,7 @@ def room(token):
1076
 
1077
  const guessButton = document.createElement('button');
1078
  guessButton.textContent = 'Угадать';
 
1079
  guessButton.id = 'crocodile-guess-button';
1080
  guessButton.disabled = true;
1081
  gameContent.appendChild(guessButton);
@@ -1172,153 +1172,523 @@ def room(token):
1172
  }
1173
  }
1174
  }
1175
-
1176
- socket.on('timer_tick', (data) => {
1177
- const gameId = data.game_id;
1178
- if(gameId === 'game2' && document.getElementById('crocodile-timer')){
1179
- document.getElementById('crocodile-timer').textContent = `Время: ${data.time}`;
1180
- }
1181
- })
1182
-
1183
- //Игра викторина
1184
- function initQuiz(gameId, gameInfo, gameContent){
1185
- const questionInput = document.createElement('input');
1186
- questionInput.type = 'text';
1187
- questionInput.id = 'quiz-question-input';
1188
- questionInput.placeholder = 'Введите вопрос';
1189
- gameContent.appendChild(questionInput);
1190
-
1191
- const answerInput = document.createElement('input');
1192
- answerInput.type = 'text';
1193
- answerInput.id = 'quiz-answer-input';
1194
- answerInput.placeholder = 'Введите правильный ответ';
1195
- gameContent.appendChild(answerInput);
1196
-
1197
- const askQuestionButton = document.createElement('button');
1198
- askQuestionButton.textContent = 'Задать вопрос';
1199
- askQuestionButton.id = 'ask-question-button';
1200
- gameContent.appendChild(askQuestionButton);
1201
-
1202
- const userAnswerInput = document.createElement('input');
1203
- userAnswerInput.type = 'text';
1204
- userAnswerInput.id = 'user-answer-input';
1205
- userAnswerInput.placeholder = 'Ваш ответ';
1206
- userAnswerInput.disabled = true;
1207
- gameContent.appendChild(userAnswerInput);
1208
-
1209
- const answerButton = document.createElement('button');
1210
- answerButton.textContent = 'Ответить';
1211
- answerButton.id = 'answer-button';
1212
- answerButton.disabled = true;
1213
- gameContent.appendChild(answerButton);
1214
 
1215
  const resultDiv = document.createElement('div');
1216
- resultDiv.id = 'quiz-result';
1217
  gameContent.appendChild(resultDiv);
1218
 
1219
- if (isAdmin) {
1220
- questionInput.disabled = false;
1221
- answerInput.disabled = false;
1222
- askQuestionButton.onclick = () => {
1223
- const question = questionInput.value.trim();
1224
- const answer = answerInput.value.trim();
1225
- if (question && answer) {
1226
- socket.emit('set_game_state', { token, game_id: gameId, state: { question: question, answer: answer, answers: [], currentPlayerIndex: 0, scores: {} } });
1227
- questionInput.value = '';
1228
- answerInput.value = '';
1229
- // Блокируем поля ввода после отправки вопроса
1230
- questionInput.disabled = true;
1231
- answerInput.disabled = true;
1232
-
1233
- } else {
1234
- alert('Введите вопрос и ответ!');
1235
- }
1236
- };
1237
- } else {
1238
- questionInput.disabled = true;
1239
- answerInput.disabled = true;
1240
- }
1241
-
1242
- answerButton.onclick = () => {
1243
- const userAnswer = userAnswerInput.value.trim();
1244
- if (userAnswer) {
1245
- socket.emit('game_action', { token, game_id: gameId, action: 'answer', value: userAnswer, user: username });
1246
- userAnswerInput.value = '';
1247
  }
1248
- }
1249
 
 
 
 
 
 
 
 
1250
  }
1251
 
1252
- function updateQuizState(gameId, gameState){
1253
- const resultDiv = document.getElementById('quiz-result');
1254
- const userAnswerInput = document.getElementById('user-answer-input');
1255
- const answerButton = document.getElementById('answer-button');
1256
- const questionInput = document.getElementById('quiz-question-input');
1257
- const askQuestionButton = document.getElementById('ask-question-button');
1258
 
1259
- if (!resultDiv || !userAnswerInput || !answerButton) return;
1260
 
1261
- resultDiv.innerHTML = ''; // Очистка
 
 
 
1262
 
1263
- if (gameState.question) {
 
 
 
 
 
 
1264
 
1265
- if (!isAdmin){
1266
- const questionText = document.createElement('p');
1267
- questionText.textContent = `Вопрос: ${gameState.question}`;
1268
- resultDiv.appendChild(questionText);
1269
- }
1270
 
1271
- // Включаем поля для ответа для текущего игрока.
1272
- const users = {{ rooms[token]['users']|tojson }};
 
 
 
 
 
 
 
 
 
 
 
 
 
1273
 
1274
- if(gameState.currentPlayerIndex < users.length){
1275
- const currentPlayer = users[gameState.currentPlayerIndex];
1276
 
1277
- if (username === currentPlayer) {
1278
- userAnswerInput.disabled = false;
1279
- answerButton.disabled = false;
1280
- } else {
1281
- userAnswerInput.disabled = true;
1282
- answerButton.disabled = true;
1283
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1284
  }
1285
 
1286
- if (gameState.answers && gameState.answers.length > 0){
1287
- gameState.answers.forEach(ans => {
1288
- const p = document.createElement('p');
1289
- p.textContent = `${ans.user}: ${ans.value} - ${ans.isCorrect ? 'Верно' : 'Неверно'}`;
1290
- resultDiv.appendChild(p);
1291
-
1292
- if(gameState.scores && gameState.scores[ans.user] !== undefined){
1293
- const scoreText = document.createElement('p');
1294
- scoreText.textContent = `Текущий счет ${ans.user}: ${gameState.scores[ans.user]}`;
1295
- resultDiv.appendChild(scoreText);
1296
- }
1297
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1298
  }
 
 
 
 
 
1299
 
1300
- // Проверяем, все ли игроки ответили
1301
- if(gameState.currentPlayerIndex >= users.length){
 
1302
 
1303
- const winner = Object.entries(gameState.scores).sort(([,a],[,b]) => b-a)[0];
1304
 
1305
- if(winner){
1306
- const winMessage = document.createElement('p');
1307
- winMessage.textContent = `Игра окончена, победитель ${winner[0]}!`;
1308
- resultDiv.appendChild(winMessage);
1309
  }
 
1310
 
1311
- userAnswerInput.disabled = true;
1312
- answerButton.disabled = true;
1313
- if(isAdmin && questionInput && askQuestionButton){
1314
- questionInput.disabled = false;
1315
- askQuestionButton.disabled = false; // Разблокировка кнопки задать вопрос
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1316
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1318
  }
1319
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1320
  }
1321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1322
  function leaveRoom() {
1323
  socket.emit('leave', { token, username });
1324
  if (localStream) {
@@ -1339,7 +1709,7 @@ def room(token):
1339
  def handle_join(data):
1340
  token = data['token']
1341
  username = data['username']
1342
- print(f"User {username} joining room {token}")
1343
  if token in rooms and len(rooms[token]['users']) < rooms[token]['max_users']:
1344
  join_room(token)
1345
  if username not in rooms[token]['users']:
@@ -1377,14 +1747,7 @@ def handle_leave(data):
1377
  if rooms[token].get('current_game'): # Если игра в процессе
1378
  game_id = rooms[token]['current_game']
1379
  if games_data[game_id]['state'].get(token):
1380
- #Если "угадай число"
1381
- if game_id == 'game1':
1382
- if(games_data[game_id]['state'][token].get('guesses')):
1383
- new_guesses = [guess for guess in games_data[game_id]['state'][token]['guesses'] if guess['user'] != username]
1384
- games_data[game_id]['state'][token]['guesses'] = new_guesses
1385
-
1386
- #Если "крокодил"
1387
- elif game_id == 'game2':
1388
  if games_data[game_id]['state'][token].get('presenter') == username:
1389
  # Сброс игры, если уходит ведущий
1390
  del games_data[game_id]['state'][token]
@@ -1394,22 +1757,35 @@ def handle_leave(data):
1394
  #Удаление догадок
1395
  if games_data[game_id]['state'][token].get('guesses'):
1396
  games_data[game_id]['state'][token]['guesses'] = [guess for guess in games_data[game_id]['state'][token]['guesses'] if guess['user'] != username]
1397
-
1398
- elif game_id == 'game3':
1399
- if games_data[game_id]['state'][token].get('answers'):
1400
- games_data[game_id]['state'][token]['answers'] = [answer for answer in games_data[game_id]['state'][token]['answers'] if answer['user'] != username]
1401
-
1402
- if games_data[game_id]['state'][token].get('scores') and username in games_data[game_id]['state'][token]['scores']:
1403
- del games_data[game_id]['state'][token]['scores'][username]
1404
-
1405
- if games_data[game_id]['state'][token].get('currentPlayerIndex') != None:
1406
- users_in_room = rooms.get(token, {}).get('users',[])
1407
- current_index = games_data[game_id]['state'][token]['currentPlayerIndex']
1408
-
1409
- if len(users_in_room) > 0 :
1410
- games_data[game_id]['state'][token]['currentPlayerIndex'] = current_index % len(users_in_room)
1411
- else:
1412
- games_data[game_id]['state'][token]['currentPlayerIndex'] = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
1413
  save_json(GAMES_DB, games_data); # Сохраняем изменения в любом случае
1414
  emit('update_game_state', {'game_id': game_id, 'state': games_data[game_id]['state'][token]}, room=token);
1415
 
@@ -1445,6 +1821,7 @@ def handle_set_game_state(data):
1445
  token = data['token']
1446
  game_id = data['game_id']
1447
  state = data['state']
 
1448
 
1449
  if token in rooms:
1450
  if rooms[token]['admin'] == session.get('username'):
@@ -1454,7 +1831,7 @@ def handle_set_game_state(data):
1454
  save_json(GAMES_DB, games_data)
1455
  emit('update_game_state', {'game_id': game_id, 'state': state}, room=token)
1456
 
1457
- if game_id == 'game2' and state.get('isRunning'): # Запуск таймера для крокодила
1458
  start_timer(token, game_id)
1459
 
1460
  @socketio.on('game_action')
@@ -1462,31 +1839,14 @@ def handle_game_action(data):
1462
  token = data['token']
1463
  game_id = data['game_id']
1464
  action = data['action']
1465
- value = data['value']
1466
  user = data['user']
 
1467
 
1468
  if token in rooms and game_id == rooms[token]['current_game']:
1469
  current_state = games_data[game_id]['state'].get(token, {})
1470
 
1471
- if game_id == 'game1': # Угадай число
1472
- if action == 'guess' and current_state.get('number') is not None:
1473
- result = ""
1474
- if value < current_state['number']:
1475
- result = "Больше"
1476
- elif value > current_state['number']:
1477
- result = "Меньше"
1478
- else:
1479
- result = "Угадано!"
1480
-
1481
- if 'guesses' not in current_state:
1482
- current_state['guesses'] = []
1483
-
1484
- current_state['guesses'].append({'user': user, 'value': value, 'result': result})
1485
- games_data[game_id]['state'][token] = current_state # Сохраняем обновленное состояние
1486
- save_json(GAMES_DB, games_data) # Сохраняем в файл
1487
- emit('update_game_state', { 'game_id': game_id, 'state': current_state}, room=token)
1488
-
1489
- elif game_id == 'game2': # Крокодил
1490
  if action == 'guess' and current_state.get('isRunning'):
1491
  result = "Не угадано"
1492
  if value.lower() == current_state.get('word', '').lower():
@@ -1500,30 +1860,124 @@ def handle_game_action(data):
1500
  save_json(GAMES_DB, games_data)
1501
  emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token)
1502
 
1503
- elif game_id == 'game3':
1504
- if action == 'answer' and current_state.get('question'):
1505
- is_correct = value.lower() == current_state.get('answer', '').lower()
 
1506
 
1507
- if 'answers' not in current_state:
1508
- current_state['answers'] = []
1509
- current_state['answers'].append({'user':user, 'value': value, 'isCorrect': is_correct})
1510
 
1511
- if 'scores' not in current_state:
1512
- current_state['scores'] = {}
1513
- if is_correct:
1514
- current_state['scores'][user] = current_state['scores'].get(user, 0) + 1
1515
-
1516
- # Переходим к следующему игроку
1517
- users_in_room = rooms.get(token,{}).get('users',[])
1518
- current_state['currentPlayerIndex'] = (current_state.get('currentPlayerIndex', 0) + 1) % len(users_in_room)
1519
 
1520
  games_data[game_id]['state'][token] = current_state
1521
- save_json(GAMES_DB, games_data)
1522
  emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token)
1523
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1524
 
1525
  def start_timer(token, game_id):
1526
- if game_id != 'game2': # Таймер пока только для крокодила
1527
  return
1528
 
1529
  def timer_loop():
 
12
 
13
  ROOMS_DB = os.path.join(app.root_path, 'rooms.json')
14
  USERS_DB = os.path.join(app.root_path, 'users.json')
15
+ GAMES_DB = os.path.join(app.root_path, 'games.json')
16
 
17
 
18
  def load_json(file_path, default={}):
 
37
  rooms = load_json(ROOMS_DB)
38
  users = load_json(USERS_DB)
39
  games_data = load_json(GAMES_DB, default={
40
+ "crocodile": {
 
 
 
 
 
 
 
41
  "name": "Крокодил",
42
  "description": "Один игрок (ведущий) получает слово и должен показать его жестами остальным игрокам, не произнося ни слова. Остальные игроки пытаются угадать слово.",
43
  "min_players": 2,
44
  "max_players": 5,
45
+ "state": {}
46
  },
47
+ "alias": {
48
+ "name": "Alias",
49
+ "description": "Один игрок (ведущий) получает слово и должен объяснить его другими словами, не используя однокоренные. Остальные игроки пытаются угадать слово.",
50
+ "min_players": 2,
51
  "max_players": 5,
52
  "state": {}
53
+ },
54
+ "mafia": {
55
+ "name": "Мафия",
56
+ "description": "Игроки делятся на две команды: мафию и мирных жителей. Мафия пытается тайно убивать мирных жителей, а мирные жители пытаются вычислить и казнить мафию.",
57
+ "min_players": 4,
58
+ "max_players": 5, # Можно увеличить, но для теста оставим так
59
+ "state": {}
60
+ },
61
+ "durak": {
62
+ "name": "Дурак",
63
+ "description": "Карточная игра, в которой игроки стараются избавиться от всех своих карт.",
64
+ "min_players": 2,
65
+ "max_players": 5,
66
+ "state": {} # Состояние игры
67
  }
68
  })
69
  save_json(GAMES_DB, games_data)
 
107
  <meta charset="UTF-8">
108
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
109
  <title>Видеоконференция</title>
110
+ <style>
111
  :root {
112
  --primary-color: #6200ee;
113
  --secondary-color: #3700b3;
 
225
  <meta charset="UTF-8">
226
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
227
  <title>Панель управления</title>
228
+ <style>
229
  :root {
230
  --primary-color: #6200ee;
231
  --secondary-color: #3700b3;
 
353
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
354
  <title>Комната {{ token }}</title>
355
  <style>
356
+ :root {
357
  --primary-color: #4CAF50;
358
  --secondary-color: #388E3C;
359
  --background-color: #f0f0f0;
 
561
  background-color: var(--secondary-color);
562
  }
563
 
564
+ #game-display {
565
+ margin-top: 20px;
566
+ padding: 20px;
567
+ background-color: var(--surface-color);
568
+ border-radius: var(--border-radius);
569
+ box-shadow: var(--box-shadow);
570
+ text-align: center;
571
+ display: none; /* Скрыто по умолчанию */
572
+ width: 80%; /* Ширина */
573
+ max-width: 800px; /* Максимальная ширина */
574
+ margin-left: auto; /* Центрирование по горизонтали */
575
+ margin-right: auto;
576
+ }
577
+ #game-display h2 {
578
+ color: var(--primary-color);
579
+ margin-bottom: 10px;
580
+ }
581
+ #game-description {
582
+ margin-bottom: 15px;
583
+ font-size: 1.1rem;
584
+ color: var(--text-color);
585
+ }
586
+ #game-content {
587
+ display: flex;
588
+ flex-direction: column;
589
+ align-items: center; /* Центрирование элементов */
590
+ gap: 10px; /* Расстояние между элементами */
591
+ }
592
+ .game-input {
593
+ padding: 8px;
594
+ border: 1px solid #ccc;
595
+ border-radius: var(--border-radius);
596
+ font-size: 1rem;
597
+ width: 60%; /* Ширина инпутов */
598
+ max-width: 300px; /* Максимальная ширина */
599
+ }
600
 
601
+ .game-button {
602
+ background-color: var(--primary-color);
603
+ color: white;
604
+ border: none;
605
+ border-radius: var(--border-radius);
606
+ padding: 10px 15px;
607
+ cursor: pointer;
608
+ transition: background-color 0.2s;
609
+ font-size: 1rem;
610
+ }
611
+ .game-button:hover {
612
+ background-color: var(--secondary-color);
613
+ }
614
+
615
+ #game-result {
616
+ margin-top: 15px;
617
+ font-size: 1rem;
618
+ color: var(--text-color);
619
+ width: 100%; /* Ширина */
620
+ }
621
+ #game-result p{
622
+ word-break: break-word;
623
+ }
624
+ #crocodile-timer {
625
+ font-size: 1.2rem;
626
+ font-weight: bold;
627
+ margin-top: 10px;
628
+ color: var(--primary-color);
629
+ }
630
+ /*Стили для карт*/
631
+ .card {
632
+ width: 60px;
633
+ height: 90px;
634
+ border: 1px solid black;
635
+ border-radius: 5px;
636
+ display: inline-block; /* Чтобы карты шли в ряд */
637
+ margin: 2px;
638
+ text-align: center;
639
+ font-size: 1rem;
640
+ background-color: white;
641
+ user-select: none; /* Предотвращает выделение текста */
642
+ }
643
+ .card.selected {
644
+ border-color: blue;
645
+ box-shadow: 0 0 5px blue;
646
+ }
647
+ .card.back {
648
+ background-color: #ddd; /* Цвет рубашки */
649
+ color: transparent; /* Скрываем текст на рубашке */
650
+ }
651
+
652
+ .card-container{
653
+ display: flex;
654
+ flex-wrap: wrap;
655
+ justify-content: center;
656
+ gap: 5px;
657
+ margin-bottom: 10px;
658
+
659
+ }
660
+ #durak-buttons{
661
+ margin-top: 10px;
662
+ display: flex;
663
+ justify-content: center;
664
+ gap: 10px;
665
+
666
+ }
667
+ #durak-table{
668
+ display: flex;
669
+ flex-wrap: wrap;
670
+ justify-content: center;
671
+ gap: 10px;
672
+ min-height: 100px;
673
+ border: 2px dashed #ccc;
674
+ padding: 10px;
675
+ margin-top: 10px;
676
+ margin-bottom: 10px;
677
+
678
+ }
679
+ #durak-info{
680
+ font-size: 1rem;
681
+ font-weight: bold;
682
+ }
683
 
 
 
 
684
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
  @media (max-width: 768px) {
686
  .main-container {
687
  flex-direction: column;
 
1038
  gameContent.innerHTML = '';
1039
 
1040
  // Инициализация игры в зависимости от gameId
1041
+ if (gameId === 'crocodile'){
 
 
1042
  initCrocodile(gameId, gameInfo, gameContent);
1043
+ } else if (gameId === 'alias'){
1044
+ initAlias(gameId, gameInfo, gameContent);
1045
+ } else if(gameId === 'mafia'){
1046
+ initMafia(gameId, gameInfo, gameContent);
1047
+ }else if(gameId === 'durak'){
1048
+ initDurak(gameId, gameInfo, gameContent);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1049
  }
1050
  });
1051
 
1052
+ // Игра "Крокодил"
 
1053
  function initCrocodile(gameId, gameInfo, gameContent){
1054
  const wordInput = document.createElement('input');
1055
  wordInput.type = 'text';
1056
  wordInput.id = 'crocodile-word-input';
1057
  wordInput.placeholder = 'Введите слово';
1058
+ wordInput.classList.add('game-input');
1059
  wordInput.disabled = true; // По умолчанию отключен
1060
  gameContent.appendChild(wordInput);
1061
 
1062
  const startTurnButton = document.createElement('button');
1063
  startTurnButton.textContent = 'Начать ход';
1064
+ startTurnButton.classList.add('game-button');
1065
  startTurnButton.id = 'start-turn-button';
1066
  gameContent.appendChild(startTurnButton);
1067
 
1068
  const guessInput = document.createElement('input');
1069
  guessInput.type = 'text';
1070
+ guessInput.classList.add('game-input');
1071
  guessInput.id = 'crocodile-guess-input';
1072
  guessInput.placeholder = 'Ваша догадка';
1073
  guessInput.disabled = true;
 
1075
 
1076
  const guessButton = document.createElement('button');
1077
  guessButton.textContent = 'Угадать';
1078
+ guessButton.classList.add('game-button');
1079
  guessButton.id = 'crocodile-guess-button';
1080
  guessButton.disabled = true;
1081
  gameContent.appendChild(guessButton);
 
1172
  }
1173
  }
1174
  }
1175
+
1176
+ //игра "Alias"
1177
+ function initAlias(gameId, gameInfo, gameContent){
1178
+ const wordInput = document.createElement('input');
1179
+ wordInput.type = 'text';
1180
+ wordInput.id = 'alias-word-input';
1181
+ wordInput.placeholder = 'Введите слово';
1182
+ wordInput.classList.add('game-input');
1183
+ wordInput.disabled = true; // По умолчанию отключен
1184
+ gameContent.appendChild(wordInput);
1185
+
1186
+ const startTurnButton = document.createElement('button');
1187
+ startTurnButton.textContent = 'Начать ход';
1188
+ startTurnButton.classList.add('game-button');
1189
+ startTurnButton.id = 'start-turn-button';
1190
+ gameContent.appendChild(startTurnButton);
1191
+
1192
+ const guessInput = document.createElement('input');
1193
+ guessInput.type = 'text';
1194
+ guessInput.id = 'alias-guess-input';
1195
+ guessInput.classList.add('game-input');
1196
+ guessInput.placeholder = 'Ваша догадка';
1197
+ guessInput.disabled = true;
1198
+ gameContent.appendChild(guessInput);
1199
+
1200
+ const guessButton = document.createElement('button');
1201
+ guessButton.textContent = 'Угадать';
1202
+ guessButton.classList.add('game-button');
1203
+ guessButton.id = 'alias-guess-button';
1204
+ guessButton.disabled = true;
1205
+ gameContent.appendChild(guessButton);
 
 
 
 
 
 
 
 
1206
 
1207
  const resultDiv = document.createElement('div');
1208
+ resultDiv.id = 'alias-result';
1209
  gameContent.appendChild(resultDiv);
1210
 
1211
+ const timerDisplay = document.createElement('div');
1212
+ timerDisplay.id = 'alias-timer';
1213
+ gameContent.appendChild(timerDisplay);
1214
+
1215
+
1216
+ if(isAdmin){
1217
+ wordInput.disabled = false;
1218
+ startTurnButton.onclick = () => {
1219
+ const word = wordInput.value.trim();
1220
+ if(word){
1221
+ const users = {{ rooms[token]['users']|tojson }};
1222
+ const nonAdminUsers = users.filter(u => u !== username);
1223
+ const presenter = nonAdminUsers.length > 0 ? nonAdminUsers[Math.floor(Math.random() * nonAdminUsers.length)] : null;
1224
+
1225
+ if(presenter){
1226
+ socket.emit('set_game_state', {token, game_id: gameId, state: {word: word, presenter: presenter, guesses: [], timer: 60, isRunning: true}});
1227
+ }else{
1228
+ alert("Нет игроков для выбора ведущего");
1229
+ }
1230
+ }else{
1231
+ alert("Введите слово");
1232
+ }
 
 
 
 
 
 
1233
  }
1234
+ }
1235
 
1236
+ guessButton.onclick = () => {
1237
+ const guess = guessInput.value.trim();
1238
+ if(guess){
1239
+ socket.emit('game_action', {token, game_id: gameId, action: 'guess', value: guess, user: username});
1240
+ guessInput.value = '';
1241
+ }
1242
+ }
1243
  }
1244
 
1245
+ function updateAliasState(gameId, gameState){
1246
+ const resultDiv = document.getElementById('alias-result');
1247
+ const wordInput = document.getElementById('alias-word-input');
1248
+ const guessInput = document.getElementById('alias-guess-input');
1249
+ const guessButton = document.getElementById('alias-guess-button');
1250
+ const timerDisplay = document.getElementById('alias-timer');
1251
 
1252
+ if(!resultDiv || !wordInput || !guessInput || !guessButton || !timerDisplay) return;
1253
 
1254
+ resultDiv.innerHTML = '';
1255
+
1256
+ if(gameState.isRunning){
1257
+ timerDisplay.textContent = `Время: ${gameState.timer}`;
1258
 
1259
+ if(username === gameState.presenter){
1260
+ wordInput.value = gameState.word;
1261
+ wordInput.disabled = true;
1262
+ }else{
1263
+ wordInput.value = '';
1264
+ wordInput.disabled = true;
1265
+ }
1266
 
1267
+ guessInput.disabled = username === gameState.presenter;
1268
+ guessButton.disabled = username === gameState.presenter;
 
 
 
1269
 
1270
+ if(gameState.guesses && gameState.guesses.length > 0){
1271
+ gameState.guesses.forEach(guess => {
1272
+ const p = document.createElement('p');
1273
+ p.textContent = `${guess.user}: ${guess.value} - ${guess.result}`;
1274
+ resultDiv.appendChild(p);
1275
+ });
1276
+ }
1277
+ if (gameState.guesses.length>0 && gameState.guesses[gameState.guesses.length - 1].result === "Угадано!"){
1278
+ const winMessage = document.createElement('p');
1279
+ winMessage.textContent = `Победил ${gameState.guesses[gameState.guesses.length - 1].user}!`;
1280
+ resultDiv.appendChild(winMessage);
1281
+ guessInput.disabled = true;
1282
+ guessButton.disabled = true;
1283
+ timerDisplay.textContent = '';
1284
+ gameState.isRunning = false; // Остановка игры
1285
 
1286
+ //Очистка
1287
+ wordInput.value = '';
1288
 
1289
+ }
1290
+ }
1291
+ }
1292
+
1293
+ //Игра "Мафия"
1294
+ function initMafia(gameId, gameInfo, gameContent) {
1295
+ const startMafiaButton = document.createElement('button');
1296
+ startMafiaButton.textContent = 'Начать игру';
1297
+ startMafiaButton.classList.add('game-button');
1298
+ startMafiaButton.id = 'start-mafia-button';
1299
+ gameContent.appendChild(startMafiaButton);
1300
+
1301
+ const resultDiv = document.createElement('div');
1302
+ resultDiv.id = 'mafia-result';
1303
+ gameContent.appendChild(resultDiv);
1304
+
1305
+ const voteInput = document.createElement('input');
1306
+ voteInput.type = 'text';
1307
+ voteInput.id = 'mafia-vote-input';
1308
+ voteInput.placeholder = 'За кого голосуете?';
1309
+ voteInput.classList.add('game-input');
1310
+ voteInput.disabled = true;
1311
+ gameContent.appendChild(voteInput);
1312
+
1313
+ const voteButton = document.createElement('button');
1314
+ voteButton.textContent = 'Голосовать';
1315
+ voteButton.classList.add('game-button');
1316
+ voteButton.id = 'mafia-vote-button';
1317
+ voteButton.disabled = true;
1318
+ gameContent.appendChild(voteButton);
1319
+
1320
+ if (isAdmin) {
1321
+ startMafiaButton.onclick = () => {
1322
+ const users = {{ rooms[token]['users']|tojson }};
1323
+ // Раздача ролей
1324
+ const roles = assignMafiaRoles(users);
1325
+ socket.emit('set_game_state', { token, game_id: gameId, state: { roles: roles, phase: 'night', votes: {}, isRunning: true, killed: null, checked: null } });
1326
+ };
1327
  }
1328
 
1329
+ voteButton.onclick = () => {
1330
+ const vote = voteInput.value.trim();
1331
+ if (vote) {
1332
+ socket.emit('game_action', { token, game_id: gameId, action: 'vote', value: vote, user: username });
1333
+ voteInput.value = ''; //Очищаем поле
1334
+ }
1335
+ };
1336
+ }
1337
+
1338
+ function assignMafiaRoles(users) {
1339
+ const numPlayers = users.length;
1340
+ let numMafia = 1;
1341
+ if (numPlayers >= 5) {
1342
+ numMafia = 2;
1343
+ }
1344
+ // Создаем массив ролей: сначала мафия, потом мирные
1345
+ const roles = Array(numMafia).fill('mafia').concat(Array(numPlayers - numMafia).fill('civilian'));
1346
+
1347
+ // Перемешиваем роли
1348
+ for (let i = roles.length - 1; i > 0; i--) {
1349
+ const j = Math.floor(Math.random() * (i + 1));
1350
+ [roles[i], roles[j]] = [roles[j], roles[i]]; // ES6 деструктуризация для обмена
1351
+ }
1352
+
1353
+ // Назначаем роли пользователям
1354
+ const assignedRoles = {};
1355
+ for (let i = 0; i < numPlayers; i++) {
1356
+ assignedRoles[users[i]] = roles[i];
1357
+ }
1358
+ return assignedRoles;
1359
+ }
1360
+
1361
+ function updateMafiaState(gameId, gameState) {
1362
+
1363
+ const resultDiv = document.getElementById('mafia-result');
1364
+ const voteInput = document.getElementById('mafia-vote-input');
1365
+ const voteButton = document.getElementById('mafia-vote-button');
1366
+
1367
+
1368
+ if (!resultDiv || !voteInput || !voteButton) return;
1369
+
1370
+ resultDiv.innerHTML = ''; // Очищаем
1371
+
1372
+ // Ночь
1373
+ if (gameState.isRunning && gameState.phase === 'night') {
1374
+ voteInput.disabled = true; // Отключаем голосование ночью
1375
+ voteButton.disabled = true;
1376
+ if (gameState.roles[username] === 'mafia') {
1377
+ resultDiv.innerHTML = '<p>Вы мафия. Обсудите с другими мафиози, кого убить.</p>';
1378
+ } else {
1379
+ resultDiv.innerHTML = '<p>Ночь. Все спят.</p>';
1380
  }
1381
+ }
1382
+ // День
1383
+ else if(gameState.isRunning && gameState.phase === 'day'){
1384
+ voteInput.disabled = false;
1385
+ voteButton.disabled = false;
1386
 
1387
+ if (gameState.killed) {
1388
+ resultDiv.innerHTML += `<p>Ночью был убит ${gameState.killed}.</p>`;
1389
+ }
1390
 
1391
+ resultDiv.innerHTML += '<p>День. Обсуждение и голосование.</p>';
1392
 
1393
+ if (gameState.votes && Object.keys(gameState.votes).length > 0) {
1394
+ resultDiv.innerHTML += '<p>Голоса:</p>';
1395
+ for (const voter in gameState.votes) {
1396
+ resultDiv.innerHTML += `<p>${voter}: ${gameState.votes[voter]}</p>`;
1397
  }
1398
+ }
1399
 
1400
+ // Подсчет голосов и определение, кого казнят
1401
+ if(Object.keys(gameState.votes).length === {{ rooms[token]['users']|length }}){
1402
+ const voteCounts = {};
1403
+ let maxVotes = 0;
1404
+ let votedOut = null;
1405
+ let tied = false; // Флаг ничьей
1406
+
1407
+ for(const voter in gameState.votes){
1408
+ const votedFor = gameState.votes[voter];
1409
+ voteCounts[votedFor] = (voteCounts[votedFor] || 0) + 1;
1410
+
1411
+ if(voteCounts[votedFor] > maxVotes){
1412
+ maxVotes = voteCounts[votedFor];
1413
+ votedOut = votedFor;
1414
+ tied = false; // Сбрасываем ничью, если новый лидер
1415
+ }else if(voteCounts[votedFor] === maxVotes){
1416
+ tied = true; // Ничья
1417
+ }
1418
+ }
1419
+
1420
+ if (tied) {
1421
+ resultDiv.innerHTML += '<p>Голосование завершилось ничьей. Никто не казнен.</p>';
1422
+ }else{
1423
+ resultDiv.innerHTML += `<p>По итогам голосования казнен ${votedOut}.</p>`;
1424
+ // Проверяем, была ли это мафия
1425
+ if (gameState.roles[votedOut] === 'mafia') {
1426
+ resultDiv.innerHTML += '<p>Мирные жители победили!</p>';
1427
+ gameState.isRunning = false;
1428
+ }
1429
+ }
1430
+ // Проверяем, остались ли мафиози
1431
+ const remainingMafia = Object.values(gameState.roles).filter(role => role === 'mafia').length;
1432
+ if (remainingMafia === 0) {
1433
+ resultDiv.innerHTML += '<p>Мирные жители победили!</p>';
1434
+ gameState.isRunning = false;
1435
  }
1436
+ }
1437
+ }
1438
+ }
1439
+
1440
+ //Карточная игра "Дурак"
1441
+ function initDurak(gameId, gameInfo, gameContent){
1442
+ if (isAdmin) {
1443
+ const startButton = document.createElement('button');
1444
+ startButton.textContent = 'Раздать карты';
1445
+ startButton.classList.add('game-button');
1446
+ startButton.onclick = () => {
1447
+ const { deck, hands } = dealDurakCards({{ rooms[token]['users']|tojson }});
1448
+ socket.emit('set_game_state', { token, game_id: gameId, state: { deck, hands, table: [], turn: 0, attacker: null, defender: null, trumpSuit: deck.length > 0 ? deck[deck.length-1].suit : null, isGameEnd: false, winner: null} });
1449
+ };
1450
+ gameContent.appendChild(startButton);
1451
+ }
1452
+
1453
+ const playerHandContainer = document.createElement('div');
1454
+ playerHandContainer.id = 'player-hand';
1455
+ playerHandContainer.classList.add('card-container');
1456
+ gameContent.appendChild(playerHandContainer);
1457
 
1458
+ const tableContainer = document.createElement('div');
1459
+ tableContainer.id = 'durak-table';
1460
+ gameContent.appendChild(tableContainer);
1461
+
1462
+
1463
+ const buttonsDiv = document.createElement('div');
1464
+ buttonsDiv.id = 'durak-buttons';
1465
+ gameContent.appendChild(buttonsDiv);
1466
+
1467
+ const takeButton = document.createElement('button');
1468
+ takeButton.textContent = 'Взять';
1469
+ takeButton.id = 'take-button';
1470
+ takeButton.classList.add('game-button');
1471
+ buttonsDiv.appendChild(takeButton);
1472
+
1473
+
1474
+ const doneButton = document.createElement('button');
1475
+ doneButton.textContent = 'Готово/Пас';
1476
+ doneButton.id = 'done-button';
1477
+ doneButton.classList.add('game-button');
1478
+ buttonsDiv.appendChild(doneButton);
1479
+
1480
+ const infoDiv = document.createElement('div');
1481
+ infoDiv.id = 'durak-info';
1482
+ gameContent.appendChild(infoDiv);
1483
+
1484
+ takeButton.onclick = () => {
1485
+ socket.emit('game_action', { token, game_id: gameId, action: 'take', user: username });
1486
+ };
1487
+ doneButton.onclick = () => {
1488
+ socket.emit('game_action', { token, game_id: gameId, action: 'done', user: username });
1489
+ }
1490
+ }
1491
+
1492
+ function dealDurakCards(players) {
1493
+ const suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades'];
1494
+ const ranks = ['6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
1495
+ let deck = [];
1496
+
1497
+ // Создаем колоду
1498
+ for (const suit of suits) {
1499
+ for (const rank of ranks) {
1500
+ deck.push({ suit, rank });
1501
  }
1502
+ }
1503
+
1504
+ // Перемешиваем колоду
1505
+ for (let i = deck.length - 1; i > 0; i--) {
1506
+ const j = Math.floor(Math.random() * (i + 1));
1507
+ [deck[i], deck[j]] = [deck[j], deck[i]];
1508
+ }
1509
+
1510
+ // Раздаем карты
1511
+ const hands = {};
1512
+ players.forEach(player => hands[player] = []);
1513
+ let cardIndex = 0;
1514
+ while (cardIndex < 6 * players.length && cardIndex < deck.length) {
1515
+ players.forEach(player => {
1516
+ if (cardIndex < deck.length) {
1517
+ hands[player].push(deck[cardIndex]);
1518
+ cardIndex++;
1519
+ }
1520
+ });
1521
+ }
1522
+ deck = deck.slice(cardIndex); // Оставшиеся карты - колода.
1523
+ return { deck, hands };
1524
+ }
1525
+
1526
+ function renderCard(card, isHidden) {
1527
+ const cardElement = document.createElement('div');
1528
+ cardElement.classList.add('card');
1529
+ if (isHidden) {
1530
+ cardElement.classList.add('back'); // Рубашка
1531
+ } else {
1532
+ cardElement.textContent = `${card.rank}${getSuitSymbol(card.suit)}`;
1533
+ cardElement.dataset.suit = card.suit;
1534
+ cardElement.dataset.rank = card.rank;
1535
+ }
1536
+
1537
+ return cardElement;
1538
+ }
1539
+ function getSuitSymbol(suit) {
1540
+ switch (suit) {
1541
+ case 'Hearts': return '♥';
1542
+ case 'Diamonds': return '♦';
1543
+ case 'Clubs': return '♣';
1544
+ case 'Spades': return '♠';
1545
+ default: return '';
1546
+ }
1547
  }
1548
 
1549
+ function canBeat(attackingCard, defendingCard, trumpSuit){
1550
+ if(attackingCard.suit === defendingCard.suit){
1551
+ return compareRanks(defendingCard.rank, attackingCard.rank) > 0;
1552
+ }else{
1553
+ return defendingCard.suit === trumpSuit;
1554
+ }
1555
+ }
1556
+
1557
+ function compareRanks(rank1, rank2){
1558
+ const ranks = ['6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
1559
+ return ranks.indexOf(rank1) - ranks.indexOf(rank2);
1560
+ }
1561
+
1562
+ function updateDurakState(gameId, gameState){
1563
+ const playerHandContainer = document.getElementById('player-hand');
1564
+ const tableContainer = document.getElementById('durak-table');
1565
+ const infoDiv = document.getElementById('durak-info');
1566
+
1567
+ if (!playerHandContainer || !tableContainer || !infoDiv) return;
1568
+ playerHandContainer.innerHTML = ''; // Очистка
1569
+ tableContainer.innerHTML = '';
1570
+ infoDiv.innerHTML = '';
1571
+
1572
+ const trumpCardEl = document.createElement('div');
1573
+ if(gameState.trumpSuit){
1574
+ infoDiv.innerHTML = `Козырь: ${getSuitSymbol(gameState.trumpSuit)}`;
1575
+
1576
+ trumpCardEl.classList.add('card');
1577
+
1578
+ if(gameState.deck.length > 0){
1579
+ trumpCardEl.textContent = `${gameState.deck[gameState.deck.length -1].rank} ${getSuitSymbol(gameState.deck[gameState.deck.length -1].suit)}`;
1580
+ infoDiv.appendChild(trumpCardEl)
1581
+ }
1582
+
1583
+ }
1584
+
1585
+ const myHand = gameState.hands[username] || [];
1586
+ myHand.forEach(card => {
1587
+ const cardElement = renderCard(card, false);
1588
+ cardElement.addEventListener('click', () => {
1589
+
1590
+ const selectedCards = playerHandContainer.querySelectorAll('.selected');
1591
+
1592
+ if (cardElement.classList.contains('selected')) {
1593
+ cardElement.classList.remove('selected');
1594
+ } else {
1595
+ // Логика выбора карты для хода/защиты
1596
+ if(gameState.attacker === username){ //Если игрок атакующий
1597
+ if(gameState.table.length < 6){
1598
+ //Выбираем только одну карту
1599
+ selectedCards.forEach(c => c.classList.remove('selected'));
1600
+ cardElement.classList.add('selected');
1601
+ }
1602
+ }
1603
+ else if(gameState.defender === username){
1604
+ // Можно выбрать только одну карту для защиты
1605
+ selectedCards.forEach(c => c.classList.remove('selected'));
1606
+ cardElement.classList.add('selected');
1607
+ }
1608
+
1609
+ //Выделяем карту
1610
+ //cardElement.classList.add('selected');
1611
+ }
1612
+ //Определяем, можем ли мы сделать ход выбранной картой
1613
+ const selected = playerHandContainer.querySelector('.selected'); //Одна карта
1614
+ if(selected){
1615
+ const selectedCard = {suit: selected.dataset.suit, rank: selected.dataset.rank};
1616
+ socket.emit('game_action', {token, game_id: gameId, action: 'move', card: selectedCard, user: username});
1617
+ }
1618
+
1619
+ });
1620
+ playerHandContainer.appendChild(cardElement);
1621
+ });
1622
+
1623
+ const takeButton = document.getElementById('take-button');
1624
+ const doneButton = document.getElementById('done-button');
1625
+
1626
+ // Обновляем кнопки
1627
+ takeButton.style.display = 'none';
1628
+ doneButton.style.display = 'none';
1629
+
1630
+ // Если текущий игрок - защищающийся, и он еще не взял карты, показываем кнопку
1631
+ if (gameState.defender === username && gameState.turn === 1) { //turn === 1 - Защищающийся
1632
+ takeButton.style.display = 'inline-block';
1633
+ }
1634
+ // Если текущий игрок - атакующий, и он еще не закончил ход, показываем "Готово"
1635
+ if(gameState.attacker === username && gameState.turn === 0){
1636
+ doneButton.style.display = 'inline-block';
1637
+ }
1638
+
1639
+ // Отображаем карты на столе
1640
+ gameState.table.forEach(pair => {
1641
+ const pairDiv = document.createElement('div');
1642
+ pairDiv.classList.add('card-pair');
1643
+
1644
+ const attackingCardEl = renderCard(pair.attackingCard, false);
1645
+ pairDiv.appendChild(attackingCardEl);
1646
+
1647
+ if (pair.defendingCard) {
1648
+ const defendingCardEl = renderCard(pair.defendingCard, false);
1649
+ pairDiv.appendChild(defendingCardEl);
1650
+ }
1651
+ tableContainer.appendChild(pairDiv);
1652
+ });
1653
+
1654
+ if(gameState.isGameEnd){
1655
+ infoDiv.innerHTML += `<p>Игра окончена, победитель - ${gameState.winner} </p>`
1656
+ }
1657
+ }
1658
+
1659
+ socket.on('update_game_state', (data) => {
1660
+ const gameId = data.game_id;
1661
+ const gameState = data.state;
1662
+
1663
+ if (gameId === 'crocodile'){
1664
+ updateCrocodileState(gameId, gameState);
1665
+ }else if(gameId === 'alias'){
1666
+ updateAliasState(gameId, gameState);
1667
+ }else if(gameId === 'mafia'){
1668
+ updateMafiaState(gameId, gameState)
1669
+ }else if(gameId === 'durak'){
1670
+ updateDurakState(gameId, gameState);
1671
+ }
1672
+
1673
+ });
1674
+
1675
+ socket.on('timer_tick', (data) => {
1676
+ const gameId = data.game_id;
1677
+ if(gameId === 'crocodile' && document.getElementById('crocodile-timer')){
1678
+ document.getElementById('crocodile-timer').textContent = `Время: ${data.time}`;
1679
+ }else if(gameId === 'alias' && document.getElementById('alias-timer')){
1680
+ document.getElementById('alias-timer').textContent = `Время: ${data.time}`;
1681
+ }
1682
+ });
1683
+
1684
+ socket.on('game_action_result', (data) => {
1685
+ const gameId = data.game_id;
1686
+ const action = data.action;
1687
+ //console.log("LOG:", gameId, action)
1688
+ if(gameId === 'durak' && action === 'move'){ // Для дурака убрал, так как все через update
1689
+ }
1690
+ });
1691
+
1692
  function leaveRoom() {
1693
  socket.emit('leave', { token, username });
1694
  if (localStream) {
 
1709
  def handle_join(data):
1710
  token = data['token']
1711
  username = data['username']
1712
+ print( f"User {username} joining room {token}")
1713
  if token in rooms and len(rooms[token]['users']) < rooms[token]['max_users']:
1714
  join_room(token)
1715
  if username not in rooms[token]['users']:
 
1747
  if rooms[token].get('current_game'): # Если игра в процессе
1748
  game_id = rooms[token]['current_game']
1749
  if games_data[game_id]['state'].get(token):
1750
+ if game_id == 'crocodile':
 
 
 
 
 
 
 
1751
  if games_data[game_id]['state'][token].get('presenter') == username:
1752
  # Сброс игры, если уходит ведущий
1753
  del games_data[game_id]['state'][token]
 
1757
  #Удаление догадок
1758
  if games_data[game_id]['state'][token].get('guesses'):
1759
  games_data[game_id]['state'][token]['guesses'] = [guess for guess in games_data[game_id]['state'][token]['guesses'] if guess['user'] != username]
1760
+
1761
+ elif game_id == 'alias':
1762
+ if games_data[game_id]['state'][token].get('presenter') == username:
1763
+ del games_data[game_id]['state'][token]
1764
+ emit('update_game_state', {'game_id': game_id, 'state': {}}, room=token)
1765
+ save_json(GAMES_DB, games_data)
1766
+ return
1767
+ if games_data[game_id]['state'][token].get('guesses'):
1768
+ games_data[game_id]['state'][token]['guesses'] = [guess for guess in games_data[game_id]['state'][token]['guesses'] if guess['user'] != username]
1769
+
1770
+ elif game_id == 'mafia':
1771
+ if games_data[game_id]['state'][token].get('roles') and username in games_data[game_id]['state'][token]['roles']:
1772
+ del games_data[game_id]['state'][token]['roles'][username]
1773
+
1774
+ if games_data[game_id]['state'][token].get('votes'):
1775
+ #Удаляем голос, если он был
1776
+ if username in games_data[game_id]['state'][token]['votes']:
1777
+ del games_data[game_id]['state'][token]['votes'][username]
1778
+
1779
+ elif game_id == 'durak':
1780
+ if games_data[game_id]['state'][token].get('hands') and username in games_data[game_id]['state'][token]['hands']:
1781
+ del games_data[game_id]['state'][token]['hands'][username]
1782
+
1783
+ #Если ушел атакующий или защищающийся
1784
+ if games_data[game_id]['state'][token].get('attacker') == username:
1785
+ games_data[game_id]['state'][token]['attacker'] = None;
1786
+
1787
+ if games_data[game_id]['state'][token].get('defender') == username:
1788
+ games_data[game_id]['state'][token]['defender'] = None;
1789
  save_json(GAMES_DB, games_data); # Сохраняем изменения в любом случае
1790
  emit('update_game_state', {'game_id': game_id, 'state': games_data[game_id]['state'][token]}, room=token);
1791
 
 
1821
  token = data['token']
1822
  game_id = data['game_id']
1823
  state = data['state']
1824
+ #print("SET STATE", data)
1825
 
1826
  if token in rooms:
1827
  if rooms[token]['admin'] == session.get('username'):
 
1831
  save_json(GAMES_DB, games_data)
1832
  emit('update_game_state', {'game_id': game_id, 'state': state}, room=token)
1833
 
1834
+ if (game_id == 'crocodile' or game_id == 'alias') and state.get('isRunning'): # Запуск таймера
1835
  start_timer(token, game_id)
1836
 
1837
  @socketio.on('game_action')
 
1839
  token = data['token']
1840
  game_id = data['game_id']
1841
  action = data['action']
1842
+ value = data.get('value') # Могут быть и другие данные (не только value)
1843
  user = data['user']
1844
+ card = data.get('card') # Для карт
1845
 
1846
  if token in rooms and game_id == rooms[token]['current_game']:
1847
  current_state = games_data[game_id]['state'].get(token, {})
1848
 
1849
+ if game_id == 'crocodile' or game_id == 'alias':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1850
  if action == 'guess' and current_state.get('isRunning'):
1851
  result = "Не угадано"
1852
  if value.lower() == current_state.get('word', '').lower():
 
1860
  save_json(GAMES_DB, games_data)
1861
  emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token)
1862
 
1863
+ elif game_id == 'mafia':
1864
+ if action == 'vote' and current_state.get('phase') == 'day' and current_state.get('isRunning'):
1865
+ if 'votes' not in current_state:
1866
+ current_state['votes'] = {}
1867
 
1868
+ current_state['votes'][user] = value # Сохраняем голос
 
 
1869
 
1870
+ #Если все проголосовали
1871
+ if len(current_state['votes']) == len(rooms[token]['users']):
1872
+ #Меняем фазу на ночь
1873
+ current_state['phase'] = 'night'
 
 
 
 
1874
 
1875
  games_data[game_id]['state'][token] = current_state
1876
+ save_json(GAMES_DB, games_data);
1877
  emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token)
1878
 
1879
+ elif game_id == 'durak':
1880
+ if action == 'move':
1881
+ if current_state.get('attacker') == user:
1882
+ # Проверяем, можно ли походить этой картой
1883
+ if len(current_state['table']) == 0 or any(c['rank'] == card['rank'] for pair in current_state['table'] for c in [pair.get('attackingCard'), pair.get('defendingCard')] if c):
1884
+ current_state['table'].append({'attackingCard': card, 'defendingCard': None})
1885
+ current_state['hands'][user].remove(card)
1886
+ #Передаем ход защищающемуся
1887
+ current_state['turn'] = 1;
1888
+ current_state['defender'] = get_next_player(token, current_state['attacker']);
1889
+
1890
+ games_data[game_id]['state'][token] = current_state
1891
+ save_json(GAMES_DB, games_data)
1892
+ emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token)
1893
+
1894
+ elif current_state.get('defender') == user:
1895
+
1896
+ if len(current_state['table']) > 0 :
1897
+ last_pair = current_state['table'][-1]
1898
+ if last_pair.get('defendingCard') is None:
1899
+ attacking_card = last_pair['attackingCard']
1900
+ #Можем ли побить
1901
+ if canBeat(attacking_card, card, current_state['trumpSuit']):
1902
+ last_pair['defendingCard'] = card
1903
+ current_state['hands'][user].remove(card)
1904
+ current_state['turn'] = 0 #Ход переходит атакующему
1905
+ #Меняем атакующего и защищающегося, если нужно
1906
+ current_state['attacker'] = get_next_player(token, current_state['defender']);
1907
+ current_state['defender'] = get_next_player(token, current_state['attacker']);
1908
+
1909
+ games_data[game_id]['state'][token] = current_state
1910
+ save_json(GAMES_DB, games_data)
1911
+ emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token)
1912
+
1913
+ elif action == 'take':
1914
+ if current_state.get('defender') == user:
1915
+ # Защищающийся берет карты со стола
1916
+ taken_cards = []
1917
+ for pair in current_state['table']:
1918
+ taken_cards.append(pair['attackingCard'])
1919
+ if pair.get('defendingCard'):
1920
+ taken_cards.append(pair['defendingCard'])
1921
+ current_state['hands'][user].extend(taken_cards)
1922
+ current_state['table'] = [] # Очищаем стол
1923
+ #Добираем карты
1924
+ current_state = refill_hands(current_state, token);
1925
+
1926
+ # Ход переходит к следующему игроку после защищавшегося.
1927
+ current_state['attacker'] = get_next_player(token, current_state['defender']);
1928
+ current_state['defender'] = get_next_player(token, current_state['attacker']);
1929
+ current_state['turn'] = 0; # Ходит атакующий
1930
+
1931
+ games_data[game_id]['state'][token] = current_state
1932
+ save_json(GAMES_DB, games_data)
1933
+ emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token)
1934
+
1935
+ elif action == 'done':
1936
+ if current_state.get('attacker') == user:
1937
+ current_state['table'] = []
1938
+ current_state = refill_hands(current_state, token); #Раздача
1939
+
1940
+ #Определение следующего атакующего и защищающегося
1941
+ current_state['attacker'] = get_next_player(token, current_state['attacker']);
1942
+ current_state['defender'] = get_next_player(token, current_state['attacker']);
1943
+ current_state['turn'] = 0; # Ходит атакующий
1944
+
1945
+ games_data[game_id]['state'][token] = current_state;
1946
+ save_json(GAMES_DB, games_data);
1947
+ emit('update_game_state', {'game_id': game_id, 'state':current_state}, room=token);
1948
+
1949
+
1950
+ def get_next_player(token, current_player):
1951
+ """Определяет следующего игрока в комнате."""
1952
+ if token not in rooms:
1953
+ return None
1954
+
1955
+ users = rooms[token]['users']
1956
+ if not users:
1957
+ return None
1958
+
1959
+ current_index = users.index(current_player)
1960
+ next_index = (current_index + 1) % len(users) # Циклический переход
1961
+ return users[next_index]
1962
+
1963
+ def refill_hands(game_state, token):
1964
+ """Раздает карты игрокам до 6, если в колоде еще есть карты."""
1965
+ players = rooms[token]['users']
1966
+ for player in players:
1967
+ while len(game_state['hands'].get(player, [])) < 6 and len(game_state['deck']) > 0:
1968
+ game_state['hands'][player].append(game_state['deck'].pop())
1969
+
1970
+ #Проверка, не закончилась ли игра
1971
+ if len(game_state['deck']) == 0:
1972
+ players_without_cards = [player for player in players if len(game_state['hands'].get(player,[])) == 0]
1973
+ if len(players_without_cards) > 0:
1974
+ game_state['isGameEnd'] = True
1975
+ game_state['winner'] = players_without_cards[0] # Первый, кто избавился от карт
1976
+
1977
+ return game_state;
1978
 
1979
  def start_timer(token, game_id):
1980
+ if game_id != 'crocodile' and game_id != 'alias': # Таймер пока только для крокодила
1981
  return
1982
 
1983
  def timer_loop():