Extra¶
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.