From b563c8422f996c386a4c1339177ac3dd0908f63f Mon Sep 17 00:00:00 2001 From: Bram van den Heuvel Date: Tue, 23 Jun 2026 23:45:33 +0200 Subject: [PATCH] Create proof-of-concept update for mute & chaos agents --- agents/__init__.py | 5 +++- agents/chaos.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++ agents/mute.py | 45 ++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 agents/chaos.py create mode 100644 agents/mute.py diff --git a/agents/__init__.py b/agents/__init__.py index cc664a8..91fb4ed 100644 --- a/agents/__init__.py +++ b/agents/__init__.py @@ -8,7 +8,10 @@ """ from .agent import Agent +from .chaos import AgentOfChaos +from .mute import MuteAgent __all__ = [ - "Agent" + "Agent", + "MuteAgent", ] diff --git a/agents/chaos.py b/agents/chaos.py new file mode 100644 index 0000000..372d5c1 --- /dev/null +++ b/agents/chaos.py @@ -0,0 +1,64 @@ +""" + The agent of chaos is an agent that always attempts to return a random + response. + + In most games, the Agent of chaos makes two considerations: + + 1. Which moves are valid? Since invalid moves fallback to "default" moves, + this can destabilize the probability distribution, making some moves + more likely to be chosen than others. As a result, we refrain from + "invalid" moves and take the effort to filter them out. + + 2. What's a reasonable probability distribution? Sometimes, a uniform + distribution across all options doesn't make a lot of sense. For + example, imagine asking the agent every turn whether they want to + use their super duper special single-use ability, and leaving that to + a 50/50 call every turn. It feels much more random if such an ability + is more randomly used throughout the GAME, than to have every choice + be a uniformly distributed decision. +""" + +from __future__ import annotations + +import random + +from .agent import Agent, Payload + + +class AgentOfChaos(Agent): + """ + The agent of chaos always aims to deliver a random response. + """ + + def __init__(self) -> None: + """ + Create a new instance of the agent of chaos. + """ + super().__init__( + name="Agent of chaos", + author="Bram", + version="1.0.1", + profile={ + "me.noordstar.peanuts.is_ai": False, + }, + ) + + self.add_tic_tac_toe(on_move=play_tic_tac_toe, profile={}) + + +def play_tic_tac_toe(payload : Payload) -> Payload: + """ + In tic-tac-toe, the agent of chaos makes uniformly distributed choices + on unclaimed tiles. + + :param payload: The incoming game state. + :type payload: dict[str, Any] + :return: The agent of chaos' random choice + :rtype: dict[str, Any] + """ + options = [ + int(k) + for k, v in dict(payload).items() + if k in "0123456789" and v == "" + ] + return { "move": random.choice(options) } \ No newline at end of file diff --git a/agents/mute.py b/agents/mute.py new file mode 100644 index 0000000..86b28c2 --- /dev/null +++ b/agents/mute.py @@ -0,0 +1,45 @@ +""" + The mute agent is a proof-of-concept agent of an agent that simply doesn't + respond - it always responds with an empty object. + + This is the simplest agent to implement, and it can be used as a test to + make sure that the "default" move can be picked properly each turn. +""" + +from __future__ import annotations + +from .agent import Agent, Payload + +def respond_mute(payload : Payload) -> Payload: + """ + Standard response from the mute. Returns an empty object, always. + + :param payload: Incoming game state. + :type payload: dict[str, Any] + :return: The empty object. + :rtype: dict[str, Any] + """ + return {} + +class MuteAgent(Agent): + """ + The mute agent class refuses to respond to any incoming game states, + and simply responds with an empty dictionary. For most games, + this means that the mute player takes the "default" option as a result + of not responding. + """ + + def __init__(self) -> None: + """ + Create a new instance of the mute agent. + """ + super().__init__( + name="Mute", + author="Bram", + version="1.0.1", + profile={ + "me.noordstar.peanuts.is_ai": False, + }, + ) + + self.add_tic_tac_toe(on_move=respond_mute, profile={})