implemented more of the game with the network

This commit is contained in:
Faraphel 2023-02-22 15:50:45 +01:00
parent 972327cdc7
commit 4e376bc009
17 changed files with 327 additions and 66 deletions

View file

@ -5,6 +5,8 @@ A faire :
- Faire une scène incluant par défaut les boutons "Retour" (?) - Faire une scène incluant par défaut les boutons "Retour" (?)
- Police d'écriture - Police d'écriture
- Voir si les event listener intégré à pyglet sont plus pratique que l'event propagation
Bug : Bug :
- / - /
@ -17,4 +19,4 @@ Autre :
Bonus ultime : Bonus ultime :
- Envoyer la texture de la grille à l'adversaire - Envoyer la texture de la grille à l'adversaire (???)

View file

@ -81,13 +81,13 @@ class Board:
if x >= self._columns or y >= self._rows: raise InvalidBombPosition(position) if x >= self._columns or y >= self._rows: raise InvalidBombPosition(position)
# if this position have already been shot # 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 # get the old board matrice
board_mat_old_sum = self.get_matrice().sum() 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) # 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 # get the new board matrice
board_mat_new = self.get_matrice() board_mat_new = self.get_matrice()

View file

@ -8,3 +8,10 @@ class BombState(Enum):
WON = 3 WON = 3
ERROR = 10 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"))

View file

@ -8,6 +8,8 @@ from source.gui import widget, texture
from source.gui.widget.grid import GameGridAlly, GameGridEnemy from source.gui.widget.grid import GameGridAlly, GameGridEnemy
from source import core from source import core
from source.network.SocketType import SocketType from source.network.SocketType import SocketType
from source.network.packet.Bomb import Bomb
from source.type import Point2D
if TYPE_CHECKING: if TYPE_CHECKING:
from source.gui.window import Window from source.gui.window import Window
@ -53,6 +55,11 @@ class Game(Scene):
boat_batch=self.batch_grid_boat, 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( self.grid_enemy = self.add_widget(
GameGridEnemy, GameGridEnemy,
@ -68,6 +75,12 @@ class Game(Scene):
bomb_batch=self.batch_grid_bomb 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( self.name_ally = self.add_widget(
widget.Text, widget.Text,
@ -119,10 +132,10 @@ class Game(Scene):
self.chat_log = self.add_widget( self.chat_log = self.add_widget(
widget.Text, widget.Text,
x=10, y=70, width=0.5, x=10, y=35, width=0.5,
text="FARAPHEL - HELLO BILLY\nLEO - HELLO BOLLO", text="",
anchor_x="left", anchor_y="baseline", anchor_x="left",
multiline=True, multiline=True,
batch=self.batch_label, batch=self.batch_label,
@ -131,7 +144,7 @@ class Game(Scene):
self.chat_input = self.add_widget( self.chat_input = self.add_widget(
widget.Input, widget.Input,
x=10, y=10, width=0.5, height=50, x=10, y=10, width=0.5, height=30,
style=texture.Button.Style1, style=texture.Button.Style1,
@ -140,8 +153,14 @@ class Game(Scene):
) )
def send_chat(): 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(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) self.chat_input.add_listener("on_enter", send_chat)

View file

@ -1,11 +1,10 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import pyglet import pyglet
import requests
from source import network
from source.gui.scene.abc import Scene
from source.gui import widget, texture from source.gui import widget, texture
from source.gui.scene import RoomHost
from source.gui.scene.abc import Scene
if TYPE_CHECKING: if TYPE_CHECKING:
from source.gui.window import Window from source.gui.window import Window
@ -15,16 +14,9 @@ class RoomCreate(Scene):
def __init__(self, window: "Window", **kwargs): def __init__(self, window: "Window", **kwargs):
super().__init__(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_label = pyglet.graphics.Batch()
self.batch_input_background = pyglet.graphics.Batch()
self.batch_button_background = pyglet.graphics.Batch()
self.back = self.add_widget( self.back = self.add_widget(
widget.Button, widget.Button,
@ -38,39 +30,74 @@ class RoomCreate(Scene):
label_batch=self.batch_label 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 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): def on_draw(self):
self.batch_input_background.draw()
self.batch_button_background.draw() self.batch_button_background.draw()
self.batch_label.draw() self.batch_label.draw()

View 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()

View file

@ -1,6 +1,7 @@
from .Game import Game from .Game import Game
from .Settings import Settings from .Settings import Settings
from .RoomCreate import RoomCreate from .RoomHost import RoomHost
from .RoomJoin import RoomJoin from .RoomJoin import RoomJoin
from .RoomCreate import RoomCreate
from .MainMenu import MainMenu from .MainMenu import MainMenu

View file

@ -32,6 +32,14 @@ class Text(BoxWidget):
self._refresh_size() 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): def _refresh_size(self):
self.label.x, self.label.y = self.xy self.label.x, self.label.y = self.xy
self.label.width, self.label.height = self.size self.label.width, self.label.height = self.size

View file

@ -6,7 +6,6 @@ import numpy as np
from source.core.enums import Orientation from source.core.enums import Orientation
from source.core.error import InvalidBoatPosition from source.core.error import InvalidBoatPosition
from source.gui import texture
from source.gui.sprite import Sprite from source.gui.sprite import Sprite
from source.gui.texture.abc import Style from source.gui.texture.abc import Style
from source.gui.widget.grid.abc import GameGrid from source.gui.widget.grid.abc import GameGrid
@ -126,6 +125,8 @@ class GameGridAlly(GameGrid):
else: # if the boat have been placed else: # if the boat have been placed
self.boats_length.pop(0) # remove the boat from the list of boat to place 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) self.display_board(self.board)

View file

@ -40,11 +40,9 @@ class GameGridEnemy(GameGrid):
sprite.width = self.cell_width sprite.width = self.cell_width
sprite.height = self.cell_height sprite.height = self.cell_height
def place_bomb(self, cell: Point2D): def place_bomb(self, cell: Point2D, touched: bool):
from random import randint
self.cell_sprites[cell] = Sprite( 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 **self._bomb_kwargs
) )
@ -54,7 +52,7 @@ class GameGridEnemy(GameGrid):
cell = self.get_cell_from_rel(rel_x, rel_y) cell = self.get_cell_from_rel(rel_x, rel_y)
if button == pyglet.window.mouse.LEFT: if button == pyglet.window.mouse.LEFT:
self.place_bomb(cell) self.trigger_event("on_request_place_bomb", cell)
def draw(self): def draw(self):
self.background.draw() self.background.draw()

View file

@ -1,10 +1,15 @@
import socket import socket
from queue import Queue
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import pyglet.clock import pyglet.clock
from source.core.enums import BombState
from source.core.error import PositionAlreadyShot, InvalidBombPosition
from source.gui import scene from source.gui import scene
from source.network.SocketType import SocketType 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 from source.utils import StoppableThread
if TYPE_CHECKING: if TYPE_CHECKING:
@ -29,7 +34,13 @@ class Client(StoppableThread):
print(f"[Client] Connecté avec {connection}") 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: while True:
data = None data = None
@ -43,7 +54,31 @@ class Client(StoppableThread):
socket_type = SocketType(int.from_bytes(data, "big")) socket_type = SocketType(int.from_bytes(data, "big"))
print(socket_type)
match socket_type: match socket_type:
case SocketType.CHAT: print(connection.recv(1024).decode()) 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
)

View file

@ -1,10 +1,15 @@
import socket import socket
from queue import Queue
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import pyglet import pyglet
from source.core.enums import BombState
from source.core.error import InvalidBombPosition, PositionAlreadyShot
from source.gui import scene from source.gui import scene
from source.network.SocketType import SocketType 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 from source.utils import StoppableThread
if TYPE_CHECKING: if TYPE_CHECKING:
@ -38,7 +43,13 @@ class Host(StoppableThread):
print(f"[Serveur] Connecté avec {address}") 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: while True:
data = None data = None
@ -52,7 +63,31 @@ class Host(StoppableThread):
socket_type = SocketType(int.from_bytes(data, "big")) socket_type = SocketType(int.from_bytes(data, "big"))
print(socket_type)
match socket_type: match socket_type:
case SocketType.CHAT: print(connection.recv(1024).decode()) 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
)

View file

@ -5,3 +5,4 @@ class SocketType(Enum):
CHAT = 0 CHAT = 0
BOAT_PLACED = 1 BOAT_PLACED = 1
BOMB = 2 BOMB = 2
BOMB_STATE = 3

View 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"),
)

View file

@ -0,0 +1,6 @@
from dataclasses import dataclass, field
@dataclass
class Chat:
message: str = field()

View 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])
)