from flask import Flask, render_template_string, request, redirect, url_for, session import random import string import json import os from flask_socketio import SocketIO, join_room, leave_room, emit import hashlib app = Flask(__name__) app.config['SECRET_KEY'] = 'your-very-secret-key-here' socketio = SocketIO(app) ROOMS_DB = os.path.join(app.root_path, 'rooms.json') USERS_DB = os.path.join(app.root_path, 'users.json') GAMES_DB = os.path.join(app.root_path, 'games.json') def load_json(file_path, default={}): try: if os.path.exists(file_path): with open(file_path, 'r', encoding='utf-8') as f: return json.load(f) return default except (FileNotFoundError, json.JSONDecodeError) as e: print(f"Ошибка загрузки JSON из {file_path}: {e}") return default def save_json(file_path, data): try: with open(file_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=4, ensure_ascii=False) except OSError as e: print(f"Ошибка сохранения JSON в {file_path}: {e}") rooms = load_json(ROOMS_DB) users = load_json(USERS_DB) games_data = load_json(GAMES_DB, default={ "crocodile": { "name": "Крокодил", "description": "Один игрок (ведущий) получает слово и должен показать его жестами остальным игрокам, не произнося ни слова. Остальные игроки пытаются угадать слово.", "min_players": 2, "max_players": 5, "state": {} }, "alias": { "name": "Alias", "description": "Один игрок (ведущий) получает слово и должен объяснить его другими словами, не используя однокоренные. Остальные игроки пытаются угадать слово.", "min_players": 2, "max_players": 5, "state": {} }, "mafia": { "name": "Мафия", "description": "Игроки делятся на две команды: мафию и мирных жителей. Мафия пытается тайно убивать мирных жителей, а мирные жители пытаются вычислить и казнить мафию.", "min_players": 4, "max_players": 5, # Можно увеличить, но для теста оставим так "state": {} }, "durak": { "name": "Дурак", "description": "Карточная игра, в которой игроки стараются избавиться от всех своих карт.", "min_players": 2, "max_players": 5, "state": {} # Состояние игры } }) save_json(GAMES_DB, games_data) def generate_token(): return ''.join(random.choices(string.ascii_letters + string.digits, k=15)) def hash_password(password): return hashlib.sha256(password.encode('utf-8')).hexdigest() @app.route('/', methods=['GET', 'POST']) def index(): if 'username' in session: return redirect(url_for('dashboard')) if request.method == 'POST': action = request.form.get('action') username = request.form.get('username') password = request.form.get('password') if action == 'register': if username in users: return "Пользователь уже существует", 400 users[username] = hash_password(password) save_json(USERS_DB, users) session['username'] = username return redirect(url_for('dashboard')) elif action == 'login': if username in users and users[username] == hash_password(password): session['username'] = username return redirect(url_for('dashboard')) return "Неверный логин или пароль", 401 return render_template_string(''' Видеоконференция

Видеоконференция

''') @app.route('/dashboard', methods=['GET', 'POST']) def dashboard(): if 'username' not in session: return redirect(url_for('index')) if request.method == 'POST': action = request.form.get('action') if action == 'create': token = generate_token() rooms[token] = {'users': [], 'max_users': 5, 'admin': session['username'], 'current_game': None} save_json(ROOMS_DB, rooms) return redirect(url_for('room', token=token)) elif action == 'join': token = request.form.get('token') if token in rooms and len(rooms[token]['users']) < rooms[token]['max_users']: return redirect(url_for('room', token=token)) return "Комната не найдена или переполнена", 404 return render_template_string(''' Панель управления

Добро пожаловать, {{ session['username'] }}

''', session=session) @app.route('/logout', methods=['POST']) def logout(): session.pop('username', None) return redirect(url_for('index')) @app.route('/room/') def room(token): if 'username' not in session: return redirect(url_for('index')) if token not in rooms: return redirect(url_for('dashboard')) is_admin = rooms[token]['admin'] == session['username'] current_game = rooms[token].get('current_game') return render_template_string(''' Комната {{ token }}

Комната: {{ token }}

Пользователи: {% for user in rooms[token]['users'] %}{{ user }}{% if not loop.last %}, {% endif %}{% endfor %}

Доступные игры

{% for game_id, game_info in games_data.items() %} {% if rooms[token]['users']|length >= game_info.min_players and rooms[token]['users']|length <= game_info.max_players %}

{{ game_info.name }}

{{ game_info.description }}

{% if is_admin %} {% endif %}
{% endif %} {% endfor %}

''', token=token, session=session, is_admin=is_admin, rooms=rooms, games_data=games_data) @socketio.on('join') def handle_join(data): token = data['token'] username = data['username'] print( f"User {username} joining room {token}") if token in rooms and len(rooms[token]['users']) < rooms[token]['max_users']: join_room(token) if username not in rooms[token]['users']: rooms[token]['users'].append(username) save_json(ROOMS_DB, rooms) emit('user_joined', {'username': username, 'users': rooms[token]['users']}, room=token) emit('init_users', {'users': rooms[token]['users']}, to=request.sid) if rooms[token]['current_game']: # Если игра уже идет, отправляем новому пользователю текущее состояние emit('game_started', {'game_id': rooms[token]['current_game']}, to=request.sid) emit('update_game_state', {'game_id': rooms[token]['current_game'], 'state': games_data[rooms[token]['current_game']]['state'][token]}, to=request.sid); else: emit('error_message', {'message': 'Комната заполнена или не существует'}, to=request.sid) @socketio.on('leave') def handle_leave(data): token = data['token'] username = data['username'] print(f"User {username} leaving room {token}") if token in rooms and username in rooms[token]['users']: leave_room(token) rooms[token]['users'].remove(username) if rooms[token]['admin'] == username: # Если админ выходит и игра активна, завершаем игру if rooms[token].get('current_game'): game_id = rooms[token]['current_game'] if games_data[game_id]['state'].get(token): del games_data[game_id]['state'][token] save_json(GAMES_DB, games_data) del rooms[token] save_json(ROOMS_DB, rooms) emit('user_left', {'username': username, 'users': []}, room=token) # Важно уведомить всех, что комната больше не существует return; if rooms[token].get('current_game'): # Если игра в процессе game_id = rooms[token]['current_game'] if games_data[game_id]['state'].get(token): if game_id == 'crocodile': if games_data[game_id]['state'][token].get('presenter') == username: # Сброс игры, если уходит ведущий del games_data[game_id]['state'][token] emit('update_game_state', {'game_id': game_id, 'state': {}}, room=token); # Уведомляем, что состояние сброшено save_json(GAMES_DB, games_data) return #Удаление догадок if games_data[game_id]['state'][token].get('guesses'): games_data[game_id]['state'][token]['guesses'] = [guess for guess in games_data[game_id]['state'][token]['guesses'] if guess['user'] != username] elif game_id == 'alias': if games_data[game_id]['state'][token].get('presenter') == username: del games_data[game_id]['state'][token] emit('update_game_state', {'game_id': game_id, 'state': {}}, room=token) save_json(GAMES_DB, games_data) return if games_data[game_id]['state'][token].get('guesses'): games_data[game_id]['state'][token]['guesses'] = [guess for guess in games_data[game_id]['state'][token]['guesses'] if guess['user'] != username] elif game_id == 'mafia': if games_data[game_id]['state'][token].get('roles') and username in games_data[game_id]['state'][token]['roles']: del games_data[game_id]['state'][token]['roles'][username] if games_data[game_id]['state'][token].get('votes'): #Удаляем голос, если он был if username in games_data[game_id]['state'][token]['votes']: del games_data[game_id]['state'][token]['votes'][username] elif game_id == 'durak': if games_data[game_id]['state'][token].get('hands') and username in games_data[game_id]['state'][token]['hands']: del games_data[game_id]['state'][token]['hands'][username] #Если ушел атакующий или защищающийся if games_data[game_id]['state'][token].get('attacker') == username: games_data[game_id]['state'][token]['attacker'] = None; if games_data[game_id]['state'][token].get('defender') == username: games_data[game_id]['state'][token]['defender'] = None; save_json(GAMES_DB, games_data); # Сохраняем изменения в любом случае emit('update_game_state', {'game_id': game_id, 'state': games_data[game_id]['state'][token]}, room=token); save_json(ROOMS_DB, rooms) emit('user_left', {'username': username, 'users': rooms[token]['users']}, room=token) @socketio.on('signal') def handle_signal(data): emit('signal', data, room=data['token'], include_self=False) @socketio.on('admin_mute') def handle_admin_mute(data): token = data['token'] target_user = data['targetUser'] by_user = data['byUser'] if token in rooms and rooms[token].get('admin') == by_user: emit('admin_muted', {'targetUser': target_user}, room=token) @socketio.on('start_game') def handle_start_game(data): token = data['token'] game_id = data['game_id'] if token in rooms and rooms[token]['admin'] == session.get('username'): rooms[token]['current_game'] = game_id games_data[game_id]['state'][token] = {} # Инициализируем состояние игры для этой комнаты save_json(ROOMS_DB, rooms) save_json(GAMES_DB, games_data) # Сохраняем изменения в games_data emit('game_started', {'game_id': game_id}, room=token) @socketio.on('set_game_state') def handle_set_game_state(data): token = data['token'] game_id = data['game_id'] state = data['state'] #print("SET STATE", data) if token in rooms: if rooms[token]['admin'] == session.get('username'): # Проверяем, что игра выбрана и текущий игрок - админ if rooms[token]['current_game'] == game_id : games_data[game_id]['state'][token] = state # Сохраняем в games_data save_json(GAMES_DB, games_data) emit('update_game_state', {'game_id': game_id, 'state': state}, room=token) if (game_id == 'crocodile' or game_id == 'alias') and state.get('isRunning'): # Запуск таймера start_timer(token, game_id) @socketio.on('game_action') def handle_game_action(data): token = data['token'] game_id = data['game_id'] action = data['action'] value = data.get('value') # Могут быть и другие данные (не только value) user = data['user'] card = data.get('card') # Для карт if token in rooms and game_id == rooms[token]['current_game']: current_state = games_data[game_id]['state'].get(token, {}) if game_id == 'crocodile' or game_id == 'alias': if action == 'guess' and current_state.get('isRunning'): result = "Не угадано" if value.lower() == current_state.get('word', '').lower(): result = "Угадано!" if 'guesses' not in current_state: current_state['guesses'] = [] current_state['guesses'].append({'user':user, 'value': value, 'result':result}) games_data[game_id]['state'][token] = current_state save_json(GAMES_DB, games_data) emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token) elif game_id == 'mafia': if action == 'vote' and current_state.get('phase') == 'day' and current_state.get('isRunning'): if 'votes' not in current_state: current_state['votes'] = {} current_state['votes'][user] = value # Сохраняем голос #Если все проголосовали if len(current_state['votes']) == len(rooms[token]['users']): #Меняем фазу на ночь current_state['phase'] = 'night' games_data[game_id]['state'][token] = current_state save_json(GAMES_DB, games_data); emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token) elif game_id == 'durak': if action == 'move': if current_state.get('attacker') == user: # Проверяем, можно ли походить этой картой 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): current_state['table'].append({'attackingCard': card, 'defendingCard': None}) current_state['hands'][user].remove(card) #Передаем ход защищающемуся current_state['turn'] = 1; current_state['defender'] = get_next_player(token, current_state['attacker']); games_data[game_id]['state'][token] = current_state save_json(GAMES_DB, games_data) emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token) elif current_state.get('defender') == user: if len(current_state['table']) > 0 : last_pair = current_state['table'][-1] if last_pair.get('defendingCard') is None: attacking_card = last_pair['attackingCard'] #Можем ли побить if canBeat(attacking_card, card, current_state['trumpSuit']): last_pair['defendingCard'] = card current_state['hands'][user].remove(card) current_state['turn'] = 0 #Ход переходит атакующему #Меняем атакующего и защищающегося, если нужно current_state['attacker'] = get_next_player(token, current_state['defender']); current_state['defender'] = get_next_player(token, current_state['attacker']); games_data[game_id]['state'][token] = current_state save_json(GAMES_DB, games_data) emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token) elif action == 'take': if current_state.get('defender') == user: # Защищающийся берет карты со стола taken_cards = [] for pair in current_state['table']: taken_cards.append(pair['attackingCard']) if pair.get('defendingCard'): taken_cards.append(pair['defendingCard']) current_state['hands'][user].extend(taken_cards) current_state['table'] = [] # Очищаем стол #Добираем карты current_state = refill_hands(current_state, token); # Ход переходит к следующему игроку после защищавшегося. current_state['attacker'] = get_next_player(token, current_state['defender']); current_state['defender'] = get_next_player(token, current_state['attacker']); current_state['turn'] = 0; # Ходит атакующий games_data[game_id]['state'][token] = current_state save_json(GAMES_DB, games_data) emit('update_game_state', {'game_id': game_id, 'state': current_state}, room=token) elif action == 'done': if current_state.get('attacker') == user: current_state['table'] = [] current_state = refill_hands(current_state, token); #Раздача #Определение следующего атакующего и защищающегося current_state['attacker'] = get_next_player(token, current_state['attacker']); current_state['defender'] = get_next_player(token, current_state['attacker']); current_state['turn'] = 0; # Ходит атакующий games_data[game_id]['state'][token] = current_state; save_json(GAMES_DB, games_data); emit('update_game_state', {'game_id': game_id, 'state':current_state}, room=token); def get_next_player(token, current_player): """Определяет следующего игрока в комнате.""" if token not in rooms: return None users = rooms[token]['users'] if not users: return None current_index = users.index(current_player) next_index = (current_index + 1) % len(users) # Циклический переход return users[next_index] def refill_hands(game_state, token): """Раздает карты игрокам до 6, если в колоде еще есть карты.""" players = rooms[token]['users'] for player in players: while len(game_state['hands'].get(player, [])) < 6 and len(game_state['deck']) > 0: game_state['hands'][player].append(game_state['deck'].pop()) #Проверка, не закончилась ли игра if len(game_state['deck']) == 0: players_without_cards = [player for player in players if len(game_state['hands'].get(player,[])) == 0] if len(players_without_cards) > 0: game_state['isGameEnd'] = True game_state['winner'] = players_without_cards[0] # Первый, кто избавился от карт return game_state; def start_timer(token, game_id): if game_id != 'crocodile' and game_id != 'alias': # Таймер пока только для крокодила return def timer_loop(): with app.app_context(): while True: if token not in rooms or rooms[token]['current_game'] != game_id: break # Выход, если комната удалена или игра сменилась if not games_data[game_id]['state'].get(token) or not games_data[game_id]['state'][token].get('isRunning'): break #Выход , исли игра не идет games_data[game_id]['state'][token]['timer'] -= 1 save_json(GAMES_DB, games_data) socketio.emit('timer_tick', {'game_id': game_id, 'time': games_data[game_id]['state'][token]['timer']}, room=token) if games_data[game_id]['state'][token]['timer'] <= 0: games_data[game_id]['state'][token]['isRunning'] = False save_json(GAMES_DB, games_data) socketio.emit('update_game_state', {'game_id': game_id, 'state': games_data[game_id]['state'][token]}, room=token) break # Конец отсчета socketio.sleep(1) socketio.start_background_task(timer_loop) if __name__ == '__main__': socketio.run(app, host='0.0.0.0', port=7860, debug=True, allow_unsafe_werkzeug=True)