Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding networking #3

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions examples/networking_rockpaperscisors/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Online Rock Paper Scisors

Small example of how to create a simple rock paper scisors game using the networking library

The server.py file contains the server code and the client.py file contains the client code.

First start the server and then start the client.
The client will try to connect to the server to start the game.

The content.py file implement shared content between the server and the client.
169 changes: 169 additions & 0 deletions examples/networking_rockpaperscisors/content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"""Shared content for the game."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from functools import cached_property
from typing import Any

import pygame
from pygame_emojis import load_emoji
from pygame_cards.hands import AlignedHand
from pygame_cards.manager import CardSetRights, CardsManager

import pygame_cards.events

from pygame_cards.set import CardsSet
from pygame_cards.abstract import AbstractCard, AbstractCardGraphics


class Sign(Enum):
ROCK = "rock"
PAPER = "paper"
SCISSORS = "scissors"

# Override the > method to allow comparison between the signs
def __gt__(self, other: Sign) -> bool:
if self == Sign.ROCK:
return other == Sign.SCISSORS
if self == Sign.PAPER:
return other == Sign.ROCK
if self == Sign.SCISSORS:
return other == Sign.PAPER

return False


class Player(Enum):
PLAYER1 = "player1"
PLAYER2 = "player2"


EMOJI_TO_USE: dict[Sign, str] = {
Sign.ROCK: "🪨",
Sign.PAPER: "📄",
Sign.SCISSORS: "✂️",
}


@dataclass
class RockPaperScisorsGraphics(AbstractCardGraphics):
card: RockPaperScissorsCard

@cached_property
def surface(self) -> pygame.Surface:
# Transparent background
surf = pygame.Surface(self.size, pygame.SRCALPHA, 32)

# Load the emoji as a pygame.Surface
emoji = EMOJI_TO_USE[self.card.sign]
surface = load_emoji(emoji, self.size)
# Draw the emoji
surf.blit(surface, (0, 0))

return surf


@dataclass
class RockPaperScissorsCard(AbstractCard):
sign: Sign
graphics_type = RockPaperScisorsGraphics


ROCK_PAPER_SCISSORS_CARDSET = CardsSet(
# Iterate over enum to create the cards
[RockPaperScissorsCard(sign=sign, name=sign.value) for sign in Sign]
)


class RockPaperScissors:
"""
A Rock paper scisors game.

Play plays with :meth:`play`.

Get past moves with :attr:`moves`.

"""

def __init__(self):
self.curent_moves: dict[Player, Sign] = {}
self.points: dict[Player, int] = {Player.PLAYER1: 0, Player.PLAYER2: 0}
self.points_to_win = 3

self.past_moves: list[tuple(Player, Sign)] = []

self.players = []

def play(self, player: Player, event: Any) -> Any | None:
"""player plays sign

Gets an event from a play message.

Returns a message (json serializable object),
to be broadcasted to all players or None,
if nothing should be broadcasted.

"""
if player in self.curent_moves:
raise RuntimeError("Already played, waiting for other player.")

try:
sign = Sign(event)
except ValueError:
raise RuntimeError(f"Invalid sign {event}. Accepted values are in {Sign}")

self.curent_moves[player] = sign
self.past_moves.append((player, sign))
if len(self.curent_moves) < 2:
# Wait for other player
return None

# Both players played
player1_sign = self.curent_moves[Player.PLAYER1]
player2_sign = self.curent_moves[Player.PLAYER2]

# Reset the current moves
self.curent_moves = {}

# Check who won
if player1_sign == player2_sign:
winner = None
else:
if player1_sign > player2_sign:
winner = Player.PLAYER1
else:
winner = Player.PLAYER2

self.points[winner] += 1

if self.points[winner] >= self.points_to_win:
# If move is winning, send a "win" message.
return {
"type": "win",
"player": winner.value,
}

# Send a "results" message to update the UI.
return {
"type": "results",
"winner": winner.value if winner else None,
}

def get_past_events(self) -> list[str]:
"""Returns a list of past moves as strings."""
return [f"{player.value},{sign.value}" for player, sign in self.past_moves]

def add_player(self) -> Player:
"""Add a player to the game."""

if len(self.players) >= 2:
raise RuntimeError("Already 2 players")

player = Player(f"player{len(self.players)+1}")
self.players.append(player)
return player


if __name__ == "__main__":
sing = Sign("rock")
print(sing)
103 changes: 103 additions & 0 deletions examples/networking_rockpaperscisors/pg_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Pygame example using websockets to communicate with a server."""
import json
import logging
import threading
from time import sleep
import pygame
import websocket
from content import RockPaperScissors, Player, ROCK_PAPER_SCISSORS_CARDSET
import pygame_cards.events
from pygame_cards.hands import AlignedHand
from pygame_cards.manager import CardSetRights, CardsManager


logging.basicConfig()
socket = "ws://localhost:8765/"
# websocket.enableTrace(True)

PLAYING = True

# Game to join
join_key = "cUVtaORqA0SfPjTT"
# If you are the first play
# join_key = None


# Call backs from the websocket
def on_open(ws):
if join_key is None:
ws.send(json.dumps({"type": "init"}))
else:
ws.send(json.dumps({"type": "init", "join": join_key}))
print(">>>>>>OPENED")


def on_message(ws, message):
print("Message received: ", message)


def on_close(ws, close_status_code, close_msg):
global PLAYING
PLAYING = False
print(">>>>>>CLOSED")


def on_error(ws, error):
print(error)


# pygame setup
pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()


ws = websocket.WebSocketApp(
socket, on_open=on_open, on_message=on_message, on_close=on_close, on_error=on_error
)

wst = threading.Thread(target=lambda: ws.run_forever())
wst.daemon = True
wst.start()


# Create the cardset graphics
cardset_graphics = AlignedHand(cardset=ROCK_PAPER_SCISSORS_CARDSET, card_halo_ratio=0.2)

# Create a manager and add the carset to it
manager = CardsManager()
manager.add_set(
cardset_graphics,
position=(500, 500),
card_set_rights=CardSetRights(
clickable=True, draggable_out=False, draggable_in=False
),
)
# manager.logger.setLevel(logging.DEBUG)

while PLAYING:
# poll for events
# pygame.QUIT event means the user clicked X to close your window
for event in pygame.event.get():
if event.type == pygame.QUIT:
PLAYING = False
ws.close()
if (
event.type == pygame_cards.events.CARDSSET_CLICKED
and event.card is not None
):
ws.send(json.dumps({"type": "play", "event": event.card.name}))

# fill the screen with a color to wipe away anything from last frame
screen.fill("purple")

manager.process_events(event)
time_delta = clock.tick(60) # limits FPS to 60

manager.update(time_delta)
manager.draw(screen)
# flip() the display to put your work on screen
pygame.display.flip()

pygame.quit()
wst.join()
Loading