-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move src files of the server application into a source dir
- move from twisted to asyncio - move from autobahn to websockets - move files into a game directory Signed-off-by: yzamir <kobi.zamir@gmail.com>
- Loading branch information
Showing
12 changed files
with
569 additions
and
398 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import asyncio | ||
import logging | ||
import random | ||
import aiohttp | ||
|
||
from common import config | ||
|
||
from game import score, net | ||
from game.player import Player | ||
from game.track import Track | ||
|
||
log = logging.getLogger("logic") | ||
|
||
|
||
async def initialize_game(state): | ||
"""Reset game settings and return re-initialized track and players.""" | ||
state["reset"] = None | ||
state["running"] = 0 | ||
state["timeleft"] = config.game_duration | ||
track = initialize_track(state["track_type"] != "same") | ||
players = await initialize_players(state["drivers"]) | ||
return track, players | ||
|
||
|
||
def initialize_track(is_track_random): | ||
""" | ||
Initialize and return a new track. | ||
Args: | ||
is_track_random (bool): If False, the track will have the same obstacles for both players. | ||
If True, obstacles will be randomized. | ||
Returns: | ||
Track: An initialized track object. | ||
""" | ||
track = Track(is_track_random) | ||
track.reset() | ||
return track | ||
|
||
|
||
async def initialize_players(drivers): | ||
""" | ||
Asynchronously initialize players from a list of driver URLs. | ||
Args: | ||
drivers (list): List of driver URLs to initialize players from. | ||
players (list): List of Player objects. | ||
""" | ||
|
||
# Init players | ||
players = [] | ||
|
||
if not drivers: | ||
return players | ||
|
||
base_color = random.randint(0, 3) | ||
|
||
async with aiohttp.ClientSession() as session: | ||
for index, driver in enumerate(drivers): | ||
try: | ||
async with session.get(driver) as info: | ||
info_data = await info.json() | ||
player_name = info_data.get("info", {}).get("name") | ||
player_car = (base_color + index) % 3 | ||
player_lane = index | ||
player = Player(player_name, player_car, player_lane) | ||
player.reset() | ||
player.URL = driver | ||
players.append(player) | ||
except Exception as e: | ||
log.error(f"error: {e}") | ||
|
||
return players | ||
|
||
|
||
async def game_loop(state, active_websockets): | ||
""" | ||
Asynchronously execute the game loop, using provided state and active websockets. | ||
Args: | ||
state (dict): Dictionary containing game state data (rate, running status, time left, etc.). | ||
active_websockets (set): A set of active websocket connections for communication. | ||
Returns: | ||
None | ||
""" | ||
|
||
# Initialize or reset the game, set up track and players | ||
track, players = await initialize_game(state) | ||
|
||
# Begin the main game loop | ||
while True: | ||
# Check if the game needs a reset (based on state) | ||
if state["reset"] == 1: | ||
track, players = await initialize_game(state) | ||
|
||
# Stop game if timeleft is zero | ||
if state["timeleft"] < 1: | ||
state["running"] = 0 | ||
|
||
# Check if the game is currently running and there's time left to play | ||
if state["running"] == 1: | ||
# Start executing a step in the game | ||
task = asyncio.create_task( | ||
game_step(state, players, track, active_websockets) | ||
) | ||
|
||
# Pause the game loop for a specified duration, based on the rate defined in the state | ||
await asyncio.sleep(1 / state["rate"]) | ||
|
||
# If for some reason the game step hasn't finished executing, cancel it | ||
if not task.done(): | ||
task.cancel() | ||
|
||
# If the game is not running (e.g., paused or finished) | ||
else: | ||
# Update all connected clients (via websockets) with the current game state | ||
await net.update_websockets(False, state, players, track, active_websockets) | ||
|
||
await asyncio.sleep(1) | ||
|
||
|
||
async def game_step(state, players, track, active_websockets): | ||
""" | ||
Execute a game step: Update the track, fetch drivers' actions, process actions, and update websockets. | ||
Args: | ||
state (dict): Dictionary containing game state data (rate, running status, etc.). | ||
players (list): List of Player objects. | ||
track (Track): the game track. | ||
active_websockets (Any): Active websockets for communication (assuming a suitable data structure). | ||
""" | ||
|
||
try: | ||
# Fetch players actions using an asynchronous HTTP session | ||
await net.fetch_drivers_actions(players, track.matrix()) | ||
|
||
# Update track | ||
track.update() | ||
|
||
# Process the actions of the players | ||
score.process(players, track) | ||
|
||
# Send data to all WebSocket connections | ||
await net.update_websockets(True, state, players, track, active_websockets) | ||
|
||
# Progress the game's timer | ||
state["timeleft"] -= 1 | ||
|
||
except asyncio.CancelledError: | ||
log.info("Game step was canceled!") | ||
raise |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import asyncio | ||
import aiohttp | ||
import json | ||
import time | ||
import logging | ||
|
||
|
||
log = logging.getLogger("net") | ||
|
||
|
||
async def fetch_drivers_actions(players, track_matrix): | ||
""" | ||
Asynchronously fetch actions for each player. | ||
Args: | ||
players (list): List of Player objects. | ||
track_matrix (list of list): The matrix representation a 2D array of the track. | ||
Returns: | ||
list: List of player actions fetched. | ||
""" | ||
async with aiohttp.ClientSession() as session: | ||
await asyncio.gather( | ||
*(fetch_driver_action(session, player, track_matrix) for player in players), | ||
return_exceptions=True, | ||
) | ||
|
||
|
||
async def fetch_driver_action(session, player, track_matrix): | ||
""" | ||
Asynchronously fetch content from a URL using a POST request and return the parsed JSON | ||
along with the time it took to get the response. | ||
Args: | ||
session (aiohttp.ClientSession): An active ClientSession for making the request. | ||
player (Player): The player object containing name, URL, and position. | ||
track_matrix (list of list): The matrix representation a 2D array of the track. | ||
Returns: | ||
tuple: A tuple containing: | ||
- dict or None: The JSON-decoded response from the server or None if an error occurred. | ||
- float: The time taken (in seconds) to get the response. | ||
- str or None: The error message, if any. None if no error occurred. | ||
""" | ||
start_time = time.time() | ||
|
||
try: | ||
response_data = await send_post_request(session, player, track_matrix) | ||
return process_driver_response(player, response_data, start_time) | ||
|
||
except Exception as e: | ||
elapsed_time = time.time() - start_time | ||
player.response_time = elapsed_time | ||
error_msg = f"Error during POST request {e}" | ||
log.error(error_msg) | ||
|
||
player.action = None | ||
player.httperror = "Error POST to driver" | ||
|
||
return None, elapsed_time | ||
|
||
|
||
async def send_post_request(session, player, track_matrix): | ||
data = {"info": {"car": {"x": player.x, "y": player.y}}, "track": track_matrix} | ||
|
||
async with session.post(player.URL, data=json.dumps(data).encode()) as response: | ||
return await response.json() | ||
|
||
|
||
def process_driver_response(player, response_data, start_time): | ||
elapsed_time = time.time() - start_time | ||
player.response_time = elapsed_time | ||
|
||
try: | ||
player.name = response_data.get("info").get("name") | ||
player.action = response_data.get("info").get("action") | ||
player.httperror = None | ||
return response_data, elapsed_time | ||
|
||
except Exception as e: | ||
error_msg = f"Post to driver error {e}" | ||
log.error(error_msg) | ||
|
||
player.action = None | ||
player.httperror = error_msg | ||
|
||
return None, elapsed_time | ||
|
||
|
||
async def update_websockets(started, state, players, track, active_websockets): | ||
"""Generate game state data and send it to all active websockets.""" | ||
data = { | ||
"action": "update", | ||
"payload": { | ||
"started": started, | ||
"rate": state["rate"], | ||
"timeleft": state["timeleft"], | ||
"players": [player.state() for player in players], | ||
"track": track.state(), | ||
}, | ||
} | ||
|
||
await send_to_all_websockets(data, active_websockets) | ||
|
||
|
||
async def send_to_all_websockets(data, active_websockets): | ||
"""Send the given data to all active websocket connections.""" | ||
json_encoded_data = json.dumps(data) | ||
for ws in active_websockets: | ||
try: | ||
await ws.send(json_encoded_data) | ||
except Exception as e: | ||
log.error("Fail ws send", e) |
Oops, something went wrong.