multiplayer 0.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multiplayer/IPClogging/__init__.py +42 -0
- multiplayer/IPClogging/echoing.py +34 -0
- multiplayer/IPClogging/server.py +338 -0
- multiplayer/IPClogging/test.py +71 -0
- multiplayer/__init__.py +52 -0
- multiplayer/client.py +531 -0
- multiplayer/data/__init__.py +1 -0
- multiplayer/data/cities.csv +103 -0
- multiplayer/data/countries.csv +152 -0
- multiplayer/data/egyptian_gods.csv +110 -0
- multiplayer/data/european_kings.csv +109 -0
- multiplayer/data/european_queens.csv +105 -0
- multiplayer/data/greek_gods.csv +122 -0
- multiplayer/data/planets_moons.csv +123 -0
- multiplayer/data/rivers.csv +103 -0
- multiplayer/data/roman_gods.csv +109 -0
- multiplayer/data/seas_oceans.csv +104 -0
- multiplayer/exceptions.py +39 -0
- multiplayer/game.py +275 -0
- multiplayer/language/__init__.py +23 -0
- multiplayer/language/language.py +445 -0
- multiplayer/py.typed +0 -0
- multiplayer/run_log_server.py +33 -0
- multiplayer/run_server.py +91 -0
- multiplayer/server.py +676 -0
- multiplayer/utils.py +215 -0
- multiplayer-0.11.0.dist-info/METADATA +284 -0
- multiplayer-0.11.0.dist-info/RECORD +31 -0
- multiplayer-0.11.0.dist-info/WHEEL +4 -0
- multiplayer-0.11.0.dist-info/entry_points.txt +4 -0
- multiplayer-0.11.0.dist-info/licenses/LICENSE.md +23 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
multiplayer, a Python library for managing multiplayer games
|
|
3
|
+
Copyright (C) 2025 [devfred78](https://github.com/devfred78)
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
This package provides tools to manage a local IPC (Inter Process Communication) logging system
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
# local path
|
|
25
|
+
if getattr(sys, "frozen", False):
|
|
26
|
+
local_path = Path(sys.executable).parent
|
|
27
|
+
else:
|
|
28
|
+
local_path = Path(__file__).resolve().parent
|
|
29
|
+
|
|
30
|
+
UNIX_SOCKET_PATH = local_path / Path('logging_socket')
|
|
31
|
+
|
|
32
|
+
# echoing_path = local_path / Path('echoing.py')
|
|
33
|
+
|
|
34
|
+
# with subprocess.Popen(['wt', 'new-tab', 'cmd', '/k', 'uv', 'run', 'python', str(echoing_path)], stdin = subprocess.PIPE, shell = True) as terminal:
|
|
35
|
+
# # with subprocess.Popen(['uv', 'run', 'python', str(echoing_path)], stdin = subprocess.PIPE, shell = True) as terminal:
|
|
36
|
+
# terminal.stdin.write(b'Hello/n')
|
|
37
|
+
# terminal.stdin.flush()
|
|
38
|
+
# terminal.stdin.write(b'Word !/n')
|
|
39
|
+
# terminal.stdin.flush()
|
|
40
|
+
# terminal.stdin.write(b'EXIT')
|
|
41
|
+
# terminal.stdin.flush()
|
|
42
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
multiplayer, a Python library for managing multiplayer games
|
|
3
|
+
Copyright (C) 2025 [devfred78](https://github.com/devfred78)
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
Simple Python script echoing in the standard output what is given in the standard input.
|
|
19
|
+
|
|
20
|
+
Exits if the stdin is b"EXIT" (in capitals)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from sys import exit
|
|
24
|
+
|
|
25
|
+
while(True):
|
|
26
|
+
print("Waiting for stdin...")
|
|
27
|
+
raw_data = input()
|
|
28
|
+
# raw_data = stdin.read()
|
|
29
|
+
print(f"Input receipt !: {raw_data}")
|
|
30
|
+
if 'EXIT' in raw_data:
|
|
31
|
+
exit(0)
|
|
32
|
+
print(raw_data, flush = True)
|
|
33
|
+
# stdout.write(raw_data)
|
|
34
|
+
# stdout.flush()
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
multiplayer, a Python library for managing multiplayer games
|
|
3
|
+
Copyright (C) 2025 [devfred78](https://github.com/devfred78)
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
This module provides tools to manage a log server
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
import pickle
|
|
24
|
+
import platform
|
|
25
|
+
import selectors
|
|
26
|
+
import socket
|
|
27
|
+
import sys
|
|
28
|
+
import threading
|
|
29
|
+
from time import sleep
|
|
30
|
+
|
|
31
|
+
from colorlog import ColoredFormatter
|
|
32
|
+
from colorlog.escape_codes import escape_codes
|
|
33
|
+
|
|
34
|
+
class OriginColoredFormatter(ColoredFormatter):
|
|
35
|
+
def __init__(self, *args, **kwargs):
|
|
36
|
+
super().__init__(*args, **kwargs)
|
|
37
|
+
self.origin_colors = {
|
|
38
|
+
"GameServer": "purple",
|
|
39
|
+
"GameClient": "green",
|
|
40
|
+
"ServerAdmin": "red",
|
|
41
|
+
"RemoteGame": "blue",
|
|
42
|
+
"Observer": "cyan",
|
|
43
|
+
}
|
|
44
|
+
self.available_colors = ["blue", "cyan", "purple", "red", "white", "yellow"]
|
|
45
|
+
self._next_color_idx = 0
|
|
46
|
+
self._assigned_colors = {}
|
|
47
|
+
|
|
48
|
+
def format(self, record):
|
|
49
|
+
# Use the name as origin
|
|
50
|
+
origin = record.name
|
|
51
|
+
|
|
52
|
+
# Check if we have an assigned color for this specific origin
|
|
53
|
+
if origin in self._assigned_colors:
|
|
54
|
+
color = self._assigned_colors[origin]
|
|
55
|
+
# Check if it's a known fixed origin
|
|
56
|
+
elif origin in self.origin_colors:
|
|
57
|
+
color = self.origin_colors[origin]
|
|
58
|
+
else:
|
|
59
|
+
# Try matching by base (e.g., "RemoteGame.Alice" -> "RemoteGame")
|
|
60
|
+
base_origin = origin.split('.')[0]
|
|
61
|
+
if base_origin in self.origin_colors:
|
|
62
|
+
color = self.origin_colors[base_origin]
|
|
63
|
+
else:
|
|
64
|
+
# Assign a new dynamic color for this specific origin
|
|
65
|
+
color = self.available_colors[self._next_color_idx % len(self.available_colors)]
|
|
66
|
+
self._assigned_colors[origin] = color
|
|
67
|
+
self._next_color_idx += 1
|
|
68
|
+
|
|
69
|
+
# We manually apply the color to avoid ColoredFormatter overriding it
|
|
70
|
+
# based on the log level.
|
|
71
|
+
message = super().format(record)
|
|
72
|
+
|
|
73
|
+
# super().format(record) will have used ColoredFormatter's logic
|
|
74
|
+
# which might have injected a color based on level if %(log_color)s was present.
|
|
75
|
+
# But since we want to OVERRIDE it with the origin color:
|
|
76
|
+
|
|
77
|
+
color_code = escape_codes.get(color, "")
|
|
78
|
+
reset_code = escape_codes.get("reset", "")
|
|
79
|
+
|
|
80
|
+
# If the message already has color codes at the beginning, we replace them.
|
|
81
|
+
# ColoredFormatter usually starts with the color code.
|
|
82
|
+
if message.startswith('\x1b['):
|
|
83
|
+
# Find the end of the first escape sequence
|
|
84
|
+
end_idx = message.find('m')
|
|
85
|
+
if end_idx != -1:
|
|
86
|
+
return f"{color_code}{message[end_idx+1:]}"
|
|
87
|
+
|
|
88
|
+
return f"{color_code}{message}{reset_code}"
|
|
89
|
+
|
|
90
|
+
if __name__ != '__main__':
|
|
91
|
+
from . import UNIX_SOCKET_PATH
|
|
92
|
+
else:
|
|
93
|
+
# local path
|
|
94
|
+
if getattr(sys, "frozen", False):
|
|
95
|
+
local_path = Path(sys.executable).parent
|
|
96
|
+
else:
|
|
97
|
+
local_path = Path(__file__).resolve().parent
|
|
98
|
+
|
|
99
|
+
UNIX_SOCKET_PATH = local_path / Path('logging_socket')
|
|
100
|
+
|
|
101
|
+
class LoggingServer(threading.Thread):
|
|
102
|
+
"""
|
|
103
|
+
This class implements a server that receives and processes logs transmitted by SocketHandler handlers. Like the client side, the server manages stream sockets (SOCK_STREAM sockets) from both the AF_UNIX and AF_INET families.
|
|
104
|
+
|
|
105
|
+
The family is defined when the server is instantiated: if the port argument is None (its default value), then the family is AF_UNIX, and the host argument is used to define the file to be used (in this case, host can be either a pathlib.Path object or a string). Otherwise, the family is AF_INET.
|
|
106
|
+
|
|
107
|
+
Warning: in the case of an AF_UNIX family, if *host* is filled in with an existing file, then it is deleted (provided you have the right to do so). It is therefore particularly advisable to provide a non-existent file, or to leave the default value.
|
|
108
|
+
|
|
109
|
+
This class inherits from threading.Thread, meaning that it starts with the `start()` method, and it is NON blocking.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(self, host: Path = UNIX_SOCKET_PATH, port: int|None = None, timeout: int = 5, color_mode: str = "level"):
|
|
113
|
+
|
|
114
|
+
super().__init__()
|
|
115
|
+
|
|
116
|
+
# logging initialization
|
|
117
|
+
self.logger = logging.getLogger("ServerLog")
|
|
118
|
+
self.logger.setLevel(logging.NOTSET)
|
|
119
|
+
handler = logging.StreamHandler()
|
|
120
|
+
|
|
121
|
+
if color_mode == "origin":
|
|
122
|
+
formatter = OriginColoredFormatter(
|
|
123
|
+
"%(log_color)s[%(asctime)s][%(levelname)s][%(name)s]:%(message)s",
|
|
124
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
125
|
+
reset=True,
|
|
126
|
+
style="%",
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
formatter = ColoredFormatter(
|
|
130
|
+
"%(log_color)s[%(asctime)s][%(levelname)s][%(module)s]:%(message)s",
|
|
131
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
132
|
+
reset=True,
|
|
133
|
+
log_colors={
|
|
134
|
+
"DEBUG": "cyan",
|
|
135
|
+
"INFO": "green",
|
|
136
|
+
"WARNING": "yellow",
|
|
137
|
+
"ERROR": "red",
|
|
138
|
+
"CRITICAL": "red,bg_white",
|
|
139
|
+
},
|
|
140
|
+
secondary_log_colors={},
|
|
141
|
+
style="%",
|
|
142
|
+
)
|
|
143
|
+
handler.setFormatter(formatter)
|
|
144
|
+
self.logger.addHandler(handler)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if port is None and platform.system().lower() != "windows" : # AF_UNIX family
|
|
148
|
+
self.address = str(host)
|
|
149
|
+
host.unlink(missing_ok = True) # Delete the socket file if existing
|
|
150
|
+
sock_family = socket.AF_UNIX
|
|
151
|
+
else: # AF_INET family
|
|
152
|
+
self.address = (str(host), port)
|
|
153
|
+
sock_family = socket.AF_INET
|
|
154
|
+
|
|
155
|
+
# maximum wait time (in seconds) before detecting a closed connection
|
|
156
|
+
self._timeout = timeout
|
|
157
|
+
|
|
158
|
+
# Server socket creation
|
|
159
|
+
self._socket = socket.socket(sock_family, socket.SOCK_STREAM)
|
|
160
|
+
self._socket.settimeout(self._timeout)
|
|
161
|
+
|
|
162
|
+
def run(self):
|
|
163
|
+
"""
|
|
164
|
+
Waiting for connections until an error occurs or the `stop()` method is called.
|
|
165
|
+
|
|
166
|
+
Automatically called by the `start()` method in a separated thread.
|
|
167
|
+
"""
|
|
168
|
+
try:
|
|
169
|
+
# Link the socket to the address
|
|
170
|
+
self._socket.bind(self.address)
|
|
171
|
+
except OSError:
|
|
172
|
+
print(f"\x1b[31m!!! Another server is already running on the port {self.address[1]} !!!")
|
|
173
|
+
else:
|
|
174
|
+
|
|
175
|
+
# Enable the server to accept connections
|
|
176
|
+
self._socket.listen()
|
|
177
|
+
|
|
178
|
+
# Flags
|
|
179
|
+
self._running = True # If True, the server is running
|
|
180
|
+
|
|
181
|
+
# Internal variables
|
|
182
|
+
self._sel = selectors.DefaultSelector() # Selector to deal with connections to clients
|
|
183
|
+
self._registered_connections = list() # list of registered connections
|
|
184
|
+
|
|
185
|
+
# Launch the logging loop (in a separated thread)
|
|
186
|
+
threading.Thread(target = self._logging).start()
|
|
187
|
+
|
|
188
|
+
while self._running:
|
|
189
|
+
try:
|
|
190
|
+
conn, addr = self._socket.accept()
|
|
191
|
+
# print("CONNECTION ESTABLISHED !!")
|
|
192
|
+
conn.setblocking(False) # the connection is now non-blocking
|
|
193
|
+
# self._connections.append(conn)
|
|
194
|
+
self._sel.register(conn, selectors.EVENT_READ)
|
|
195
|
+
self._registered_connections.append(conn)
|
|
196
|
+
# print(f"Number of available connection(s): {len(self._registered_connections)}")
|
|
197
|
+
except socket.timeout:
|
|
198
|
+
pass
|
|
199
|
+
except socket.error as err:
|
|
200
|
+
print("\x1b[31m!!! SOCKET ERROR !!!")
|
|
201
|
+
print(err)
|
|
202
|
+
self.stop()
|
|
203
|
+
# break
|
|
204
|
+
|
|
205
|
+
def stop(self):
|
|
206
|
+
"""
|
|
207
|
+
Stop the server.
|
|
208
|
+
"""
|
|
209
|
+
self._running = False
|
|
210
|
+
try:
|
|
211
|
+
self._socket.shutdown(socket.SHUT_RD)
|
|
212
|
+
except OSError:
|
|
213
|
+
# The connection has already been shutting down !
|
|
214
|
+
pass
|
|
215
|
+
else:
|
|
216
|
+
self._socket.close()
|
|
217
|
+
|
|
218
|
+
def _receive(self, sock: socket.socket, nb_bytes: int = 1) -> bytearray:
|
|
219
|
+
"""
|
|
220
|
+
Receive `nb_bytes` bytes from the socket `sock`.
|
|
221
|
+
|
|
222
|
+
Returns a bytearray of length `nb_bytes` containing the reveived bytes
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
received_bytes = list()
|
|
226
|
+
for _ in range(nb_bytes):
|
|
227
|
+
try:
|
|
228
|
+
rbyte = sock.recv(1)
|
|
229
|
+
except (ConnectionResetError, BrokenPipeError, OSError):
|
|
230
|
+
rbyte = b''
|
|
231
|
+
|
|
232
|
+
if rbyte == b'': # socket connection broken
|
|
233
|
+
self._sel.unregister(sock)
|
|
234
|
+
try:
|
|
235
|
+
sock.shutdown(socket.SHUT_RD)
|
|
236
|
+
except OSError:
|
|
237
|
+
pass
|
|
238
|
+
sock.close()
|
|
239
|
+
if sock in self._registered_connections:
|
|
240
|
+
self._registered_connections.remove(sock)
|
|
241
|
+
# print("CONNECTION CLOSED")
|
|
242
|
+
# print(f"Number of remaining available connection(s): {len(self._registered_connections)}")
|
|
243
|
+
if len(self._registered_connections) == 0:
|
|
244
|
+
print("No connection left: stopping the log server...")
|
|
245
|
+
self._running = False
|
|
246
|
+
self.stop()
|
|
247
|
+
break
|
|
248
|
+
else:
|
|
249
|
+
# print(f"Received byte: {rbyte}")
|
|
250
|
+
received_bytes.append(rbyte)
|
|
251
|
+
return bytearray(b''.join(received_bytes))
|
|
252
|
+
|
|
253
|
+
def _logging(self):
|
|
254
|
+
"""
|
|
255
|
+
Print all strings given by connected clients.
|
|
256
|
+
"""
|
|
257
|
+
sleep(2)
|
|
258
|
+
print("Log server is now ready")
|
|
259
|
+
while self._running:
|
|
260
|
+
if len(self._registered_connections) > 0:
|
|
261
|
+
events = self._sel.select(self._timeout)
|
|
262
|
+
avail_conns = [key.fileobj for (key, mask) in events]
|
|
263
|
+
|
|
264
|
+
# Check for connections to close
|
|
265
|
+
for conn in list(self._registered_connections):
|
|
266
|
+
if conn not in avail_conns:
|
|
267
|
+
try:
|
|
268
|
+
self._sel.unregister(conn)
|
|
269
|
+
except KeyError:
|
|
270
|
+
pass
|
|
271
|
+
try:
|
|
272
|
+
conn.shutdown(socket.SHUT_RD)
|
|
273
|
+
except OSError:
|
|
274
|
+
pass
|
|
275
|
+
conn.close()
|
|
276
|
+
self._registered_connections.remove(conn)
|
|
277
|
+
|
|
278
|
+
# Read receipt strings from available connections and print them on the standard output
|
|
279
|
+
for conn in avail_conns:
|
|
280
|
+
# 4 first bytes indicate the message length in big-endian order
|
|
281
|
+
# See source code of makePickle() method from SocketHandler class in module [logging.handlers](https://github.com/python/cpython/blob/3.14/Lib/logging/handlers.py)
|
|
282
|
+
msg_length = int.from_bytes(self._receive(conn, 4), byteorder = 'big')
|
|
283
|
+
msg = self._receive(conn, msg_length)
|
|
284
|
+
try:
|
|
285
|
+
msg_dict = pickle.loads(msg)
|
|
286
|
+
record = logging.makeLogRecord(msg_dict)
|
|
287
|
+
self.logger.handle(record)
|
|
288
|
+
except UnicodeError:
|
|
289
|
+
print("\x1b[31m!!! LOG MESSAGE NOT ENCODED PROPERLY !!!")
|
|
290
|
+
except pickle.UnpicklingError:
|
|
291
|
+
print("\x1b[31m!!! UNABLE TO DECODE THE RECEIVED LOG MESSAGE !!!")
|
|
292
|
+
except EOFError:
|
|
293
|
+
# print("CONNECTION CLOSED")
|
|
294
|
+
# print(f"Number of remaining available connection(s): {len(self._registered_connections)}")
|
|
295
|
+
if len(self._registered_connections) == 0:
|
|
296
|
+
# print("No connection left: stopping the log server...")
|
|
297
|
+
self._running = False
|
|
298
|
+
self.stop()
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def server(port:int = 5000, color_mode: str = "level"):
|
|
302
|
+
print("****************************")
|
|
303
|
+
print("* Logging server *")
|
|
304
|
+
print(f"* port = {port} *")
|
|
305
|
+
print(f"* color mode = {color_mode} *")
|
|
306
|
+
print("****************************")
|
|
307
|
+
print()
|
|
308
|
+
lserv = LoggingServer(host = "localhost", port=port, color_mode=color_mode)
|
|
309
|
+
lserv.start()
|
|
310
|
+
try:
|
|
311
|
+
lserv.join()
|
|
312
|
+
except KeyboardInterrupt:
|
|
313
|
+
lserv.stop()
|
|
314
|
+
|
|
315
|
+
if __name__ == '__main__':
|
|
316
|
+
### Launch the logging server in a separated thread ###
|
|
317
|
+
|
|
318
|
+
if len(sys.argv) >= 2:
|
|
319
|
+
port = 5000
|
|
320
|
+
color_mode = "level"
|
|
321
|
+
|
|
322
|
+
if "--port" in sys.argv:
|
|
323
|
+
idx = sys.argv.index("--port")
|
|
324
|
+
try:
|
|
325
|
+
port = int(sys.argv[idx + 1])
|
|
326
|
+
except (ValueError, IndexError):
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
if "--color-mode" in sys.argv:
|
|
330
|
+
idx = sys.argv.index("--color-mode")
|
|
331
|
+
try:
|
|
332
|
+
color_mode = sys.argv[idx + 1]
|
|
333
|
+
except IndexError:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
server(port, color_mode)
|
|
337
|
+
else:
|
|
338
|
+
server()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
multiplayer, a Python library for managing multiplayer games
|
|
3
|
+
Copyright (C) 2025 [devfred78](https://github.com/devfred78)
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
Script that sends logs to the logging server
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
from logging.handlers import SocketHandler
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
from time import sleep
|
|
27
|
+
|
|
28
|
+
# local path
|
|
29
|
+
if getattr(sys, "frozen", False):
|
|
30
|
+
local_path = Path(sys.executable).parent
|
|
31
|
+
else:
|
|
32
|
+
local_path = Path(__file__).resolve().parent
|
|
33
|
+
|
|
34
|
+
UNIX_SOCKET_PATH = local_path / Path('logging_socket')
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
def main(port:int = 5000):
|
|
39
|
+
logger.setLevel(logging.INFO)
|
|
40
|
+
handler = SocketHandler("localhost", port)
|
|
41
|
+
logger.addHandler(handler)
|
|
42
|
+
|
|
43
|
+
server_path = local_path / Path('server.py')
|
|
44
|
+
subprocess.run(['wt', 'new-tab', 'cmd', '/k', 'uv', 'run', 'python', str(server_path), '--port', str(port)], stdin = subprocess.PIPE, shell = True)
|
|
45
|
+
|
|
46
|
+
print("Waiting for 2 secs before sending logs...")
|
|
47
|
+
sleep(2)
|
|
48
|
+
print("Debug logging")
|
|
49
|
+
logger.debug("This is a DEBUG message")
|
|
50
|
+
print("Info logging")
|
|
51
|
+
logger.info("This is an INFO message")
|
|
52
|
+
print("Warning logging")
|
|
53
|
+
logger.warning("This is a WARNING message")
|
|
54
|
+
print("Error logging")
|
|
55
|
+
logger.error("This is an ERROR message")
|
|
56
|
+
print("Critical logging")
|
|
57
|
+
logger.critical("This is a CRITICAL message")
|
|
58
|
+
print("Error logging")
|
|
59
|
+
logger.error("This is an ERROR message")
|
|
60
|
+
print("Warning logging")
|
|
61
|
+
logger.warning("This is a WARNING message")
|
|
62
|
+
print("Info logging")
|
|
63
|
+
logger.info("This is an INFO message")
|
|
64
|
+
print("Debug logging")
|
|
65
|
+
logger.debug("This is a DEBUG message")
|
|
66
|
+
|
|
67
|
+
input("Please push ENTER to finish...")
|
|
68
|
+
|
|
69
|
+
if __name__ == '__main__':
|
|
70
|
+
port = 5005
|
|
71
|
+
main(port)
|
multiplayer/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This package provides classes for managing a multiplayer game, both locally and over a network.
|
|
3
|
+
"""
|
|
4
|
+
from .game import Game, Player, Observer, GameState, GameGroup
|
|
5
|
+
from .server import GameServer
|
|
6
|
+
from .client import GameClient, RemoteGame, ServerAdmin, GroupAdmin
|
|
7
|
+
|
|
8
|
+
from .utils import (
|
|
9
|
+
suggest_game_name,
|
|
10
|
+
suggest_player_name,
|
|
11
|
+
get_available_categories,
|
|
12
|
+
register_name_category,
|
|
13
|
+
unregister_name_category,
|
|
14
|
+
)
|
|
15
|
+
from .exceptions import (
|
|
16
|
+
MultiplayerError,
|
|
17
|
+
GameLogicError,
|
|
18
|
+
PlayerLimitReachedError,
|
|
19
|
+
ObserverLimitReachedError,
|
|
20
|
+
GameNotFoundError,
|
|
21
|
+
NetworkError,
|
|
22
|
+
ConnectionError,
|
|
23
|
+
ServerError,
|
|
24
|
+
AuthenticationError,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
'Game',
|
|
29
|
+
'Player',
|
|
30
|
+
'Observer',
|
|
31
|
+
'GameState',
|
|
32
|
+
'GameGroup',
|
|
33
|
+
'GameServer',
|
|
34
|
+
'GameClient',
|
|
35
|
+
'RemoteGame',
|
|
36
|
+
'ServerAdmin',
|
|
37
|
+
'GroupAdmin',
|
|
38
|
+
'suggest_game_name',
|
|
39
|
+
'suggest_player_name',
|
|
40
|
+
'get_available_categories',
|
|
41
|
+
'register_name_category',
|
|
42
|
+
'unregister_name_category',
|
|
43
|
+
'MultiplayerError',
|
|
44
|
+
'GameLogicError',
|
|
45
|
+
'PlayerLimitReachedError',
|
|
46
|
+
'ObserverLimitReachedError',
|
|
47
|
+
'GameNotFoundError',
|
|
48
|
+
'NetworkError',
|
|
49
|
+
'ConnectionError',
|
|
50
|
+
'ServerError',
|
|
51
|
+
'AuthenticationError',
|
|
52
|
+
]
|