Skip to content

Commit 24e99b8

Browse files
committed
Change protos to cover engine service
1 parent 7fcb15e commit 24e99b8

25 files changed

Lines changed: 593 additions & 143 deletions

.gitignore

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
MANIFEST
27+
28+
# PyInstaller
29+
# Usually these files are written by a python script from a template
30+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
31+
*.manifest
32+
*.spec
33+
34+
# Installer logs
35+
pip-log.txt
36+
pip-delete-this-directory.txt
37+
38+
# Unit test / coverage reports
39+
htmlcov/
40+
.tox/
41+
.coverage
42+
.coverage.*
43+
.cache
44+
nosetests.xml
45+
coverage.xml
46+
*.cover
47+
.hypothesis/
48+
.pytest_cache/
49+
50+
# Translations
51+
*.mo
52+
*.pot
53+
54+
# Django stuff:
55+
*.log
56+
local_settings.py
57+
db.sqlite3
58+
59+
# Flask stuff:
60+
instance/
61+
.webassets-cache
62+
63+
# Scrapy stuff:
64+
.scrapy
65+
66+
# Sphinx documentation
67+
docs/_build/
68+
69+
# PyBuilder
70+
target/
71+
72+
# Jupyter Notebook
73+
.ipynb_checkpoints
74+
75+
# pyenv
76+
.python-version
77+
78+
# celery beat schedule file
79+
celerybeat-schedule
80+
81+
# SageMath parsed files
82+
*.sage.py
83+
84+
# Environments
85+
.env
86+
.venv
87+
env/
88+
venv/
89+
ENV/
90+
env.bak/
91+
venv.bak/
92+
93+
# Spyder project settings
94+
.spyderproject
95+
.spyproject
96+
97+
# Rope project settings
98+
.ropeproject
99+
100+
# mkdocs documentation
101+
/site
102+
103+
# mypy
104+
.mypy_cache/
105+
106+
# IDE-specific files
107+
.vscode/
108+
.idea/

AIService/Server.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from threading import Lock
2+
from typing import Dict, List, Type
3+
import grpc
4+
from concurrent import futures
5+
from Protos import main_pb2_grpc, main_pb2
6+
from AIService.base_ai import BaseAI
7+
8+
class AIService(main_pb2_grpc.AIServiceServicer):
9+
def __init__(self, ai: BaseAI, server_instance):
10+
self.ai = ai
11+
self.server_instance = server_instance
12+
13+
def RegisterBot(self, request, context):
14+
return main_pb2.RegistrationStatus(name=self.ai.bot_name, message="")
15+
16+
def PregamePrepare(self, request, context):
17+
self.ai.pregame_prepare()
18+
return main_pb2.Empty()
19+
20+
def SelectPatron(self, request, context):
21+
patron = self.ai.select_patron(request.availablePatrons)
22+
return main_pb2.PatronIdMessage(patronId=patron)
23+
24+
def Play(self, request, context):
25+
import pdb; pdb.set_trace()
26+
move = self.ai.play(request.gameState, request.possibleMoves)
27+
return move
28+
29+
def GameEnd(self, request, context):
30+
self.ai.game_end(request)
31+
return main_pb2.Empty()
32+
33+
def CloseServer(self, request, context):
34+
print("Received CloseServer request. Shutting down server...")
35+
self.server_instance.bot_disconnected()
36+
context.set_code(grpc.StatusCode.OK)
37+
context.set_details(f"Bot {self.ai.bot_name}'s connection closed.")
38+
return main_pb2.Empty()
39+
40+
41+
class Server:
42+
43+
_instance = None
44+
_lock = Lock()
45+
46+
def __new__(cls, *args, **kwargs):
47+
with cls._lock:
48+
if not cls._instance:
49+
cls._instance = super().__new__(cls)
50+
cls._instance.active_bots = 0
51+
cls._instance.server = None
52+
cls._instance.lock = Lock()
53+
return cls._instance
54+
55+
def add_bot(self):
56+
with self.lock:
57+
self.active_bots += 1
58+
print(f"Bot connected. Active bots: {self.active_bots}")
59+
60+
def bot_disconnected(self):
61+
with self.lock:
62+
self.active_bots -= 1
63+
print(f"Bot disconnected. Active bots: {self.active_bots}")
64+
if self.active_bots == 0:
65+
self.shutdown_server()
66+
67+
def shutdown_server(self):
68+
if self.server:
69+
print("No active bots. Shutting down the server...")
70+
self.server.stop(0)
71+
else:
72+
print("Server is already stopped.")
73+
74+
def run_grpc_server(self, ai_instances: List[BaseAI], port=50000, debug_prints=True):
75+
self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
76+
for i, ai in enumerate(ai_instances):
77+
self.add_bot()
78+
main_pb2_grpc.add_AIServiceServicer_to_server(AIService(ai, self), self.server)
79+
self.server.add_insecure_port(f"[::]:{port+i}")
80+
self.server.start()
81+
return self.server

AIService/base_ai.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class BaseAI:
2+
def __init__(self, bot_name):
3+
self.bot_name = bot_name
4+
5+
def pregame_prepare(self):
6+
pass
7+
8+
def select_patron(self, available_patrons):
9+
raise NotImplementedError
10+
11+
def play(self, game_state, possible_moves):
12+
raise NotImplementedError
13+
14+
def game_end(self, final_state):
15+
pass

Game/game.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import List
2+
from AIService.base_ai import BaseAI
3+
from Game.runner import run_game_runner
4+
from AIService.Server import Server
5+
6+
class Game:
7+
def __init__(self):
8+
self.bots: List[BaseAI] = []
9+
10+
def register_bot(self, bot_instance: BaseAI):
11+
self.bots.append(bot_instance)
12+
13+
def run(self, bot1Name: str, bot2Name: str, start_game_runner=True, runs=1, enable_logs="NONE", log_destination="", seed=None, timeout=30):
14+
server = Server()
15+
grpc_server = server.run_grpc_server(filter(lambda bot: bot.bot_name == bot1Name or bot.bot_name == bot2Name, self.bots))
16+
if start_game_runner:
17+
if any([bot1Name == bot.bot_name for bot in self.bots]):
18+
bot1Name = "grpc:" + bot1Name
19+
if any([bot2Name == bot.bot_name for bot in self.bots]):
20+
bot2Name = "grpc:" + bot2Name
21+
run_game_runner(
22+
bot1Name,
23+
bot2Name,
24+
runs=runs,
25+
enable_logs=enable_logs,
26+
log_destination=log_destination,
27+
seed=seed,
28+
timeout=timeout
29+
)
30+
try:
31+
grpc_server.wait_for_termination()
32+
except KeyboardInterrupt:
33+
print("Server interrupted by user.")

Game/runner.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import os
2+
import subprocess
3+
4+
def run_game_runner(bot1: str, bot2: str, runs=1, threads=1, enable_logs="NONE", log_destination="", seed=None, timeout=30):
5+
game_runner_path = "GameRunner.exe"
6+
game_runner_dir = os.path.join(os.getcwd(), "GameRunner")
7+
8+
if not os.path.exists(game_runner_dir):
9+
raise FileNotFoundError(f"Couldn't find directory {game_runner_dir}")
10+
11+
os.chdir(game_runner_dir)
12+
13+
if not os.path.exists(game_runner_path):
14+
raise FileNotFoundError(f"Couldn't find file {game_runner_path}")
15+
16+
args = [game_runner_path, bot1, bot2]
17+
args += ["-n", str(runs)]
18+
args += ["-t", str(threads)]
19+
args += ["-l", enable_logs]
20+
if log_destination:
21+
args += ["-d", log_destination]
22+
if seed:
23+
args += ["-s", str(seed)]
24+
args += ["-to", str(timeout)]
25+
26+
try:
27+
result = subprocess.run(
28+
args,
29+
check=True,
30+
stdout=subprocess.PIPE,
31+
stderr=subprocess.PIPE,
32+
text=True
33+
)
34+
print(f"{result.stdout}")
35+
except subprocess.CalledProcessError as e:
36+
print(f"Error running GameRunner.exe:\n{e.stderr}")
37+
raise RuntimeError(e)
38+

GameRunner/GameRunner.dll

0 Bytes
Binary file not shown.

GameRunner/GameRunner.exe

0 Bytes
Binary file not shown.

GameRunner/GameRunner.pdb

28 Bytes
Binary file not shown.

GameRunner/TalesOfTribute.dll

0 Bytes
Binary file not shown.

GameRunner/TalesOfTribute.pdb

-8 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)