Bot für das Brettspiel Tak

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Andreas Schneider
User
Beiträge: 16
Registriert: Mittwoch 29. März 2023, 18:37

Hallo :) Ich arbeite an einem Bot für das Brettspiel Tak. Das Spiel und die Regeln findet ihr auf playtak.com.
Leider komme ich bei meinem Code gerade seit längerer Zeit nicht weiter. Es wäre schön, wenn mir jemand weiterhelfen könnte.
Ich möchte zwei Bots mit verschiedenen Taktiken gegeneinander antreten lassen. Die Bots sollen eine beliebige Anzahl an Spielen gegeneinander antreten. In der ersten Runde spielt Blau Taktik2 in der zweiten Runde spielt Rot Taktik2. Am Ende soll in der Konsole ausgegeben werden, welche Taktik wie oft gewonnen hat. Zum Beispiel: Taktik1: 0 Siege, Taaktik2: 2 Siege. Ich habe mir zum Testen sowohl runde wie auch ende in der Konsole ausgeben lassen. Zur Zeit ist es leider so, dass die Runde bereits nach dem ersten Spiel auf 3 steht und nicht wie gewünscht auf 1. Außerdem steht ende nach dem ersten Spiel auf True und nicht wie gewünscht auf False. Bitte hilf mir, meinen Code dementsprechend anzupassen. Hier ist mein aktueller Code:

# cli.py
from bot import bot_move
from takpy import new_game, GameResult, Move, Color, Game, Piece

def pretty_print(game: Game):
print(game) # Print the TPS.
for rank, row in reversed(list(enumerate(game.board, 1))):
print(rank, end=" ")
for square in row:
if square is None:
print("🔳", end=" ")
continue
piece, colors = square
if colors[-1] == Color.White:
if piece == Piece.Flat:
print("🟧", end=" ")
elif piece == Piece.Wall:
print("🔶", end=" ")
elif piece == Piece.Cap:
print("🟠", end=" ")
elif colors[-1] == Color.Black:
if piece == Piece.Flat:
print("🟦", end=" ")
elif piece == Piece.Wall:
print("🔷", end=" ")
elif piece == Piece.Cap:
print("🔵", end=" ")
print()
print(" a b c d e f g h"[: 1 + game.size * 3])

taktik1Wins = 0
taktik2Wins = 0


def play_game(size, player_color, player_tactics):
global taktik1Wins, taktik2Wins # Make the variables global

game = new_game(size)
botgame = True
runde = 1
runden = 3
ende = False

while game.result == GameResult.Ongoing or runde < runden:
pretty_print(game) # Directly call pretty_print with the current game
if game.to_move == player_color and not botgame:
while True:
print("Possible moves:")
var = game.possible_moves
print(var)
user_input = input("enter move: ")
try:
move = Move.from_ptn(user_input)
except ValueError as error:
print(f"invalid PTN: {error}")
continue
try:
game.play(move)
break # valid move was entered and played
except ValueError as error:
print(f"invalid move: {error}")
else:
bot_tactics = player_tactics[runde - 1]
bot_move(game, runde, bot_tactics[0], bot_tactics[1])

if (
game.result == GameResult.WhiteWin
or game.result == GameResult.BlackWin
or game.result == GameResult.Draw
) and not ende:
runde += 1
ende = runde > runden-1

pretty_print(game)
if game.result == GameResult.WhiteWin:
print("🟧 wins!")
print("ende: ", ende)
print("runde: ", runde)
if runde == 1:
taktik1Wins += 1
elif runde == 2:
taktik2Wins += 1
print("Taktik1 : ", taktik1Wins)
print("Taktik2 : ", taktik2Wins)
elif game.result == GameResult.BlackWin:
print("🟦 wins!")
print("ende: ", ende)
print("runde: ", runde)
if runde == 1:
taktik2Wins += 1
elif runde == 2:
taktik1Wins += 1
print("Taktik1 : ", taktik1Wins)
print("Taktik2 : ", taktik2Wins)
elif game.result == GameResult.Draw:
print("It's a draw!")
print("ende: ", ende)
print("runde: ", runde)
print("Taktik1 : ", taktik1Wins)
print("Taktik2 : ", taktik2Wins)

if __name__ == "__main__":
# Spiel für Weiß
print("Spiel für Weiß:")
play_game(6, Color.White, [(2, 1), (1, 2)])

# Spiel für Schwarz
print("Spiel für Schwarz:")
play_game(6, Color.Black, [(1, 2), (2, 1)])



# bot.py
from takpy import Game, MoveKind, Piece, Color, Move
from typing import List, Tuple, Iterable, Optional, Union
import random

# Type aliases
Stack = Tuple[Piece, List[Color]]
Board = List[List[Optional[Stack]]]

def road_piece(piece: Piece) -> bool:
return piece == Piece.Flat or piece == Piece.Cap

def count_road_pieces(stacks: Iterable[Optional[Stack]], color: Color) -> int:
only_stacks = (stack for stack in stacks if stack is not None)
return sum(
road_piece(piece) for piece, colors in only_stacks if colors[-1] == color
)

def columns(board: Board) -> Board:
return [[row for row in board] for i in range(len(board[0]))]

def row_score(board: Board, color: Color) -> List[int]:
return [count_road_pieces(row, color) for row in board]

def col_score(board: Board, color: Color) -> List[int]:
return [count_road_pieces(col, color) for col in columns(board)]

# Constants for tactic 1
TACTIC1_PLACEMENT = 100
TACTIC1_SPREAD = 0
TACTIC1_FLAT = 100
TACTIC1_CAP = 50
TACTIC1_WALL = 0
TACTIC1_ROW_COLUMN_ROAD = 10
TACTIC1_CAP_NEXT_TO_OPPONENT_STACK = 50
TACTIC1_CENTER_PLACEMENT = 10

# Constants for tactic 2
TACTIC2_PLACEMENT = 100
TACTIC2_SPREAD = 0
TACTIC2_FLAT = 100
TACTIC2_CAP = 700
TACTIC2_WALL = 0
TACTIC2_ROW_COLUMN_ROAD = 20# bot.py
from takpy import Game, MoveKind, Piece, Color, Move
from typing import List, Tuple, Iterable, Optional, Union
import random

# Type aliases
Stack = Tuple[Piece, List[Color]]
Board = List[List[Optional[Stack]]]

def road_piece(piece: Piece) -> bool:
return piece == Piece.Flat or piece == Piece.Cap

def count_road_pieces(stacks: Iterable[Optional[Stack]], color: Color) -> int:
only_stacks = (stack for stack in stacks if stack is not None)
return sum(
road_piece(piece) for piece, colors in only_stacks if colors[-1] == color
)

def columns(board: Board) -> Board:
return [[row for row in board] for i in range(len(board[0]))]

def row_score(board: Board, color: Color) -> List[int]:
return [count_road_pieces(row, color) for row in board]

def col_score(board: Board, color: Color) -> List[int]:
return [count_road_pieces(col, color) for col in columns(board)]

# Constants for tactic 1
TACTIC1_PLACEMENT = 100
TACTIC1_SPREAD = 0
TACTIC1_FLAT = 100
TACTIC1_CAP = 50
TACTIC1_WALL = 0
TACTIC1_ROW_COLUMN_ROAD = 10
TACTIC1_CAP_NEXT_TO_OPPONENT_STACK = 50
TACTIC1_CENTER_PLACEMENT = 10

# Constants for tactic 2
TACTIC2_PLACEMENT = 100
TACTIC2_SPREAD = 0
TACTIC2_FLAT = 100
TACTIC2_CAP = 700
TACTIC2_WALL = 0
TACTIC2_ROW_COLUMN_ROAD = 20
TACTIC2_CAP_NEXT_TO_OPPONENT_STACK = 50
TACTIC2_CENTER_PLACEMENT = 10

def winning_move(game: Game, moves: List[Move]) -> Union[Move, None]:
for move in moves:
after_move = game.clone_and_play(move)
if after_move.result.color() == game.to_move:
return move
return None

def distance_from_center(row: int, col: int, size: int) -> float:
mid = (size - 1) / 2
return abs(row - mid) + abs(col - mid)

def neighbor_stacks(board: Board, size: int, row: int, col: int) -> List[Stack]:
neighbors = []
if row < size - 1:
neighbors.append(board[row + 1][col])
if row >= 1:
neighbors.append(board[row - 1][col])
if col < size - 1:
neighbors.append(board[row][col + 1])
if col >= 1:
neighbors.append(board[row][col - 1])
return [n for n in neighbors if n is not None]

def move_ordering(round: int, game: Game, tactic: int) -> List[Move]:
board = game.board
my_color = game.to_move
opp_color = game.to_move.next()
my_row_score = row_score(board, my_color)
my_col_score = col_score(board, my_color)

if tactic == 1 and round==1:
placement = TACTIC1_PLACEMENT
spread = TACTIC1_SPREAD
flat = TACTIC1_FLAT
cap = TACTIC1_CAP
wall = TACTIC1_WALL
row_column_road = TACTIC1_ROW_COLUMN_ROAD
cap_next_to_opponent_stack = TACTIC1_CAP_NEXT_TO_OPPONENT_STACK
center_placement = TACTIC1_CENTER_PLACEMENT
elif tactic == 2 and round==1:
placement = TACTIC2_PLACEMENT
spread = TACTIC2_SPREAD
flat = TACTIC2_FLAT
cap = TACTIC2_CAP
wall = TACTIC2_WALL
row_column_road = TACTIC2_ROW_COLUMN_ROAD
cap_next_to_opponent_stack = TACTIC2_CAP_NEXT_TO_OPPONENT_STACK
center_placement = TACTIC2_CENTER_PLACEMENT
elif tactic == 1 and round==2:
placement = TACTIC2_PLACEMENT
spread = TACTIC2_SPREAD
flat = TACTIC2_FLAT
cap = TACTIC2_CAP
wall = TACTIC2_WALL
row_column_road = TACTIC2_ROW_COLUMN_ROAD
cap_next_to_opponent_stack = TACTIC2_CAP_NEXT_TO_OPPONENT_STACK
center_placement = TACTIC2_CENTER_PLACEMENT
elif tactic == 2 and round==2:
placement = TACTIC1_PLACEMENT
spread = TACTIC1_SPREAD
flat = TACTIC1_FLAT
cap = TACTIC1_CAP
wall = TACTIC1_WALL
row_column_road = TACTIC1_ROW_COLUMN_ROAD
cap_next_to_opponent_stack = TACTIC1_CAP_NEXT_TO_OPPONENT_STACK
center_placement = TACTIC1_CENTER_PLACEMENT
else:
raise ValueError("Invalid tactic number")

def move_score(move: Move) -> Tuple[float, float]:
row, column = move.square
neighbors = neighbor_stacks(board, game.size, row, column)
distance = distance_from_center(row, column, game.size)

score = 0

if move.kind == MoveKind.Place:
score += placement
score -= center_placement * distance
elif move.kind == MoveKind.Spread:
score += spread

if move.piece == Piece.Flat:
score += flat
elif move.piece == Piece.Cap:
for _piece, colors in neighbors:
if colors[-1] == opp_color:
score += cap_next_to_opponent_stack * len(colors)
score += cap
elif move.piece == Piece.Wall:
score += wall

if road_piece(move.piece):
score += row_column_road * (my_row_score[row] + my_col_score[column])

# Return a tuple of score and a random value
return score, random.random()

moves = game.possible_moves
return sorted(moves, key=move_score, reverse=game.ply > 1)

def bot_move(game: Game, round, int, tactic: int = 1):
moves = move_ordering(game, tactic, round)
best_move = moves[0]
possibly_winning = winning_move(game, moves)
if possibly_winning is not None:
# Take immediate wins.
best_move = possibly_winning
else:
# Look for the first non-losing move.
for my_move in moves:
after_my_move = game.clone_and_play(my_move)
if after_my_move.result.color() == after_my_move.to_move:
continue # I made a road for the opponent accidentally.
if winning_move(after_my_move, move_ordering(after_my_move, tactic)) is None:
best_move = my_move
break
game.play(best_move)
print(f"the bot played {best_move}")

TACTIC2_CAP_NEXT_TO_OPPONENT_STACK = 50
TACTIC2_CENTER_PLACEMENT = 10

def winning_move(game: Game, moves: List[Move]) -> Union[Move, None]:
for move in moves:
after_move = game.clone_and_play(move)
if after_move.result.color() == game.to_move:
return move
return None

def distance_from_center(row: int, col: int, size: int) -> float:
mid = (size - 1) / 2
return abs(row - mid) + abs(col - mid)

def neighbor_stacks(board: Board, size: int, row: int, col: int) -> List[Stack]:
neighbors = []
if row < size - 1:
neighbors.append(board[row + 1][col])
if row >= 1:
neighbors.append(board[row - 1][col])
if col < size - 1:
neighbors.append(board[row][col + 1])
if col >= 1:
neighbors.append(board[row][col - 1])
return [n for n in neighbors if n is not None]

def move_ordering(game: Game, tactic: int) -> List[Move]:
board = game.board
my_color = game.to_move
opp_color = game.to_move.next()
my_row_score = row_score(board, my_color)
my_col_score = col_score(board, my_color)

if tactic == 1:
placement = TACTIC1_PLACEMENT
spread = TACTIC1_SPREAD
flat = TACTIC1_FLAT
cap = TACTIC1_CAP
wall = TACTIC1_WALL
row_column_road = TACTIC1_ROW_COLUMN_ROAD
cap_next_to_opponent_stack = TACTIC1_CAP_NEXT_TO_OPPONENT_STACK
center_placement = TACTIC1_CENTER_PLACEMENT
elif tactic == 2:
placement = TACTIC2_PLACEMENT
spread = TACTIC2_SPREAD
flat = TACTIC2_FLAT
cap = TACTIC2_CAP
wall = TACTIC2_WALL
row_column_road = TACTIC2_ROW_COLUMN_ROAD
cap_next_to_opponent_stack = TACTIC2_CAP_NEXT_TO_OPPONENT_STACK
center_placement = TACTIC2_CENTER_PLACEMENT
else:
raise ValueError("Invalid tactic number")

def move_score(move: Move) -> Tuple[float, float]:
row, column = move.square
neighbors = neighbor_stacks(board, game.size, row, column)
distance = distance_from_center(row, column, game.size)

score = 0

if move.kind == MoveKind.Place:
score += placement
score -= center_placement * distance
elif move.kind == MoveKind.Spread:
score += spread

if move.piece == Piece.Flat:
score += flat
elif move.piece == Piece.Cap:
for _piece, colors in neighbors:
if colors[-1] == opp_color:
score += cap_next_to_opponent_stack * len(colors)
score += cap
elif move.piece == Piece.Wall:
score += wall

if road_piece(move.piece):
score += row_column_road * (my_row_score[row] + my_col_score[column])

# Return a tuple of score and a random value
return score, random.random()

moves = game.possible_moves
return sorted(moves, key=move_score, reverse=game.ply > 1)

def bot_move(game: Game, round: int, tactic1: int, tactic2: int):
if game.to_move.next() == Color.White:
tactic = tactic2 if round == 2 else tactic1
elif game.to_move.next() == Color.Black:
tactic = tactic1 if round == 2 else tactic2
else:
raise ValueError("Invalid color")

moves = move_ordering(game, tactic)
best_move = moves[0]
possibly_winning = winning_move(game, moves)
if possibly_winning is not None:
# Take immediate wins.
best_move = possibly_winning
else:
# Look for the first non-losing move.
for my_move in moves:
after_my_move = game.clone_and_play(my_move)
if after_my_move.result.color() == after_my_move.to_move:
continue # I made a road for the opponent accidentally.
if winning_move(after_my_move, move_ordering(after_my_move, tactic)) is None:
best_move = my_move
break
game.play(best_move)
print(f"the bot played {best_move}")
Benutzeravatar
__blackjack__
User
Beiträge: 13129
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Andreas Schneider: Das ist in der Form ja nicht wirklich lesbar. Nicht nur das es nicht als Code gesetzt ist, im `bot.py`-Teil ist der Quelltext auch noch zweimal drin, in sich ”verschachtelt”, wobei die Kopien auch nicht identisch zu sein scheinen.

In dem ganzen kommt nicht einmal ``class`` vor — sollte es aber. Dafür hat da ``global`` nichts zu suchen.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Antworten