def __init__(self, host="127.0.0.1", port=7664): threading.Thread.__init__(self, daemon=True) self.host = host self.port = port self._is_running = False self._server = TCPServer(host, port, 2) self.init_callbacks() self._buffers = dict() self._game_state = GameState() self._client_start_round = 1 # Second player starts first self._ball_speed = 1.0 self._ball_speed_max = 2.5 self._game_win_pts = 5 # first to 5 points wins game self._game_end_signal = False self._last_time = None self._this_time = None self._play_again_signals = [False, False]
class PyPongServerThread(threading.Thread): def __init__(self, host="127.0.0.1", port=7664): threading.Thread.__init__(self, daemon=True) self.host = host self.port = port self._is_running = False self._server = TCPServer(host, port, 2) self.init_callbacks() self._buffers = dict() self._game_state = GameState() self._client_start_round = 1 # Second player starts first self._ball_speed = 1.0 self._ball_speed_max = 2.5 self._game_win_pts = 5 # first to 5 points wins game self._game_end_signal = False self._last_time = None self._this_time = None self._play_again_signals = [False, False] def _add_buffer(self, client): self._buffers[client.getpeername()] = TokenBuffer() return None def _remove_buffer(self, client): del self._buffers[client.getpeername()] return None def init_callbacks(self): # Index Assignment lambda self._server.index_assign_msg = lambda index: IndexAssignmentProc(index).to_json() # New client connected self._server.callbacks_connect.append(lambda client: self._add_buffer(client)) self._server.callbacks_connect.append( lambda client: print("[SERVER] New client ({}, {}) connected.".format( self._server.get_client_index(client), client.getpeername()) ) ) self._server.callbacks_connect.append(lambda client: self._signal_start_if_ready()) # Server full self._server.callbacks_server_full.append( lambda client: print("[SERVER] Attempt to connect to full server from {}".format(client.getpeername())) ) # Client lost connection self._server.callbacks_connection_lost.append( lambda client: print("[SERVER] Connection lost from client {}".format(client.getpeername())) ) self._server.callbacks_connection_lost.append(lambda client: self._remove_buffer(client)) # Disconnected client self._server.callbacks_disconnect.append( lambda client: print("[SERVER] Client {} disconnected".format(client.getpeername())) ) self._server.callbacks_disconnect.append(lambda client: self._remove_buffer(client)) # Incoming data self._server.callbacks_incoming_data.append( lambda client, data: self._process_data(client, data) ) def run(self): if not self._is_running: self._server.bind() self._server.listen() self._is_running = True def stop(self): self._server._listening = False def _reset_game_state(self): self._game_state = GameState() self._client_start_round = 1 self._ball_speed = 1.0 self._game_end_signal = False self._play_again_signals = [False, False] def _signal_start_if_ready(self): if len(self._server.clients) == 2: self._reset_game_state() self._server.send_all(ServerReadyProc().to_json()) def _process_data(self, client, data): client_buffer = self._buffers.get(client.getpeername()) client_buffer.push(data) while True: current_proc = client_buffer.get_first_token('$', '&') if len(current_proc) > 0: try: json_proc = json.loads(current_proc) if json_proc['proc'] == 'start_round': self._process_signal_start_round(client) elif json_proc['proc'] == 'game_state_update': self._process_game_state_update(client, json_proc) elif json_proc['proc'] == 'play_again': self._process_play_again_proc(client) except json.JSONDecodeError: print("[SERVER] Invalid JSON procedure: '{}'".format(current_proc)) except Exception as exc: print("[SERVER] Unhandled exception while parsing JSON procedure: {}".format(exc)) else: break def _process_signal_start_round(self, client): if self._game_state.ball.vx == 0.0 and self._game_state.ball.vy == 0.0: client_index = self._server.get_client_index(client) if client_index == self._client_start_round: if client_index == 0: self._game_state.ball.vx = 0.33 self._game_state.ball.vy = 0.66 else: self._game_state.ball.vx = -0.33 self._game_state.ball.vy = -0.66 def _process_play_again_proc(self, client): if self._game_end_signal: client_index = self._server.get_client_index(client) self._play_again_signals[client_index] = True # Check if both sent signals, if so then start new game if self._play_again_signals[0] and self._play_again_signals[1]: self._reset_game_state() self._server.send_all(ServerReadyProc().to_json()) def _increase_ball_speed_on_hit(self): self._ball_speed += 0.1 if self._ball_speed > self._ball_speed_max: self._ball_speed = self._ball_speed_max def _is_game_over(self): return max(self._game_state.player1.pts, self._game_state.player2.pts) >= self._game_win_pts def _process_game_state_update(self, client, json_proc): gsup = GameStateUpdateProc(None).from_json(json_proc) ball = self._game_state.ball p1 = gsup.data['game_state'].player1 p2 = gsup.data['game_state'].player2 # Check it this data didn't come after game ended, if so then stop processing it if self._is_game_over(): return # Delta time computing self._this_time = Time.now() if self._last_time is None: self._last_time = self._this_time dt = Time.interval_as_float(self._this_time - self._last_time) self._last_time = self._this_time # Update ball position ball.x += ball.vx * self._ball_speed * dt ball.y += ball.vy * self._ball_speed * dt # Check collision with paddles p1_rect = self.get_player_rect(p1) p2_rect = self.get_player_rect(p2) b_rect = self.get_ball_rect(ball) if self.intersect(p1_rect, b_rect): ball.x = (p1_rect[0] + p1_rect[2]) * 2.0 - 1.0 ball.vx = abs(ball.vx) self._increase_ball_speed_on_hit() elif self.intersect(p2_rect, b_rect): ball.x = p2_rect[0] - b_rect[2] ball.vx = -1.0 * abs(ball.vx) self._increase_ball_speed_on_hit() # Check if ball doesn't touch upper or lower edge and has to bounce if ball.y < -1.0: ball.y = -1.0 ball.vy = -ball.vy if ball.y > 1.0: ball.y = 1.0 ball.vy = -ball.vy # Check if ball didn't escape from game area (leading to end of round) if ball.x < -1.0: ball.reset() self._game_state.player2.pts += 1 self._client_start_round = 0 if ball.x > 1.0: ball.reset() self._game_state.player1.pts += 1 self._client_start_round = 1 # Check if it's not end of game if self._is_game_over(): self._send_game_over_signals() # If it's not, send back updated gameplay state else: # Update ball's state in client's message and broadcast it along gsup.data['game_state'].ball = ball gsup.data['game_state'].player1.pts = self._game_state.player1.pts gsup.data['game_state'].player2.pts = self._game_state.player2.pts self._server.send_all(gsup.to_json()) def _send_game_over_signals(self): if self._is_game_over() and not self._game_end_signal: players = self._server.clients player1 = players[0] player2 = players[1] if self._game_state.player1.pts >= self._game_win_pts: print("[SERVER] Player1 won this round.") self._server.send_to(player1, GameOverProc(result="win").to_json()) self._server.send_to(player2, GameOverProc(result="lose").to_json()) else: print("[SERVER] Player2 won this round.") self._server.send_to(player1, GameOverProc(result="lose").to_json()) self._server.send_to(player2, GameOverProc(result="win").to_json()) self._game_end_signal = True @staticmethod def intersect(r1, r2): return \ (r1[0] < r2[0] + r2[2]) and (r1[0] + r1[2] > r2[0]) and \ (r1[1] < r2[1] + r2[3]) and (r1[1] + r1[3] > r2[1]) @staticmethod def get_player_rect(player_data): width = 1.0 / 30.0 height = 1.0 / 5.0 pos_x = (player_data.x + 1.0) * 0.5 * (1.0 - width) pos_y = (player_data.y + 1.0) * 0.5 * (1.0 - height) return pos_x, pos_y, width, height @staticmethod def get_ball_rect(ball_data): width = 1.0 / 30.0 height = 1.0 / 20.0 pos_x = (ball_data.x + 1.0) * 0.5 * (1.0 - width) pos_y = (ball_data.y + 1.0) * 0.5 * (1.0 - height) return pos_x, pos_y, width, height