Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added player's choice for playing pawn #2

Merged
merged 4 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions examples/player_list_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
# It will display a table with the results of each player against each other player.

players_classes = [
BasicPlayer,
RandomPlayer,
FirstChoicePlayer,
BasicPlayer,

]

# Init the tester
Expand All @@ -24,19 +25,25 @@
nb_games = 1000
results = {} # We will count the number of victories for each player

# Initialize global victory type evaluator
dic_global_win_lose_type = {}

# Match all combinations of players
for i, player1_class in enumerate(players_classes):
# Get the name of the player
player1_name = player1_class().name()
player1_name = player1_class(i).name()
results[player1_name] = {}

for j, player2_class in enumerate(players_classes):
if i == j:
continue

# Init the players
p1 = player1_class()
p2 = player2_class()
p1 = player1_class(1)
p2 = player2_class(2)

# Initialize victory type evaluator dic
dic_win_lose_type = {p1.name(): {}, p2.name(): {}}

# Get the name of the player 2
player2_name = p2.name()
Expand All @@ -45,9 +52,12 @@
print(f"\n\nPlaying {player1_name} vs {player2_name}:")

# Play 100 games
victories_number = tester.play_1v1(p1, p2, nb_games=nb_games)
victories_number, dic_global_win_lose_type[f"{p1.name()}vs{p2.name()}"] = \
tester.play_1v1(p1, p2, nb_games=nb_games, dic_win_lose_type=dic_win_lose_type)

results[player1_name][player2_name] = victories_number[player1_name]

print(f"dic_global_win_lose_type = \n{dic_global_win_lose_type}")

print()
print("Results:")
Expand Down
8 changes: 5 additions & 3 deletions examples/random_players_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from santorinai.tester import Tester
from santorinai.player_examples.random_player import RandomPlayer
from santorinai.player_examples.first_choice_player import FirstChoicePlayer
from santorinai.player_examples.basic_player import BasicPlayer

# Init the tester
tester = Tester()
Expand All @@ -12,8 +13,9 @@
tester.display_board = True # Display a graphical view of the board in a window

# Init the players
my_player = FirstChoicePlayer()
random_payer = RandomPlayer()
my_player = FirstChoicePlayer(1)
basic_player = BasicPlayer(1)
random_payer = RandomPlayer(2)

# Play 100 games
tester.play_1v1(my_player, random_payer, nb_games=100)
tester.play_1v1(basic_player, random_payer, nb_games=100)
31 changes: 26 additions & 5 deletions santorinai/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def __init__(self, number_of_players: int):
self.pawn_turn = 1
self.winner_player_number = None
self.turn_number = 1
self.player_turn = 1

def is_move_possible(
self, start_pos: Tuple[int, int], end_pos: Tuple[int, int]
Expand Down Expand Up @@ -212,6 +213,20 @@ def get_playing_pawn(self) -> Pawn:
"""
return self.pawns[self.pawn_turn - 1]

def get_player_pawns(self, player_number: int) -> List[Pawn]:
"""
Gets the pawn of the current player.

Returns:
Pawn: The pawn of the current player.
"""
l_pawns = []
for pawn in self.pawns:
if pawn.player_number == player_number:
l_pawns.append(pawn)

return l_pawns

def get_possible_movement_positions(self, pawn: Pawn) -> List[Tuple[int, int]]:
"""
Gets all the possible moves for a given pawn.
Expand Down Expand Up @@ -347,10 +362,10 @@ def place_pawn(self, position: Tuple[int, int]) -> Tuple[bool, str]:
return True, "The pawn was placed."

def play_move(
self, move_position: Tuple[int, int], build_position: Tuple[int, int]
self, pawn_number:int, move_position: Tuple[int, int], build_position: Tuple[int, int]
) -> Tuple[bool, str]:
"""
Plays a move on the board with the current playing pawn.
Plays a move on the board with the chosen playing pawn.

Args:
move_position (tuple): The position (x, y) to move the pawn to.
Expand All @@ -360,12 +375,15 @@ def play_move(
bool: True if the move was played, False otherwise.
str: A string describing why the move was not played.
"""
# Get the moving pawn
pawn = self.pawns[pawn_number - 1]

# Check if the game is over
if self.is_game_over():
return False, "The game is over."

# Get the pawn of the current player
pawn = self.get_playing_pawn()
# pawn = self.get_playing_pawn()

# Check if the pawn has been placed
if pawn.pos[0] is None or pawn.pos[1] is None:
Expand Down Expand Up @@ -492,8 +510,11 @@ def next_turn(self):
Changes the turn.
"""
self.pawn_turn += 1
if self.pawn_turn > self.nb_pawns:
self.pawn_turn = 1
# if self.pawn_turn > self.nb_pawns:
# self.pawn_turn = 1
self.player_turn += 1
if self.player_turn > self.nb_players:
self.player_turn = 1

self.turn_number += 1

Expand Down
10 changes: 6 additions & 4 deletions santorinai/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ class Player:
"""
A player of Santorini, has a name and can play a move given a board
"""
def __init__(self, player_number: int, log_level=0) -> None:
self.log_level = log_level
self.player_number = player_number

@abstractmethod
def name(self):
Expand All @@ -32,12 +35,11 @@ def place_pawn(self, board: Board, pawn: Pawn) -> Tuple[int, int]:

@abstractmethod
def play_move(
self, board: Board, pawn: Pawn
) -> Tuple[Tuple[int, int], Tuple[int, int]]:
self, board: Board
) -> Tuple[int, Tuple[int, int], Tuple[int, int]]:
"""
Play a move given a board
Choose a pawn and play a move given a board
:param board: the board
:param pawn: the pawn that needs to be moved and that needs to build
:return: two positions of the form (x1, y1), (x2, y2)

The first coordinate corresponds to the new position of the pawn
Expand Down
103 changes: 62 additions & 41 deletions santorinai/player_examples/basic_player.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
from santorinai.player import Player
from santorinai.board import Board
from santorinai.pawn import Pawn
Expand All @@ -17,9 +18,8 @@ class BasicPlayer(Player):
:log_level: 0: no output, 1: Move choices
"""

def __init__(self, log_level=0) -> None:
super().__init__()
self.log_level = log_level
def __init__(self, player_number, log_level=0) -> None:
super().__init__(player_number, log_level)

def name(self):
return "Extra BaThick!"
Expand Down Expand Up @@ -67,52 +67,73 @@ def place_pawn(self, board: Board, pawn):

return choice(available_positions)

def play_move(self, board, pawn):
available_positions = board.get_possible_movement_positions(pawn)

if pawn.pos[0] is None or pawn.pos[1] is None:
# Pawn is not placed yet
raise Exception("Pawn is not placed yet")

current_level = board.board[pawn.pos[0]][pawn.pos[1]]
def play_move(self, board):
available_pawns = []
best_spot = None
best_spot_level = 0
for pos in available_positions:
if board.board[pos[0]][pos[1]] == 3:
# We can win!
if self.log_level:
print("Winning move")
return pos, (None, None)

pos_level = board.board[pos[0]][pos[1]]
if pos_level <= current_level + 1 and pos_level > best_spot_level:
# We can go up
best_spot = pos
best_spot_level = pos_level

# Check if we can prevent the opponent from winning
enemy_pawns = self.get_enemy_pawns(board, pawn)
for enemy_pawn in enemy_pawns:
winning_moves = self.get_winning_moves(board, enemy_pawn)
for winning_move in winning_moves:
for available_pos in available_positions:
if board.is_position_adjacent(winning_move, available_pos):
# We can prevent the opponent from winning
# Building on the winning move
if self.log_level:
print("Preventing opponent from winning")
return available_pos, winning_move
best_spot_pawn_idx = None
best_spot_level = -2

# Iterate over available pawns
for idx, pawn in enumerate(board.get_player_pawns(self.player_number)):
if pawn.pos[0] is None or pawn.pos[1] is None:
# Pawn is not placed yet
raise Exception("Pawn is not placed yet")

available_pawns.append(pawn)
available_positions = board.get_possible_movement_positions(pawn)

current_level = board.board[pawn.pos[0]][pawn.pos[1]]
for pos in available_positions:
# Check if winning position available
if board.board[pos[0]][pos[1]] == 3:
# We can win!
if self.log_level:
print("Winning move")
return available_pawns[idx].number, pos, (None, None)

# Check if possible to go up
pos_level = board.board[pos[0]][pos[1]]
if pos_level <= current_level + 1 and pos_level > best_spot_level + current_level:
# We can go up
best_spot = pos
best_spot_pawn_idx = idx
best_spot_level = pos_level

# Check if we can prevent the opponent from winning
enemy_pawns = self.get_enemy_pawns(board, pawn)
for enemy_pawn in enemy_pawns:
winning_moves = self.get_winning_moves(board, enemy_pawn)
for winning_move in winning_moves:
for available_pos in available_positions:
if board.is_position_adjacent(winning_move, available_pos):
# We can prevent the opponent from winning
# Building on the winning move
if self.log_level:
print("Preventing opponent from winning")
return available_pawns[idx].number, available_pos, winning_move

# Move up if we can
if best_spot:
if self.log_level:
print("Moving up")
pawn.move(best_spot)
build_positions = board.get_possible_building_positions(pawn)
return best_spot, choice(build_positions)
best_pawn = available_pawns[best_spot_pawn_idx]
best_pawn.move(best_spot)
available_build_pos = board.get_possible_building_positions(best_pawn)
if available_build_pos:
build_choice = choice(available_build_pos)
else:
build_choice = None

return available_pawns[best_spot_pawn_idx].number, best_spot, build_choice

if self.log_level:
print("Random move")

# play randomly
return choice(board.get_possible_movement_and_building_positions(pawn))
pawn = choice(board.get_player_pawns(self.player_number))
t_move_build = board.get_possible_movement_and_building_positions(pawn)
if t_move_build:
t_move_build = choice(t_move_build)
else:
t_move_build = (None,None)
return (pawn.number,) + t_move_build
14 changes: 10 additions & 4 deletions santorinai/player_examples/first_choice_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class FirstChoicePlayer(Player):
"""
A player that places his pawns and moves them at the first possible position
"""
def __init__(self, player_number, log_level=0) -> None:
super().__init__(player_number, log_level)

def name(self):
return "Firsty First"
Expand All @@ -16,16 +18,20 @@ def place_pawn(self, board: Board, pawn: Pawn):
my_choice = available_positions[0]
return my_choice

def play_move(self, board: Board, pawn: Pawn):
def play_move(self, board: Board):
# Choose always first pawn
l_pawns = board.get_player_pawns(self.player_number)
pawn = l_pawns[0]

# Get movement positions
available_move_positions = board.get_possible_movement_positions(pawn)
if len(available_move_positions) == 0:
# The pawn cannot move
return None, None
return pawn.number, None, None

my_move_choice = available_move_positions[0]

# Simulate the move (this will not impact the actual board)
# Simulate the move (Need to use pawn copy since returned pawn will be moved)
pawn.move(my_move_choice)

# Get construction positions
Expand All @@ -37,4 +43,4 @@ def play_move(self, board: Board, pawn: Pawn):
# Their is always at least one position available
my_build_choice = available_build_positions[0]

return my_move_choice, my_build_choice
return pawn.number, my_move_choice, my_build_choice
15 changes: 10 additions & 5 deletions santorinai/player_examples/random_player.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from santorinai.player import Player

from random import choice


class RandomPlayer(Player):
"""
A player that places his pawns and moves them randomly
"""
def __init__(self, player_number, log_level=0) -> None:
super().__init__(player_number, log_level)

def name(self):
return "Randy Random"
Expand All @@ -16,20 +17,24 @@ def place_pawn(self, board, pawn):
my_choice = choice(available_positions)
return my_choice

def play_move(self, board, pawn):
def play_move(self, board):
# Choose pawn randomly
l_pawns = board.get_player_pawns(self.player_number)
pawn = choice(l_pawns)

# Get movement positions
available_positions = board.get_possible_movement_positions(pawn)
if len(available_positions) == 0:
# The pawn cannot move
return None, None
return pawn.number, None, None

my_move_choice = choice(available_positions)

# Simulate the move
# Simulate the move (Need to use pawn copy since returned pawn will be moved)
pawn.move(my_move_choice)

# Get construction positions for the new position
available_positions = board.get_possible_building_positions(pawn)
my_build_choice = choice(available_positions)

return my_move_choice, my_build_choice
return pawn.number, my_move_choice, my_build_choice
Loading
Loading