Compare commits

..

1 Commits

Author SHA1 Message Date
Bram van den Heuvel bb3bb36665 Create functional ELO tracker 2026-06-23 19:42:18 +02:00
4 changed files with 63 additions and 25 deletions

9
elo.py
View File

@ -5,9 +5,6 @@
from elo_tracker import EloTracker from elo_tracker import EloTracker
from pyclient.games import TicTacToe from pyclient.games import TicTacToe
import pyclient
import time
GAME_FILE = "games.jsonl" GAME_FILE = "games.jsonl"
PLAYER_FILE = "known_players.json" PLAYER_FILE = "known_players.json"
@ -20,14 +17,14 @@ def main() -> int:
tracker.start_periodic_matches( tracker.start_periodic_matches(
game=TicTacToe.empty(), game=TicTacToe.empty(),
interval_seconds=60, interval_seconds=10 * 60,
player_count=2, player_count=2,
) )
try: try:
while True: tracker.start_server(import_name=__name__)
pass
except KeyboardInterrupt: except KeyboardInterrupt:
print("Noticed KeyboardInterrupt, stopping match daemon...")
tracker.stop_periodic_matches() tracker.stop_periodic_matches()
return 0 return 0

View File

@ -206,7 +206,7 @@ class Match:
participants : list[tuple[PlayerIdentifier, FinishState]] = [] participants : list[tuple[PlayerIdentifier, FinishState]] = []
for i, agent in enumerate(players): for i, agent in enumerate(players):
finish_state = results.get(i, None) finish_state = results.get(i + 1, None)
if finish_state is None: if finish_state is None:
continue continue
@ -395,6 +395,9 @@ class EloTracker:
rating_1 = self.__get_stat(player_id=player_id1).elo rating_1 = self.__get_stat(player_id=player_id1).elo
for player_id2, result2 in m.participants: for player_id2, result2 in m.participants:
if player_id1 == player_id2:
continue
rating_2 = self.__get_stat(player_id=player_id2).elo rating_2 = self.__get_stat(player_id=player_id2).elo
expected_score = 1 / (1 + 10 ** ((rating_2 - rating_1) / STD_DEV_DIFF)) expected_score = 1 / (1 + 10 ** ((rating_2 - rating_1) / STD_DEV_DIFF))
@ -583,7 +586,7 @@ class EloTracker:
:rtype: pyclient.GameReplay :rtype: pyclient.GameReplay
:raises ValueError: One of the URLs could not be accessed. :raises ValueError: One of the URLs could not be accessed.
""" """
agents = [ agents : list[Any] = [
pyclient.Agent.from_url(url, debug=self.debug) pyclient.Agent.from_url(url, debug=self.debug)
for url in players for url in players
] ]

View File

@ -1,5 +1,6 @@
{ {
"players": [ "players": [
"https://bmt001.noordstar.me" "https://bmt001.noordstar.me",
"https://bmt002.noordstar.me"
] ]
} }

View File

@ -18,23 +18,17 @@ def main() -> int:
""" """
player = PyServer( player = PyServer(
# Customize this to whatever you'd like to call your player # Customize this to whatever you'd like to call your player
name="Mute", name="My super smart robot player",
# Custom information that you can use to tell people about this player # Custom information that you can use to tell people about this player
profile={ profile={},
"me.noordstar.peanuts.agent.version": "1.0.0",
"me.noordstar.peanuts.is_ai": False,
"me.noordstar.peanuts.author": "Bram",
"me.noordstar.peanuts.containerized": True,
"version": "1.0.0",
},
# Unless you know what you're doing, don't touch this. # Unless you know what you're doing, don't touch this.
import_name=__name__, import_name=__name__,
) )
# Register games! Comment out any you don't want your player to play. # Register games! Comment out any you don't want your player to play.
player.add_tic_tac_toe(on_move=respond_mute, profile={}) player.add_tic_tac_toe(on_move=play_tic_tac_toe, profile={})
# Start listening for games # Start listening for games
player.start( player.start(
@ -44,19 +38,62 @@ def main() -> int:
return 0 return 0
def respond_mute(payload : dict[str, Any]) -> dict[str, Any]: def play_tic_tac_toe(payload : dict[str, Any]) -> dict[str, Any]:
""" """
Always respond with an empty dictionary. This means the user should Play a game of tic-tac-toe.
always take the "default" move.
A well-programmed game must NOT raise an error as a result of this. You receive a payload that looks like this:
:param payload: The game state which is completely ignored. {
"1": "X", "2": "", "3": "O",
"4": "X", "5": "O", "6": "",
"7": "", "8": "", "9": "",
"your_token": "X"
}
And you're expected to return a response of which field you'd like to
place your piece in. For example, if you wish to place your token in
field 7, your response should look like this:
{ "move": 7 }
The board is arranged as follows:
1 | 2 | 3
---+---+---
4 | 5 | 6
---+---+---
7 | 8 | 9
:param payload: The incoming JSON that contains the game state.
:type payload: dict[str, Any] :type payload: dict[str, Any]
:return: An empty dictionary :return: The move you wish to take.
:rtype: dict[str, Any] :rtype: dict[str, Any]
""" """
return {}
# Try printing the payload to see what it looks like!
print(payload)
options = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, ]
# 1. Try filtering out the impossible moves!
# If an X or O was already placed at a field, remove it from the options
#
# 2. Try finding two in a row! If possible, you can try to place the third
# item on the board and get 3 in a row.
#
# 3. Perhaps you can block the opponent from getting 3 in a row?
#
# Now, pick any of the remaining options.
# This is just a simple implementation. Naturally, you're welcome to try
# your own logic.
return { "move": random.choice(options) }
if __name__ == "__main__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())