Skip to content

Commit

Permalink
feat: Improve online play
Browse files Browse the repository at this point in the history
  • Loading branch information
strakam committed Jan 7, 2025
1 parent 19ac5c7 commit 37e706f
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 41 deletions.
3 changes: 1 addition & 2 deletions generals/remote/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from .generalsio_client import GeneralsIOClient, GeneralsIOClientError, autopilot
from .generalsio_client import GeneralsIOClient, autopilot

__all__ = [
"autopilot",
"GeneralsIOClientError",
"GeneralsIOClient",
]
18 changes: 0 additions & 18 deletions generals/remote/exceptions.py

This file was deleted.

55 changes: 34 additions & 21 deletions generals/remote/generalsio_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import time

import numpy as np
from socketio import SimpleClient # type: ignore

Expand All @@ -8,10 +6,11 @@
from generals.core.observation import Observation
from generals.remote.generalsio_state import GeneralsIOstate

from .exceptions import GeneralsIOClientError, RegisterAgentError

DIRECTIONS = [Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT]

PUBLIC_ENDPOINT = "https://ws.generals.io/"
BOT_ENDPOINT = "https://botws.generals.io/"


def autopilot(agent: Agent, user_id: str, lobby_id: str) -> None:
"""
Expand All @@ -33,19 +32,23 @@ class GeneralsIOClient(SimpleClient):
GeneralsIO lobby.
"""

def __init__(self, agent: Agent, user_id: str):
def __init__(self, agent: Agent, user_id: str, public_server: bool = False):
super().__init__()
self.connect("https://botws.generals.io")
self.public_server = public_server
self.user_id = user_id
self.agent = agent
self._queue_id = ""
self._replay_id = ""
self._status = "off" # can be "off","game","lobby","queue"
self.bot_key = "sd09fjd203i0ejwi_changeme"

self.connect(PUBLIC_ENDPOINT if public_server else BOT_ENDPOINT)
print("Connected to server!")

@property
def queue_id(self):
if not self._queue_id:
raise GeneralsIOClientError("Queue ID is not set.\nIs agent in the game lobby?")
raise ValueError("No queue ID available.")

return self._queue_id

Expand All @@ -70,19 +73,22 @@ def register_agent(self, username: str) -> None:
:param user_id: secret ID of Agent
:param username: agent username, must be prefixed with `[Bot]`
"""
event, response = self._emit_receive("set_username", (self.user_id, username))
if response:
# in case of success the response is empty
raise RegisterAgentError(response)
payload = (self.user_id, username, self.bot_key)
_, response = self._emit_receive("set_username", payload)
if response: # in case of success the response is empty
raise ValueError(f"Failed to register the agent: {response}.")
print(f"Agent {username} registered!")

def join_private_lobby(self, queue_id: str) -> None:
def join_private_lobby(self, lobby_id: str) -> None:
"""
Join (or create) private game lobby.
:param queue_id: Either URL or lobby ID number
"""
self._status = "lobby"
self._emit_receive("join_private", (queue_id, self.user_id))
self._queue_id = queue_id
payload = (lobby_id, self.user_id, self.bot_key)
self._emit_receive("join_private", payload)
self._queue_id = lobby_id
print(f"Joined private lobby {lobby_id}.")

def join_game(self, force_start: bool = True) -> None:
"""
Expand All @@ -91,7 +97,6 @@ def join_game(self, force_start: bool = True) -> None:
"""
self._status = "queue"
while True:
time.sleep(2)
event, *data = self.receive()
self.emit("set_force_start", (self.queue_id, force_start))
if event == "game_start":
Expand All @@ -105,7 +110,8 @@ def join_1v1_queue(self) -> None:
Join 1v1 queue.
"""
self._status = "queue"
self._emit_receive("join_1v1", self.user_id)
payload = (self.user_id, self.bot_key)
self._emit_receive("join_1v1", payload)
while True:
event, *data = self.receive()
if event == "game_start":
Expand All @@ -121,11 +127,13 @@ def _initialize_game(self, data: dict) -> None:
"""
self.game_state = GeneralsIOstate(data[0])
self._replay_id = data[0]["replay_id"]
print("Game started!")

def _generate_action(self, observation: Observation) -> tuple[int, int, int] | None:
"""
Translate action from Agent to the server format.
:param action: dictionary representing the action
If your agent passes actions correctly into our simulator, it will work here too.
"""
action = self.agent.act(observation)
pass_or_play = action[0]
Expand All @@ -136,7 +144,6 @@ def _generate_action(self, observation: Observation) -> tuple[int, int, int] | N
source: np.ndarray = np.array([i, j])
direction = np.array(DIRECTIONS[direction].value)
destination = source + direction
# convert to index
source_index = source[0] * self.game_state.map[0] + source[1]
destination_index = destination[0] * self.game_state.map[0] + destination[1]
return (int(source_index), int(destination_index), int(split))
Expand All @@ -148,7 +155,12 @@ def _play_game(self) -> None:
TODO: spawn a new thread in which Agent will calculate its moves
"""
while True:
event, data, _ = self.receive()
try:
event, data, _ = self.receive()
except ValueError:
print(event)
print("Opponent disconnected, you won!")
return
match event:
case "game_update":
self.game_state.update(data)
Expand All @@ -158,14 +170,15 @@ def _play_game(self) -> None:
self.emit("attack", action)
case "game_lost" | "game_won":
self._finish_game(event == "game_won")
break
return

def _finish_game(self, is_winner: bool) -> None:
"""
Triggered after server finishes the game.
:param is_winner: True if Agent won the game
"""
self._status = "off"
status = "won!" if is_winner else "lost."
print(f"Game is finished, you {status}, replay ID: https://bot.generals.io/replays/{self.replay_id}")
status = "Won!" if is_winner else "Lost."
prefix = "bot." if not self.public_server else ""
print(f"You {status}, replay link: https://{prefix}generals.io/replays/{self.replay_id}")
self.emit("leave_game")

0 comments on commit 37e706f

Please sign in to comment.