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)