Ramesh-vani commited on
Commit
978755f
·
verified ·
1 Parent(s): 5eb71ae

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +152 -49
app.py CHANGED
@@ -1,95 +1,198 @@
 
 
1
  import asyncio
2
  import json
3
  import os
4
  import secrets
5
  import signal
6
- from collections import defaultdict
7
- from websockets import serve, ConnectionClosed
8
 
9
- # Dictionary to store editor content for each user
10
- editor_content = defaultdict(str)
 
 
 
 
11
 
12
- # Set of connected users
13
- connected_users = set()
14
 
15
 
16
- async def broadcast_changes(websocket, user, change):
17
  """
18
- Broadcast code editing changes to all connected clients.
 
19
  """
20
  event = {
21
- "type": "edit",
22
- "user": user,
23
- "change": change,
24
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
- # Broadcast the change to all connected clients
27
- for user_socket in connected_users:
28
- if user_socket != websocket:
29
- await user_socket.send(json.dumps(event))
30
 
 
 
31
 
32
- async def handle_code_editing(websocket, user):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  """
34
- Handle code editing events from a user.
 
35
  """
36
- connected_users.add(websocket)
 
 
 
 
 
37
 
 
 
38
  try:
39
- # Send the initial state of the document to the new user
40
- await websocket.send(json.dumps({"type": "init", "content": editor_content[user]}))
 
 
 
 
41
 
42
- async for message in websocket:
43
- event = json.loads(message)
44
- assert event["type"] == "edit"
45
- change = event["change"]
46
 
47
- # Apply the change to the document
48
- editor_content[user] = change["text"]
 
49
 
50
- # Broadcast the change to all connected clients
51
- await broadcast_changes(websocket, user, change)
 
 
 
 
 
52
 
53
- except ConnectionClosed:
54
- pass
 
 
 
 
 
55
  finally:
56
- # Remove the user when the WebSocket connection is closed
57
- connected_users.remove(websocket)
58
 
59
 
60
- async def editor_handler(websocket, path):
61
  """
62
  Handle a connection and dispatch it according to who is connecting.
 
63
  """
64
  # Receive and parse the "init" event from the UI.
65
  message = await websocket.recv()
66
  event = json.loads(message)
67
  assert event["type"] == "init"
68
 
69
- user = event.get("user", None)
70
-
71
- if not user:
72
- # Assign a random username for simplicity
73
- user = secrets.token_urlsafe(8)
74
-
75
- try:
76
- await handle_code_editing(websocket, user)
77
- except Exception as e:
78
- print(f"Error: {e}")
79
- finally:
80
- connected_users.remove(websocket)
81
 
82
 
83
  async def main():
84
- # Set the stop condition when receiving SIGTERM
85
  loop = asyncio.get_running_loop()
86
  stop = loop.create_future()
87
  loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
88
 
89
-
90
- async with serve(editor_handler, "0.0.0.0", 7860):
91
  await stop
92
 
93
 
94
  if __name__ == "__main__":
95
- asyncio.run(main())
 
1
+ #!/usr/bin/env python
2
+
3
  import asyncio
4
  import json
5
  import os
6
  import secrets
7
  import signal
 
 
8
 
9
+ import websockets
10
+
11
+ from connect4 import PLAYER1, PLAYER2, Connect4
12
+
13
+
14
+ JOIN = {}
15
 
16
+ WATCH = {}
 
17
 
18
 
19
+ async def error(websocket, message):
20
  """
21
+ Send an error message.
22
+
23
  """
24
  event = {
25
+ "type": "error",
26
+ "message": message,
 
27
  }
28
+ await websocket.send(json.dumps(event))
29
+
30
+
31
+ async def replay(websocket, game):
32
+ """
33
+ Send previous moves.
34
+
35
+ """
36
+ # Make a copy to avoid an exception if game.moves changes while iteration
37
+ # is in progress. If a move is played while replay is running, moves will
38
+ # be sent out of order but each move will be sent once and eventually the
39
+ # UI will be consistent.
40
+ for player, column, row in game.moves.copy():
41
+ event = {
42
+ "type": "play",
43
+ "player": player,
44
+ "column": column,
45
+ "row": row,
46
+ }
47
+ await websocket.send(json.dumps(event))
48
+
49
+
50
+ async def play(websocket, game, player, connected):
51
+ """
52
+ Receive and process moves from a player.
53
+
54
+ """
55
+ async for message in websocket:
56
+ # Parse a "play" event from the UI.
57
+ event = json.loads(message)
58
+ assert event["type"] == "play"
59
+ column = event["column"]
60
+
61
+ try:
62
+ # Play the move.
63
+ row = game.play(player, column)
64
+ except RuntimeError as exc:
65
+ # Send an "error" event if the move was illegal.
66
+ await error(websocket, str(exc))
67
+ continue
68
+
69
+ # Send a "play" event to update the UI.
70
+ event = {
71
+ "type": "play",
72
+ "player": player,
73
+ "column": column,
74
+ "row": row,
75
+ }
76
+ websockets.broadcast(connected, json.dumps(event))
77
+
78
+ # If move is winning, send a "win" event.
79
+ if game.winner is not None:
80
+ event = {
81
+ "type": "win",
82
+ "player": game.winner,
83
+ }
84
+ websockets.broadcast(connected, json.dumps(event))
85
+
86
+
87
+ async def start(websocket):
88
+ """
89
+ Handle a connection from the first player: start a new game.
90
+
91
+ """
92
+ # Initialize a Connect Four game, the set of WebSocket connections
93
+ # receiving moves from this game, and secret access tokens.
94
+ game = Connect4()
95
+ connected = {websocket}
96
 
97
+ join_key = secrets.token_urlsafe(12)
98
+ JOIN[join_key] = game, connected
 
 
99
 
100
+ watch_key = secrets.token_urlsafe(12)
101
+ WATCH[watch_key] = game, connected
102
 
103
+ try:
104
+ # Send the secret access tokens to the browser of the first player,
105
+ # where they'll be used for building "join" and "watch" links.
106
+ event = {
107
+ "type": "init",
108
+ "join": join_key,
109
+ "watch": watch_key,
110
+ }
111
+ await websocket.send(json.dumps(event))
112
+ # Receive and process moves from the first player.
113
+ await play(websocket, game, PLAYER1, connected)
114
+ finally:
115
+ del JOIN[join_key]
116
+ del WATCH[watch_key]
117
+
118
+
119
+ async def join(websocket, join_key):
120
  """
121
+ Handle a connection from the second player: join an existing game.
122
+
123
  """
124
+ # Find the Connect Four game.
125
+ try:
126
+ game, connected = JOIN[join_key]
127
+ except KeyError:
128
+ await error(websocket, "Game not found.")
129
+ return
130
 
131
+ # Register to receive moves from this game.
132
+ connected.add(websocket)
133
  try:
134
+ # Send the first move, in case the first player already played it.
135
+ await replay(websocket, game)
136
+ # Receive and process moves from the second player.
137
+ await play(websocket, game, PLAYER2, connected)
138
+ finally:
139
+ connected.remove(websocket)
140
 
 
 
 
 
141
 
142
+ async def watch(websocket, watch_key):
143
+ """
144
+ Handle a connection from a spectator: watch an existing game.
145
 
146
+ """
147
+ # Find the Connect Four game.
148
+ try:
149
+ game, connected = WATCH[watch_key]
150
+ except KeyError:
151
+ await error(websocket, "Game not found.")
152
+ return
153
 
154
+ # Register to receive moves from this game.
155
+ connected.add(websocket)
156
+ try:
157
+ # Send previous moves, in case the game already started.
158
+ await replay(websocket, game)
159
+ # Keep the connection open, but don't receive any messages.
160
+ await websocket.wait_closed()
161
  finally:
162
+ connected.remove(websocket)
 
163
 
164
 
165
+ async def handler(websocket):
166
  """
167
  Handle a connection and dispatch it according to who is connecting.
168
+
169
  """
170
  # Receive and parse the "init" event from the UI.
171
  message = await websocket.recv()
172
  event = json.loads(message)
173
  assert event["type"] == "init"
174
 
175
+ if "join" in event:
176
+ # Second player joins an existing game.
177
+ await join(websocket, event["join"])
178
+ elif "watch" in event:
179
+ # Spectator watches an existing game.
180
+ await watch(websocket, event["watch"])
181
+ else:
182
+ # First player starts a new game.
183
+ await start(websocket)
 
 
 
184
 
185
 
186
  async def main():
187
+ # Set the stop condition when receiving SIGTERM.
188
  loop = asyncio.get_running_loop()
189
  stop = loop.create_future()
190
  loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
191
 
192
+ port = int(os.environ.get("PORT", "7860"))
193
+ async with websockets.serve(handler, "0.0.0.0", port):
194
  await stop
195
 
196
 
197
  if __name__ == "__main__":
198
+ asyncio.run(main())