wip: add stack and piece communication
This commit is contained in:
@@ -48,10 +48,8 @@ position:
|
||||
score-value-player-2: [540, 40]
|
||||
lines-label-player-1: [100, 540]
|
||||
lines-label-player-2: [500, 540]
|
||||
next-label-player-1: [340, 120]
|
||||
next-label-player-1: [360, 120]
|
||||
next-piece-player-1: [360, 160]
|
||||
next-label-player-2: [340, 420]
|
||||
next-piece-player-2: [360, 460]
|
||||
spawn-piece-player-1: [-200, -60]
|
||||
spawn-piece-player-2: [200, -360]
|
||||
|
||||
|
||||
@@ -148,11 +148,13 @@ class Piece(Entity):
|
||||
self._applying_drop_delay = not self._applying_lock_delay
|
||||
|
||||
|
||||
def draw(self, surface: pygame.Surface, well: Well = None, stack: "Stack" = None) -> None:
|
||||
def draw(self, surface: pygame.Surface, well: Well = None, stack: "Stack" = None, ghost_piece_off: bool = False) -> None:
|
||||
# ghost piece
|
||||
if well and stack:
|
||||
for square in self._get_ghost_piece_points(well, stack):
|
||||
pygame.draw.polygon(surface, pygame.Color(self._ghost_piece_color), square, max(self._tile_size // 6, 1)) # TODO add white to the yaml
|
||||
if well and stack and not ghost_piece_off:
|
||||
ghost_piece_points = self._get_ghost_piece_points(well, stack)
|
||||
if ghost_piece_points is not None:
|
||||
for square in ghost_piece_points:
|
||||
pygame.draw.polygon(surface, pygame.Color(self._ghost_piece_color), square, max(self._tile_size // 6, 1)) # TODO add white to the yaml
|
||||
|
||||
super().draw(surface)
|
||||
|
||||
@@ -313,6 +315,7 @@ class Piece(Entity):
|
||||
|
||||
def _get_ghost_piece_points(self, well: Well, stack: "Stack") -> List:
|
||||
current_points = copy.deepcopy(self._points)
|
||||
prior_points = None
|
||||
while not well._collide(current_points) and not stack._collide(current_points):
|
||||
prior_points = copy.deepcopy(current_points)
|
||||
for square in current_points:
|
||||
@@ -369,7 +372,7 @@ class Stack(Entity):
|
||||
|
||||
def add_piece(self, piece: Piece) -> None:
|
||||
self._points += piece._points
|
||||
self._square_designs += [_SquareDesign(piece._color, piece._inner_border_color, piece._border_color) for _ in range(len(piece._points))]
|
||||
self._square_designs += [SquareDesign(piece._color, piece._inner_border_color, piece._border_color) for _ in range(len(piece._points))]
|
||||
|
||||
# TODO refactor into multiple functions
|
||||
def _complete_rows(self) -> int:
|
||||
@@ -417,7 +420,7 @@ class Stack(Entity):
|
||||
"""
|
||||
TODO description
|
||||
"""
|
||||
class _SquareDesign:
|
||||
class SquareDesign:
|
||||
def __init__(self, base_color: str, inner_color: str, outer_color: str):
|
||||
self.base_color = base_color
|
||||
self.inner_color = inner_color
|
||||
@@ -428,17 +431,20 @@ class _SquareDesign:
|
||||
"""
|
||||
class PieceGenerator:
|
||||
|
||||
_bucket_one = []
|
||||
_bucket_two = []
|
||||
_bucket = []
|
||||
|
||||
@classmethod
|
||||
def get_piece(cls, position: Tuple, is_player_two: bool = False) -> Piece:
|
||||
bucket = cls._bucket_one if not is_player_two else cls._bucket_two
|
||||
if len(bucket) == 0:
|
||||
cls._generate_bucket(bucket)
|
||||
def get_piece(cls, position: Tuple) -> Piece:
|
||||
if len(cls._bucket) == 0:
|
||||
cls._generate_bucket(cls._bucket)
|
||||
|
||||
base_color, inner_border_color, outer_border_color = cls._get_piece_color(is_player_two)
|
||||
return Piece(cls._get_piece_shape(bucket.pop()), position, base_color, inner_border_color, outer_border_color)
|
||||
base_color, inner_border_color, outer_border_color = cls._get_piece_color()
|
||||
return Piece(cls._get_piece_shape(cls._bucket.pop()), position, base_color, inner_border_color, outer_border_color)
|
||||
|
||||
@classmethod
|
||||
def get_opponent_piece(cls) -> Piece:
|
||||
base_color, inner_border_color, outer_border_color = cls._get_piece_color(True)
|
||||
return Piece(Piece.Z_SHAPE, (-250, -250), base_color, inner_border_color, outer_border_color)
|
||||
|
||||
@classmethod
|
||||
def _generate_bucket(cls, bucket: List) -> None:
|
||||
@@ -467,7 +473,7 @@ class PieceGenerator:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _get_piece_color(cls, is_player_two: bool) -> Tuple:
|
||||
def _get_piece_color(cls, is_player_two: bool = False) -> Tuple:
|
||||
random_number = random.randint(1, 3)
|
||||
player_mod = "player-1" if not is_player_two else "player-2"
|
||||
|
||||
|
||||
127
tetri5/online.py
127
tetri5/online.py
@@ -3,18 +3,22 @@ import websockets
|
||||
import json
|
||||
import queue # refer to https://docs.python.org/3/library/queue.html for cross threading communication
|
||||
import uuid
|
||||
from typing import Dict
|
||||
from typing import Dict, List
|
||||
from threading import Thread
|
||||
|
||||
class MultiplayerService():
|
||||
_thread = None
|
||||
_receive_piece_queue = queue.Queue()
|
||||
_send_piece_queue = queue.Queue()
|
||||
_receive_message_queue = queue.Queue()
|
||||
_send_message_queue = queue.Queue()
|
||||
_current_game_id = None
|
||||
_client_id = str(uuid.uuid4())
|
||||
|
||||
""" QUEUES """
|
||||
_receive_piece_queue = queue.Queue()
|
||||
_send_piece_queue = queue.Queue()
|
||||
_receive_stack_queue = queue.Queue()
|
||||
_send_stack_queue = queue.Queue()
|
||||
_receive_message_queue = queue.Queue()
|
||||
_send_message_queue = queue.Queue()
|
||||
|
||||
WAIT_FOR_OPPONENT = "wait_for_opponent"
|
||||
START_GAME = "start_game"
|
||||
|
||||
@@ -24,19 +28,20 @@ class MultiplayerService():
|
||||
args=(_NetworkConnectionService.init(),))
|
||||
thread.start()
|
||||
|
||||
@classmethod
|
||||
def enter_game(cls, game_id: str) -> None:
|
||||
cls._current_game_id = game_id
|
||||
_NetworkConnectionService._join_game = True # TODO: change this to a function
|
||||
|
||||
""" SEND """
|
||||
@classmethod
|
||||
def send_piece(cls, piece: "PieceDto") -> None:
|
||||
cls._send_piece_queue.put(piece)
|
||||
|
||||
@classmethod
|
||||
def send_stack(cls, stack: "StackDto") -> None:
|
||||
cls._send_stack_queue.put(stack)
|
||||
|
||||
@classmethod
|
||||
def send_message(cls, message: str) -> None:
|
||||
cls._send_message_queue.put(message)
|
||||
|
||||
""" RECEIVE """
|
||||
@classmethod
|
||||
def try_receive_piece(cls) -> "PieceDto":
|
||||
if cls._receive_piece_queue.empty():
|
||||
@@ -46,6 +51,15 @@ class MultiplayerService():
|
||||
cls._receive_piece_queue.task_done()
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def try_receive_stack(cls) -> "StackDto":
|
||||
if cls._receive_stack_queue.empty():
|
||||
return None
|
||||
|
||||
result = cls._receive_stack_queue.get()
|
||||
cls._receive_stack_queue.task_done()
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def try_receive_message(cls) -> str:
|
||||
if cls._receive_message_queue.empty():
|
||||
@@ -55,6 +69,12 @@ class MultiplayerService():
|
||||
cls._receive_message_queue.task_done()
|
||||
return result
|
||||
|
||||
""" MISC """
|
||||
@classmethod
|
||||
def enter_game(cls, game_id: str) -> None:
|
||||
cls._current_game_id = game_id
|
||||
_NetworkConnectionService._join_game = True # TODO: change this to a function
|
||||
|
||||
@classmethod
|
||||
def quit(cls) -> None:
|
||||
_NetworkConnectionService.close_connection()
|
||||
@@ -70,26 +90,8 @@ class _NetworkConnectionService():
|
||||
async def init(cls) -> None:
|
||||
await cls._connect_to_server()
|
||||
await cls._run_network_loop()
|
||||
|
||||
@classmethod
|
||||
def close_connection(cls) -> None:
|
||||
cls._is_closed = True
|
||||
|
||||
@classmethod
|
||||
async def _run_network_loop(cls) -> None:
|
||||
while True:
|
||||
await asyncio.sleep(16e-3) # TODO add clock tick instead
|
||||
|
||||
await cls._try_enter_game()
|
||||
await cls._try_send_piece()
|
||||
await cls._try_send_message()
|
||||
await cls._try_receive_message()
|
||||
|
||||
# if conection is closed, exit loop
|
||||
if cls._is_closed:
|
||||
await cls._websocket.close()
|
||||
break
|
||||
|
||||
""" NETWORK SEND """
|
||||
@classmethod
|
||||
async def _try_enter_game(cls) -> None:
|
||||
if not cls._join_game:
|
||||
@@ -119,6 +121,24 @@ class _NetworkConnectionService():
|
||||
await cls._websocket.send(json_message)
|
||||
MultiplayerService._send_piece_queue.task_done()
|
||||
|
||||
@classmethod
|
||||
async def _try_send_stack(cls) -> None:
|
||||
# if no messages to proccess, return
|
||||
if MultiplayerService._send_stack_queue.empty():
|
||||
return
|
||||
|
||||
# get next piece to send and send to server
|
||||
stack = MultiplayerService._send_stack_queue.get()
|
||||
|
||||
# construct json message
|
||||
json_message = json.dumps({"action": "send_stack",\
|
||||
"clientId": MultiplayerService._client_id,\
|
||||
"gameId": MultiplayerService._current_game_id,\
|
||||
"stack": stack.__dict__})
|
||||
|
||||
await cls._websocket.send(json_message)
|
||||
MultiplayerService._send_stack_queue.task_done()
|
||||
|
||||
@classmethod
|
||||
async def _try_send_message(cls) -> None:
|
||||
# if no messages to proccess, return
|
||||
@@ -131,11 +151,13 @@ class _NetworkConnectionService():
|
||||
await cls._websocket.send(message)
|
||||
MultiplayerService._send_message_queue.task_done()
|
||||
|
||||
""" NETWORK RECEIVE """
|
||||
# todo refactor
|
||||
@classmethod
|
||||
async def _try_receive_message(cls) -> None:
|
||||
try:
|
||||
task = cls._pending_receive_task or asyncio.create_task(cls._websocket.recv())
|
||||
done, pending = await asyncio.wait({task}, timeout=8e-3) # TODO experiment with the timeout
|
||||
done, pending = await asyncio.wait({task}, timeout=4e-3) # TODO experiment with the timeout
|
||||
|
||||
if task in done:
|
||||
json_str = await task
|
||||
@@ -145,15 +167,14 @@ class _NetworkConnectionService():
|
||||
return
|
||||
|
||||
data = json.loads(json_str)
|
||||
print(data)
|
||||
|
||||
if data["type"] == "wait_for_opponent":
|
||||
MultiplayerService._receive_message_queue.put(MultiplayerService.WAIT_FOR_OPPONENT)
|
||||
if data["type"] == "start_game":
|
||||
MultiplayerService._receive_message_queue.put(MultiplayerService.START_GAME)
|
||||
if data["type"] == "receive_piece":
|
||||
print("Receive a piece!")
|
||||
MultiplayerService._receive_piece_queue.put(PieceDto.create(data["piece"]))
|
||||
if data["type"] == "receive_stack":
|
||||
MultiplayerService._receive_stack_queue.put(StackDto.create(data["stack"]))
|
||||
if data["type"] == "exit_game":
|
||||
print("Exit the game!")
|
||||
cls.close_connection()
|
||||
@@ -162,7 +183,24 @@ class _NetworkConnectionService():
|
||||
elif len(pending):
|
||||
cls._pending_receive_task = pending.pop()
|
||||
finally:
|
||||
pass # TODO handle connection closed exception
|
||||
pass # TODO handle connection closed exception and attempt to reconnect
|
||||
|
||||
""" MISC """
|
||||
@classmethod
|
||||
async def _run_network_loop(cls) -> None:
|
||||
while True:
|
||||
await asyncio.sleep(2e-3) # TODO add clock tick instead
|
||||
|
||||
await cls._try_enter_game()
|
||||
await cls._try_send_piece()
|
||||
await cls._try_send_stack()
|
||||
await cls._try_send_message()
|
||||
await cls._try_receive_message()
|
||||
|
||||
# if conection is closed, exit loop
|
||||
if cls._is_closed:
|
||||
await cls._websocket.close()
|
||||
break
|
||||
|
||||
@classmethod
|
||||
async def _connect_to_server(cls) -> None:
|
||||
@@ -172,14 +210,25 @@ class _NetworkConnectionService():
|
||||
# https://stackoverflow.com/a/58993145/11512104
|
||||
print("Connected to server...") # TODO replace with logging
|
||||
|
||||
@classmethod
|
||||
def close_connection(cls) -> None:
|
||||
cls._is_closed = True
|
||||
|
||||
# DTOs
|
||||
|
||||
class PieceDto():
|
||||
def __init__(self, x: int, y: int, type_: str) -> None:
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.type = type_
|
||||
def __init__(self, points: List, center: List) -> None:
|
||||
self.points = points
|
||||
self.center = center
|
||||
|
||||
@staticmethod
|
||||
def create(data: Dict) -> "PieceDto":
|
||||
return PieceDto(data["x"], data["y"], data["type"])
|
||||
return PieceDto(data["points"], data["center"])
|
||||
|
||||
class StackDto():
|
||||
def __init__(self, points: List) -> None:
|
||||
self.points = points
|
||||
|
||||
@staticmethod
|
||||
def create(data: Dict) -> "StackDto":
|
||||
return StackDto(data["points"])
|
||||
@@ -5,10 +5,10 @@ from tetri5.util import ConfigurationManager
|
||||
from tetri5.util import TextGenerator
|
||||
from tetri5.util import Controller
|
||||
from tetri5.util import SoundManager
|
||||
from tetri5.entity import Well
|
||||
from tetri5.entity import SquareDesign, Well
|
||||
from tetri5.entity import Stack
|
||||
from tetri5.entity import PieceGenerator
|
||||
from tetri5.online import MultiplayerService
|
||||
from tetri5.online import *
|
||||
|
||||
"""
|
||||
TODO
|
||||
@@ -292,22 +292,18 @@ class MultiplayerScene(Scene):
|
||||
|
||||
# next positions
|
||||
self._next_label_player_one_pos = ConfigurationManager.get("position", "next-label-player-1")
|
||||
self._next_label_player_two_pos = ConfigurationManager.get("position", "next-label-player-2")
|
||||
|
||||
# piece positions
|
||||
self._next_piece_player_one_pos = ConfigurationManager.get("position", "next-piece-player-1")
|
||||
self._next_piece_player_two_pos = ConfigurationManager.get("position", "next-piece-player-2")
|
||||
self._spawn_piece_shift_player_one = ConfigurationManager.get("position", "spawn-piece-player-1")
|
||||
self._spawn_piece_shift_player_two = ConfigurationManager.get("position", "spawn-piece-player-2")
|
||||
|
||||
# entities
|
||||
self._next_piece_player_one = PieceGenerator.get_piece(self._next_piece_player_one_pos)
|
||||
self._next_piece_player_two = PieceGenerator.get_piece(self._next_piece_player_two_pos, True)
|
||||
self._current_piece_player_one = None
|
||||
self._current_piece_player_two = None
|
||||
|
||||
self._change_scence = change_scene
|
||||
MultiplayerService.init()
|
||||
|
||||
def draw(self, surface: pygame.Surface) -> None:
|
||||
surface.fill(self._background_color)
|
||||
@@ -322,11 +318,9 @@ class MultiplayerScene(Scene):
|
||||
if self._current_piece_player_one is not None:
|
||||
self._current_piece_player_one.draw(surface, self._well_player_one, self._stack_player_one)
|
||||
if self._current_piece_player_two is not None:
|
||||
self._current_piece_player_two.draw(surface, self._well_player_two, self._stack_player_two)
|
||||
self._current_piece_player_two.draw(surface, self._well_player_two, self._stack_player_two, True)
|
||||
if self._next_piece_player_one is not None:
|
||||
self._next_piece_player_one.draw(surface)
|
||||
if self._next_piece_player_two is not None:
|
||||
self._next_piece_player_two.draw(surface)
|
||||
|
||||
# wells
|
||||
if self._well_player_one is not None:
|
||||
@@ -345,16 +339,37 @@ class MultiplayerScene(Scene):
|
||||
TextGenerator.draw("Lines 0000", self._lines_label_player_two_pos, surface)
|
||||
|
||||
# next
|
||||
TextGenerator.draw("P1 NXT", self._next_label_player_one_pos, surface)
|
||||
TextGenerator.draw("P2 NXT", self._next_label_player_two_pos, surface)
|
||||
TextGenerator.draw("NEXT", self._next_label_player_one_pos, surface)
|
||||
|
||||
def update(self, elapsed_time: int) -> None:
|
||||
self._update_piece_player_one(elapsed_time)
|
||||
self._update_piece_player_two(elapsed_time)
|
||||
self._update_piece_player_two()
|
||||
|
||||
if self._stack_player_one:
|
||||
if self._stack_player_one is not None:
|
||||
self._stack_player_one.update(elapsed_time)
|
||||
|
||||
if self._stack_player_two is not None:
|
||||
# get opponent stack from server and modify for current client
|
||||
opponent_stack = MultiplayerService.try_receive_stack()
|
||||
if opponent_stack is not None:
|
||||
for square in opponent_stack.points:
|
||||
for vertex in square:
|
||||
vertex[0] += 400
|
||||
|
||||
if len(opponent_stack.points) > len(self._stack_player_two._points):
|
||||
for _ in range(len(opponent_stack.points) - len(self._stack_player_two._points)):
|
||||
self._stack_player_two._square_designs.append(self._last_square_design)
|
||||
|
||||
# load opponent stack into game
|
||||
self._stack_player_two._points = opponent_stack.points
|
||||
|
||||
print(opponent_stack)
|
||||
|
||||
if self._current_piece_player_one is not None:
|
||||
MultiplayerService.send_piece(PieceDto(self._current_piece_player_one._points, self._current_piece_player_one._center))
|
||||
if self._stack_player_one is not None:
|
||||
MultiplayerService.send_stack(StackDto(self._stack_player_one._points))
|
||||
|
||||
def _update_piece_player_one(self, elapsed_time: int) -> None:
|
||||
if self._current_piece_player_one is not None:
|
||||
self._current_piece_player_one.update(elapsed_time,\
|
||||
@@ -373,23 +388,30 @@ class MultiplayerScene(Scene):
|
||||
MultiplayerService.quit()
|
||||
sys.exit()
|
||||
|
||||
def _update_piece_player_two(self, elapsed_time: int) -> None:
|
||||
def _update_piece_player_two(self) -> None:
|
||||
if self._current_piece_player_two is not None:
|
||||
self._current_piece_player_two.update(elapsed_time,\
|
||||
self._well_player_two,\
|
||||
self._stack_player_two,\
|
||||
self._get_level_player_two(),\
|
||||
self._clear_current_piece_player_two)
|
||||
else:
|
||||
self._current_piece_player_two = self._next_piece_player_two
|
||||
self._current_piece_player_two.move(self._spawn_piece_shift_player_two)
|
||||
self._next_piece_player_two = PieceGenerator.get_piece(self._next_piece_player_two_pos, True)
|
||||
# get opponent piece from server and modify for current client
|
||||
opponent_piece = MultiplayerService.try_receive_piece()
|
||||
if opponent_piece is not None:
|
||||
for square in opponent_piece.points:
|
||||
for vertex in square:
|
||||
vertex[0] += 400
|
||||
opponent_piece.center[0] += 400
|
||||
|
||||
# TODO create game over scene
|
||||
if self._stack_player_two and self._current_piece_player_two.collide(self._stack_player_two):
|
||||
pygame.quit()
|
||||
MultiplayerService.quit()
|
||||
sys.exit()
|
||||
# if it was added to stack then update colors
|
||||
if opponent_piece.center[1] < self._current_piece_player_two._center[1]:
|
||||
base_color = self._current_piece_player_two._color
|
||||
inner_color = self._current_piece_player_two._inner_border_color
|
||||
outer_color = self._current_piece_player_two._border_color
|
||||
|
||||
self._last_square_design = SquareDesign(base_color, inner_color, outer_color)
|
||||
self._current_piece_player_two = PieceGenerator.get_opponent_piece()
|
||||
|
||||
# load opponent piece into game
|
||||
self._current_piece_player_two._points = opponent_piece.points
|
||||
self._current_piece_player_two._center = opponent_piece.center
|
||||
else:
|
||||
self._current_piece_player_two = PieceGenerator.get_opponent_piece()
|
||||
|
||||
def _get_level_player_one(self) -> int:
|
||||
return 0 if self._stack_player_one is None else self._stack_player_one.total_lines // self._lines_per_level
|
||||
|
||||
Reference in New Issue
Block a user