Lösungen

Aufgabe: Interaktiven Spieler implementieren

class Human(Player):
    def select_move(self):
        moves = self.game.valid_moves()
        choice = ""
        while not choice.isnumeric() or len(moves) < int(choice):
            print("Your choice: ", end="")
            choice = input()
        return moves[int(choice) - 1]

Aufgabe: Verallgemeinertes Nim-Spiel implementieren

class Nim(Game):
    def __init__(self, player1, player2, counts):
        super().__init__(player1, player2)
        self.counts = counts

    def has_ended(self):
        for index in range(0, len(self.counts)):
            if self.counts[index] > 0:
                return False
        return True

    def winner(self):
        return self.current_player

    def make_move(self, move):
        heap = move[0]
        count = move[1]
        self.counts[heap] = self.counts[heap] - count

    def valid_moves(self):
        moves = []
        for heap in range(0, len(self.counts)):
            for count in range(1, self.counts[heap] + 1):
                moves.append([heap, count])
        return moves

    def __str__(self):
        lines = []
        lines.append(super().__str__())

        min = 0
        for index in range(0, len(self.counts)):
            count = self.counts[index]
            options = []
            for number in range(min + 1, min + count + 1):
                options.append(str(number))
            lines.append(" ".join(options))
            min = min + count

        return "\n".join(lines)

Bonusaufgabe: Tic-Tac-Toe implementieren

class TicTacToe(Game):
    def __init__(self, player1, player2):
        super().__init__(player1, player2)
        self.grid = [" "] * 9

    def has_ended(self):
        return not (" " in self.grid) or self.winner() != None

    def lines(self):
        result = []

        for row in range(0, 3):
            result.append(self.grid[3 * row : 3 * (row + 1)])

        for col in range(0, 3):
            result.append(
                self.grid[col : col + 1]
                + self.grid[col + 3 : col + 4]
                + self.grid[col + 6 : col + 7]
            )

        result.append(self.grid[0:1] + self.grid[4:5] + self.grid[8:9])
        result.append(self.grid[2:3] + self.grid[4:5] + self.grid[6:7])

        return result

    def winner(self):
        current_char = str(self.current_player)[0]
        waiting_char = str(self.waiting_player)[0]

        if ([current_char] * 3) in self.lines():
            return self.current_player

        if ([waiting_char] * 3) in self.lines():
            return self.waiting_player

        return None

    def make_move(self, move):
        self.grid[move] = str(self.current_player)[0]

    def undo_move(self, move):
        self.grid[move] = " "

    def valid_moves(self):
        moves = []
        for index in range(0, len(self.grid)):
            if self.grid[index] == " ":
                moves.append(index)
        return moves

    def __str__(self):
        grid = self.grid + []
        moves = self.valid_moves()
        for index in range(0, len(moves)):
            grid[moves[index]] = str(index + 1)

        result = super().__str__() + "\n"
        for row in range(0, 3):
            for col in range(0, 3):
                result = result + " " + grid[3 * row + col]
            result = result + "\n"

        return result

Bonusaufgabe: Reversi-Spielstände bewerten

Da bei Reversi eine zufällige Bewertungsfunktion in Kombination mit Spielbaumsuche erstaunlich gute Ergebnisse liefert, ist es gar nicht so leicht, eine bessere anzugeben. Die folgende Implementierung basiert auf den in der Aufgabenstellung genannten Kriterien.

def relative_eval(one, two):
    total = one + two
    if total == 0:
        return 0.5
    return one / total


class ReversiPlayer(PruningPlayer):
    def count_discs_at(self, player, indices):
        tiles = []
        for index in range(0, len(indices)):
            tiles.append(self.game.all_tiles[indices[index]])
        return self.game.count_discs(player, tiles)

    def eval_on_stop(self):
        if self.game.has_ended():
            return self.game.eval_on_end()

        current_discs = self.game.count_discs(
            self.game.current_player, self.game.all_tiles
        )
        waiting_discs = self.game.count_discs(
            self.game.waiting_player, self.game.all_tiles
        )
        disc_eval = relative_eval(current_discs, waiting_discs)

        current_moves = len(self.game.valid_moves())
        self.game.next_turn()
        waiting_moves = len(self.game.valid_moves())
        self.game.next_turn()
        move_eval = relative_eval(current_moves, waiting_moves)

        progress = (current_discs + waiting_discs) / 64
        disc_move_eval = progress * disc_eval + (1 - progress) * move_eval

        corners = [0, 7, 56, 63]
        current_corners = self.count_discs_at(self.game.current_player, corners)
        waiting_corners = self.count_discs_at(self.game.waiting_player, corners)
        corner_eval = relative_eval(current_corners, waiting_corners)

        return (disc_move_eval + corner_eval) / 2