implemented more of the game with the network
This commit is contained in:
parent
972327cdc7
commit
4e376bc009
17 changed files with 327 additions and 66 deletions
4
NOTE.md
4
NOTE.md
|
@ -5,6 +5,8 @@ A faire :
|
|||
- Faire une scène incluant par défaut les boutons "Retour" (?)
|
||||
- Police d'écriture
|
||||
|
||||
- Voir si les event listener intégré à pyglet sont plus pratique que l'event propagation
|
||||
|
||||
Bug :
|
||||
- /
|
||||
|
||||
|
@ -17,4 +19,4 @@ Autre :
|
|||
|
||||
|
||||
Bonus ultime :
|
||||
- Envoyer la texture de la grille à l'adversaire
|
||||
- Envoyer la texture de la grille à l'adversaire (???)
|
|
@ -81,13 +81,13 @@ class Board:
|
|||
if x >= self._columns or y >= self._rows: raise InvalidBombPosition(position)
|
||||
|
||||
# if this position have already been shot
|
||||
if not self._bombs[position]: raise PositionAlreadyShot(position)
|
||||
if not self._bombs[y, x]: raise PositionAlreadyShot(position)
|
||||
|
||||
# get the old board matrice
|
||||
board_mat_old_sum = self.get_matrice().sum()
|
||||
|
||||
# place the bomb (setting the position to False cause the matrice multiplication to remove the boat if any)
|
||||
self._bombs[position] = False
|
||||
self._bombs[y, x] = False
|
||||
|
||||
# get the new board matrice
|
||||
board_mat_new = self.get_matrice()
|
||||
|
|
|
@ -8,3 +8,10 @@ class BombState(Enum):
|
|||
WON = 3
|
||||
|
||||
ERROR = 10
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
return self.value.to_bytes(1, "big")
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes):
|
||||
return cls(int.from_bytes(data, "big"))
|
||||
|
|
|
@ -8,6 +8,8 @@ from source.gui import widget, texture
|
|||
from source.gui.widget.grid import GameGridAlly, GameGridEnemy
|
||||
from source import core
|
||||
from source.network.SocketType import SocketType
|
||||
from source.network.packet.Bomb import Bomb
|
||||
from source.type import Point2D
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from source.gui.window import Window
|
||||
|
@ -53,6 +55,11 @@ class Game(Scene):
|
|||
boat_batch=self.batch_grid_boat,
|
||||
)
|
||||
|
||||
def board_ally_ready():
|
||||
connection.send(SocketType.BOAT_PLACED.value.to_bytes(1, "big"))
|
||||
|
||||
self.grid_ally.add_listener("on_all_boats_placed", board_ally_ready)
|
||||
|
||||
self.grid_enemy = self.add_widget(
|
||||
GameGridEnemy,
|
||||
|
||||
|
@ -68,6 +75,12 @@ class Game(Scene):
|
|||
bomb_batch=self.batch_grid_bomb
|
||||
)
|
||||
|
||||
def board_enemy_bomb(cell: Point2D):
|
||||
connection.send(SocketType.BOMB.value.to_bytes(1, "big"))
|
||||
connection.send(Bomb(x=cell[0], y=cell[1]).to_bytes())
|
||||
|
||||
self.grid_enemy.add_listener("on_request_place_bomb", board_enemy_bomb)
|
||||
|
||||
self.name_ally = self.add_widget(
|
||||
widget.Text,
|
||||
|
||||
|
@ -119,10 +132,10 @@ class Game(Scene):
|
|||
self.chat_log = self.add_widget(
|
||||
widget.Text,
|
||||
|
||||
x=10, y=70, width=0.5,
|
||||
x=10, y=35, width=0.5,
|
||||
|
||||
text="FARAPHEL - HELLO BILLY\nLEO - HELLO BOLLO",
|
||||
anchor_x="left", anchor_y="baseline",
|
||||
text="",
|
||||
anchor_x="left",
|
||||
multiline=True,
|
||||
|
||||
batch=self.batch_label,
|
||||
|
@ -131,7 +144,7 @@ class Game(Scene):
|
|||
self.chat_input = self.add_widget(
|
||||
widget.Input,
|
||||
|
||||
x=10, y=10, width=0.5, height=50,
|
||||
x=10, y=10, width=0.5, height=30,
|
||||
|
||||
style=texture.Button.Style1,
|
||||
|
||||
|
@ -140,8 +153,14 @@ class Game(Scene):
|
|||
)
|
||||
|
||||
def send_chat():
|
||||
text = self.chat_input.text
|
||||
self.chat_input.text = ""
|
||||
|
||||
self.chat_log.text += "\n" + text
|
||||
self.chat_log.label.y = self.chat_log.y + self.chat_log.label.content_height
|
||||
|
||||
connection.send(SocketType["CHAT"].value.to_bytes(1, "big"))
|
||||
connection.send(self.chat_input.text.encode())
|
||||
connection.send(text.encode())
|
||||
|
||||
self.chat_input.add_listener("on_enter", send_chat)
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
import pyglet
|
||||
import requests
|
||||
|
||||
from source import network
|
||||
from source.gui.scene.abc import Scene
|
||||
from source.gui import widget, texture
|
||||
from source.gui.scene import RoomHost
|
||||
from source.gui.scene.abc import Scene
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from source.gui.window import Window
|
||||
|
@ -15,16 +14,9 @@ class RoomCreate(Scene):
|
|||
def __init__(self, window: "Window", **kwargs):
|
||||
super().__init__(window, **kwargs)
|
||||
|
||||
"""r = requests.get('https://api.ipify.org')
|
||||
r.raise_for_status()
|
||||
ip_address: str = r.content.decode('utf8')
|
||||
port: int = 52321"""
|
||||
|
||||
ip_address = "127.0.0.1"
|
||||
port = 52321
|
||||
|
||||
self.batch_button_background = pyglet.graphics.Batch()
|
||||
self.batch_label = pyglet.graphics.Batch()
|
||||
self.batch_input_background = pyglet.graphics.Batch()
|
||||
self.batch_button_background = pyglet.graphics.Batch()
|
||||
|
||||
self.back = self.add_widget(
|
||||
widget.Button,
|
||||
|
@ -38,39 +30,74 @@ class RoomCreate(Scene):
|
|||
label_batch=self.batch_label
|
||||
)
|
||||
|
||||
self.back.add_listener("on_click_release", self.button_back_callback)
|
||||
|
||||
self.label_ip = self.add_widget(
|
||||
widget.Text,
|
||||
|
||||
x=0.5, y=0.55,
|
||||
|
||||
anchor_x="center", anchor_y="center",
|
||||
text=f"Votre IP - {ip_address}:{port}",
|
||||
font_size=20,
|
||||
|
||||
batch=self.batch_label
|
||||
)
|
||||
|
||||
self.description = self.add_widget(
|
||||
widget.Text,
|
||||
|
||||
x=0.5, y=0.45,
|
||||
|
||||
anchor_x="center", anchor_y="center",
|
||||
text="En attente d'un second joueur...",
|
||||
|
||||
batch=self.batch_label
|
||||
)
|
||||
|
||||
self.thread = network.Host(window=self.window, daemon=True, username="Host")
|
||||
self.thread.start()
|
||||
|
||||
def button_back_callback(self, *_):
|
||||
self.thread.stop()
|
||||
from source.gui.scene import MainMenu
|
||||
self.window.set_scene(MainMenu)
|
||||
self.back.add_listener("on_click_release", lambda *_: self.window.set_scene(MainMenu))
|
||||
|
||||
self.add_widget(
|
||||
widget.Text,
|
||||
|
||||
x=0.1, y=0.9,
|
||||
anchor_x="center", anchor_y="center",
|
||||
text=f"Largeur de la grille",
|
||||
|
||||
batch=self.batch_label
|
||||
)
|
||||
|
||||
input_width = self.add_widget(
|
||||
widget.Input,
|
||||
|
||||
x=0.2, y=0.86, width=0.1, height=0.08,
|
||||
|
||||
regex=r"\d+",
|
||||
|
||||
style=texture.Input.Style1,
|
||||
|
||||
label_text="8",
|
||||
|
||||
background_batch=self.batch_input_background,
|
||||
label_batch=self.batch_label
|
||||
)
|
||||
|
||||
self.add_widget(
|
||||
widget.Text,
|
||||
|
||||
x=0.1, y=0.8,
|
||||
anchor_x="center", anchor_y="center",
|
||||
text=f"Longueur de la grille",
|
||||
|
||||
batch=self.batch_label
|
||||
)
|
||||
|
||||
input_height = self.add_widget(
|
||||
widget.Input,
|
||||
|
||||
x=0.2, y=0.76, width=0.1, height=0.08,
|
||||
|
||||
regex=r"\d+",
|
||||
|
||||
style=texture.Input.Style1,
|
||||
|
||||
label_text="8",
|
||||
|
||||
background_batch=self.batch_input_background,
|
||||
label_batch=self.batch_label
|
||||
)
|
||||
|
||||
self.start = self.add_widget(
|
||||
widget.Button,
|
||||
x=lambda widget: widget.scene.window.width - 20 - widget.width, y=20, width=0.2, height=0.1,
|
||||
|
||||
label_text="Continuer",
|
||||
|
||||
style=texture.Button.Style1,
|
||||
|
||||
background_batch=self.batch_button_background,
|
||||
label_batch=self.batch_label
|
||||
)
|
||||
|
||||
self.start.add_listener("on_click_release", lambda *_: self.window.set_scene(RoomHost))
|
||||
|
||||
def on_draw(self):
|
||||
self.batch_input_background.draw()
|
||||
self.batch_button_background.draw()
|
||||
self.batch_label.draw()
|
||||
|
|
76
source/gui/scene/RoomHost.py
Normal file
76
source/gui/scene/RoomHost.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
import pyglet
|
||||
import requests
|
||||
|
||||
from source import network
|
||||
from source.gui.scene.abc import Scene
|
||||
from source.gui import widget, texture
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from source.gui.window import Window
|
||||
|
||||
|
||||
class RoomHost(Scene):
|
||||
def __init__(self, window: "Window", **kwargs):
|
||||
super().__init__(window, **kwargs)
|
||||
|
||||
"""r = requests.get('https://api.ipify.org')
|
||||
r.raise_for_status()
|
||||
ip_address: str = r.content.decode('utf8')
|
||||
port: int = 52321"""
|
||||
|
||||
ip_address = "127.0.0.1"
|
||||
port = 52321
|
||||
|
||||
self.batch_button_background = pyglet.graphics.Batch()
|
||||
self.batch_label = pyglet.graphics.Batch()
|
||||
|
||||
self.back = self.add_widget(
|
||||
widget.Button,
|
||||
x=20, y=20, width=0.2, height=0.1,
|
||||
|
||||
label_text="Retour",
|
||||
|
||||
style=texture.Button.Style1,
|
||||
|
||||
background_batch=self.batch_button_background,
|
||||
label_batch=self.batch_label
|
||||
)
|
||||
|
||||
self.back.add_listener("on_click_release", self.button_back_callback)
|
||||
|
||||
self.label_ip = self.add_widget(
|
||||
widget.Text,
|
||||
|
||||
x=0.5, y=0.55,
|
||||
|
||||
anchor_x="center", anchor_y="center",
|
||||
text=f"Votre IP - {ip_address}:{port}",
|
||||
font_size=20,
|
||||
|
||||
batch=self.batch_label
|
||||
)
|
||||
|
||||
self.description = self.add_widget(
|
||||
widget.Text,
|
||||
|
||||
x=0.5, y=0.45,
|
||||
|
||||
anchor_x="center", anchor_y="center",
|
||||
text="En attente d'un second joueur...",
|
||||
|
||||
batch=self.batch_label
|
||||
)
|
||||
|
||||
self.thread = network.Host(window=self.window, daemon=True, username="Host")
|
||||
self.thread.start()
|
||||
|
||||
def button_back_callback(self, *_):
|
||||
self.thread.stop()
|
||||
from source.gui.scene import MainMenu
|
||||
self.window.set_scene(MainMenu)
|
||||
|
||||
def on_draw(self):
|
||||
self.batch_button_background.draw()
|
||||
self.batch_label.draw()
|
|
@ -1,6 +1,7 @@
|
|||
from .Game import Game
|
||||
from .Settings import Settings
|
||||
from .RoomCreate import RoomCreate
|
||||
from .RoomHost import RoomHost
|
||||
from .RoomJoin import RoomJoin
|
||||
from .RoomCreate import RoomCreate
|
||||
|
||||
from .MainMenu import MainMenu
|
||||
|
|
|
@ -20,8 +20,8 @@ class Grid:
|
|||
class Bomb:
|
||||
class Style1(Style):
|
||||
_animation = sorted(
|
||||
(path_bomb / "animation").iterdir(),
|
||||
key=lambda path: int(path.stem)
|
||||
(path_bomb / "animation").iterdir(),
|
||||
key=lambda path: int(path.stem)
|
||||
)
|
||||
|
||||
missed = [*_animation, path_bomb / "missed.png"], 0.03, False
|
||||
|
|
|
@ -32,6 +32,14 @@ class Text(BoxWidget):
|
|||
|
||||
self._refresh_size()
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self.label.text
|
||||
|
||||
@text.setter
|
||||
def text(self, text: str):
|
||||
self.label.text = text
|
||||
|
||||
def _refresh_size(self):
|
||||
self.label.x, self.label.y = self.xy
|
||||
self.label.width, self.label.height = self.size
|
||||
|
|
|
@ -6,7 +6,6 @@ import numpy as np
|
|||
|
||||
from source.core.enums import Orientation
|
||||
from source.core.error import InvalidBoatPosition
|
||||
from source.gui import texture
|
||||
from source.gui.sprite import Sprite
|
||||
from source.gui.texture.abc import Style
|
||||
from source.gui.widget.grid.abc import GameGrid
|
||||
|
@ -126,6 +125,8 @@ class GameGridAlly(GameGrid):
|
|||
|
||||
else: # if the boat have been placed
|
||||
self.boats_length.pop(0) # remove the boat from the list of boat to place
|
||||
if len(self.boats_length) == 0:
|
||||
self.trigger_event("on_all_boats_placed")
|
||||
|
||||
self.display_board(self.board)
|
||||
|
||||
|
|
|
@ -40,11 +40,9 @@ class GameGridEnemy(GameGrid):
|
|||
sprite.width = self.cell_width
|
||||
sprite.height = self.cell_height
|
||||
|
||||
def place_bomb(self, cell: Point2D):
|
||||
from random import randint
|
||||
|
||||
def place_bomb(self, cell: Point2D, touched: bool):
|
||||
self.cell_sprites[cell] = Sprite(
|
||||
img=self.bomb_style.get("touched" if randint(0, 1) else "missed"),
|
||||
img=self.bomb_style.get("touched" if touched else "missed"),
|
||||
**self._bomb_kwargs
|
||||
)
|
||||
|
||||
|
@ -54,7 +52,7 @@ class GameGridEnemy(GameGrid):
|
|||
cell = self.get_cell_from_rel(rel_x, rel_y)
|
||||
|
||||
if button == pyglet.window.mouse.LEFT:
|
||||
self.place_bomb(cell)
|
||||
self.trigger_event("on_request_place_bomb", cell)
|
||||
|
||||
def draw(self):
|
||||
self.background.draw()
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import socket
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pyglet.clock
|
||||
|
||||
from source.core.enums import BombState
|
||||
from source.core.error import PositionAlreadyShot, InvalidBombPosition
|
||||
from source.gui import scene
|
||||
from source.network.SocketType import SocketType
|
||||
from source.network.packet.Bomb import Bomb
|
||||
from source.network.packet.PacketBombState import PacketBombState
|
||||
from source.utils import StoppableThread
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -29,7 +34,13 @@ class Client(StoppableThread):
|
|||
|
||||
print(f"[Client] Connecté avec {connection}")
|
||||
|
||||
pyglet.clock.schedule_once(lambda dt: self.window.set_scene(scene.Game, connection=connection), 0)
|
||||
def create_game_scene(dt: float, queue: Queue):
|
||||
game_scene = self.window.set_scene(scene.Game, connection=connection)
|
||||
queue.put(game_scene)
|
||||
|
||||
queue = Queue()
|
||||
pyglet.clock.schedule_once(create_game_scene, 0, queue)
|
||||
game_scene = queue.get()
|
||||
|
||||
while True:
|
||||
data = None
|
||||
|
@ -43,7 +54,31 @@ class Client(StoppableThread):
|
|||
|
||||
socket_type = SocketType(int.from_bytes(data, "big"))
|
||||
|
||||
print(socket_type)
|
||||
|
||||
match socket_type:
|
||||
case SocketType.CHAT: print(connection.recv(1024).decode())
|
||||
case SocketType.BOAT_PLACED: print("adversaire à posé ses bateaux")
|
||||
case SocketType.BOMB:
|
||||
bomb = Bomb.from_bytes(connection.recv(2))
|
||||
|
||||
try: bomb_state = game_scene.grid_ally.board.bomb((bomb.x, bomb.y))
|
||||
except (InvalidBombPosition, PositionAlreadyShot): pass # TODO: gérer les erreurs
|
||||
|
||||
connection.send(SocketType.BOMB_STATE.value.to_bytes(1, "big"))
|
||||
|
||||
packet_bomb_state = PacketBombState(
|
||||
x=bomb.x,
|
||||
y=bomb.y,
|
||||
bomb_state=bomb_state
|
||||
)
|
||||
connection.send(packet_bomb_state.to_bytes())
|
||||
|
||||
case SocketType.BOMB_STATE:
|
||||
packet_bomb_state = PacketBombState.from_bytes(connection.recv(3))
|
||||
|
||||
touched = packet_bomb_state.bomb_state in [BombState.TOUCHED, BombState.SUNKEN, BombState.WON]
|
||||
|
||||
pyglet.clock.schedule_once(
|
||||
lambda dt: game_scene.grid_enemy.place_bomb((packet_bomb_state.x, packet_bomb_state.y),
|
||||
touched),
|
||||
0
|
||||
)
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import socket
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pyglet
|
||||
|
||||
from source.core.enums import BombState
|
||||
from source.core.error import InvalidBombPosition, PositionAlreadyShot
|
||||
from source.gui import scene
|
||||
from source.network.SocketType import SocketType
|
||||
from source.network.packet.Bomb import Bomb
|
||||
from source.network.packet.PacketBombState import PacketBombState
|
||||
from source.utils import StoppableThread
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -38,7 +43,13 @@ class Host(StoppableThread):
|
|||
|
||||
print(f"[Serveur] Connecté avec {address}")
|
||||
|
||||
pyglet.clock.schedule_once(lambda dt: self.window.set_scene(scene.Game, connection=connection), 0)
|
||||
def create_game_scene(dt: float, queue: Queue):
|
||||
game_scene = self.window.set_scene(scene.Game, connection=connection)
|
||||
queue.put(game_scene)
|
||||
|
||||
queue = Queue()
|
||||
pyglet.clock.schedule_once(create_game_scene, 0, queue)
|
||||
game_scene = queue.get()
|
||||
|
||||
while True:
|
||||
data = None
|
||||
|
@ -52,7 +63,31 @@ class Host(StoppableThread):
|
|||
|
||||
socket_type = SocketType(int.from_bytes(data, "big"))
|
||||
|
||||
print(socket_type)
|
||||
|
||||
match socket_type:
|
||||
case SocketType.CHAT: print(connection.recv(1024).decode())
|
||||
case SocketType.BOAT_PLACED: print("adversaire à posé ses bateaux")
|
||||
case SocketType.BOMB:
|
||||
bomb = Bomb.from_bytes(connection.recv(2))
|
||||
|
||||
try: bomb_state = game_scene.grid_ally.board.bomb((bomb.x, bomb.y))
|
||||
except (InvalidBombPosition, PositionAlreadyShot): pass # TODO: gérer les erreurs
|
||||
|
||||
connection.send(SocketType.BOMB_STATE.value.to_bytes(1, "big"))
|
||||
|
||||
packet_bomb_state = PacketBombState(
|
||||
x=bomb.x,
|
||||
y=bomb.y,
|
||||
bomb_state=bomb_state
|
||||
)
|
||||
connection.send(packet_bomb_state.to_bytes())
|
||||
|
||||
case SocketType.BOMB_STATE:
|
||||
packet_bomb_state = PacketBombState.from_bytes(connection.recv(3))
|
||||
|
||||
touched = packet_bomb_state.bomb_state in [BombState.TOUCHED, BombState.SUNKEN, BombState.WON]
|
||||
|
||||
pyglet.clock.schedule_once(
|
||||
lambda dt: game_scene.grid_enemy.place_bomb((packet_bomb_state.x, packet_bomb_state.y),
|
||||
touched),
|
||||
0
|
||||
)
|
||||
|
|
|
@ -5,3 +5,4 @@ class SocketType(Enum):
|
|||
CHAT = 0
|
||||
BOAT_PLACED = 1
|
||||
BOMB = 2
|
||||
BOMB_STATE = 3
|
||||
|
|
20
source/network/packet/Bomb.py
Normal file
20
source/network/packet/Bomb.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class Bomb:
|
||||
x: int = field()
|
||||
y: int = field()
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
return (
|
||||
self.x.to_bytes(1, "big") +
|
||||
self.y.to_bytes(1, "big")
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes):
|
||||
return cls(
|
||||
x=int.from_bytes(data[0:1], "big"),
|
||||
y=int.from_bytes(data[1:2], "big"),
|
||||
)
|
6
source/network/packet/Chat.py
Normal file
6
source/network/packet/Chat.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class Chat:
|
||||
message: str = field()
|
25
source/network/packet/PacketBombState.py
Normal file
25
source/network/packet/PacketBombState.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from dataclasses import dataclass, field
|
||||
|
||||
from source.core.enums import BombState
|
||||
|
||||
|
||||
@dataclass
|
||||
class PacketBombState:
|
||||
x: int = field()
|
||||
y: int = field()
|
||||
bomb_state: BombState = field()
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
return (
|
||||
self.x.to_bytes(1, "big") +
|
||||
self.y.to_bytes(1, "big") +
|
||||
self.bomb_state.value.to_bytes()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes):
|
||||
return cls(
|
||||
x=int.from_bytes(data[0:1], "big"),
|
||||
y=int.from_bytes(data[1:2], "big"),
|
||||
bomb_state=BombState.from_bytes(data[2:3])
|
||||
)
|
Loading…
Reference in a new issue