Compare commits

..

No commits in common. "agent/ur-heuristic-1.0.0" and "main" have entirely different histories.

3 changed files with 5 additions and 131 deletions

View File

@ -1,121 +0,0 @@
"""
This module contains a heuristic in the Royal Game of Ur.
"""
from __future__ import annotations
import random
from .agent import Agent, Payload
BAD_OMEN = {
-1 : 4 / 16,
-2 : 6 / 16,
-3 : 4 / 16,
-4 : 1 / 16,
}
class UrHeuristic(Agent):
"""
Describe here what your agent does and how it behaves! This will help
others understand how your agent works.
"""
def __init__(self) -> None:
"""
Create a custom instance of your agent. This function allows you
to define which games your agent can play.
You may add parameters to the function if your agent requires more
information to be able to operate.
"""
super().__init__(
name="Ur heuristic agent",
author="Bram",
version="1.0.0",
profile={
"me.noordstar.peanuts.is_ai": False,
},
)
# Indicate that you're willing to play tic-tac-toe
# Remove this if you don't want your bot to participate there.
self.add_royal_game_of_ur(on_move=self.play_ur, profile={})
def enemy_at(self, payload : Payload, i : int) -> bool:
"""
Determine whether an enemy is at a given position.
"""
return payload.get(f"{i}_enemy", payload.get(str(i), "")) == "ENEMY"
def play_ur(self, payload : Payload) -> Payload:
"""
Use a heuristic. The score of each state is the total number of
spaces that your pieces have moved forward minus the total number
of spaces that your opponent's pieces have moved forward.
"""
moves = [ int(n) for n in payload.get("valid_moves", [0]) ]
star_fields = [ int(n) for n in payload.get("star_fields", []) ]
safe_fields = [ int(n) for n in payload.get("safe_fields", []) ]
roll = int(payload.get("roll", 0))
scores : dict[int, float] = { i : 0 for i in moves }
for m in moves:
dest = m + roll
# The total sum of steps taken will be the same regardless of which
# piece we move. Hence we ignore the roll itself.
#
# There's a few other aspects we need to keep in mind, however.
# If we can hit a star, we'll be able to roll again.
# The expected value is that we'll be able to take 2 extra steps.
if dest in star_fields:
scores[m] += 2
# If we can capture an enemy's piece, they'll be put back to start.
# That is a massive gain of points.
if self.enemy_at(payload=payload, i=dest) and dest not in safe_fields and dest not in star_fields:
scores[m] += dest
# Every spot has a certain danger to it.
# Improve the score based on the danger of the spot we're leaving.
scores[m] += self.risk_score(payload=payload, i=m)
# Similarly, punish the score based on the danger we're getting
# ourselves into.
scores[m] -= self.risk_score(payload=payload, i=dest)
# Near the end, you can get stuck if it takes too long to finish
# Therefore, slightly before the finish, you're rewarded with
# finishing precisely.
off = m - 15
if off in BAD_OMEN:
scores[m] = 1 / BAD_OMEN[off]
highscore = max(scores.values())
keys = [ pos for pos, score in scores.items() if score == highscore ]
return { "move": random.choice(keys) }
def risk_score(self, payload : Payload, i : int) -> float:
"""
What is the risk score of a given field?
The risk score is the expected number of steps you'll need to move
back as a result of your opponent's next turn.
"""
if i in list(payload.get("safe_fields", [])) or i in list(payload.get("star_fields", [])):
return 0.0
score : float = 0.0
for offset, risk in BAD_OMEN.items():
if self.enemy_at(payload=payload, i=i+offset):
score += risk
return i * score

View File

@ -76,10 +76,6 @@ class RoyalGameOfUr(Game):
for i in range(BOARD_LENGTH - self.roll): for i in range(BOARD_LENGTH - self.roll):
dest = i + self.roll dest = i + self.roll
# Skip star fields if they're already occupied
if dest in STAR_FIELDS and dest not in SAFE_FIELDS and self.board[dest] != ( 0, 0 ):
continue
if self.board[i][self.mover] > 0: if self.board[i][self.mover] > 0:
if dest in STACK_FIELDS or self.board[dest][self.mover] == 0: if dest in STACK_FIELDS or self.board[dest][self.mover] == 0:
yield i yield i
@ -93,20 +89,19 @@ class RoyalGameOfUr(Game):
d : dict[str, Any] = dict( d : dict[str, Any] = dict(
enemies_at_start=self.board[0][ot_index], enemies_at_start=self.board[0][ot_index],
enemies_at_finish=self.board[BOARD_LENGTH-1][ot_index], enemies_at_finish=self.board[15][ot_index],
roll=self.roll,
safe_fields=list(SAFE_FIELDS), safe_fields=list(SAFE_FIELDS),
stack_fields=list(STACK_FIELDS), stack_fields=list(STACK_FIELDS),
star_fields=list(STAR_FIELDS), star_fields=list(STAR_FIELDS),
tokens_at_start=self.board[0][my_index], tokens_at_start=self.board[0][my_index],
tokens_at_finish=self.board[BOARD_LENGTH-1][my_index], tokens_at_finish=self.board[15][my_index],
valid_moves=list(self.__valid_moves()), valid_moves=list(self.__valid_moves()),
) )
for i in range(BOARD_LENGTH): for i in range(BOARD_LENGTH):
d[str(i)] = ( d[str(i)] = (
"YOU" if self.board[i][my_index] > 0 else ( "YOU" if self.board[i][my_index] > 0 else (
"ENEMY" if i not in SAFE_FIELDS and self.board[i][ot_index] > 0 else ( "ENEMY" if my_index not in SAFE_FIELDS and self.board[i][ot_index] > 0 else (
"" ""
))) )))

View File

@ -4,7 +4,7 @@
from __future__ import annotations from __future__ import annotations
import agents.ur_heuristic import agents
from pyserver import PyServer from pyserver import PyServer
@ -15,7 +15,7 @@ def main() -> int:
:return: Exit code :return: Exit code
:rtype: int :rtype: int
""" """
player = PyServer(agents.ur_heuristic.UrHeuristic()) player = PyServer(agents.AgentOfChaos())
# Start listening for games # Start listening for games
player.start( player.start(