Create UrHeuristic agent
parent
5453a8acc3
commit
a98e2c3c1e
|
|
@ -0,0 +1,121 @@
|
|||
"""
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue