From 59ae85eff96ad770b704411f32f763cc761df4da Mon Sep 17 00:00:00 2001 From: Gabriel Palacios Date: Mon, 11 Nov 2024 19:48:08 +0100 Subject: [PATCH] First commit --- .github/workflows/build-push-github.yml | 59 +++++++ Dockerfile | 11 ++ internal/handler/coms/game_pb2.py | 57 ++++++ internal/handler/coms/game_pb2.pyi | 104 +++++++++++ internal/handler/coms/game_pb2_grpc.py | 183 ++++++++++++++++++++ randbot.py | 219 ++++++++++++++++++++++++ requirements.txt | 2 + 7 files changed, 635 insertions(+) create mode 100644 .github/workflows/build-push-github.yml create mode 100644 Dockerfile create mode 100644 internal/handler/coms/game_pb2.py create mode 100644 internal/handler/coms/game_pb2.pyi create mode 100644 internal/handler/coms/game_pb2_grpc.py create mode 100644 randbot.py create mode 100644 requirements.txt diff --git a/.github/workflows/build-push-github.yml b/.github/workflows/build-push-github.yml new file mode 100644 index 0000000..2e7abb5 --- /dev/null +++ b/.github/workflows/build-push-github.yml @@ -0,0 +1,59 @@ +####################################################### +### example for Github Container Registry (ghcr.io) +### example for Docker Hub +### +### Your container image name will be: +### ghcr.io//:latest +### +####################################################### +####################################################### + +####################################################### +### Uncomment all lines below to use this template ### +####################################################### + +name: "Docker build & push - GHCR" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +on: + pull_request: + types: [closed] + push: + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}:latest + +jobs: + docker-build-push: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: "Build and push Docker image" + uses: docker/build-push-action@v6 + with: + push: true + tags: ${{ env.REGISTRY }}/${{env.IMAGE_NAME}} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..30b6d5e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.10-slim +ARG BOT_NAME="intelygenz-codeconz-lighthouses-py-bot" + +WORKDIR /app +COPY ./ ./ +COPY ./randbot.py ./main.py + +RUN pip install --no-cache-dir -r requirements.txt + +EXPOSE 3001 +CMD [ "python3", "./main.py", "--bn=${BOT_NAME}", "--la=${BOT_NAME}:3001", "--gs=game:50051" diff --git a/internal/handler/coms/game_pb2.py b/internal/handler/coms/game_pb2.py new file mode 100644 index 0000000..069e217 --- /dev/null +++ b/internal/handler/coms/game_pb2.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: game.proto +# Protobuf Python Version: 5.27.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 2, + '', + 'game.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ngame.proto\"0\n\tNewPlayer\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x15\n\rserverAddress\x18\x02 \x01(\t\"\x15\n\x06MapRow\x12\x0b\n\x03Row\x18\x01 \x03(\x05\" \n\x08Position\x12\t\n\x01X\x18\x01 \x01(\x05\x12\t\n\x01Y\x18\x02 \x01(\x05\"y\n\nLighthouse\x12\x1b\n\x08Position\x18\x01 \x01(\x0b\x32\t.Position\x12\r\n\x05Owner\x18\x02 \x01(\x05\x12\x0e\n\x06\x45nergy\x18\x03 \x01(\x05\x12\x1e\n\x0b\x43onnections\x18\x04 \x03(\x0b\x32\t.Position\x12\x0f\n\x07HaveKey\x18\x05 \x01(\x08\"\x1c\n\x08PlayerID\x12\x10\n\x08PlayerID\x18\x01 \x01(\x05\"\x93\x01\n\x15NewPlayerInitialState\x12\x10\n\x08PlayerID\x18\x01 \x01(\x05\x12\x13\n\x0bPlayerCount\x18\x02 \x01(\x05\x12\x1b\n\x08Position\x18\x03 \x01(\x0b\x32\t.Position\x12\x14\n\x03Map\x18\x04 \x03(\x0b\x32\x07.MapRow\x12 \n\x0bLighthouses\x18\x05 \x03(\x0b\x32\x0b.Lighthouse\"~\n\x07NewTurn\x12\x1b\n\x08Position\x18\x01 \x01(\x0b\x32\t.Position\x12\r\n\x05Score\x18\x02 \x01(\x05\x12\x0e\n\x06\x45nergy\x18\x03 \x01(\x05\x12\x15\n\x04View\x18\x04 \x03(\x0b\x32\x07.MapRow\x12 \n\x0bLighthouses\x18\x05 \x03(\x0b\x32\x0b.Lighthouse\"T\n\tNewAction\x12\x17\n\x06\x41\x63tion\x18\x01 \x01(\x0e\x32\x07.Action\x12\x1e\n\x0b\x44\x65stination\x18\x02 \x01(\x0b\x32\t.Position\x12\x0e\n\x06\x45nergy\x18\x03 \x01(\x05\"\x1c\n\x0bPlayerReady\x12\r\n\x05Ready\x18\x01 \x01(\x08*5\n\x06\x41\x63tion\x12\x08\n\x04PASS\x10\x00\x12\x08\n\x04MOVE\x10\x01\x12\n\n\x06\x41TTACK\x10\x02\x12\x0b\n\x07\x43ONNECT\x10\x03\x32\x86\x01\n\x0bGameService\x12\x1f\n\x04Join\x12\n.NewPlayer\x1a\t.PlayerID\"\x00\x12\x36\n\x0cInitialState\x12\x16.NewPlayerInitialState\x1a\x0c.PlayerReady\"\x00\x12\x1e\n\x04Turn\x12\x08.NewTurn\x1a\n.NewAction\"\x00\x42\x33Z1github.com/jonasdacruz/lighthouses_aicontest/comsb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'game_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'Z1github.com/jonasdacruz/lighthouses_aicontest/coms' + _globals['_ACTION']._serialized_start=668 + _globals['_ACTION']._serialized_end=721 + _globals['_NEWPLAYER']._serialized_start=14 + _globals['_NEWPLAYER']._serialized_end=62 + _globals['_MAPROW']._serialized_start=64 + _globals['_MAPROW']._serialized_end=85 + _globals['_POSITION']._serialized_start=87 + _globals['_POSITION']._serialized_end=119 + _globals['_LIGHTHOUSE']._serialized_start=121 + _globals['_LIGHTHOUSE']._serialized_end=242 + _globals['_PLAYERID']._serialized_start=244 + _globals['_PLAYERID']._serialized_end=272 + _globals['_NEWPLAYERINITIALSTATE']._serialized_start=275 + _globals['_NEWPLAYERINITIALSTATE']._serialized_end=422 + _globals['_NEWTURN']._serialized_start=424 + _globals['_NEWTURN']._serialized_end=550 + _globals['_NEWACTION']._serialized_start=552 + _globals['_NEWACTION']._serialized_end=636 + _globals['_PLAYERREADY']._serialized_start=638 + _globals['_PLAYERREADY']._serialized_end=666 + _globals['_GAMESERVICE']._serialized_start=724 + _globals['_GAMESERVICE']._serialized_end=858 +# @@protoc_insertion_point(module_scope) diff --git a/internal/handler/coms/game_pb2.pyi b/internal/handler/coms/game_pb2.pyi new file mode 100644 index 0000000..3666387 --- /dev/null +++ b/internal/handler/coms/game_pb2.pyi @@ -0,0 +1,104 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Action(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PASS: _ClassVar[Action] + MOVE: _ClassVar[Action] + ATTACK: _ClassVar[Action] + CONNECT: _ClassVar[Action] +PASS: Action +MOVE: Action +ATTACK: Action +CONNECT: Action + +class NewPlayer(_message.Message): + __slots__ = ("name", "serverAddress") + NAME_FIELD_NUMBER: _ClassVar[int] + SERVERADDRESS_FIELD_NUMBER: _ClassVar[int] + name: str + serverAddress: str + def __init__(self, name: _Optional[str] = ..., serverAddress: _Optional[str] = ...) -> None: ... + +class MapRow(_message.Message): + __slots__ = ("Row",) + ROW_FIELD_NUMBER: _ClassVar[int] + Row: _containers.RepeatedScalarFieldContainer[int] + def __init__(self, Row: _Optional[_Iterable[int]] = ...) -> None: ... + +class Position(_message.Message): + __slots__ = ("X", "Y") + X_FIELD_NUMBER: _ClassVar[int] + Y_FIELD_NUMBER: _ClassVar[int] + X: int + Y: int + def __init__(self, X: _Optional[int] = ..., Y: _Optional[int] = ...) -> None: ... + +class Lighthouse(_message.Message): + __slots__ = ("Position", "Owner", "Energy", "Connections", "HaveKey") + POSITION_FIELD_NUMBER: _ClassVar[int] + OWNER_FIELD_NUMBER: _ClassVar[int] + ENERGY_FIELD_NUMBER: _ClassVar[int] + CONNECTIONS_FIELD_NUMBER: _ClassVar[int] + HAVEKEY_FIELD_NUMBER: _ClassVar[int] + Position: Position + Owner: int + Energy: int + Connections: _containers.RepeatedCompositeFieldContainer[Position] + HaveKey: bool + def __init__(self, Position: _Optional[_Union[Position, _Mapping]] = ..., Owner: _Optional[int] = ..., Energy: _Optional[int] = ..., Connections: _Optional[_Iterable[_Union[Position, _Mapping]]] = ..., HaveKey: bool = ...) -> None: ... + +class PlayerID(_message.Message): + __slots__ = ("PlayerID",) + PLAYERID_FIELD_NUMBER: _ClassVar[int] + PlayerID: int + def __init__(self, PlayerID: _Optional[int] = ...) -> None: ... + +class NewPlayerInitialState(_message.Message): + __slots__ = ("PlayerID", "PlayerCount", "Position", "Map", "Lighthouses") + PLAYERID_FIELD_NUMBER: _ClassVar[int] + PLAYERCOUNT_FIELD_NUMBER: _ClassVar[int] + POSITION_FIELD_NUMBER: _ClassVar[int] + MAP_FIELD_NUMBER: _ClassVar[int] + LIGHTHOUSES_FIELD_NUMBER: _ClassVar[int] + PlayerID: int + PlayerCount: int + Position: Position + Map: _containers.RepeatedCompositeFieldContainer[MapRow] + Lighthouses: _containers.RepeatedCompositeFieldContainer[Lighthouse] + def __init__(self, PlayerID: _Optional[int] = ..., PlayerCount: _Optional[int] = ..., Position: _Optional[_Union[Position, _Mapping]] = ..., Map: _Optional[_Iterable[_Union[MapRow, _Mapping]]] = ..., Lighthouses: _Optional[_Iterable[_Union[Lighthouse, _Mapping]]] = ...) -> None: ... + +class NewTurn(_message.Message): + __slots__ = ("Position", "Score", "Energy", "View", "Lighthouses") + POSITION_FIELD_NUMBER: _ClassVar[int] + SCORE_FIELD_NUMBER: _ClassVar[int] + ENERGY_FIELD_NUMBER: _ClassVar[int] + VIEW_FIELD_NUMBER: _ClassVar[int] + LIGHTHOUSES_FIELD_NUMBER: _ClassVar[int] + Position: Position + Score: int + Energy: int + View: _containers.RepeatedCompositeFieldContainer[MapRow] + Lighthouses: _containers.RepeatedCompositeFieldContainer[Lighthouse] + def __init__(self, Position: _Optional[_Union[Position, _Mapping]] = ..., Score: _Optional[int] = ..., Energy: _Optional[int] = ..., View: _Optional[_Iterable[_Union[MapRow, _Mapping]]] = ..., Lighthouses: _Optional[_Iterable[_Union[Lighthouse, _Mapping]]] = ...) -> None: ... + +class NewAction(_message.Message): + __slots__ = ("Action", "Destination", "Energy") + ACTION_FIELD_NUMBER: _ClassVar[int] + DESTINATION_FIELD_NUMBER: _ClassVar[int] + ENERGY_FIELD_NUMBER: _ClassVar[int] + Action: Action + Destination: Position + Energy: int + def __init__(self, Action: _Optional[_Union[Action, str]] = ..., Destination: _Optional[_Union[Position, _Mapping]] = ..., Energy: _Optional[int] = ...) -> None: ... + +class PlayerReady(_message.Message): + __slots__ = ("Ready",) + READY_FIELD_NUMBER: _ClassVar[int] + Ready: bool + def __init__(self, Ready: bool = ...) -> None: ... diff --git a/internal/handler/coms/game_pb2_grpc.py b/internal/handler/coms/game_pb2_grpc.py new file mode 100644 index 0000000..7fb6ff5 --- /dev/null +++ b/internal/handler/coms/game_pb2_grpc.py @@ -0,0 +1,183 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from internal.handler.coms import game_pb2 as game__pb2 + +GRPC_GENERATED_VERSION = '1.66.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in game_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class GameServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Join = channel.unary_unary( + '/GameService/Join', + request_serializer=game__pb2.NewPlayer.SerializeToString, + response_deserializer=game__pb2.PlayerID.FromString, + _registered_method=True) + self.InitialState = channel.unary_unary( + '/GameService/InitialState', + request_serializer=game__pb2.NewPlayerInitialState.SerializeToString, + response_deserializer=game__pb2.PlayerReady.FromString, + _registered_method=True) + self.Turn = channel.unary_unary( + '/GameService/Turn', + request_serializer=game__pb2.NewTurn.SerializeToString, + response_deserializer=game__pb2.NewAction.FromString, + _registered_method=True) + + +class GameServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def Join(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def InitialState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Turn(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_GameServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Join': grpc.unary_unary_rpc_method_handler( + servicer.Join, + request_deserializer=game__pb2.NewPlayer.FromString, + response_serializer=game__pb2.PlayerID.SerializeToString, + ), + 'InitialState': grpc.unary_unary_rpc_method_handler( + servicer.InitialState, + request_deserializer=game__pb2.NewPlayerInitialState.FromString, + response_serializer=game__pb2.PlayerReady.SerializeToString, + ), + 'Turn': grpc.unary_unary_rpc_method_handler( + servicer.Turn, + request_deserializer=game__pb2.NewTurn.FromString, + response_serializer=game__pb2.NewAction.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'GameService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('GameService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class GameService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def Join(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GameService/Join', + game__pb2.NewPlayer.SerializeToString, + game__pb2.PlayerID.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def InitialState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GameService/InitialState', + game__pb2.NewPlayerInitialState.SerializeToString, + game__pb2.PlayerReady.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Turn(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GameService/Turn', + game__pb2.NewTurn.SerializeToString, + game__pb2.NewAction.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/randbot.py b/randbot.py new file mode 100644 index 0000000..a15a735 --- /dev/null +++ b/randbot.py @@ -0,0 +1,219 @@ +import time +import argparse +import grpc +import random +from concurrent import futures +from grpc import RpcError +from google.protobuf import json_format + +from internal.handler.coms import game_pb2 +from internal.handler.coms import game_pb2_grpc as game_grpc + +timeout_to_response = 1 # 1 second + +class BotGameTurn: + def __init__(self, turn, action): + self.turn = turn + self.action = action + + +class BotGame: + def __init__(self, player_num=None): + self.player_num = player_num + self.initial_state = None + self.turn_states = [] + self.countT = 1 + + def new_turn_action(self, turn: game_pb2.NewTurn) -> game_pb2.NewAction: + cx, cy = turn.Position.X, turn.Position.Y + + lighthouses = dict() + for lh in turn.Lighthouses: + lighthouses[(lh.Position.X, lh.Position.Y)] = lh + + # Si estamos en un faro... + if (cx, cy) in lighthouses: + # Probabilidad 60%: conectar con faro remoto válido + if lighthouses[(cx, cy)].Owner == self.player_num: + if random.randrange(100) < 60: + possible_connections = [] + for dest in lighthouses: + # No conectar con sigo mismo + # No conectar si no tenemos la clave + # No conectar si ya existe la conexión + # No conectar si no controlamos el destino + # Nota: no comprobamos si la conexión se cruza. + if (dest != (cx, cy) and + lighthouses[dest].HaveKey and + [cx, cy] not in lighthouses[dest].Connections and + lighthouses[dest].Owner == self.player_num): + possible_connections.append(dest) + + if possible_connections: + possible_connection = random.choice(possible_connections) + action = game_pb2.NewAction( + Action=game_pb2.CONNECT, + Destination=game_pb2.Position(X=possible_connection[0], Y=possible_connection[1]) + ) + bgt = BotGameTurn(turn, action) + self.turn_states.append(bgt) + + self.countT += 1 + return action + + # Probabilidad 60%: recargar el faro + if random.randrange(100) < 60: + energy = random.randrange(turn.Energy + 1) + action = game_pb2.NewAction( + Action=game_pb2.ATTACK, + Energy=energy, + Destination=game_pb2.Position( + X=turn.Position.X, + Y=turn.Position.Y + ) + ) + bgt = BotGameTurn(turn, action) + self.turn_states.append(bgt) + + self.countT += 1 + return action + + # Mover aleatoriamente + moves = ((-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)) + move = random.choice(moves) + action = game_pb2.NewAction( + Action=game_pb2.MOVE, + Destination=game_pb2.Position( + X=turn.Position.X + move[0], + Y=turn.Position.Y + move[1] + ) + ) + + bgt = BotGameTurn(turn, action) + self.turn_states.append(bgt) + + self.countT += 1 + return action + + +class BotComs: + def __init__(self, bot_name, my_address, game_server_address, verbose=False): + self.bot_id = None + self.bot_name = bot_name + self.my_address = my_address + self.game_server_address = game_server_address + self.verbose = verbose + + def wait_to_join_game(self): + channel = grpc.insecure_channel(self.game_server_address) + client = game_grpc.GameServiceStub(channel) + + player = game_pb2.NewPlayer(name=self.bot_name, serverAddress=self.my_address) + + while True: + try: + player_id = client.Join(player, timeout=timeout_to_response) + self.bot_id = player_id.PlayerID + print(f"Joined game with ID {player_id.PlayerID}") + if self.verbose: + print(json_format.MessageToJson(player_id)) + break + except RpcError as e: + print(f"Could not join game: {e.details()}") + time.sleep(1) + + + def start_listening(self): + print("Starting to listen on", self.my_address) + + # configure gRPC server + grpc_server = grpc.server( + futures.ThreadPoolExecutor(max_workers=10), + interceptors=(ServerInterceptor(),) + ) + + # registry of the service + cs = ClientServer(bot_id=self.bot_id, verbose=self.verbose) + game_grpc.add_GameServiceServicer_to_server(cs, grpc_server) + + # server start + grpc_server.add_insecure_port(self.my_address) + grpc_server.start() + + try: + grpc_server.wait_for_termination() # wait until server finish + except KeyboardInterrupt: + grpc_server.stop(0) + + +class ServerInterceptor(grpc.ServerInterceptor): + def intercept_service(self, continuation, handler_call_details): + start_time = time.time_ns() + method_name = handler_call_details.method + + # Invoke the actual RPC + response = continuation(handler_call_details) + + # Log after the call + duration = time.time_ns() - start_time + print(f"Unary call: {method_name}, Duration: {duration:.2f} nanoseconds") + return response + + +class ClientServer(game_grpc.GameServiceServicer): + def __init__(self, bot_id, verbose=False): + self.bg = BotGame(bot_id) + self.verbose = verbose + + def Join(self, request, context): + return None + + def InitialState(self, request, context): + print("Receiving InitialState") + if self.verbose: + print(json_format.MessageToJson(request)) + self.bg.initial_state = request + return game_pb2.PlayerReady(Ready=True) + + def Turn(self, request, context): + print(f"Processing turn: {self.bg.countT}") + if self.verbose: + print(json_format.MessageToJson(request)) + action = self.bg.new_turn_action(request) + return action + + +def ensure_params(): + parser = argparse.ArgumentParser(description="Bot configuration") + parser.add_argument("--bn", type=str, default="random-bot", help="Bot name") + parser.add_argument("--la", type=str, required=True, help="Listen address") + parser.add_argument("--gs", type=str, required=True, help="Game server address") + + args = parser.parse_args() + + if not args.bn: + raise ValueError("Bot name is required") + if not args.la: + raise ValueError("Listen address is required") + if not args.gs: + raise ValueError("Game server address is required") + + return args.bn, args.la, args.gs + + +def main(): + verbose = False + bot_name, listen_address, game_server_address = ensure_params() + + bot = BotComs( + bot_name=bot_name, + my_address=listen_address, + game_server_address=game_server_address, + verbose=verbose, + ) + bot.wait_to_join_game() + bot.start_listening() + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f532a96 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +grpcio +protobuf