Extra

Deze pagina bevat uitvoerbare code.

Vier op een rij: spelers

Opgave: Vier op een rij: spelers

De klasse Board hieronder is die van de uitwerking van week 5, met twee veranderingen: de nieuwe methode cols_to_win (stap 1) en host_game met twee spelers (stap 4). De andere methoden zijn hetzelfde gebleven.

class Board:
    """Een bord voor Vier op een rij, met een willekeurig aantal rijen en kolommen."""

    def __init__(self, width, height):
        """Maak een leeg bord met de gegeven breedte en hoogte."""
        self._width = width
        self._height = height
        self._data = [[" "] * width for row in range(height)]

    @property
    def width(self):
        """Het aantal kolommen, alleen om te lezen."""
        return self._width

    @property
    def height(self):
        """Het aantal rijen, alleen om te lezen."""
        return self._height

    def __repr__(self):
        """Geeft het bord als string, met de kolomnummers eronder."""
        s = ""
        for row in range(self._height):
            s += "|"
            for col in range(self._width):
                s += self._data[row][col] + "|"
            s += "\n"
        s += (2 * self._width + 1) * "-" + "\n"
        for col in range(self._width):
            s += " " + str(col % 10)
        return s

    def add_move(self, col, ox):
        """Laat een steen ox in kolom col vallen."""
        for row in range(self._height - 1, -1, -1):
            if self._data[row][col] == " ":
                self._data[row][col] = ox
                return

    def clear(self):
        """Maakt het bord leeg."""
        for row in range(self._height):
            for col in range(self._width):
                self._data[row][col] = " "

    def set_board(self, move_string):
        """Speelt de kolommen in move_string, om en om X en O, te beginnen met X.

        b.set_board("012345") zet X en O om en om op de onderste rij,
        b.set_board("000000") zet ze om en om in de linkerkolom.
        move_string bestaat uit cijfers van één teken.
        """
        next_checker = "X"
        for col_char in move_string:
            col = int(col_char)
            if 0 <= col < self._width:
                self.add_move(col, next_checker)
            if next_checker == "X":
                next_checker = "O"
            else:
                next_checker = "X"

    def allows_move(self, col):
        """Geeft True als er in kolom col nog een steen bij kan."""
        return 0 <= col < self._width and self._data[0][col] == " "

    def is_full(self):
        """Geeft True als er nergens meer een steen bij kan."""
        for col in range(self._width):
            if self.allows_move(col):
                return False
        return True

    def del_move(self, col):
        """Haalt de bovenste steen uit kolom col; doet niets bij een lege kolom."""
        for row in range(self._height):
            if self._data[row][col] != " ":
                self._data[row][col] = " "
                return

    def wins_for(self, ox):
        """Geeft True als ox vier stenen op een rij heeft, in welke richting ook."""
        for row in range(self._height):
            for col in range(self._width):
                if self.in_a_row(ox, row, col, 0, 1):
                    return True
                if self.in_a_row(ox, row, col, 1, 0):
                    return True
                if self.in_a_row(ox, row, col, 1, 1):
                    return True
                if self.in_a_row(ox, row, col, -1, 1):
                    return True
        return False

    def in_a_row(self, ox, row, col, d_row, d_col):
        """Geeft True als er vanaf (row, col) vier keer ox ligt in richting (d_row, d_col)."""
        for i in range(4):
            r = row + i * d_row
            c = col + i * d_col
            if not (0 <= r < self._height and 0 <= c < self._width):
                return False
            if self._data[r][c] != ox:
                return False
        return True

    def cols_to_win(self, ox):
        """Geeft een oplopende lijst van de kolommen waarin ox met één zet wint."""
        cols = []
        for col in range(self._width):
            if self.allows_move(col):
                self.add_move(col, ox)
                if self.wins_for(ox):
                    cols.append(col)
                self.del_move(col)
        return cols

    def host_game(self, px, po):
        """Laat px (met X) en po (met O) Vier op een rij spelen; px begint."""
        print("Welkom bij Vier op een rij!")
        print()
        print(self)
        print()
        player = px
        while True:
            col = player.next_move(self)
            self.add_move(col, player.ox)
            print()
            print(self)
            print()
            if self.wins_for(player.ox):
                print(f"{player.ox} wint -- Gefeliciteerd!")
                break
            if self.is_full():
                print("Gelijkspel!")
                break
            if player is px:
                player = po
            else:
                player = px

Stap 1: cols_to_win(self, ox)

cols_to_win probeert elke kolom waarin een zet mag, en haalt de steen daarna meteen weer weg. Daarom is het bord na afloop hetzelfde als ervoor.

b = Board(7, 6)
b.set_board("334050505")
print(b)
before = repr(b)
assert b.cols_to_win("X") == [2, 5, 6]
assert b.cols_to_win("O") == [0]
assert repr(b) == before
assert Board(7, 6).cols_to_win("X") == []
| | | | | | | |
| | | | | | | |
| | | | | | | |
|O| | | | |X| |
|O| | |O| |X| |
|O| | |X|X|X| |
---------------
 0 1 2 3 4 5 6

Stap 2: de klasse Player

next_move geeft de eerste kolom waarin een zet mag. Op een vol bord zou de lus geen kolom vinden, maar daar wordt next_move niet aangeroepen: host_game stopt al bij een vol bord.

class Player:
    """Een speler van Vier op een rij, met een eenvoudige standaardzet."""

    def __init__(self, ox):
        """Maak een speler met de steen ox: "X" of "O"."""
        self.ox = ox

    def opponent(self):
        """Geeft de steen van de tegenstander."""
        if self.ox == "X":
            return "O"
        return "X"

    def next_move(self, board):
        """Geeft de meest linkse kolom waarin een zet mag."""
        for col in range(board.width):
            if board.allows_move(col):
                return col
b = Board(7, 6)
p = Player("X")
assert p.ox == "X"
assert p.opponent() == "O"
assert Player("O").opponent() == "X"
assert p.next_move(b) == 0
b.set_board("000000")
assert p.next_move(b) == 1

Stap 3: HumanPlayer

HumanPlayer erft de constructor en opponent van Player, en overschrijft alleen next_move. De klasse vraagt om invoer met input, en wordt hier daarom alleen gedefinieerd en niet gespeeld.

class HumanPlayer(Player):
    """Een mens, die zijn zet intypt."""

    def next_move(self, board):
        """Vraagt om een kolom, net zo lang tot de zet mag."""
        col = -1
        while not board.allows_move(col):
            col = int(input(f"Keuze van {self.ox}: "))
        return col

Stap 4: host_game(self, px, po)

De methode staat in de klasse Board hierboven. player is px vraagt of het object in player dezelfde speler is als px; zo wisselt de beurt zonder dat host_game naar "X" of "O" kijkt. De test staat bij stap 6, met spelers die niets hoeven in te typen.

Stap 5: SimpleAIPlayer

SimpleAIPlayer roept alleen methoden van het bord aan, en super().next_move voor de terugval.

class SimpleAIPlayer(Player):
    """Een computerspeler die één zet vooruitkijkt."""

    def next_move(self, board):
        """Wint als dat kan, blokkeert als dat moet, en kiest anders de standaardzet."""
        wins = board.cols_to_win(self.ox)
        if len(wins) > 0:
            return wins[0]
        blocks = board.cols_to_win(self.opponent())
        if len(blocks) > 0:
            return blocks[0]
        return super().next_move(board)
b = Board(7, 6)
b.set_board("42424")
assert SimpleAIPlayer("X").next_move(b) == 4
assert SimpleAIPlayer("O").next_move(b) == 4

b = Board(7, 6)
b.set_board("334050505")
assert SimpleAIPlayer("X").next_move(b) == 2
assert SimpleAIPlayer("O").next_move(b) == 0

b = Board(7, 6)
assert SimpleAIPlayer("X").next_move(b) == 0

Stap 6: ScriptedPlayer

ScriptedPlayer erft van niets. host_game gebruikt alleen ox en next_move, en die heeft hij. Het spel uit week 5, zonder in te typen:

class ScriptedPlayer:
    """Een speler die een vaste lijst zetten afwerkt; geen subklasse van Player."""

    def __init__(self, ox, moves):
        """Maak een speler met de steen ox die de kolommen in moves speelt."""
        self.ox = ox
        self._moves = list(moves)
        self._turn = 0

    def next_move(self, board):
        """Geeft de volgende kolom uit de lijst."""
        col = self._moves[self._turn]
        self._turn += 1
        return col
b = Board(7, 6)
b.host_game(ScriptedPlayer("X", [3, 2, 1, 0]), ScriptedPlayer("O", [4, 4, 2]))
assert b.wins_for("X")
assert not b.wins_for("O")
Welkom bij Vier op een rij!

| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | |X| | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | |X|O| | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | |X|X|O| | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | |O| | |
| | |X|X|O| | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | |O| | |
| |X|X|X|O| | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | |O| |O| | |
| |X|X|X|O| | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | |O| |O| | |
|X|X|X|X|O| | |
---------------
 0 1 2 3 4 5 6

X wint -- Gefeliciteerd!

Stap 7: spelen

Twee SimpleAIPlayers. De eerste twaalf zetten vallen allebei terug op de meest linkse kolom, en vullen kolom 0 en 1. De dertiende zet, van X, komt in kolom 2: dan heeft X drie stenen op de onderste rij, en blokkeert O in kolom 3. Aan het eind wint O. Er zit geen toeval in deze spelers, dus het spel loopt elke keer zo.

b = Board(7, 6)
b.host_game(SimpleAIPlayer("X"), SimpleAIPlayer("O"))
assert b.wins_for("O")
assert not b.wins_for("X")
Welkom bij Vier op een rij!

| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
|X| | | | | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
|O| | | | | | |
|X| | | | | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
|X| | | | | | |
|O| | | | | | |
|X| | | | | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
|O| | | | | | |
|X| | | | | | |
|O| | | | | | |
|X| | | | | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
|X| | | | | | |
|O| | | | | | |
|X| | | | | | |
|O| | | | | | |
|X| | | | | | |
---------------
 0 1 2 3 4 5 6


|O| | | | | | |
|X| | | | | | |
|O| | | | | | |
|X| | | | | | |
|O| | | | | | |
|X| | | | | | |
---------------
 0 1 2 3 4 5 6


|O| | | | | | |
|X| | | | | | |
|O| | | | | | |
|X| | | | | | |
|O| | | | | | |
|X|X| | | | | |
---------------
 0 1 2 3 4 5 6


|O| | | | | | |
|X| | | | | | |
|O| | | | | | |
|X| | | | | | |
|O|O| | | | | |
|X|X| | | | | |
---------------
 0 1 2 3 4 5 6


|O| | | | | | |
|X| | | | | | |
|O| | | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X| | | | | |
---------------
 0 1 2 3 4 5 6


|O| | | | | | |
|X| | | | | | |
|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X| | | | | |
---------------
 0 1 2 3 4 5 6


|O| | | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X| | | | | |
---------------
 0 1 2 3 4 5 6


|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X| | | | | |
---------------
 0 1 2 3 4 5 6


|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X|X| | | | |
---------------
 0 1 2 3 4 5 6


|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X|X|O| | | |
---------------
 0 1 2 3 4 5 6


|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X| | | | | |
|O|O|X| | | | |
|X|X|X|O| | | |
---------------
 0 1 2 3 4 5 6


|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X|O| | | | |
|O|O|X| | | | |
|X|X|X|O| | | |
---------------
 0 1 2 3 4 5 6


|O|O| | | | | |
|X|X| | | | | |
|O|O|X| | | | |
|X|X|O| | | | |
|O|O|X| | | | |
|X|X|X|O| | | |
---------------
 0 1 2 3 4 5 6


|O|O| | | | | |
|X|X|O| | | | |
|O|O|X| | | | |
|X|X|O| | | | |
|O|O|X| | | | |
|X|X|X|O| | | |
---------------
 0 1 2 3 4 5 6


|O|O|X| | | | |
|X|X|O| | | | |
|O|O|X| | | | |
|X|X|O| | | | |
|O|O|X| | | | |
|X|X|X|O| | | |
---------------
 0 1 2 3 4 5 6


|O|O|X| | | | |
|X|X|O| | | | |
|O|O|X| | | | |
|X|X|O| | | | |
|O|O|X|O| | | |
|X|X|X|O| | | |
---------------
 0 1 2 3 4 5 6


|O|O|X| | | | |
|X|X|O| | | | |
|O|O|X| | | | |
|X|X|O| | | | |
|O|O|X|O| | | |
|X|X|X|O|X| | |
---------------
 0 1 2 3 4 5 6


|O|O|X| | | | |
|X|X|O| | | | |
|O|O|X| | | | |
|X|X|O|O| | | |
|O|O|X|O| | | |
|X|X|X|O|X| | |
---------------
 0 1 2 3 4 5 6


|O|O|X| | | | |
|X|X|O| | | | |
|O|O|X|X| | | |
|X|X|O|O| | | |
|O|O|X|O| | | |
|X|X|X|O|X| | |
---------------
 0 1 2 3 4 5 6


|O|O|X| | | | |
|X|X|O|O| | | |
|O|O|X|X| | | |
|X|X|O|O| | | |
|O|O|X|O| | | |
|X|X|X|O|X| | |
---------------
 0 1 2 3 4 5 6


|O|O|X|X| | | |
|X|X|O|O| | | |
|O|O|X|X| | | |
|X|X|O|O| | | |
|O|O|X|O| | | |
|X|X|X|O|X| | |
---------------
 0 1 2 3 4 5 6


|O|O|X|X| | | |
|X|X|O|O| | | |
|O|O|X|X| | | |
|X|X|O|O| | | |
|O|O|X|O|O| | |
|X|X|X|O|X| | |
---------------
 0 1 2 3 4 5 6


|O|O|X|X| | | |
|X|X|O|O| | | |
|O|O|X|X| | | |
|X|X|O|O|X| | |
|O|O|X|O|O| | |
|X|X|X|O|X| | |
---------------
 0 1 2 3 4 5 6


|O|O|X|X| | | |
|X|X|O|O| | | |
|O|O|X|X|O| | |
|X|X|O|O|X| | |
|O|O|X|O|O| | |
|X|X|X|O|X| | |
---------------
 0 1 2 3 4 5 6


|O|O|X|X| | | |
|X|X|O|O|X| | |
|O|O|X|X|O| | |
|X|X|O|O|X| | |
|O|O|X|O|O| | |
|X|X|X|O|X| | |
---------------
 0 1 2 3 4 5 6


|O|O|X|X|O| | |
|X|X|O|O|X| | |
|O|O|X|X|O| | |
|X|X|O|O|X| | |
|O|O|X|O|O| | |
|X|X|X|O|X| | |
---------------
 0 1 2 3 4 5 6


|O|O|X|X|O| | |
|X|X|O|O|X| | |
|O|O|X|X|O| | |
|X|X|O|O|X| | |
|O|O|X|O|O| | |
|X|X|X|O|X|X| |
---------------
 0 1 2 3 4 5 6


|O|O|X|X|O| | |
|X|X|O|O|X| | |
|O|O|X|X|O| | |
|X|X|O|O|X| | |
|O|O|X|O|O|O| |
|X|X|X|O|X|X| |
---------------
 0 1 2 3 4 5 6


|O|O|X|X|O| | |
|X|X|O|O|X| | |
|O|O|X|X|O| | |
|X|X|O|O|X|X| |
|O|O|X|O|O|O| |
|X|X|X|O|X|X| |
---------------
 0 1 2 3 4 5 6


|O|O|X|X|O| | |
|X|X|O|O|X| | |
|O|O|X|X|O|O| |
|X|X|O|O|X|X| |
|O|O|X|O|O|O| |
|X|X|X|O|X|X| |
---------------
 0 1 2 3 4 5 6


|O|O|X|X|O| | |
|X|X|O|O|X|X| |
|O|O|X|X|O|O| |
|X|X|O|O|X|X| |
|O|O|X|O|O|O| |
|X|X|X|O|X|X| |
---------------
 0 1 2 3 4 5 6


|O|O|X|X|O|O| |
|X|X|O|O|X|X| |
|O|O|X|X|O|O| |
|X|X|O|O|X|X| |
|O|O|X|O|O|O| |
|X|X|X|O|X|X| |
---------------
 0 1 2 3 4 5 6


|O|O|X|X|O|O| |
|X|X|O|O|X|X| |
|O|O|X|X|O|O| |
|X|X|O|O|X|X| |
|O|O|X|O|O|O| |
|X|X|X|O|X|X|X|
---------------
 0 1 2 3 4 5 6


|O|O|X|X|O|O| |
|X|X|O|O|X|X| |
|O|O|X|X|O|O| |
|X|X|O|O|X|X| |
|O|O|X|O|O|O|O|
|X|X|X|O|X|X|X|
---------------
 0 1 2 3 4 5 6

O wint -- Gefeliciteerd!

Speel je als X kolom 2, 3 en 4, dan kiest O eerst twee keer de terugval, kolom 0, en blokkeert daarna kolom 1. Maar X kan in kolom 1 én in kolom 5 winnen, en O blokkeert er maar één. Een ScriptedPlayer laat dat zien, tegen een SimpleAIPlayer: een duck-typed speler en een subklasse van Player in één spel.

b = Board(7, 6)
b.host_game(ScriptedPlayer("X", [2, 3, 4, 5]), SimpleAIPlayer("O"))
assert b.wins_for("X")
Welkom bij Vier op een rij!

| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | |X| | | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
|O| |X| | | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
|O| |X|X| | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
|O| | | | | | |
|O| |X|X| | | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
|O| | | | | | |
|O| |X|X|X| | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
|O| | | | | | |
|O|O|X|X|X| | |
---------------
 0 1 2 3 4 5 6


| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
|O| | | | | | |
|O|O|X|X|X|X| |
---------------
 0 1 2 3 4 5 6

X wint -- Gefeliciteerd!

Tegen de computer spelen met HumanPlayer vraagt invoer, en staat hier daarom niet. Speel het zelf, in je eigen bestand.