import sqlite3 import string import secrets import io import zoneinfo import os import markdown from datetime import datetime, timezone from passlib.hash import argon2 import pyotp import qrcode import qrcode.image.svg from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.date import DateTrigger from flask import Flask, request, session, redirect, render_template, url_for, abort, send_from_directory from flask_socketio import SocketIO, emit, join_room application = Flask(__name__) application.secret_key = secrets.token_hex(64) websocket_manager = SocketIO(application, manage_session=False) scheduler_instance = BackgroundScheduler(timezone="Europe/Berlin") european_timezone = zoneinfo.ZoneInfo("Europe/Berlin") IDENTIFIER_LENGTH = 5 def get_database_connection(): connection = sqlite3.connect("game_database.sqlite") connection.row_factory = sqlite3.Row return connection def initialize_database(): connection = get_database_connection() connection.execute(""" CREATE TABLE IF NOT EXISTS game_configuration ( configuration_key TEXT PRIMARY KEY, configuration_value TEXT ) """) connection.execute(""" CREATE TABLE IF NOT EXISTS player ( player_identifier TEXT PRIMARY KEY, password_hash TEXT, display_name TEXT, is_alive INTEGER DEFAULT 1, score INTEGER DEFAULT 0, target_identifier TEXT, reset_token TEXT, pending_attacker_identifier TEXT, is_approved INTEGER DEFAULT 0, pending_display_name TEXT, late_joiner INTEGER DEFAULT 0 ) """) connection.execute(""" CREATE TABLE IF NOT EXISTS administrator ( administrator_identifier INTEGER PRIMARY KEY, password_hash TEXT, two_factor_secret TEXT, two_factor_enabled INTEGER DEFAULT 0 ) """) connection.execute(""" CREATE TABLE IF NOT EXISTS kill_log ( kill_identifier INTEGER PRIMARY KEY AUTOINCREMENT, killer_identifier TEXT, victim_identifier TEXT, victim_previous_target TEXT, kill_timestamp TEXT ) """) try: connection.execute("ALTER TABLE administrator ADD COLUMN two_factor_enabled INTEGER DEFAULT 0") except sqlite3.OperationalError: pass try: connection.execute("ALTER TABLE player ADD COLUMN is_approved INTEGER DEFAULT 0") except sqlite3.OperationalError: pass try: connection.execute("ALTER TABLE player ADD COLUMN pending_display_name TEXT") except sqlite3.OperationalError: pass try: connection.execute("ALTER TABLE player ADD COLUMN late_joiner INTEGER DEFAULT 0") except sqlite3.OperationalError: pass cursor = connection.execute("SELECT configuration_value FROM game_configuration WHERE configuration_key = 'game_status'") if not cursor.fetchone(): connection.execute("INSERT INTO game_configuration (configuration_key, configuration_value) VALUES ('game_status', 'pending')") connection.execute("INSERT INTO game_configuration (configuration_key, configuration_value) VALUES ('start_time', '')") connection.commit() connection.close() def require_player_authentication(function): def wrapper(*arguments, **keyword_arguments): if "player_identifier" not in session: return redirect(url_for("handle_login")) return function(*arguments, **keyword_arguments) wrapper.__name__ = function.__name__ return wrapper def require_administrator_authentication(function): def wrapper(*arguments, **keyword_arguments): if not session.get("administrator_authenticated"): return redirect(url_for("handle_administrator")) return function(*arguments, **keyword_arguments) wrapper.__name__ = function.__name__ return wrapper def get_markdown_rules(): if os.path.exists("rules.md"): with open("rules.md", "r", encoding="utf-8") as markdown_file: return markdown.markdown(markdown_file.read()) return "

Regelwerk nicht gefunden

" def shuffle_player_targets(): connection = get_database_connection() players = connection.execute("SELECT player_identifier FROM player WHERE is_alive = 1 AND is_approved = 1").fetchall() if len(players) > 1: identifiers = [player_record["player_identifier"] for player_record in players] secure_random_generator = secrets.SystemRandom() secure_random_generator.shuffle(identifiers) for index, identifier in enumerate(identifiers): target = identifiers[(index + 1) % len(identifiers)] connection.execute("UPDATE player SET target_identifier = ? WHERE player_identifier = ?", (target, identifier)) connection.commit() connection.close() websocket_manager.emit("game_state_changed") def process_administrator_action(action_type, form_data): connection = get_database_connection() has_game_state_changed = False if action_type == "start_game": alive_count_record = connection.execute("SELECT COUNT(*) as alive_count FROM player WHERE is_approved = 1").fetchone() if alive_count_record["alive_count"] >= 2: connection.execute("UPDATE game_configuration SET configuration_value = 'running' WHERE configuration_key = 'game_status'") connection.execute("UPDATE game_configuration SET configuration_value = ? WHERE configuration_key = 'start_time'", (datetime.now(european_timezone).isoformat(),)) connection.commit() connection.close() shuffle_player_targets() websocket_manager.emit("admin_state_changed", to="administrator_room") return elif action_type == "stop_game": connection.execute("UPDATE game_configuration SET configuration_value = 'finished' WHERE configuration_key = 'game_status'") connection.commit() has_game_state_changed = True elif action_type == "pause_game": connection.execute("UPDATE game_configuration SET configuration_value = 'paused' WHERE configuration_key = 'game_status'") connection.commit() has_game_state_changed = True elif action_type == "resume_game": connection.execute("UPDATE game_configuration SET configuration_value = 'running' WHERE configuration_key = 'game_status'") connection.commit() has_game_state_changed = True elif action_type == "reset_game": connection.execute("UPDATE game_configuration SET configuration_value = 'pending' WHERE configuration_key = 'game_status'") connection.execute("UPDATE game_configuration SET configuration_value = '' WHERE configuration_key = 'start_time'") connection.execute("UPDATE player SET is_alive = 1, late_joiner = 0, score = 0, target_identifier = NULL, pending_attacker_identifier = NULL") connection.execute("DELETE FROM kill_log") connection.commit() has_game_state_changed = True elif action_type == "create_player": display_name = form_data.get("display_name") new_identifier = ''.join(secrets.choice(string.ascii_uppercase) for _ in range(IDENTIFIER_LENGTH)) temporary_password = secrets.token_urlsafe(16) hashed_password = argon2.hash(temporary_password) connection.execute("INSERT INTO player (player_identifier, password_hash, display_name, is_approved) VALUES (?, ?, ?, 0)", (new_identifier, hashed_password, display_name)) connection.commit() elif action_type == "approve_player": target_identifier = form_data.get("player_identifier") status_record = connection.execute("SELECT configuration_value FROM game_configuration WHERE configuration_key = 'game_status'").fetchone() is_late_joiner = 1 if status_record["configuration_value"] in ['running', 'paused'] else 0 is_alive_status = 0 if is_late_joiner else 1 connection.execute("UPDATE player SET is_approved = 1, is_alive = ?, late_joiner = ? WHERE player_identifier = ?", (is_alive_status, is_late_joiner, target_identifier)) connection.commit() has_game_state_changed = True elif action_type == "edit_name": target_identifier = form_data.get("player_identifier") new_name = form_data.get("new_display_name") connection.execute("UPDATE player SET display_name = ? WHERE player_identifier = ?", (new_name, target_identifier)) connection.commit() has_game_state_changed = True elif action_type == "approve_name_change": target_identifier = form_data.get("player_identifier") connection.execute("UPDATE player SET display_name = pending_display_name, pending_display_name = NULL WHERE player_identifier = ?", (target_identifier,)) connection.commit() has_game_state_changed = True elif action_type == "reject_name_change": target_identifier = form_data.get("player_identifier") connection.execute("UPDATE player SET pending_display_name = NULL WHERE player_identifier = ?", (target_identifier,)) connection.commit() websocket_manager.emit("game_state_changed", to=target_identifier) elif action_type == "delete_player": target_identifier = form_data.get("player_identifier") connection.execute("DELETE FROM player WHERE player_identifier = ?", (target_identifier,)) status_record = connection.execute("SELECT configuration_value FROM game_configuration WHERE configuration_key = 'game_status'").fetchone() if status_record["configuration_value"] == 'running': alive_count_record = connection.execute("SELECT COUNT(*) as alive_count FROM player WHERE is_alive = 1 AND is_approved = 1").fetchone() if alive_count_record["alive_count"] <= 1: connection.execute("UPDATE game_configuration SET configuration_value = 'finished' WHERE configuration_key = 'game_status'") if alive_count_record["alive_count"] == 1: connection.execute("UPDATE player SET score = score + 3 WHERE is_alive = 1 AND is_approved = 1") connection.commit() has_game_state_changed = True elif action_type == "revive_player": target_identifier = form_data.get("player_identifier") player_to_revive = connection.execute("SELECT * FROM player WHERE player_identifier = ?", (target_identifier,)).fetchone() if player_to_revive and player_to_revive["is_alive"] == 0: alive_players = connection.execute("SELECT * FROM player WHERE is_alive = 1 AND is_approved = 1").fetchall() if len(alive_players) > 0: random_living_player = secrets.choice(alive_players) random_living_identifier = random_living_player["player_identifier"] attacker = connection.execute("SELECT player_identifier FROM player WHERE target_identifier = ?", (random_living_identifier,)).fetchone() if attacker: connection.execute("UPDATE player SET target_identifier = ? WHERE player_identifier = ?", (target_identifier, attacker["player_identifier"])) connection.execute("UPDATE player SET is_alive = 1, late_joiner = 0, target_identifier = ? WHERE player_identifier = ?", (random_living_identifier, target_identifier)) else: connection.execute("UPDATE player SET is_alive = 1, late_joiner = 0, target_identifier = NULL WHERE player_identifier = ?", (target_identifier,)) connection.commit() has_game_state_changed = True elif action_type == "eliminate_player": target_identifier = form_data.get("player_identifier") victim = connection.execute("SELECT * FROM player WHERE player_identifier = ?", (target_identifier,)).fetchone() if victim and victim["is_alive"] == 1: attacker = connection.execute("SELECT player_identifier FROM player WHERE target_identifier = ?", (target_identifier,)).fetchone() new_target = victim["target_identifier"] connection.execute("UPDATE player SET is_alive = 0, target_identifier = NULL, pending_attacker_identifier = NULL WHERE player_identifier = ?", (target_identifier,)) attacker_identifier = None if attacker: attacker_identifier = attacker["player_identifier"] connection.execute("UPDATE player SET target_identifier = ? WHERE player_identifier = ?", (new_target, attacker_identifier)) timestamp = datetime.now(european_timezone).isoformat() connection.execute("INSERT INTO kill_log (killer_identifier, victim_identifier, victim_previous_target, kill_timestamp) VALUES (?, ?, ?, ?)", (attacker_identifier, target_identifier, new_target, timestamp)) alive_count_record = connection.execute("SELECT COUNT(*) as alive_count FROM player WHERE is_alive = 1 AND is_approved = 1").fetchone() if alive_count_record["alive_count"] <= 1: status_record = connection.execute("SELECT configuration_value FROM game_configuration WHERE configuration_key = 'game_status'").fetchone() current_status = status_record["configuration_value"] if status_record else "unknown" connection.execute("UPDATE game_configuration SET configuration_value = 'finished' WHERE configuration_key = 'game_status'") if alive_count_record["alive_count"] == 1 and current_status != 'finished': connection.execute("UPDATE player SET score = score + 3 WHERE is_alive = 1 AND is_approved = 1") connection.commit() has_game_state_changed = True elif action_type == "edit_kill": kill_identifier = form_data.get("kill_identifier") new_killer_identifier = form_data.get("new_killer_identifier") if new_killer_identifier == "": new_killer_identifier = None new_timestamp = form_data.get("kill_timestamp") old_kill = connection.execute("SELECT * FROM kill_log WHERE kill_identifier = ?", (kill_identifier,)).fetchone() if old_kill: old_killer_identifier = old_kill["killer_identifier"] if old_killer_identifier != new_killer_identifier: if old_killer_identifier: connection.execute("UPDATE player SET score = MAX(0, score - 1) WHERE player_identifier = ?", (old_killer_identifier,)) if new_killer_identifier: connection.execute("UPDATE player SET score = score + 1 WHERE player_identifier = ?", (new_killer_identifier,)) connection.execute("UPDATE kill_log SET killer_identifier = ?, kill_timestamp = ? WHERE kill_identifier = ?", (new_killer_identifier, new_timestamp, kill_identifier)) connection.commit() has_game_state_changed = True elif action_type == "revoke_kill": kill_identifier = form_data.get("kill_identifier") kill_record = connection.execute("SELECT * FROM kill_log WHERE kill_identifier = ?", (kill_identifier,)).fetchone() if kill_record: if kill_record["killer_identifier"]: connection.execute("UPDATE player SET score = MAX(0, score - 1) WHERE player_identifier = ?", (kill_record["killer_identifier"],)) victim_identifier = kill_record["victim_identifier"] target_to_restore = kill_record["victim_previous_target"] player_pointing_to_target = connection.execute("SELECT player_identifier FROM player WHERE target_identifier = ?", (target_to_restore,)).fetchone() connection.execute("UPDATE player SET is_alive = 1, target_identifier = ? WHERE player_identifier = ?", (target_to_restore, victim_identifier)) if player_pointing_to_target: connection.execute("UPDATE player SET target_identifier = ? WHERE player_identifier = ?", (victim_identifier, player_pointing_to_target["player_identifier"])) connection.execute("DELETE FROM kill_log WHERE kill_identifier = ?", (kill_identifier,)) alive_count = connection.execute("SELECT COUNT(*) as alive_count FROM player WHERE is_alive = 1 AND is_approved = 1").fetchone()["alive_count"] if alive_count > 1: status = connection.execute("SELECT configuration_value FROM game_configuration WHERE configuration_key = 'game_status'").fetchone()["configuration_value"] if status == 'finished': connection.execute("UPDATE game_configuration SET configuration_value = 'running' WHERE configuration_key = 'game_status'") connection.execute("UPDATE player SET score = MAX(0, score - 3) WHERE is_alive = 1 AND is_approved = 1 AND player_identifier != ?", (victim_identifier,)) connection.commit() has_game_state_changed = True elif action_type == "change_target": player_identifier = form_data.get("player_identifier") new_target_identifier = form_data.get("new_target_identifier") if player_identifier and new_target_identifier and player_identifier != new_target_identifier: player_record = connection.execute("SELECT target_identifier FROM player WHERE player_identifier = ?", (player_identifier,)).fetchone() other_player_record = connection.execute("SELECT player_identifier FROM player WHERE target_identifier = ?", (new_target_identifier,)).fetchone() if player_record and other_player_record: connection.execute("UPDATE player SET target_identifier = ? WHERE player_identifier = ?", (new_target_identifier, player_identifier)) connection.execute("UPDATE player SET target_identifier = ? WHERE player_identifier = ?", (player_record["target_identifier"], other_player_record["player_identifier"])) connection.commit() has_game_state_changed = True elif action_type == "generate_reset": target_identifier = form_data.get("player_identifier") reset_token = secrets.token_urlsafe(32) connection.execute("UPDATE player SET reset_token = ? WHERE player_identifier = ?", (reset_token, target_identifier)) connection.commit() elif action_type == "update_credentials": new_password = form_data.get("password_string") password_confirmation = form_data.get("password_string_confirmation") if new_password and new_password == password_confirmation: hashed_password = argon2.hash(new_password) connection.execute("UPDATE administrator SET password_hash = ? WHERE administrator_identifier = 1", (hashed_password,)) connection.commit() elif action_type == "enable_two_factor": two_factor_input = form_data.get("two_factor_code") administrator_record = connection.execute("SELECT two_factor_secret FROM administrator WHERE administrator_identifier = 1").fetchone() time_based_one_time_password_validator = pyotp.TOTP(administrator_record["two_factor_secret"]) if time_based_one_time_password_validator.verify(two_factor_input): connection.execute("UPDATE administrator SET two_factor_enabled = 1 WHERE administrator_identifier = 1") connection.commit() elif action_type == "disable_two_factor": connection.execute("UPDATE administrator SET two_factor_enabled = 0 WHERE administrator_identifier = 1") connection.commit() connection.close() if has_game_state_changed: websocket_manager.emit("game_state_changed") else: websocket_manager.emit("admin_state_changed", to="administrator_room") def scheduled_action_wrapper(action_type, form_data): with application.app_context(): process_administrator_action(action_type, form_data) @application.route("/service_worker.js") def serve_service_worker(): return send_from_directory(application.static_folder, "service_worker.js") @application.route("/") @require_player_authentication def handle_index(): connection = get_database_connection() player = connection.execute("SELECT * FROM player WHERE player_identifier = ?", (session["player_identifier"],)).fetchone() if not player: session.clear() return redirect(url_for("handle_login")) status_row = connection.execute("SELECT configuration_value FROM game_configuration WHERE configuration_key = 'game_status'").fetchone() game_status = status_row["configuration_value"] time_row = connection.execute("SELECT configuration_value FROM game_configuration WHERE configuration_key = 'start_time'").fetchone() start_time = time_row["configuration_value"] target_player = None if player["target_identifier"]: target_player = connection.execute("SELECT display_name FROM player WHERE player_identifier = ?", (player["target_identifier"],)).fetchone() pending_attacker_name = None if player["pending_attacker_identifier"]: attacker = connection.execute("SELECT display_name FROM player WHERE player_identifier = ?", (player["pending_attacker_identifier"],)).fetchone() if attacker: pending_attacker_name = attacker["display_name"] all_players = connection.execute("SELECT * FROM player WHERE is_approved = 1 ORDER BY score DESC").fetchall() total_eliminations = sum(player_record["score"] for player_record in all_players) alive_count = sum(1 for player_record in all_players if player_record["is_alive"] == 1) connection.close() document_markup = get_markdown_rules() return render_template( "game.html", player=player, game_status=game_status, start_time=start_time, target_player=target_player, pending_attacker_name=pending_attacker_name, all_players=all_players, total_eliminations=total_eliminations, alive_count=alive_count, rules_markup=document_markup ) @application.route("/register", methods=["GET", "POST"]) def handle_registration(): if request.method == "POST": display_name = request.form.get("display_name") password_input = request.form.get("password_string") password_confirmation = request.form.get("password_string_confirmation") if password_input and password_input == password_confirmation: new_identifier = ''.join(secrets.choice(string.ascii_uppercase) for _ in range(IDENTIFIER_LENGTH)) hashed_password = argon2.hash(password_input) connection = get_database_connection() connection.execute("INSERT INTO player (player_identifier, password_hash, display_name, is_approved) VALUES (?, ?, ?, 0)", (new_identifier, hashed_password, display_name)) connection.commit() connection.close() websocket_manager.emit("admin_state_changed", to="administrator_room") return render_template("login.html", identifier_length=IDENTIFIER_LENGTH, registration_success=True, new_identifier=new_identifier) return render_template("login.html", identifier_length=IDENTIFIER_LENGTH, register_mode=True) @application.route("/login", methods=["GET", "POST"]) def handle_login(): if request.method == "POST": identifier_input = request.form.get("player_identifier", "").strip().upper() password_input = request.form.get("password_string", "") connection = get_database_connection() player = connection.execute("SELECT password_hash FROM player WHERE player_identifier = ?", (identifier_input,)).fetchone() connection.close() if player and argon2.verify(password_input, player["password_hash"]): session["player_identifier"] = identifier_input return redirect(url_for("handle_index")) return render_template("login.html", identifier_length=IDENTIFIER_LENGTH) @application.route("/reset_password", methods=["GET", "POST"]) def handle_password_reset(): token = request.args.get("token") or request.form.get("token") if not token: abort(400) connection = get_database_connection() player = connection.execute("SELECT player_identifier FROM player WHERE reset_token = ?", (token,)).fetchone() if not player: connection.close() abort(403) if request.method == "POST": new_password = request.form.get("password_string") password_confirmation = request.form.get("password_string_confirmation") if new_password and new_password == password_confirmation: hashed_password = argon2.hash(new_password) connection.execute("UPDATE player SET password_hash = ?, reset_token = NULL WHERE reset_token = ?", (hashed_password, token)) connection.commit() connection.close() return redirect(url_for("handle_login")) connection.close() return render_template("login.html", identifier_length=IDENTIFIER_LENGTH, reset_mode=True, token=token) @application.route("/player/action", methods=["POST"]) @require_player_authentication def handle_player_action(): action_type = request.form.get("action_type") connection = get_database_connection() if action_type == "request_name_change": pending_name = request.form.get("pending_display_name") connection.execute("UPDATE player SET pending_display_name = ? WHERE player_identifier = ?", (pending_name, session["player_identifier"])) connection.commit() websocket_manager.emit("admin_state_changed", to="administrator_room") connection.close() return redirect(url_for("handle_index")) @application.route("/logout") def handle_logout(): session.clear() return redirect(url_for("handle_login")) @application.route("/admin", methods=["GET", "POST"]) def handle_administrator(): connection = get_database_connection() administrator_record = connection.execute("SELECT * FROM administrator WHERE administrator_identifier = 1").fetchone() if not administrator_record: if request.method == "POST": password_input = request.form.get("password_string") password_confirmation = request.form.get("password_string_confirmation") if password_input and password_input == password_confirmation: hashed_password = argon2.hash(password_input) two_factor_secret = pyotp.random_base32() connection.execute("INSERT INTO administrator (administrator_identifier, password_hash, two_factor_secret, two_factor_enabled) VALUES (1, ?, ?, 0)", (hashed_password, two_factor_secret)) connection.commit() session["administrator_authenticated"] = True connection.close() return redirect(url_for("handle_administrator")) connection.close() return render_template("administrator.html", setup_required=True) if not session.get("administrator_authenticated"): if request.method == "POST": password_input = request.form.get("password_string") if argon2.verify(password_input, administrator_record["password_hash"]): if administrator_record["two_factor_enabled"]: two_factor_input = request.form.get("two_factor_code") time_based_one_time_password_validator = pyotp.TOTP(administrator_record["two_factor_secret"]) if time_based_one_time_password_validator.verify(two_factor_input): session["administrator_authenticated"] = True connection.close() return redirect(url_for("handle_administrator")) else: session["administrator_authenticated"] = True connection.close() return redirect(url_for("handle_administrator")) connection.close() return render_template("administrator.html", login_required=True, two_factor_enabled=bool(administrator_record["two_factor_enabled"])) status_row = connection.execute("SELECT configuration_value FROM game_configuration WHERE configuration_key = 'game_status'").fetchone() game_status = status_row["configuration_value"] all_players = connection.execute("SELECT * FROM player ORDER BY is_approved ASC, display_name ASC").fetchall() alive_count_record = connection.execute("SELECT COUNT(*) as alive_count FROM player WHERE is_approved = 1").fetchone() can_start = alive_count_record["alive_count"] >= 2 kill_logs = connection.execute(""" SELECT k.*, killer.display_name as killer_name, victim.display_name as victim_name FROM kill_log k LEFT JOIN player killer ON k.killer_identifier = killer.player_identifier LEFT JOIN player victim ON k.victim_identifier = victim.player_identifier ORDER BY k.kill_timestamp DESC """).fetchall() two_factor_uri = pyotp.totp.TOTP(administrator_record["two_factor_secret"]).provisioning_uri(name="Administrator", issuer_name="AssassinGame") qr_factory = qrcode.image.svg.SvgPathImage qr_image = qrcode.make(two_factor_uri, image_factory=qr_factory) qr_stream = io.BytesIO() qr_image.save(qr_stream) qr_code_svg = qr_stream.getvalue().decode("utf-8") connection.close() return render_template( "administrator.html", dashboard_active=True, game_status=game_status, can_start=can_start, all_players=all_players, kill_logs=kill_logs, two_factor_secret=administrator_record["two_factor_secret"], two_factor_enabled=bool(administrator_record["two_factor_enabled"]), qr_code_svg=qr_code_svg ) @application.route("/admin/action", methods=["POST"]) @require_administrator_authentication def handle_administrator_action(): form_data = request.form.to_dict() action_type = form_data.get("action_type") execution_time_string = form_data.get("execution_time") if execution_time_string: execution_datetime = datetime.strptime(execution_time_string[:16], "%Y-%m-%dT%H:%M").replace(tzinfo=european_timezone) scheduler_instance.add_job(scheduled_action_wrapper, DateTrigger(run_date=execution_datetime), args=[action_type, form_data]) else: process_administrator_action(action_type, form_data) return redirect(url_for("handle_administrator")) @websocket_manager.on("connect") def handle_websocket_connection(): if "player_identifier" in session: join_room(session["player_identifier"]) if session.get("administrator_authenticated"): join_room("administrator_room") @websocket_manager.on("request_elimination") def handle_elimination_request(): attacker_identifier = session.get("player_identifier") if not attacker_identifier: return connection = get_database_connection() attacker = connection.execute("SELECT target_identifier FROM player WHERE player_identifier = ?", (attacker_identifier,)).fetchone() if attacker and attacker["target_identifier"]: victim_identifier = attacker["target_identifier"] connection.execute("UPDATE player SET pending_attacker_identifier = ? WHERE player_identifier = ?", (attacker_identifier, victim_identifier)) connection.commit() emit("elimination_verification_requested", to=victim_identifier) connection.close() @websocket_manager.on("confirm_elimination") def handle_elimination_confirmation(): victim_identifier = session.get("player_identifier") if not victim_identifier: return connection = get_database_connection() victim = connection.execute("SELECT pending_attacker_identifier, target_identifier FROM player WHERE player_identifier = ?", (victim_identifier,)).fetchone() if victim and victim["pending_attacker_identifier"]: attacker_identifier = victim["pending_attacker_identifier"] new_target = victim["target_identifier"] connection.execute("UPDATE player SET is_alive = 0, target_identifier = NULL, pending_attacker_identifier = NULL WHERE player_identifier = ?", (victim_identifier,)) connection.execute("UPDATE player SET target_identifier = ?, score = score + 1 WHERE player_identifier = ?", (new_target, attacker_identifier)) timestamp = datetime.now(european_timezone).isoformat() connection.execute("INSERT INTO kill_log (killer_identifier, victim_identifier, victim_previous_target, kill_timestamp) VALUES (?, ?, ?, ?)", (attacker_identifier, victim_identifier, new_target, timestamp)) alive_count_record = connection.execute("SELECT COUNT(*) as alive_count FROM player WHERE is_alive = 1 AND is_approved = 1").fetchone() if alive_count_record["alive_count"] <= 1: status_record = connection.execute("SELECT configuration_value FROM game_configuration WHERE configuration_key = 'game_status'").fetchone() current_status = status_record["configuration_value"] if status_record else "unknown" connection.execute("UPDATE game_configuration SET configuration_value = 'finished' WHERE configuration_key = 'game_status'") if alive_count_record["alive_count"] == 1 and current_status != 'finished': connection.execute("UPDATE player SET score = score + 3 WHERE is_alive = 1 AND is_approved = 1") connection.commit() websocket_manager.emit("game_state_changed") connection.close() @websocket_manager.on("reject_elimination") def handle_elimination_rejection(): victim_identifier = session.get("player_identifier") if not victim_identifier: return connection = get_database_connection() victim = connection.execute("SELECT pending_attacker_identifier FROM player WHERE player_identifier = ?", (victim_identifier,)).fetchone() if victim and victim["pending_attacker_identifier"]: attacker_identifier = victim["pending_attacker_identifier"] connection.execute("UPDATE player SET pending_attacker_identifier = NULL WHERE player_identifier = ?", (victim_identifier,)) connection.commit() emit("elimination_rejected", to=attacker_identifier) connection.close() if __name__ == "__main__": initialize_database() scheduler_instance.start() websocket_manager.run(application, host="0.0.0.0", port=5000)