Extra¶
Vier op een rij: de klasse Board¶
Opgave: Vier op een rij: de klasse Board
De klasse hieronder bevat alle methoden van stap 1 tot en met 10. Voor
wins_for gebruikt deze uitwerking geen losse in-een-rij-functies maar één
hulpmethode, in_a_row, die vanaf een vakje vier stappen in een richting
controleert. De richting is een verschil in rij en kolom: (0, 1) is naar
rechts, (1, 0) naar beneden, en (1, 1) en (-1, 1) zijn de twee
diagonalen. Zo zijn de vier richtingen één stuk code.
De opgave zegt dat je de in-een-rij-functies van Programmeren 1 buiten de klasse
zet. Dat zijn gewone functies die een lijst van lijsten meekrijgen. in_a_row
is anders gemaakt: ze leest self._data en de afmetingen van het bord zelf, en
werkt dus alleen voor een Board. Daarom is ze hier een methode.
host_game vraagt om invoer met input en wordt hier daarom niet uitgevoerd.
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 host_game(self):
"""Laat twee mensen Vier op een rij spelen, om de beurt."""
print("Welkom bij Vier op een rij!")
print()
print(self)
print()
ox = "X"
while True:
users_col = -1
while not self.allows_move(users_col):
users_col = int(input(f"Keuze van {ox}: "))
self.add_move(users_col, ox)
print()
print(self)
print()
if self.wins_for(ox):
print(f"{ox} wint -- Gefeliciteerd!")
break
if self.is_full():
print("Gelijkspel!")
break
if ox == "X":
ox = "O"
else:
ox = "X"
Stap 1: de constructor __init__(self, width, height)¶
Breedte en hoogte zijn van buiten te lezen, via de properties.
b = Board(7, 6)
assert b.width == 7
assert b.height == 6
Maar niet te veranderen:
b.width = 8
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[3], line 1
----> 1 b.width = 8
AttributeError: property 'width' of 'Board' object has no setter
Stap 2: __repr__(self)¶
print(Board(7, 6))
print(Board(15, 5))
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
---------------
0 1 2 3 4 5 6
| | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | |
-------------------------------
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
Stap 3: add_move(self, col, ox)¶
De lus loopt van de onderste rij naar boven en zet de steen in het eerste lege vakje.
b = Board(7, 6)
b.add_move(0, "X")
b.add_move(0, "O")
b.add_move(0, "X")
b.add_move(3, "O")
b.add_move(4, "O")
b.add_move(5, "O")
b.add_move(6, "O")
print(b)
| | | | | | | |
| | | | | | | |
| | | | | | | |
|X| | | | | | |
|O| | | | | | |
|X| | |O|O|O|O|
---------------
0 1 2 3 4 5 6
Stap 4: clear(self)¶
b.clear()
print(b)
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
---------------
0 1 2 3 4 5 6
Stap 5: set_board(self, move_string)¶
De conditie is 0 <= col < self.width. Met <= zou een cijfer gelijk aan de
breedte doorgelaten worden, en dan valt add_move buiten het bord.
b = Board(7, 6)
b.set_board("7")
print(b)
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
---------------
0 1 2 3 4 5 6
Stap 6: allows_move(self, col)¶
Een kolom heeft nog ruimte zolang het bovenste vakje, in rij 0, leeg is.
b = Board(2, 2)
b.add_move(0, "X")
b.add_move(0, "O")
print(b)
assert not b.allows_move(-1)
assert not b.allows_move(0)
assert b.allows_move(1)
assert not b.allows_move(2)
|O| |
|X| |
-----
0 1
Stap 7: is_full(self)¶
b = Board(2, 2)
assert not b.is_full()
b.set_board("0011")
print(b)
assert b.is_full()
|O|O|
|X|X|
-----
0 1
Stap 8: del_move(self, col)¶
De lus loopt nu van boven naar beneden en maakt het eerste vakje met een steen leeg.
b = Board(2, 2)
b.set_board("0011")
b.del_move(1)
b.del_move(1)
b.del_move(1)
b.del_move(0)
print(b)
| | |
|X| |
-----
0 1
Stap 9: wins_for(self, ox)¶
b = Board(7, 6)
b.set_board("00102030")
assert b.wins_for("X")
assert b.wins_for("O")
b = Board(7, 6)
b.set_board("23344545515")
print(b)
assert b.wins_for("X")
assert not b.wins_for("O")
| | | | | | | |
| | | | | | | |
| | | | | |X| |
| | | | |X|X| |
| | | |X|X|O| |
| |O|X|O|O|O| |
---------------
0 1 2 3 4 5 6
Stap 10: host_game(self)¶
De methode staat in de klasse hierboven. Speel haar zelf, in je eigen bestand.