Загрузка данных


```txt
========================
requirements.txt
========================

fastapi
uvicorn
jinja2
websockets
python-multipart


========================
main.py
========================

from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

import random

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

templates = Jinja2Templates(directory="templates")

players = []
connections = []
roles = {}

WORDS = [
    "Airport",
    "School",
    "Hospital",
    "Bank",
    "Hotel"
]

@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
    return templates.TemplateResponse(
        "index.html",
        {"request": request}
    )

@app.get("/room", response_class=HTMLResponse)
async def room(request: Request, nickname: str):
    return templates.TemplateResponse(
        "room.html",
        {
            "request": request,
            "nickname": nickname
        }
    )

@app.websocket("/ws/{nickname}")
async def websocket_endpoint(websocket: WebSocket, nickname: str):
    await websocket.accept()

    connections.append(websocket)
    players.append(nickname)

    print(f"{nickname} connected")

    try:
        while True:
            data = await websocket.receive_text()

            for connection in connections:
                await connection.send_text(f"{nickname}: {data}")

    except WebSocketDisconnect:
        connections.remove(websocket)

        if nickname in players:
            players.remove(nickname)

        print(f"{nickname} disconnected")

@app.get("/start")
async def start_game():

    if len(players) < 2:
        return {
            "error": "Need at least 2 players"
        }

    spy = random.choice(players)
    location = random.choice(WORDS)

    for player in players:

        if player == spy:
            roles[player] = "SPY"
        else:
            roles[player] = location

    return roles

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "main:app",
        host="127.0.0.1",
        port=8000,
        reload=True
    )


========================
database.py
========================

import sqlite3

conn = sqlite3.connect(
    "data/skyfall.db"
)

cursor = conn.cursor()

cursor.execute("""
CREATE TABLE IF NOT EXISTS players (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nickname TEXT
)
""")

conn.commit()
conn.close()


========================
templates/index.html
========================

<!DOCTYPE html>
<html>
<head>
    <title>SkyFall</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>

<div class="container">
    <h1>SKYFALL</h1>

    <input type="text" id="nickname" placeholder="Enter nickname">

    <button onclick="joinGame()">
        Join Game
    </button>
</div>

<script>
function joinGame() {

    const nickname =
        document.getElementById("nickname").value;

    if (!nickname) {
        alert("Enter nickname");
        return;
    }

    window.location.href =
        `/room?nickname=${nickname}`;
}
</script>

</body>
</html>


========================
templates/room.html
========================

<!DOCTYPE html>
<html>
<head>
    <title>Game Room</title>

    <link rel="stylesheet"
          href="/static/style.css">
</head>
<body>

<div class="container">

    <h1>Room</h1>

    <p>
        Player:
        <b id="playerName">
            {{ nickname }}
        </b>
    </p>

    <button onclick="startGame()">
        Start Game
    </button>

    <div id="roleBox"></div>

    <div id="chat"></div>

    <input type="text"
           id="messageInput"
           placeholder="Message">

    <button onclick="sendMessage()">
        Send
    </button>

</div>

<script>

const nickname =
    "{{ nickname }}";

const socket =
    new WebSocket(
        `ws://${location.host}/ws/${nickname}`
    );

socket.onmessage = function(event) {

    const chat =
        document.getElementById("chat");

    const div =
        document.createElement("div");

    div.innerText = event.data;

    chat.appendChild(div);
};

function sendMessage() {

    const input =
        document.getElementById("messageInput");

    socket.send(input.value);

    input.value = "";
}

async function startGame() {

    const response =
        await fetch("/start");

    const data =
        await response.json();

    const role =
        data[nickname];

    const roleBox =
        document.getElementById("roleBox");

    roleBox.innerHTML =
        `<h2>Your Role:</h2>
         <h1>${role}</h1>`;
}

</script>

</body>
</html>


========================
static/style.css
========================

body {
    background: #111;
    color: white;
    font-family: Arial;
    text-align: center;
}

.container {
    margin-top: 60px;
}

input {
    padding: 10px;
    width: 250px;
    margin: 10px;
}

button {
    padding: 10px 20px;
    background: #00aaff;
    border: none;
    color: white;
    cursor: pointer;
}

#chat {
    width: 400px;
    height: 300px;
    overflow-y: scroll;
    border: 1px solid white;
    margin: 20px auto;
    padding: 10px;
    text-align: left;
}


========================
static/app.js
========================

// future scripts


========================
СТРУКТУРА ПАПОК
========================

skyfall-project/
│
├── data/
│   └── skyfall.db
│
├── static/
│   ├── app.js
│   └── style.css
│
├── templates/
│   ├── index.html
│   └── room.html
│
├── database.py
├── main.py
└── requirements.txt


========================
КОМАНДЫ ЗАПУСКА
========================

pip install -r requirements.txt

python main.py


========================
ОТКРЫТЬ В БРАУЗЕРЕ
========================

http://127.0.0.1:8000
```