multiplayer 0.11.0__tar.gz
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-0.11.0/LICENSE.md +23 -0
- multiplayer-0.11.0/PKG-INFO +284 -0
- multiplayer-0.11.0/README.md +262 -0
- multiplayer-0.11.0/pyproject.toml +42 -0
- multiplayer-0.11.0/src/multiplayer/IPClogging/__init__.py +42 -0
- multiplayer-0.11.0/src/multiplayer/IPClogging/echoing.py +34 -0
- multiplayer-0.11.0/src/multiplayer/IPClogging/server.py +338 -0
- multiplayer-0.11.0/src/multiplayer/IPClogging/test.py +71 -0
- multiplayer-0.11.0/src/multiplayer/__init__.py +52 -0
- multiplayer-0.11.0/src/multiplayer/client.py +531 -0
- multiplayer-0.11.0/src/multiplayer/data/__init__.py +1 -0
- multiplayer-0.11.0/src/multiplayer/data/cities.csv +103 -0
- multiplayer-0.11.0/src/multiplayer/data/countries.csv +152 -0
- multiplayer-0.11.0/src/multiplayer/data/egyptian_gods.csv +110 -0
- multiplayer-0.11.0/src/multiplayer/data/european_kings.csv +109 -0
- multiplayer-0.11.0/src/multiplayer/data/european_queens.csv +105 -0
- multiplayer-0.11.0/src/multiplayer/data/greek_gods.csv +122 -0
- multiplayer-0.11.0/src/multiplayer/data/planets_moons.csv +123 -0
- multiplayer-0.11.0/src/multiplayer/data/rivers.csv +103 -0
- multiplayer-0.11.0/src/multiplayer/data/roman_gods.csv +109 -0
- multiplayer-0.11.0/src/multiplayer/data/seas_oceans.csv +104 -0
- multiplayer-0.11.0/src/multiplayer/exceptions.py +39 -0
- multiplayer-0.11.0/src/multiplayer/game.py +275 -0
- multiplayer-0.11.0/src/multiplayer/language/__init__.py +23 -0
- multiplayer-0.11.0/src/multiplayer/language/language.py +445 -0
- multiplayer-0.11.0/src/multiplayer/py.typed +0 -0
- multiplayer-0.11.0/src/multiplayer/run_log_server.py +33 -0
- multiplayer-0.11.0/src/multiplayer/run_server.py +91 -0
- multiplayer-0.11.0/src/multiplayer/server.py +676 -0
- multiplayer-0.11.0/src/multiplayer/utils.py +215 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
**English** | [Español](translation/LICENSE.es.md) | [Français](translation/LICENSE.fr.md)
|
|
2
|
+
|
|
3
|
+
# MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026 devfred78
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify,merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT of OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: multiplayer
|
|
3
|
+
Version: 0.11.0
|
|
4
|
+
Summary: Library that allows you to manage multiple players, locally, on a network, or on the Internet.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE.md
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Classifier: Topic :: Games/Entertainment
|
|
14
|
+
Requires-Dist: colorlog
|
|
15
|
+
Requires-Dist: cryptography
|
|
16
|
+
Requires-Dist: pytest ; extra == 'dev'
|
|
17
|
+
Requires-Dist: requests ; extra == 'dev'
|
|
18
|
+
Requires-Dist: ruff ; extra == 'dev'
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
**English** | [Español](translation/README.es.md) | [Français](translation/README.fr.md)
|
|
24
|
+
|
|
25
|
+
# Multiplayer Game Manager
|
|
26
|
+
|
|
27
|
+
> **A Note on this Project's Origin**
|
|
28
|
+
>
|
|
29
|
+
> This project is primarily the result of a series of experiments using Gemini Code Assist for code generation and error handling. Rather than using it on academic examples, it seemed more interesting to apply it to a project that could meet a real practical need.
|
|
30
|
+
>
|
|
31
|
+
> This, therefore, is the reason for `multiplayer`'s existence: you can dissect the code to see how Gemini (with my guidance) went about building it, or you can ignore all that and just use this library for your own needs!
|
|
32
|
+
|
|
33
|
+
This Python module provides a simple and flexible framework for managing multiplayer games, both locally and over a network.
|
|
34
|
+
|
|
35
|
+
For a detailed technical description of all classes and functions, see the [API Reference](REFERENCE.md).
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
* **Local & Networked:** Use in a single process or in a client-server architecture.
|
|
40
|
+
* **Combined Game State:** A flexible system for synchronizing both the core game status (e.g., `in_progress`) and any custom game data.
|
|
41
|
+
* **Observer Support:** Ability to add observers who can view the game state without participating as players.
|
|
42
|
+
* **Administrator Role:** New `ServerAdmin` class to manage the server, kick players/observers, and monitor server status.
|
|
43
|
+
* **Game Grouping:** Organize several game sessions within the same server using the `GameGroup` class.
|
|
44
|
+
* **Multi-Layered Security:** Supports server passwords, admin passwords, and per-game passwords, with optional TLS v1.3 encryption. Passwords can be updated dynamically by administrators.
|
|
45
|
+
* **Automatic Server Discovery:** Clients can automatically find running servers on the local network.
|
|
46
|
+
* **Extensible Name Suggestions:** Includes a utility function to suggest creative names for games and players.
|
|
47
|
+
* **Multiple Games:** The server can manage multiple game sessions simultaneously, and the game list is now filtered to hide finished games.
|
|
48
|
+
* **Robust Error Handling:** A clear set of custom exceptions for both game logic and network issues.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
You can install it in two ways:
|
|
53
|
+
|
|
54
|
+
### 1. From PyPI
|
|
55
|
+
```sh
|
|
56
|
+
pip install multiplayer
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. From a Wheel file (GitHub)
|
|
60
|
+
Download the `.whl` file from the [Releases](https://github.com/devfred78/multiplayer/releases) page and run:
|
|
61
|
+
```sh
|
|
62
|
+
pip install multiplayer-0.11.0-py3-none-any.whl
|
|
63
|
+
```
|
|
64
|
+
*Replace `multiplayer-0.11.0-py3-none-any.whl` with the actual name of the downloaded file.*
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Game State Management
|
|
69
|
+
|
|
70
|
+
A key feature is the ability to manage your own game state alongside the core game status.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# On one client, set a custom state
|
|
74
|
+
game.set_state({
|
|
75
|
+
"board": [["X", "O", ""], ["", "X", ""], ["O", "", ""]],
|
|
76
|
+
"turn": "player2"
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
# On another client, retrieve the combined state
|
|
80
|
+
full_state = game.state
|
|
81
|
+
print(f"Game status: {full_state['status']}")
|
|
82
|
+
# > Game status: in_progress
|
|
83
|
+
|
|
84
|
+
print(f"Current turn: {full_state['custom']['turn']}")
|
|
85
|
+
# > Current turn: player2
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
### Game Grouping
|
|
90
|
+
|
|
91
|
+
You can group games together for better organization.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from multiplayer import Game, GameGroup
|
|
95
|
+
|
|
96
|
+
# Create a group
|
|
97
|
+
group = GameGroup("Tournament A", priority="high")
|
|
98
|
+
|
|
99
|
+
# Add games to the group
|
|
100
|
+
game1 = Game("Match 1")
|
|
101
|
+
game2 = Game("Match 2")
|
|
102
|
+
group.add_game(game1)
|
|
103
|
+
group.add_game(game2)
|
|
104
|
+
|
|
105
|
+
print(f"Game 1 ID: {game1.ID}")
|
|
106
|
+
# > Game 1 ID: 550e8400-e29b-41d4-a716-446655440000
|
|
107
|
+
|
|
108
|
+
print(f"Group '{group.name}' has {len(group.games)} games.")
|
|
109
|
+
# > Group 'Tournament A' has 2 games.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
### Full Test Environment
|
|
114
|
+
|
|
115
|
+
A script is available to launch a complete test environment with:
|
|
116
|
+
- An IPC log server (`IPClogging`) in a separate window.
|
|
117
|
+
- A game server.
|
|
118
|
+
- Multiple separate client instances (default is 2) simulating a game, each in its own terminal window.
|
|
119
|
+
|
|
120
|
+
To run it:
|
|
121
|
+
```bash
|
|
122
|
+
uv run python scripts/full_test_env.py
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
To specify the number of players:
|
|
126
|
+
```bash
|
|
127
|
+
uv run python scripts/full_test_env.py --players 3
|
|
128
|
+
```
|
|
129
|
+
This will open several Windows Terminal windows: one for the log server and one for each client instance, allowing you to see the real-time interactions and logs.
|
|
130
|
+
|
|
131
|
+
### Local Usage
|
|
132
|
+
|
|
133
|
+
You can use the `Game` class directly, including with a password for local validation.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from multiplayer import Game, Player, suggest_game_name
|
|
137
|
+
|
|
138
|
+
game = Game(name="My Awesome Game", password="local_game_pass")
|
|
139
|
+
game.add_player(Player("Alice"), password="local_game_pass")
|
|
140
|
+
game.start()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Networked Usage (Client-Server)
|
|
144
|
+
|
|
145
|
+
#### Server Setup
|
|
146
|
+
```python
|
|
147
|
+
from multiplayer import GameServer
|
|
148
|
+
|
|
149
|
+
# Start a secure server with a custom domain and self-signed certificate
|
|
150
|
+
server = GameServer(
|
|
151
|
+
host='0.0.0.0',
|
|
152
|
+
port=12345,
|
|
153
|
+
name="My Production Server",
|
|
154
|
+
password="my_server_password",
|
|
155
|
+
admin_password="my_admin_password",
|
|
156
|
+
use_tls=True,
|
|
157
|
+
tls_domain="example.com",
|
|
158
|
+
tls_self_signed=True
|
|
159
|
+
)
|
|
160
|
+
server.start()
|
|
161
|
+
|
|
162
|
+
# Or use existing certificate files
|
|
163
|
+
server = GameServer(
|
|
164
|
+
use_tls=True,
|
|
165
|
+
tls_cert="path/to/cert.pem",
|
|
166
|
+
tls_key="path/to/key.pem",
|
|
167
|
+
tls_self_signed=False
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### Running with Docker
|
|
172
|
+
|
|
173
|
+
You can run the game server using Docker. To use your own TLS certificates, map a local directory containing `cert.pem` and `privkey.pem` to `/app/certs` in the container.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
docker run -d \
|
|
177
|
+
-p 65432:65432 \
|
|
178
|
+
-v /path/to/your/certs:/app/certs \
|
|
179
|
+
ghcr.io/yourusername/multiplayer-server:latest \
|
|
180
|
+
--name "My Docker Server" \
|
|
181
|
+
--use-tls --no-self-signed
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The server will automatically look for `cert.pem`, `RSA-cert.pem`, or `ECC-cert.pem` (and their corresponding keys) in the `/app/certs` directory.
|
|
185
|
+
|
|
186
|
+
#### Administrator Usage
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from multiplayer import ServerAdmin
|
|
190
|
+
|
|
191
|
+
# Connect as administrator
|
|
192
|
+
admin = ServerAdmin(
|
|
193
|
+
host='localhost',
|
|
194
|
+
port=12345,
|
|
195
|
+
admin_password="my_admin_password",
|
|
196
|
+
use_tls=True
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Manage the server
|
|
200
|
+
info = admin.get_server_info()
|
|
201
|
+
print(f"Active games: {info['games_count']}")
|
|
202
|
+
|
|
203
|
+
# Check certificate expiration
|
|
204
|
+
expiration = admin.get_cert_expiration()
|
|
205
|
+
print(f"Certificate expires on: {expiration}")
|
|
206
|
+
|
|
207
|
+
# Kick a player if necessary
|
|
208
|
+
# admin.kick_player(game_id, player_id)
|
|
209
|
+
|
|
210
|
+
# Stop the server remotely
|
|
211
|
+
# admin.stop_server()
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### Client Usage
|
|
215
|
+
```python
|
|
216
|
+
from multiplayer import GameClient, Player, suggest_game_name
|
|
217
|
+
|
|
218
|
+
# 1. Discover and connect to the server
|
|
219
|
+
servers = GameClient.discover_servers()
|
|
220
|
+
if not servers:
|
|
221
|
+
print("No servers found.")
|
|
222
|
+
else:
|
|
223
|
+
host, port = servers[0]
|
|
224
|
+
client = GameClient(
|
|
225
|
+
host=host,
|
|
226
|
+
port=port,
|
|
227
|
+
password="my_server_password",
|
|
228
|
+
use_tls=True
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# 2. Create a private game
|
|
232
|
+
private_game = client.create_game(
|
|
233
|
+
name=suggest_game_name(),
|
|
234
|
+
password="my_game_password"
|
|
235
|
+
)
|
|
236
|
+
print(f"Created game with ID: {private_game.ID}")
|
|
237
|
+
|
|
238
|
+
# 3. Create and use a Game Group
|
|
239
|
+
group = client.create_group("Tournament A")
|
|
240
|
+
game_in_group = group.create_game(name="Final Match")
|
|
241
|
+
print(f"Game in group '{group.name}' has ID: {game_in_group.ID}")
|
|
242
|
+
|
|
243
|
+
# 4. A player joins and sets the initial state
|
|
244
|
+
private_game.add_player(Player("Charlie"), password="my_game_password")
|
|
245
|
+
private_game.set_state({"score": 0})
|
|
246
|
+
private_game.start()
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Error Handling
|
|
250
|
+
|
|
251
|
+
The module provides a set of custom exceptions, including `AuthenticationError` for both server and game passwords.
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from multiplayer import GameClient
|
|
255
|
+
from multiplayer.exceptions import ConnectionError, AuthenticationError
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
# ... connect to client ...
|
|
259
|
+
|
|
260
|
+
# Try to join a game with the wrong password
|
|
261
|
+
game.add_player(Player("Eve"), password="wrong_game_password")
|
|
262
|
+
|
|
263
|
+
except AuthenticationError as e:
|
|
264
|
+
print(f"Authentication failed as expected: {e}")
|
|
265
|
+
except ConnectionError as e:
|
|
266
|
+
print(f"A connection or discovery error occurred: {e}")
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Contributing
|
|
270
|
+
|
|
271
|
+
We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for more details on how to get started.
|
|
272
|
+
|
|
273
|
+
## Running Tests
|
|
274
|
+
|
|
275
|
+
To run the unit tests, you will need to have `pytest` installed.
|
|
276
|
+
|
|
277
|
+
```sh
|
|
278
|
+
pip install pytest
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Then, you can run the tests from the root of the project:
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
pytest
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
**English** | [Español](translation/README.es.md) | [Français](translation/README.fr.md)
|
|
2
|
+
|
|
3
|
+
# Multiplayer Game Manager
|
|
4
|
+
|
|
5
|
+
> **A Note on this Project's Origin**
|
|
6
|
+
>
|
|
7
|
+
> This project is primarily the result of a series of experiments using Gemini Code Assist for code generation and error handling. Rather than using it on academic examples, it seemed more interesting to apply it to a project that could meet a real practical need.
|
|
8
|
+
>
|
|
9
|
+
> This, therefore, is the reason for `multiplayer`'s existence: you can dissect the code to see how Gemini (with my guidance) went about building it, or you can ignore all that and just use this library for your own needs!
|
|
10
|
+
|
|
11
|
+
This Python module provides a simple and flexible framework for managing multiplayer games, both locally and over a network.
|
|
12
|
+
|
|
13
|
+
For a detailed technical description of all classes and functions, see the [API Reference](REFERENCE.md).
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
* **Local & Networked:** Use in a single process or in a client-server architecture.
|
|
18
|
+
* **Combined Game State:** A flexible system for synchronizing both the core game status (e.g., `in_progress`) and any custom game data.
|
|
19
|
+
* **Observer Support:** Ability to add observers who can view the game state without participating as players.
|
|
20
|
+
* **Administrator Role:** New `ServerAdmin` class to manage the server, kick players/observers, and monitor server status.
|
|
21
|
+
* **Game Grouping:** Organize several game sessions within the same server using the `GameGroup` class.
|
|
22
|
+
* **Multi-Layered Security:** Supports server passwords, admin passwords, and per-game passwords, with optional TLS v1.3 encryption. Passwords can be updated dynamically by administrators.
|
|
23
|
+
* **Automatic Server Discovery:** Clients can automatically find running servers on the local network.
|
|
24
|
+
* **Extensible Name Suggestions:** Includes a utility function to suggest creative names for games and players.
|
|
25
|
+
* **Multiple Games:** The server can manage multiple game sessions simultaneously, and the game list is now filtered to hide finished games.
|
|
26
|
+
* **Robust Error Handling:** A clear set of custom exceptions for both game logic and network issues.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
You can install it in two ways:
|
|
31
|
+
|
|
32
|
+
### 1. From PyPI
|
|
33
|
+
```sh
|
|
34
|
+
pip install multiplayer
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. From a Wheel file (GitHub)
|
|
38
|
+
Download the `.whl` file from the [Releases](https://github.com/devfred78/multiplayer/releases) page and run:
|
|
39
|
+
```sh
|
|
40
|
+
pip install multiplayer-0.11.0-py3-none-any.whl
|
|
41
|
+
```
|
|
42
|
+
*Replace `multiplayer-0.11.0-py3-none-any.whl` with the actual name of the downloaded file.*
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
### Game State Management
|
|
47
|
+
|
|
48
|
+
A key feature is the ability to manage your own game state alongside the core game status.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# On one client, set a custom state
|
|
52
|
+
game.set_state({
|
|
53
|
+
"board": [["X", "O", ""], ["", "X", ""], ["O", "", ""]],
|
|
54
|
+
"turn": "player2"
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
# On another client, retrieve the combined state
|
|
58
|
+
full_state = game.state
|
|
59
|
+
print(f"Game status: {full_state['status']}")
|
|
60
|
+
# > Game status: in_progress
|
|
61
|
+
|
|
62
|
+
print(f"Current turn: {full_state['custom']['turn']}")
|
|
63
|
+
# > Current turn: player2
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
### Game Grouping
|
|
68
|
+
|
|
69
|
+
You can group games together for better organization.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from multiplayer import Game, GameGroup
|
|
73
|
+
|
|
74
|
+
# Create a group
|
|
75
|
+
group = GameGroup("Tournament A", priority="high")
|
|
76
|
+
|
|
77
|
+
# Add games to the group
|
|
78
|
+
game1 = Game("Match 1")
|
|
79
|
+
game2 = Game("Match 2")
|
|
80
|
+
group.add_game(game1)
|
|
81
|
+
group.add_game(game2)
|
|
82
|
+
|
|
83
|
+
print(f"Game 1 ID: {game1.ID}")
|
|
84
|
+
# > Game 1 ID: 550e8400-e29b-41d4-a716-446655440000
|
|
85
|
+
|
|
86
|
+
print(f"Group '{group.name}' has {len(group.games)} games.")
|
|
87
|
+
# > Group 'Tournament A' has 2 games.
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
### Full Test Environment
|
|
92
|
+
|
|
93
|
+
A script is available to launch a complete test environment with:
|
|
94
|
+
- An IPC log server (`IPClogging`) in a separate window.
|
|
95
|
+
- A game server.
|
|
96
|
+
- Multiple separate client instances (default is 2) simulating a game, each in its own terminal window.
|
|
97
|
+
|
|
98
|
+
To run it:
|
|
99
|
+
```bash
|
|
100
|
+
uv run python scripts/full_test_env.py
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
To specify the number of players:
|
|
104
|
+
```bash
|
|
105
|
+
uv run python scripts/full_test_env.py --players 3
|
|
106
|
+
```
|
|
107
|
+
This will open several Windows Terminal windows: one for the log server and one for each client instance, allowing you to see the real-time interactions and logs.
|
|
108
|
+
|
|
109
|
+
### Local Usage
|
|
110
|
+
|
|
111
|
+
You can use the `Game` class directly, including with a password for local validation.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from multiplayer import Game, Player, suggest_game_name
|
|
115
|
+
|
|
116
|
+
game = Game(name="My Awesome Game", password="local_game_pass")
|
|
117
|
+
game.add_player(Player("Alice"), password="local_game_pass")
|
|
118
|
+
game.start()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Networked Usage (Client-Server)
|
|
122
|
+
|
|
123
|
+
#### Server Setup
|
|
124
|
+
```python
|
|
125
|
+
from multiplayer import GameServer
|
|
126
|
+
|
|
127
|
+
# Start a secure server with a custom domain and self-signed certificate
|
|
128
|
+
server = GameServer(
|
|
129
|
+
host='0.0.0.0',
|
|
130
|
+
port=12345,
|
|
131
|
+
name="My Production Server",
|
|
132
|
+
password="my_server_password",
|
|
133
|
+
admin_password="my_admin_password",
|
|
134
|
+
use_tls=True,
|
|
135
|
+
tls_domain="example.com",
|
|
136
|
+
tls_self_signed=True
|
|
137
|
+
)
|
|
138
|
+
server.start()
|
|
139
|
+
|
|
140
|
+
# Or use existing certificate files
|
|
141
|
+
server = GameServer(
|
|
142
|
+
use_tls=True,
|
|
143
|
+
tls_cert="path/to/cert.pem",
|
|
144
|
+
tls_key="path/to/key.pem",
|
|
145
|
+
tls_self_signed=False
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Running with Docker
|
|
150
|
+
|
|
151
|
+
You can run the game server using Docker. To use your own TLS certificates, map a local directory containing `cert.pem` and `privkey.pem` to `/app/certs` in the container.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
docker run -d \
|
|
155
|
+
-p 65432:65432 \
|
|
156
|
+
-v /path/to/your/certs:/app/certs \
|
|
157
|
+
ghcr.io/yourusername/multiplayer-server:latest \
|
|
158
|
+
--name "My Docker Server" \
|
|
159
|
+
--use-tls --no-self-signed
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The server will automatically look for `cert.pem`, `RSA-cert.pem`, or `ECC-cert.pem` (and their corresponding keys) in the `/app/certs` directory.
|
|
163
|
+
|
|
164
|
+
#### Administrator Usage
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from multiplayer import ServerAdmin
|
|
168
|
+
|
|
169
|
+
# Connect as administrator
|
|
170
|
+
admin = ServerAdmin(
|
|
171
|
+
host='localhost',
|
|
172
|
+
port=12345,
|
|
173
|
+
admin_password="my_admin_password",
|
|
174
|
+
use_tls=True
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Manage the server
|
|
178
|
+
info = admin.get_server_info()
|
|
179
|
+
print(f"Active games: {info['games_count']}")
|
|
180
|
+
|
|
181
|
+
# Check certificate expiration
|
|
182
|
+
expiration = admin.get_cert_expiration()
|
|
183
|
+
print(f"Certificate expires on: {expiration}")
|
|
184
|
+
|
|
185
|
+
# Kick a player if necessary
|
|
186
|
+
# admin.kick_player(game_id, player_id)
|
|
187
|
+
|
|
188
|
+
# Stop the server remotely
|
|
189
|
+
# admin.stop_server()
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Client Usage
|
|
193
|
+
```python
|
|
194
|
+
from multiplayer import GameClient, Player, suggest_game_name
|
|
195
|
+
|
|
196
|
+
# 1. Discover and connect to the server
|
|
197
|
+
servers = GameClient.discover_servers()
|
|
198
|
+
if not servers:
|
|
199
|
+
print("No servers found.")
|
|
200
|
+
else:
|
|
201
|
+
host, port = servers[0]
|
|
202
|
+
client = GameClient(
|
|
203
|
+
host=host,
|
|
204
|
+
port=port,
|
|
205
|
+
password="my_server_password",
|
|
206
|
+
use_tls=True
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# 2. Create a private game
|
|
210
|
+
private_game = client.create_game(
|
|
211
|
+
name=suggest_game_name(),
|
|
212
|
+
password="my_game_password"
|
|
213
|
+
)
|
|
214
|
+
print(f"Created game with ID: {private_game.ID}")
|
|
215
|
+
|
|
216
|
+
# 3. Create and use a Game Group
|
|
217
|
+
group = client.create_group("Tournament A")
|
|
218
|
+
game_in_group = group.create_game(name="Final Match")
|
|
219
|
+
print(f"Game in group '{group.name}' has ID: {game_in_group.ID}")
|
|
220
|
+
|
|
221
|
+
# 4. A player joins and sets the initial state
|
|
222
|
+
private_game.add_player(Player("Charlie"), password="my_game_password")
|
|
223
|
+
private_game.set_state({"score": 0})
|
|
224
|
+
private_game.start()
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Error Handling
|
|
228
|
+
|
|
229
|
+
The module provides a set of custom exceptions, including `AuthenticationError` for both server and game passwords.
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from multiplayer import GameClient
|
|
233
|
+
from multiplayer.exceptions import ConnectionError, AuthenticationError
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
# ... connect to client ...
|
|
237
|
+
|
|
238
|
+
# Try to join a game with the wrong password
|
|
239
|
+
game.add_player(Player("Eve"), password="wrong_game_password")
|
|
240
|
+
|
|
241
|
+
except AuthenticationError as e:
|
|
242
|
+
print(f"Authentication failed as expected: {e}")
|
|
243
|
+
except ConnectionError as e:
|
|
244
|
+
print(f"A connection or discovery error occurred: {e}")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Contributing
|
|
248
|
+
|
|
249
|
+
We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for more details on how to get started.
|
|
250
|
+
|
|
251
|
+
## Running Tests
|
|
252
|
+
|
|
253
|
+
To run the unit tests, you will need to have `pytest` installed.
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
pip install pytest
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Then, you can run the tests from the root of the project:
|
|
260
|
+
|
|
261
|
+
```sh
|
|
262
|
+
pytest
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "multiplayer"
|
|
3
|
+
version = "0.11.0"
|
|
4
|
+
description = "Library that allows you to manage multiple players, locally, on a network, or on the Internet."
|
|
5
|
+
license = "MIT"
|
|
6
|
+
license-files = ["LICENSE.md"]
|
|
7
|
+
author = "devfred78"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Programming Language :: Python :: 3.14",
|
|
17
|
+
"Topic :: Games/Entertainment",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"colorlog",
|
|
21
|
+
"cryptography",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
multiplayer-log-server = "multiplayer.run_log_server:main"
|
|
26
|
+
multiplayer-server = "multiplayer.run_server:main"
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest",
|
|
31
|
+
"requests",
|
|
32
|
+
"ruff",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["uv_build>=0.11.7,<0.12.0"]
|
|
37
|
+
build-backend = "uv_build"
|
|
38
|
+
|
|
39
|
+
[tool.uv.build-backend]
|
|
40
|
+
module-name = "multiplayer"
|
|
41
|
+
module-root = "src"
|
|
42
|
+
package-data = { "multiplayer" = ["data/*.csv"] }
|
|
@@ -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()
|