1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
#!/usr/bin/env python
"""
WebSocket game coordinator for ATC.
Provides list of currently open games for server-browser purposes,
and provides connection management and game state forwarding between players.
syntax: python game_coordinator.py [-ip <IP String>] [-port <valid port>]
e.g.: python game_coordinator.py -ip 127.0.0.1 -port 25565
"""
import asyncio
import json
import secrets
import sys
import random
from websockets.asyncio.server import serve
DEFAULT_PORT = 8181
DEFAULT_IP = "127.0.0.1"
LOBBY_STATES = [ "LOBBY", "UNDERWAY", "FINISHED" ]
GAME_STATES = [ "SETUP", "ACTION", "PLACEMENT" ]
GAMES = {}
# placeholder
#GAMES = {"1b32b1" : {"game_name" : "Game 1", "lobby_state" : "LOBBY", "current_players" : 1, "max_players" : 4, "private" : False},
# "ab1b27" : {"game_name" : "Patrick's Game", "lobby_state" : "UNDERWAY", "current_players" : 3, "max_players" : 3, "private" : True},
# "7a7df8" : {"game_name" : "New Jersey ATC", "lobby_state" : "FINISHED", "current_players" : 2, "max_players" : 2, "private" : False}}
async def select_valid_color(game, color_id, color_id_alt):
taken_ids = set(GAMES[game_id]["players"].keys())
color_okay = True
for player_color in taken_ids:
if color_id == player_color:
color_okay = False
break
if color_okay:
return color_id
color_okay = True
for player_color in taken_ids:
if color_id_alt == player_color:
color_okay = False
break
if color_okay:
return color_id_alt
available_colors = list(set(range(10)) ^ taken_ids)
return random.choice(available_colors)
# Send active games for server browser purposes
async def send_game_list(websocket):
games = {"type" : "game_list", "games" : []}
for game_id, game_info in GAMES.items():
current_game_info = {"id" : game_id, "game_name" : game_info["game_name"], "state" : game_info["lobby_state"], "current_players" : len(game_info["players"]), "max_players" : game_info["max_players"], "private" : game_info["private"]}
games["games"].append( current_game_info )
await websocket.send(json.dumps(games))
async for message in websocket: # wait for acknowledgment
print(json.loads(message))
return
# end of connection (closes automatically)
async def lobby(websocket, game, player_id):
async for message in websocket:
event = json.loads(message)
if event["type"] == "game_control":
# message forwarding
elif event["type"] == "request":
response = {"type" : "request", "data" : event["request_fields"] }
await game["players"][ int(event["source"]) ]["socket"].send( json.dumps(response) )
elif event["type"] == "request_response":
response = {"type" : "request_response", "data" : event["requested_data"] }
await game["players"][ int(event["destination"]) ]["socket"].send( json.dumps(response) )
elif event["type"] == "broadcast":
message = {"type" : "broadcast", "payload" : event["payload"]}
for player in game["players"]:
if player != player_id: await player["socket"].send(json.dumps(message))
async def join_game(websocket, event):
for required_field in ["game_id", "username", "color_id", "color_id_alt"]:
if required_field not in event:
response = {"type" : "error", "message" : f"Missing field: '{required_field}'."}
await websocket.send(json.dumps(response))
return # close connection
game_id = event["game_id"]
if game_id not in GAMES:
response = {"type" : "error", "message" : f"Game '{game_id}' does not exist."}
await websocket.send(json.dumps(response))
return # close connection
if len(GAMES[game_id]["players"]) >= GAMES[game_id]["max_players"]:
response = {"type" : "error", "message" : f"Server full."}
await websocket.send(json.dumps(response))
return # close connection
player_color = select_valid_color(GAMES[game_id], int(event["color_id"]), int(event["color_id_alt"]))
response = {"type" : "JOIN_OK", "game_id" : game_id, "color_id" : player_color}
await websocket.send(json.dumps(response))
try:
await lobby(websocket, GAMES[game_id], player_color)
finally:
del GAMES[game_id]
async def create_game(websocket, event):
for required_field in ["game_name", "username", "color_id", "private", "password", "max_players"]:
if required_field not in event:
response = {"type" : "error", "message" : f"Missing field: '{required_field}'."}
await websocket.send(json.dumps(response))
return # close connection
new_game = { "game_name" : event["game_name"], "lobby_state" : "LOBBY", "is_board_generated" : False, "max_players" : event["max_players"], "players" : {} }
event["color_id"] = int(event["color_id"])
host_player = {"socket" : websocket, "is_host" : True, "username" : event["username"] }
new_game["host_id"] = host_
new_game["players"][event["color_id"]] = host_player
game_id = secrets.token_urlsafe(12)
GAMES[game_id] = new_game
response = {"type" : "HOST_OK", "game_id" : game_id}
await websocket.send(json.dumps(response))
try:
await lobby(websocket, GAMES[game_id], event["color_id"])
finally:
del GAMES[game_id]
async def new_connection_handler(websocket):
print("Client connected.")
message = await websocket.recv()
event = json.loads(message)
if event["type"] == "list_open_games":
print("List of games requested.")
await send_game_list(websocket)
elif event["type"] == "create_game":
await create_game(websocket)
elif event["type"] == "join_game":
await join_game(websocket, event)
elif event["type"] == "watch_game":
await watch_game(websocket, event)
async def main(ip, port):
async with serve(new_connection_handler, ip, port) as server:
print("serving...")
await server.serve_forever()
if __name__ == "__main__":
IP = DEFAULT_IP
PORT = DEFAULT_PORT
args = sys.argv[1:]
for arg_i in range(len(args)):
if args[arg_i] == "-ip" and arg_i < (len(args)-1):
arg_i += 1
IP = args[arg_i]
if args[arg_i] == "-port" and arg_i < (len(args)-1):
arg_i += 1
try:
PORT = int(args[arg_i])
except:
print("Port must be an integer.")
quit()
asyncio.run(main(IP, PORT))
|