Sergidev commited on
Commit
7ef7190
·
verified ·
1 Parent(s): e6c1702

Add files v1

Browse files
Dockerfile ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as the base image
2
+ FROM python:3.9-slim
3
+
4
+ RUN useradd -m -u 1000 user
5
+ USER user
6
+ ENV PATH="/home/user/.local/bin:$PATH"
7
+
8
+ WORKDIR /app
9
+
10
+ COPY --chown=user ./requirements.txt requirements.txt
11
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
12
+
13
+ COPY --chown=user . /app
14
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
app.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import random
3
+ import string
4
+ from flask import Flask, request, jsonify, render_template
5
+ from threading import Timer
6
+
7
+ app = Flask(__name__)
8
+ app.config['SECRET_KEY'] = 'your-secret-key' # Replace with a real secret key
9
+
10
+ # In-memory storage for game sessions and player data
11
+ sessions = {}
12
+ players = {}
13
+
14
+ def generate_session_id():
15
+ return ''.join(random.choices(string.digits, k=4))
16
+
17
+ def generate_player_id():
18
+ return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
19
+
20
+ def create_game_session():
21
+ session_id = generate_session_id()
22
+ while session_id in sessions:
23
+ session_id = generate_session_id()
24
+ sessions[session_id] = {
25
+ 'players': [],
26
+ 'words': {},
27
+ 'current_player': None,
28
+ 'guesses': set(),
29
+ 'incorrect_guesses': 0
30
+ }
31
+ return session_id
32
+
33
+ def join_game_session(session_id):
34
+ if session_id not in sessions:
35
+ return None
36
+ player_id = generate_player_id()
37
+ sessions[session_id]['players'].append(player_id)
38
+ players[player_id] = {'session_id': session_id}
39
+ return player_id
40
+
41
+ def update_game_state(session_id, guess):
42
+ session = sessions[session_id]
43
+ current_player = session['current_player']
44
+ opponent = [p for p in session['players'] if p != current_player][0]
45
+
46
+ guess = guess.lower()
47
+ session['guesses'].add(guess)
48
+
49
+ if guess not in session['words'][opponent]:
50
+ session['incorrect_guesses'] += 1
51
+
52
+ if session['incorrect_guesses'] >= 6:
53
+ return 'game_over'
54
+
55
+ if all(letter in session['guesses'] for letter in session['words'][opponent]):
56
+ return 'winner'
57
+
58
+ session['current_player'] = opponent
59
+ return 'continue'
60
+
61
+ def check_win_condition(session_id):
62
+ session = sessions[session_id]
63
+ for player in session['players']:
64
+ if all(letter in session['guesses'] for letter in session['words'][player]):
65
+ return player
66
+ return None
67
+
68
+ @app.route('/')
69
+ def index():
70
+ return render_template('index.html')
71
+
72
+ @app.route('/play', methods=['POST'])
73
+ def play():
74
+ session_id = create_game_session()
75
+ player_id = join_game_session(session_id)
76
+ return jsonify({'session_id': session_id, 'player_id': player_id})
77
+
78
+ @app.route('/join/<session_id>', methods=['POST'])
79
+ def join(session_id):
80
+ player_id = join_game_session(session_id)
81
+ if player_id:
82
+ if len(sessions[session_id]['players']) == 2:
83
+ sessions[session_id]['current_player'] = sessions[session_id]['players'][0]
84
+ return jsonify({'player_id': player_id})
85
+ return jsonify({'error': 'Session not found or full'}), 404
86
+
87
+ @app.route('/submit_word', methods=['POST'])
88
+ def submit_word():
89
+ data = request.json
90
+ session_id = data['session_id']
91
+ player_id = data['player_id']
92
+ word = data['word'].lower()
93
+
94
+ if len(word) != 7:
95
+ return jsonify({'error': 'Word must be 7 letters long'}), 400
96
+
97
+ sessions[session_id]['words'][player_id] = word
98
+
99
+ if len(sessions[session_id]['words']) == 2:
100
+ return jsonify({'status': 'game_started'})
101
+ return jsonify({'status': 'waiting_for_opponent'})
102
+
103
+ @app.route('/guess', methods=['POST'])
104
+ def guess():
105
+ data = request.json
106
+ session_id = data['session_id']
107
+ player_id = data['player_id']
108
+ guess = data['guess'].lower()
109
+
110
+ if player_id != sessions[session_id]['current_player']:
111
+ return jsonify({'error': 'Not your turn'}), 400
112
+
113
+ result = update_game_state(session_id, guess)
114
+
115
+ if result == 'game_over':
116
+ winner = check_win_condition(session_id)
117
+ return jsonify({'status': 'game_over', 'winner': winner})
118
+ elif result == 'winner':
119
+ return jsonify({'status': 'game_over', 'winner': player_id})
120
+
121
+ return jsonify({'status': 'continue'})
122
+
123
+ @app.route('/game_state/<session_id>')
124
+ def game_state(session_id):
125
+ if session_id not in sessions:
126
+ return jsonify({'error': 'Session not found'}), 404
127
+
128
+ session = sessions[session_id]
129
+ return jsonify({
130
+ 'players': session['players'],
131
+ 'current_player': session['current_player'],
132
+ 'guesses': list(session['guesses']),
133
+ 'incorrect_guesses': session['incorrect_guesses'],
134
+ 'words': {player: ''.join(['_' if letter not in session['guesses'] else letter for letter in word])
135
+ for player, word in session['words'].items()}
136
+ })
137
+
138
+ def cleanup_inactive_sessions():
139
+ current_time = time.time()
140
+ for session_id in list(sessions.keys()):
141
+ if current_time - sessions[session_id].get('last_activity', 0) > 60:
142
+ del sessions[session_id]
143
+ Timer(60, cleanup_inactive_sessions).start()
144
+
145
+ if __name__ == '__main__':
146
+ cleanup_inactive_sessions()
147
+ app.run(host='0.0.0.0', port=7860)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Flask==2.0.1
2
+ gunicorn==20.1.0
static/css/style.css ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: Arial, sans-serif;
3
+ background-color: #1a1a1a;
4
+ color: #ffffff;
5
+ display: flex;
6
+ justify-content: center;
7
+ align-items: center;
8
+ height: 100vh;
9
+ margin: 0;
10
+ }
11
+
12
+ .container {
13
+ text-align: center;
14
+ background-color: #2a2a2a;
15
+ padding: 2rem;
16
+ border-radius: 10px;
17
+ box-shadow: 0 0 10px rgba(255, 255, 255, 0.1);
18
+ }
19
+
20
+ h1 {
21
+ margin-bottom: 2rem;
22
+ }
23
+
24
+ button {
25
+ background-color: #4caf50;
26
+ border: none;
27
+ color: white;
28
+ padding: 15px 32px;
29
+ text-align: center;
30
+ text-decoration: none;
31
+ display: inline-block;
32
+ font-size: 16px;
33
+ margin: 4px 2px;
34
+ cursor: pointer;
35
+ border-radius: 5px;
36
+ transition: background-color 0.3s;
37
+ }
38
+
39
+ button:hover {
40
+ background-color: #45a049;
41
+ }
42
+
43
+ input[type="text"] {
44
+ padding: 10px;
45
+ font-size: 16px;
46
+ border: 2px solid #4caf50;
47
+ border-radius: 5px;
48
+ margin-right: 10px;
49
+ background-color: #333;
50
+ color: white;
51
+ }
52
+
53
+ #sessionInfo,
54
+ #joinGame {
55
+ margin-top: 20px;
56
+ }
57
+
58
+ #hangmanSvg {
59
+ width: 200px;
60
+ height: 220px;
61
+ margin: 20px auto;
62
+ }
63
+
64
+ .word-display {
65
+ font-size: 24px;
66
+ letter-spacing: 5px;
67
+ margin: 20px 0;
68
+ }
69
+
70
+ .guesses {
71
+ margin-bottom: 20px;
72
+ }
73
+
74
+ .player-turn {
75
+ font-weight: bold;
76
+ margin-bottom: 10px;
77
+ }
78
+
79
+ #guessInput {
80
+ width: 30px;
81
+ text-align: center;
82
+ }
83
+
84
+ #message {
85
+ margin-top: 20px;
86
+ font-weight: bold;
87
+ }
static/images/hangman-stage-1.svg ADDED
static/images/hangman-stage-2.svg ADDED
static/images/hangman-stage-3.svg ADDED
static/images/hangman-stage-4.svg ADDED
static/images/hangman-stage-5.svg ADDED
static/images/hangman-stage-6.svg ADDED
static/images/hangman-stage-7.svg ADDED
static/js/game.js ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let sessionId = null;
2
+ let playerId = null;
3
+ let isMyTurn = false;
4
+
5
+ document.addEventListener("DOMContentLoaded", (event) => {
6
+ const playButton = document.getElementById("playButton");
7
+ const joinButton = document.getElementById("joinButton");
8
+ const submitWordButton = document.getElementById("submitWordButton");
9
+ const guessButton = document.getElementById("guessButton");
10
+
11
+ if (playButton) playButton.addEventListener("click", startGame);
12
+ if (joinButton) joinButton.addEventListener("click", joinGame);
13
+ if (submitWordButton) submitWordButton.addEventListener("click", submitWord);
14
+ if (guessButton) guessButton.addEventListener("click", makeGuess);
15
+ });
16
+
17
+ function startGame() {
18
+ fetch("/play", { method: "POST" })
19
+ .then((response) => response.json())
20
+ .then((data) => {
21
+ sessionId = data.session_id;
22
+ playerId = data.player_id;
23
+ document.getElementById("sessionId").textContent = sessionId;
24
+ document.getElementById("sessionInfo").style.display = "block";
25
+ document.getElementById("playButton").style.display = "none";
26
+ document.getElementById("wordInput").style.display = "block";
27
+ });
28
+ }
29
+
30
+ function joinGame() {
31
+ const joinSessionId = document.getElementById("joinSessionId").value;
32
+ fetch(`/join/${joinSessionId}`, { method: "POST" })
33
+ .then((response) => response.json())
34
+ .then((data) => {
35
+ if (data.error) {
36
+ alert(data.error);
37
+ } else {
38
+ sessionId = joinSessionId;
39
+ playerId = data.player_id;
40
+ document.getElementById("joinGame").style.display = "none";
41
+ document.getElementById("wordInput").style.display = "block";
42
+ }
43
+ });
44
+ }
45
+
46
+ function submitWord() {
47
+ const word = document.getElementById("wordInputField").value;
48
+ if (word.length !== 7) {
49
+ alert("Word must be 7 letters long");
50
+ return;
51
+ }
52
+ fetch("/submit_word", {
53
+ method: "POST",
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ },
57
+ body: JSON.stringify({
58
+ session_id: sessionId,
59
+ player_id: playerId,
60
+ word: word,
61
+ }),
62
+ })
63
+ .then((response) => response.json())
64
+ .then((data) => {
65
+ if (data.status === "waiting_for_opponent") {
66
+ document.getElementById("wordInput").style.display = "none";
67
+ document.getElementById("waitingMessage").style.display = "block";
68
+ } else if (data.status === "game_started") {
69
+ startGamePlay();
70
+ }
71
+ });
72
+ }
73
+
74
+ function startGamePlay() {
75
+ document.getElementById("waitingMessage").style.display = "none";
76
+ document.getElementById("gameArea").style.display = "block";
77
+ updateGameState();
78
+ }
79
+
80
+ function makeGuess() {
81
+ if (!isMyTurn) {
82
+ alert("It's not your turn!");
83
+ return;
84
+ }
85
+ const guess = document.getElementById("guessInput").value;
86
+ if (guess.length !== 1) {
87
+ alert("Please enter a single letter");
88
+ return;
89
+ }
90
+ fetch("/guess", {
91
+ method: "POST",
92
+ headers: {
93
+ "Content-Type": "application/json",
94
+ },
95
+ body: JSON.stringify({
96
+ session_id: sessionId,
97
+ player_id: playerId,
98
+ guess: guess,
99
+ }),
100
+ })
101
+ .then((response) => response.json())
102
+ .then((data) => {
103
+ if (data.status === "game_over") {
104
+ alert(data.winner === playerId ? "You win!" : "You lose!");
105
+ }
106
+ updateGameState();
107
+ });
108
+ }
109
+
110
+ function updateGameState() {
111
+ fetch(`/game_state/${sessionId}`)
112
+ .then((response) => response.json())
113
+ .then((data) => {
114
+ document.getElementById("playerWord").textContent = data.words[playerId];
115
+ document.getElementById("opponentWord").textContent =
116
+ data.words[data.players.find((p) => p !== playerId)];
117
+ document.getElementById("guesses").textContent =
118
+ "Guessed letters: " + data.guesses.join(", ");
119
+ isMyTurn = data.current_player === playerId;
120
+ document.getElementById("playerTurn").textContent = isMyTurn
121
+ ? "Your turn"
122
+ : "Opponent's turn";
123
+ updateHangmanSVG(data.incorrect_guesses);
124
+ });
125
+ setTimeout(updateGameState, 2000); // Poll every 2 seconds
126
+ }
127
+
128
+ function updateHangmanSVG(stage) {
129
+ fetch(`/static/images/hangman-stage-${stage + 1}.svg`)
130
+ .then((response) => response.text())
131
+ .then((svgContent) => {
132
+ document.getElementById("hangmanSvg").innerHTML = svgContent;
133
+ });
134
+ }
templates/game.html ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>1v1 Hangman - Game</title>
7
+ <link
8
+ rel="stylesheet"
9
+ href="{{ url_for('static', filename='css/style.css') }}"
10
+ />
11
+ </head>
12
+ <body>
13
+ <div class="container">
14
+ <h1>1v1 Hangman</h1>
15
+ <div id="gameArea" style="display: none">
16
+ <div id="hangmanSvg"></div>
17
+ <div class="word-display" id="playerWord"></div>
18
+ <div class="word-display" id="opponentWord"></div>
19
+ <div class="guesses" id="guesses">Guessed letters:</div>
20
+ <div class="player-turn" id="playerTurn"></div>
21
+ <input type="text" id="guessInput" maxlength="1" />
22
+ <button id="guessButton">Guess</button>
23
+ <div id="message"></div>
24
+ </div>
25
+ <div id="wordInput" style="display: none">
26
+ <input
27
+ type="text"
28
+ id="wordInputField"
29
+ maxlength="7"
30
+ placeholder="Enter a 7-letter word"
31
+ />
32
+ <button id="submitWordButton">Submit Word</button>
33
+ </div>
34
+ <div id="waitingMessage" style="display: none">
35
+ Waiting for opponent...
36
+ </div>
37
+ </div>
38
+ <script src="{{ url_for('static', filename='js/game.js') }}"></script>
39
+ </body>
40
+ </html>
templates/index.html ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>1v1 Hangman</title>
7
+ <link
8
+ rel="stylesheet"
9
+ href="{{ url_for('static', filename='css/style.css') }}"
10
+ />
11
+ </head>
12
+ <body>
13
+ <div class="container">
14
+ <h1>1v1 Hangman</h1>
15
+ <button id="playButton">Play</button>
16
+ <div id="sessionInfo" style="display: none">
17
+ <p>Session ID: <span id="sessionId"></span></p>
18
+ <p>
19
+ Share this Session ID with your opponent to join the game.
20
+ </p>
21
+ </div>
22
+ <div id="joinGame" style="display: none">
23
+ <input
24
+ type="text"
25
+ id="joinSessionId"
26
+ placeholder="Enter Session ID"
27
+ />
28
+ <button id="joinButton">Join Game</button>
29
+ </div>
30
+ </div>
31
+ <script src="{{ url_for('static', filename='js/game.js') }}"></script>
32
+ </body>
33
+ </html>