mousefox
MouseFox
MouseFox is a framework for building turn-based multiplayer games in Python with minimum boilerplate.
See the documentation for details, and be sure to join the community discord server.
Install
pip install git+https://github.com/ArielHorwitz/mousefox.git
Built-in examples
Tic-tac-toe:
import mousefox
mousefox.examples.tictactoe()
Text chat:
import mousefox
mousefox.examples.chat()
Getting started
When working with MouseFox, we will become familiar with PGNet and Kivy. Let's walk through making a simple multiplayer clicker game.
Game logic
We require a pgnet.Game
class. This class will be initialized by the server whenever a
user creates a new game:
import pgnet
class ClickerGame(pgnet.Game):
clicks = 0
def handle_heartbeat(self, packet: pgnet.Packet) -> pgnet.Response:
# Return game updates
message = f"Number of clicks: {self.clicks}"
return pgnet.Response(message)
def handle_game_packet(self, packet: pgnet.Packet) -> pgnet.Response:
# Handle requests and actions
self.clicks += 1
return pgnet.Response("Clicked.")
Game widget
We require a Kivy Widget
class. This class will be initialized by the GUI whenever the
user joins a game.
from kivy.uix.label import Label
import pgnet
class ClickerWidget(Label):
def __init__(self, client: pgnet.Client):
# Init the kivy widget and bind to client heartbeat
super().__init__(text="Waiting for data from server...")
self.client = client
self.client.on_heartbeat = self.on_heartbeat
def on_heartbeat(self, heartbeat: pgnet.Response):
# Use heartbeat to get game updates
self.text = heartbeat.message
def on_touch_down(self, touch):
# Use client to send requests and do actions
if self.collide_point(*touch.pos):
self.client.send(pgnet.Packet("Click"))
Running the app
We can now call mousefox.run
to run the app:
import mousefox
mousefox.run(
game_widget=ClickerWidget,
game_class=ClickerGame,
)
Check out the examples source code.
1""".. include:: ../README.md 2 3# Install 4```bash 5pip install git+https://github.com/ArielHorwitz/mousefox.git 6``` 7 8# Built-in examples 9Tic-tac-toe: 10```python3 11import mousefox 12 13mousefox.examples.tictactoe() 14``` 15 16Text chat: 17```python3 18import mousefox 19 20mousefox.examples.chat() 21``` 22 23# Getting started 24When working with MouseFox, we will become familiar with 25[PGNet](https://github.com/ArielHorwitz/pgnet) and [Kivy](https://kivy.org/doc/stable/). 26Let's walk through making a simple multiplayer clicker game. 27 28## Game logic 29We require a `pgnet.Game` class. This class will be initialized by the server whenever a 30user creates a new game: 31```python3 32import pgnet 33 34class ClickerGame(pgnet.Game): 35 clicks = 0 36 37 def handle_heartbeat(self, packet: pgnet.Packet) -> pgnet.Response: 38 # Return game updates 39 message = f"Number of clicks: {self.clicks}" 40 return pgnet.Response(message) 41 42 def handle_game_packet(self, packet: pgnet.Packet) -> pgnet.Response: 43 # Handle requests and actions 44 self.clicks += 1 45 return pgnet.Response("Clicked.") 46``` 47 48## Game widget 49We require a Kivy `Widget` class. This class will be initialized by the GUI whenever the 50user joins a game. 51```python3 52from kivy.uix.label import Label 53import pgnet 54 55class ClickerWidget(Label): 56 def __init__(self, client: pgnet.Client): 57 # Init the kivy widget and bind to client heartbeat 58 super().__init__(text="Waiting for data from server...") 59 self.client = client 60 self.client.on_heartbeat = self.on_heartbeat 61 62 def on_heartbeat(self, heartbeat: pgnet.Response): 63 # Use heartbeat to get game updates 64 self.text = heartbeat.message 65 66 def on_touch_down(self, touch): 67 # Use client to send requests and do actions 68 if self.collide_point(*touch.pos): 69 self.client.send(pgnet.Packet("Click")) 70``` 71 72## Running the app 73We can now call `mousefox.run` to run the app: 74```python3 75import mousefox 76 77mousefox.run( 78 game_widget=ClickerWidget, 79 game_class=ClickerGame, 80) 81``` 82 83Check out the examples 84[source code](https://github.com/ArielHorwitz/mousefox/tree/master/mousefox/examples/). 85""" # noqa: D415 86 87 88import os 89import sys 90import asyncio 91from loguru import logger 92from . import app 93from . import examples # noqa: F401 94from . import util # noqa: F401 95 96 97def run(**kwargs): 98 """Run the app. Takes arguments like `mousefox.AppConfig`. 99 100 See also: `async_run`. 101 """ 102 asyncio.run(async_run(**kwargs)) 103 104 105async def async_run(**kwargs): 106 """Coroutine to run the app. Takes arguments like `mousefox.AppConfig`. 107 108 See also: `run`. 109 """ 110 logger.info("Starting MouseFox.") 111 config = app.app.AppConfig(**kwargs) 112 mf_app = app.app.App(config) 113 exit_code = await mf_app.async_run() 114 if not config.allow_quit: 115 return 116 # Restart if exit code is -1 117 if exit_code == -1: 118 logger.info("Restarting MouseFox...") 119 os.execl(sys.executable, sys.executable, *sys.argv) 120 logger.info("Closing MouseFox.") 121 quit() 122 123 124AppConfig = app.app.AppConfig
98def run(**kwargs): 99 """Run the app. Takes arguments like `mousefox.AppConfig`. 100 101 See also: `async_run`. 102 """ 103 asyncio.run(async_run(**kwargs))
Run the app. Takes arguments like mousefox.AppConfig
.
See also: async_run
.
106async def async_run(**kwargs): 107 """Coroutine to run the app. Takes arguments like `mousefox.AppConfig`. 108 109 See also: `run`. 110 """ 111 logger.info("Starting MouseFox.") 112 config = app.app.AppConfig(**kwargs) 113 mf_app = app.app.App(config) 114 exit_code = await mf_app.async_run() 115 if not config.allow_quit: 116 return 117 # Restart if exit code is -1 118 if exit_code == -1: 119 logger.info("Restarting MouseFox...") 120 os.execl(sys.executable, sys.executable, *sys.argv) 121 logger.info("Closing MouseFox.") 122 quit()
Coroutine to run the app. Takes arguments like mousefox.AppConfig
.
See also: run
.
22@dataclass 23class AppConfig: 24 """Configuration for MouseFox app.""" 25 26 game_widget: kvex.kivy.Widget 27 """Kivy widget for the game.""" 28 game_class: Type[pgnet.Game] 29 """Game subclass for the local client and server (see `pgnet.Server`).""" 30 client_class: Type[pgnet.Client] = pgnet.Client 31 """Client subclass for the client (see `pgnet.Client`).""" 32 server_factory: Optional[Callable] = None 33 """The server factory for the local client (see `pgnet.Client.local`).""" 34 disable_local: bool = False 35 """Disable local clients.""" 36 disable_remote: bool = False 37 """Disable remote clients.""" 38 maximize: bool = False 39 """If app should start maximized.""" 40 borderless: bool = False 41 """If app should not have window borders.""" 42 size: Optional[tuple[int, int]] = (1280, 768) 43 """App window size in pixels.""" 44 offset: Optional[tuple[int, int]] = None 45 """App window offset in pixels.""" 46 title: str = "MouseFox" 47 """App window title.""" 48 info_text: str = "No info available." 49 """Text to show when ready to connect.""" 50 online_info_text: str = "No online info available." 51 """Text to show when ready to connect remotely.""" 52 allow_quit: bool = True 53 """Allow MouseFox to quit or restart the script.""" 54 55 def __getitem__(self, item): 56 """Get item.""" 57 return getattr(self, item) 58 59 def keys(self): 60 """Enables mapping. 61 62 For example: 63 ```python3 64 config = mousefox.AppConfig(...) 65 mousefox.run(**config) 66 ``` 67 """ 68 return self.__dataclass_fields__.keys()
Configuration for MouseFox app.
Client subclass for the client (see pgnet.Client
).
The server factory for the local client (see pgnet.Client.local
).