Compiled on: 2025-10-22 — printable version
https://en.wikipedia.org/wiki/Pong

In this running example we are going to
in order to exemplify the development of a distributed system project.
Here’s an overview of the final result:

Source code at: https://github.com/unibo-fc-isi-ds/dpongpy
Go on, play with it!
pip install dpongpy
python -m dpongpy --help # to see all options
python -m dpongpy --mode local # to play offline
# to start a centralised server
python -m dpongpy --mode centralised --role coordinator
python -m dpongpy --mode centralised --role terminal --side left --keys wasd --host IP_COORDINATOR
python -m dpongpy --mode centralised --role terminal --side right --keys arrows --host IP_COORDINATOR
Default key bindings:
Recall to
pip uninstall dpongpybefore proceeding with this lecture, to avoid conflicts with your working copy
PyGame is a popular Python library for writing simple games
It handles many game development tasks, such as graphics, sound, time, and input management.
Simple to use, yet powerful enough to create non-trivial games
Lightweight, portable, and easy to install
pip install pygame (better to use a virtual environment)A game loop is the main logic cycle of most video-games
It continuously runs while the game is active, managing the following aspects:
Most commonly, some wait is introduced at the end of each cycle
import pygame
# Initialize Pygame
pygame.init()
# Screen dimensions
screen_size = pygame.Vector2(800, 600)
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption("Game Loop Example")
# Colours
white = pygame.color.Color(255, 255, 255) # White color RGB
black = pygame.color.Color(0, 0, 0) # Black color RGB
# Circle settings
circle_radius = min(screen_size) / 10 # Circle radius is 1/10th of the screen size's smallest dimension
circle_posistion = screen_size / 2 # Start at the center
circle_speed = min(screen_size) / 10 # Absolute speed of the circle is 1/10th of the screen size's smallest dimension
circle_velocity = pygame.Vector2(0, 0) # Initial velocity of the circle
clock = pygame.time.Clock()
# Main game loop
dt = 0 # time elapsed since last loop iteration
running = True # game should stop when set to False
while running:
# Event handling
for event in pygame.event.get([pygame.KEYDOWN, pygame.KEYUP]):
if event.key == pygame.K_ESCAPE:
running = False # Quit the game
elif event.key == pygame.K_w: # Move up
circle_velocity.y = -circle_speed if event.type == pygame.KEYDOWN else 0
elif event.key == pygame.K_s: # Move down
circle_velocity.y = circle_speed if event.type == pygame.KEYDOWN else 0
elif event.key == pygame.K_a: # Move left
circle_velocity.x = -circle_speed if event.type == pygame.KEYDOWN else 0
elif event.key == pygame.K_d: # Move right
circle_velocity.x = circle_speed if event.type == pygame.KEYDOWN else 0
# Clear screen
screen.fill(black) # Black background
# Update and logs the circle's position
old_circle_posistion = circle_posistion.copy()
circle_posistion += dt * circle_velocity
if old_circle_posistion != circle_posistion:
print("Circle moves from", old_circle_posistion, "to", circle_posistion)
# Draw the circle
pygame.draw.circle(screen, white, circle_posistion, circle_radius)
# Update the display
pygame.display.flip()
# Ensure the game runs at 60 FPS
dt = clock.tick(60) / 1000 # Time elapsed since last loop iteration in seconds
# Quit Pygame
pygame.quit()
full example on GitHub
PyGame comes with a notion of events and event queue
each event has a type (e.g., pygame.KEYDOWN and pygame.KEYUP) and attributes (e.g., event.key)
pygame.event.Eventintegersevents can be retrieved from the queue (e.g., pygame.event.get([RELEVANT_TYPES]))…
RELEVANT_TYPES is a list of event types to be retrieved… and possibly provoked (i.e. appended to the queue) by the programmer (e.g., pygame.event.post(event))
event is an instance of pygame.event.Eventfrom pygame.math import Vector2
from pygame.rect import Rect
class GameObject:
def __init__(self, size, position=None, speed=None, name=None):
self.size = Vector2(size)
self.position = Vector2(position) if position is not None else Vector2()
self.speed = Vector2(speed) if speed is not None else Vector2()
self.name = name or self.__class__.__name__.lower()
def __eq__(self, other):
return isinstance(other, type(self)) and \
self.name == other.name and \
self.size == other.size and \
self.position == other.position and \
self.speed == other.speed
def __hash__(self):
return hash((type(self), self.name, self.size, self.position, self.speed))
def __repr__(self):
return f'<{type(self).__name__}(id={id(self)}, name={self.name}, size={self.size}, position={self.position}, speed={self.speed})>'
def __str__(self):
return f'{self.name}#{id(self)}'
@property
def bounding_box(self):
return Rect(self.position - self.size / 2, self.size)
def update(self, dt):
self.position = self.position + self.speed * dt
if __name__ == '__main__':
x = GameObject((10, 20), (100, 200), (1, 2), 'myobj')
assert x.size == Vector2(10, 20)
assert x.position == Vector2(100, 200)
assert x.speed == Vector2(1, 2)
assert x.name == 'myobj'
assert x.bounding_box.topleft == (95, 190)
assert x.bounding_box.size == (10, 20)
assert x.bounding_box.bottomright == (105, 210)
assert str(x) == 'myobj#' + str(id(x))
assert repr(x) == ('<GameObject(id=%d, name=myobj, size=[10, 20], position=[100, 200], speed=[1, 2])>' % id(x))
y = GameObject((10, 20), (100, 200), (1, 2), 'myobj')
z = GameObject((10, 20), (100, 200), (1, 2), 'myobj2')
assert x == y
assert x != z
x.update(2)
assert x.position == Vector2(102, 204)
assert x != y
class GameObject { + name: str + position: Vector2 + size: Vector2 + speed: Vector2 + bounding_box: Rect + update(dt: float) }
package “pygame” { class Vector2 { + x: float + y: float } class Rect { + left: float + top: float + width: float + height: float } }
GameObject *-d- Vector2 GameObject *– Rect
full example on GitHub
MOVE_UP, MOVE_DOWN, STOP, etc.pygame.event.custom_type()import pygame
from pygame.event import Event, custom_type
from enum import Enum
class GameEvent(Enum):
MOVE_UP: int = custom_type()
MOVE_DOWN: int = custom_type()
MOVE_LEFT: int = custom_type()
MOVE_RIGHT: int = custom_type()
STOP: int = pygame.QUIT
def create_event(self, **kwargs):
return Event(self.value, kwargs)
@classmethod
def all(cls) -> set['GameEvent']:
return set(cls.__members__.values())
@classmethod
def types(cls) -> list[int]:
return [event.value for event in cls.all()]
KEYMAP_WASD = {
pygame.K_w: GameEvent.MOVE_UP,
pygame.K_s: GameEvent.MOVE_DOWN,
pygame.K_d: GameEvent.MOVE_RIGHT,
pygame.K_a: GameEvent.MOVE_LEFT,
pygame.K_ESCAPE: GameEvent.STOP
}
class InputHandler:
def __init__(self, keymap=None):
self.keymap = dict(keymap) if keymap is not None else KEYMAP_WASD
def handle_inputs(self):
for event in pygame.event.get([pygame.KEYDOWN, pygame.KEYUP]):
if event.key in self.keymap.keys():
game_event = self.keymap[event.key].create_event(up=event.type == pygame.KEYUP)
self.post_event(game_event)
def post_event(self, game_event):
pygame.event.post(game_event)
class Controller(InputHandler):
def __init__(self, game_object, speed, keymap=None):
super().__init__(keymap)
self._game_object = game_object
self._speed = speed
def update(self, dt):
for event in pygame.event.get(GameEvent.types()):
self._update_object_according_to_event(self._game_object, event)
self._game_object.update(dt)
def _update_object_according_to_event(self, game_object, event):
match GameEvent(event.type):
case GameEvent.MOVE_UP:
game_object.speed.y = 0 if event.up else -self._speed
case GameEvent.MOVE_DOWN:
game_object.speed.y = 0 if event.up else self._speed
case GameEvent.MOVE_LEFT:
game_object.speed.x = 0 if event.up else -self._speed
case GameEvent.MOVE_RIGHT:
game_object.speed.x = 0 if event.up else self._speed
case GameEvent.STOP:
pygame.quit()
exit()
top to bottom direction
enum GameEvent { + MOVE_UP + MOVE_DOWN + MOVE_LEFT + MOVE_RIGHT + STOP + create_event(**kwargs): Event + {static} all(): set[GameEvent] + {static} types(): list[int] }
package “pygame” { package “event” { class Event { + type: int + data: dict } } }
class InputHandler { + keymap: Dict[int, int] + handle_inputs() -> List[int] + post_event(event: Event) }
class Controller { - _game_object: GameObject - _speed: float + update(dt: float) - _update_object_according_to_event(game_object, event) }
InputHandler <|-d- Controller
InputHandler .u.> GameEvent: uses InputHandler .u.> Event: uses
full example on GitHub
import pygame
class View:
def __init__(self, game_object, screen=None, size=None, background_color=None, foreground_color=None):
if screen is not None:
size = screen.get_size()
self._size = size or (800, 600)
self.background_color = pygame.Color(background_color or "black")
self.foreground_color = pygame.Color(foreground_color or "white")
self._screen = screen or pygame.display.set_mode(self._size)
assert game_object is not None, "game_object cannot be None"
self._game_object = game_object
def render(self):
self._reset_screen(self._screen, self.background_color)
self._draw_game_object(self._game_object, self.foreground_color)
pygame.display.flip()
def _reset_screen(self, screen, color):
screen.fill(color)
def _draw_game_object(self, game_object, color):
pygame.draw.ellipse(self._screen, color, game_object.bounding_box)
top to bottom direction
class View { - _size: tuple[int, int] - _screen: Surface + background_color: Color + foreground_color: Color - _game_object: GameObject + render() - _reset_screen(screen, color) - _draw_game_object(game_object, color) }
package “pygame” { class Surface { + fill(color: Color) + flip() } class Color { + r: int + g: int + b: int + a: int } }
View .d.> Surface: uses View ..> Color: uses
full example on GitHub
InputHandlerControllerViewimport pygame
from .example2_game_object import GameObject
from .example3_controller import Controller
from .example4_view import View
# Initialize Pygame
pygame.init()
# Screen dimensions
screen_size = pygame.Vector2(800, 600)
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption("Game Loop Example")
# Game objects
circle = GameObject(size=screen_size / 10, position=screen_size / 2, name="circle")
# Wire game-loop components together
controller = Controller(game_object=circle, speed=min(screen_size) / 10)
view = View(game_object=circle, screen=screen)
clock = pygame.time.Clock()
# Main game loop
dt = 0 # time elapsed since last loop iteration
running = True # game should stop when set to False
while running:
controller.handle_inputs()
controller.update(dt)
view.render()
# Ensure the game runs at 60 FPS
dt = clock.tick(60) / 1000 # Time elapsed since last loop iteration in seconds
# Quit Pygame
pygame.quit()
full example on GitHub

Pong game model comprehends:
GameObject: visible entity in the game
name, position, size, *speed*, bounding_box
position, size, and *speed* are Vector2 instancesPaddle, Ball
Paddle instances are assigned to one side (property) of the screen
Directions: UP, DOWN, LEFT, RIGHTBoard: the plane upon which the game is played (black rectangle in the figure)
size, wallsWall: invisible entity that reflects the Ball when hitAncillary classes: Vector2, Rectangle, Direction
Vector2 utility class from PyGame, representing a 2D vectorRectangle utility class, representing a rectangle, supporting collision detectionDirection is an enumeration of 4 possible directions + NONE (lack of direction)left to right direction
package “dpongpy.model” {
enum Direction {
+ {static} NONE
+ {static} LEFT
+ {static} UP
+ {static} RIGHT
+ {static} DOWN
--
+ is_vertical: bool
+ is_horizontal: bool
+ {static} values(): list[Direction]
}
interface Sized {
+size: Vector2
+width: float
+height: float
}
interface Positioned {
+position: Vector2
+x: float
+y: float
}
class Rectangle {
+ top_left: Vector2
+ top_right: Vector2
+ bottom_right: Vector2
+ bottom_left: Vector2
+ top: float
+ bottom: float
+ left: float
+ right: float
+ corners -> list[Vector2]
+ overlaps(other: Rectangle) -> bool
+ is_inside(other: Rectangle) -> bool
+ intersection_with(other: Rectangle) -> Rectangle
+ hits(other: Rectangle) -> dict[Direction, float]
}
Rectangle --|> Sized
Rectangle --|> Positioned
class GameObject {
+ name: str
+ speed: Vector2
+ bounding_box: Rectangle
+ update(dt: float)
+ override(other: GameObject)
}
GameObject --|> Sized
GameObject --|> Positioned
GameObject "1" *-- "1" Rectangle
class Paddle {
+ side: Direction
}
Paddle --|> GameObject
Paddle "1" *-- "1" Direction
class Ball
Ball --|> GameObject
class Board {
+ walls: dict[Direction, GameObject]
}
Board --|> Sized
Board "1" *-- "4" Direction
Board "1" *-- "4" GameObject
class Pong {
+ config: Config
+ random: Random
+ ball: Ball
+ paddles: list[Paddle]
+ board: Board
+ updates: int
+ time: float
--
+ reset_ball(speed: Vector2 = None)
+ add_paddle(side: Direction, paddle: Paddle = None)
+ paddle(side: Direction): Paddle
+ has_paddle(side: Direction): bool
+ remove_paddle(self, Direction)
--
+ update(dt: float)
+ move_paddle(paddle: int|Direction, direction: Direction)
+ stop_paddle(paddle: int|Direction):
+ override(self, other: Pong):
- _handle_collisions(subject, objects)
}
'Pong --|> Sized
Pong "1" *-- "1" Ball
Pong "1" *-- "4" Paddle
Pong "1" *-- "1" Board
Pong "1" *-- "1" Config
note top of Pong
model class
end note
class Config {
+ paddle_ratio: Vector2
+ ball_ratio: float
+ ball_speed_ratio: float
+ paddle_speed_ratio: float
+ paddle_padding: float
}
}
package “random” { class Random { + uniform(a: float, b: float): float } }
package “pygame” { class Vector2 { +x: float +y: float } }
Rectangle “1” *-l- “6” Vector2 Pong “1” *– “1” Random
code on GitHub
Facilities of the Pong class, to configure the game:
reset_ball(speed=None): re-locates the Ball at the center of the Board, setting its speed vector to the given value
speed is Noneadd_paddle(side, paddle=None): assigns a Paddle to the Pong, at the given side (if not already present)
Paddle is created from scratch if paddle is None
side of the Boardpaddle(side): retrieves the Paddle at the given sidehas_paddle(side): checks if a Paddle is present at the given sideremove_paddle(side): removes the Paddle at the given sideFacilities of the Pong class, to animate the game:
update(dt): updates the game state, moving the Ball and the Paddles according to the given time delta
Ball and the Paddles and the Walls
_handle_collisions method to the purposemove_paddle(side, direction): moves the selected Paddle in the given direction by setting its speed vector accordingly
paddle can either be an int (index of the Paddle in the paddles list) or a Direction (side of the Paddle)stop_paddle(side): stops the selected Paddle from movingCollision detection is a crucial aspect of game development
In Pong collisions are very simple, as they simply rely on the game objects’ bounding boxes
In Pong there are 3 sorts of relevant collisions:
Ball vs. PaddleBall vs. WallPaddle vs. WallBouncing can simply be achieved by reversing the Ball’s speed vector along the colliding axis
GameObjects (non overlapping)GameObjects (overlapping)GameObjects (inside)Ball is close to an obstacle and update() is calledBall is updated, it is now overlapping an obstacle (wall, or paddle)Bouncing = reversing the speed vector along the colliding axis + re-locating the Ball outside the obstacleupdate() call will move the Ball away from the obstacleInsight: inputs are external data, representing events that may impact the system state
(most commonly corresponding to users’ actions)
(exceptions apply)
In a simple video game like Pong, we distinguish between:
What input events are relevant for the software, during its execution?
Paddle’s movementPaddle’s stopWhat other control events are relevant for the software, during its execution?
InputHandler interprets keyboard input events and generates control eventsEventHandler processes control events and updates the game state (Pong class) accordinglyInputHandler is also in charge of generating time passing eventsPaddles are moved by players, via the keyboard
when players are distributed, the keyboards are different
when players are local, there’s only one keyboard
PlayerAction enumerates all possible actions a player can perform (on a paddle)
Direction, stop paddle, quit the gameActionMap, associating key codes to PlayerActionsleft to right direction
package “pygame.event” { class Event { + type: int + dict: dict } }
package “dpongpy.controller” { class ActionMap { + move_up: int + move_down: int + move_left: int + move_right: int + quit: int + name: str }
enum PlayerAction {
+ {static} MOVE_UP
+ {static} MOVE_DOWN
+ {static} MOVE_RIGHT
+ {static} MOVE_LEFT
+ {static} STOP
+ {static} QUIT
}
enum ControlEvent {
+ {static} PLAYER_JOIN
+ {static} PLAYER_LEAVE
+ {static} GAME_START
+ {static} GAME_OVER
+ {static} PADDLE_MOVE
+ {static} TIME_ELAPSED
}
interface InputHandler {
+ create_event(event, **kwargs)
+ post_event(event, **kwargs)
..
+ key_pressed(key: int)
+ key_released(key: int)
+ time_elapsed(dt: float)
..
+ handle_inputs(dt: float)
}
interface EventHandler {
+ handle_events()
..
+ on_player_join(pong: Pong, paddle):
+ on_player_leave(pong: Pong, paddle):
+ on_game_start(pong: Pong):
+ on_game_over(pong: Pong):
+ on_paddle_move(pong: Pong, paddle, direction):
+ on_time_elapsed(pong: Pong, dt):
}
}
package “dpongpy.model” { class Pong { stuff } }
ActionMap <.. InputHandler: knows Event <.. InputHandler: processes InputHandler ..> PlayerAction: selects\nbased on\nActionMap\nand\nEvent InputHandler ..> ControlEvent: generates PlayerAction <.. ControlEvent: wraps ControlEvent <.. EventHandler: processes EventHandler ..> Pong: updates\nbased on\nControlEvent
Paddle and to an ActionMap to govern that PaddleActionMap is a dictionary mapping key codes to PlayerActions
pygame.K_UP $\rightarrow$ MOVE_UP, pygame.K_DOWN $\rightarrow$ MOVE_DOWN for right paddle (arrow keys)pygame.K_w $\rightarrow$ MOVE_UP, pygame.K_s $\rightarrow$ MOVE_DOWN for left paddle (WASD keys)PlayerActions are one particular sort of ControlEvents that may occur in the game
GAME_START, PADDLE_MOVE, TIME_ELAPSED, etc.ControlEvents are custom PyGame events which animate the game
PADDLE_MOVE carries the information about which Paddle is moving and where it is movingpackage “dpongpy.controller” { interface EventHandler interface InputHandler interface Controller { - _pong: Pong }
EventHandler <|-d- Controller InputHandler <|-d- Controller }
we call Controller any entity which acts as both an EventHandler and an InputHandler
Insight: outputs are representations of the system state
that are perceived by the external world
(most commonly corresponding to visual or auditory feedback for the user)
In our case:
Pong, control $\approx$ EventHandler + InputHandler@startuml package “dpongpy” { class Pong
package “view” { interface PongView { - _pong: Pong + render() }
interface ShowNothingPongView
note top of ShowNothingPongView useful for hiding the game end note
interface ScreenPongView { - _screen: Surface + render_ball(ball: Ball) + render_paddles(paddle: Iterable[Paddle]) + render_paddle(paddle: Paddle) }
PongView <|-d- ScreenPongView PongView <|-d- ShowNothingPongView }
PongView *-u- Pong } @enduml
import pygame
from dpongpy.model import *
from typing import Iterable
def rect(rectangle: Rectangle) -> pygame.Rect:
return pygame.Rect(rectangle.top_left, rectangle.size)
class PongView:
def __init__(self, pong: Pong):
self._pong = pong
def render(self):
raise NotImplemented
class ShowNothingPongView(PongView):
def render(self):
pass
class ScreenPongView(PongView):
def __init__(self, pong: Pong, screen: pygame.Surface = None):
super().__init__(pong)
self._screen = screen or pygame.display.set_mode(pong.size)
def render(self):
self._screen.fill("black")
self.render_ball(self._pong.ball)
self.render_paddles(self._pong.paddles)
def render_ball(self, ball: Ball):
pygame.draw.ellipse(self._screen, "white", rect(ball.bounding_box), width=0)
def render_paddles(self, paddles: Iterable[Paddle]):
for paddle in paddles:
self.render_paddle(paddle)
def render_paddle(self, paddle: Paddle):
pygame.draw.rect(self._screen, "white", rect(paddle.bounding_box), width=0)
render() methodtop to bottom direction package “dpongpy” { class Settings { + config: Config + size: tuple[int] + fps: int + initial_paddles: tuple[Direction] }
class PongGame { + settings: Settings + pong: Pong + dt: float + clock: pygame.time.Clock + running: bool + view: PongView + controller: Controller .. + create_view(): PongView + create_controller(): Controller .. + before_run() + after_run() + at_each_run() .. + run() + stop() } Settings -d[hidden]- PongGame }
class PongGame:
def __init__(self, settings: Settings = None):
self.settings = settings or Settings()
self.pong = Pong(
size=self.settings.size,
config=self.settings.config,
paddles=self.settings.initial_paddles
)
self.dt = None
self.view = self.create_view()
self.clock = pygame.time.Clock()
self.running = True
self.controller = self.create_controller(settings.initial_paddles)
def create_view(self):
from dpongpy.view import ScreenPongView
return ScreenPongView(self.pong, debug=self.settings.debug)
def create_controller(game, paddle_commands: dict[Direction, ActionMap]):
from dpongpy.controller.local import PongLocalController
class Controller(PongLocalController):
def __init__(self, paddle_commands):
super().__init__(game.pong, paddle_commands)
def on_game_over(this, _):
game.stop()
return Controller(paddle_commands)
def before_run(self):
pygame.init()
def after_run(self):
pygame.quit()
def at_each_run(self):
pygame.display.flip()
def run(self):
try:
self.dt = 0
self.before_run()
while self.running:
self.controller.handle_inputs(self.dt)
self.controller.handle_events()
self.view.render()
self.at_each_run()
self.dt = self.clock.tick(self.settings.fps) / 1000
finally:
self.after_run()
def stop(self):
self.running = False
def main(settings = None):
if settings is None:
settings = Settings()
PongGame(settings).run()
notice the implementation of run()
See the command line options via poetry run python -m dpongpy -h:
pygame 2.6.0 (SDL 2.28.4, Python 3.12.5)
Hello from the pygame community. https://www.pygame.org/contribute.html
usage: python -m dpongpy [-h] [--mode {local}] [--side {none,left,up,right,down}] [--keys {wasd,arrows,ijkl,numpad}] [--debug] [--size SIZE SIZE] [--fps FPS]
options:
-h, --help show this help message and exit
mode:
--mode {local}, -m {local}
Run the game in local or centralised mode
game:
--side {none,left,up,right,down}, -s {none,left,up,right,down}
Side to play on
--keys {wasd,arrows,ijkl,numpad}, -k {wasd,arrows,ijkl,numpad}
Keymaps for sides
--debug, -d Enable debug mode
--size SIZE SIZE, -S SIZE SIZE
Size of the game window
--fps FPS, -f FPS Frames per second
poetry run python -m dpongpy --mode local(only DS related aspects are discussed here)
In other words, how is the infrastructure of the system organized?
Let’s focus on information flow:
Let’s suppose a central server is coordinating the game:
Let’s suppose a central server is coordinating the game, but a broker is used to relay messages:
Like the centralised one, but the server is replicated and a consensus protocol is used to keep replicas in sync:
PongGame entity updated by the server and replicated on all clients, in a master-slave fashionPongView entity per client, to render the game state on the local screenInputHandler entity per client, to grasp local inputs and send them to the serverEventHandler entity on the server, to receive remote inputs and update the PongGame entity accordingly
or
Coordinator is the central server, coordinating the game
Terminal is the client’s end point, visualising the game
Event-Based Architecture:
Aren’t we missing something?
How should players join the game?
How should players leave the game?
Assumptions
Joining:
PLAYER_JOIN message to the coordinator upon startingLeaving (gracefully):
ESC) to quit the gamePLAYER_LEAVE message to the coordinator upon quitting
what if the coordinator is not available when the terminal starts?
what if a terminal crashes before sending the PLAYER_LEAVE message?
how could the coordinator distinguish between a crashed terminal and one sending no inputs?
what if the coordinator crashes while some terminals are still running?
what if a terminal selects a side that is already taken?
what if a terminal send inputs concerning the wrong paddle?
what if the coordinator is not available when the terminal starts?
what if a terminal crashes before sending the PLAYER_LEAVE message?
how could the coordinator distinguish between a crashed terminal and one sending no inputs?
what if the coordinator crashes while some terminals are still running?
what if a terminal selects a side that is already taken?
PLAYER_JOIN messagePLAYER_JOIN message and the terminal terminates
what if a terminal send inputs concerning the wrong paddle?
Let’s focus on the currently available implementation:
https://github.com/unibo-fc-isi-ds/dpongpy
package dpongpy
├── class PongGame // entry point for local game
├── package dpongpy.model
│ └── class Pong
├── package dpongpy.controller
│ ├── interface EventHandler
│ ├── interface InputHandler
│ ├── package dpongpy.local
│ │ ├── class PongInputHandler : InputHandler
│ │ ├── class PongEventHandler : EventHandler
│ │ └── class PongLocalController : PongInputHandler,PongEventHandler
│ └── package dpongpy.view
│ ├── interface PongView
│ ├── class ScreenPongView : PongView
│ └── class ShowNothingPongView : PongView
├── package dpongpy.remote
│ ├── class Address
│ ├── interface Session
│ ├── interface Server
│ ├── interface Client
│ ├── package dpongpy.remote.centralised
│ │ ├── class PongCoordinator : PongGame // entry point for remote game, coordinator side
│ │ └── class PongTerminal : PongGame // entry point for remote game, terminal side
│ ├── package dpongpy.remote.udp
│ │ ├── class UdpSession : Session
│ │ ├── class UdpServer : Server
│ │ └── class UdpClient : Client
│ └── package dpongpy.remote.presentation
│ ├── Serializer
│ └── Deserializer
(only the organization of classes and interfaces into packages is reported, as well as sub-typing relationships)
Give a look to the code, especially the PongCoordinator and PongTerminal classes
compare the code to the design we discussed, especially the interaction and behavioural views
W.r.t. the previous corner cases, in which situations are we?
what if the coordinator is not available when the terminal starts?
what if a terminal crashes before sending the PLAYER_LEAVE message?
what if the coordinator crashes while some terminals are still running?
what if a terminal selects a side that is already taken?
what if a terminal send inputs concerning the wrong paddle?
class Controller(PongInputHandler, EventHandler): # dpongpy/remote/centralised/__init__.py, line 114
def post_event(self, event: Event | ControlEvent, **kwargs):
event = super().post_event(event, **kwargs)
if ControlEvent.PADDLE_MOVE.matches(event): # this is how you can inject the bug
event.dict["paddle_index"] = Direction.LEFT
...
Are we prioritizing availability or consistency with the current design/implementation?
Let’s try to simulate a network partition:
UDP_DROP_RATE environment variable to a number $p \in [0, 1]$ (say, 0.2) to affect the probability of a package being dropped to $p$…
20% in our case)is the gameplay fluid or laggy?
Goal: modify the current dpongpy implementation to prioritize availability over consistency, in order to make the game fluid, even in presence of network issues
Hints: implement speculative execution on the terminal-side, to make the game appear available even when the coordinator non-responsive
Deadline: December 31st, 2025
Incentive: +1 point on the final grade (if solution is satisfying)
Submission:
dpongpy repositoryexercise-lab1[A.Y. 2025/2026 Surname, Name] Exercise: Available Distributed Pong
Compiled on: 2025-10-22 — printable version