File size: 4,190 Bytes
03b0d13 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
import statistics
import random
class InvalidDropException(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
class GameState:
def __init__(self):
self.board: list[list[bool]] = [
[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
] # 4 rows of 4 columns, 4x4
def __str__(self):
ToReturn: str = ""
ToReturn = " ββββββ" + "\n"
onRow: int = 0
for row in self.board:
# add the row number in
ToReturn = ToReturn + str(onRow) + "β"
# print every square
for column in row:
if column:
ToReturn = ToReturn + "β"
else:
ToReturn = ToReturn + " "
ToReturn = ToReturn + "β\n"
onRow = onRow + 1
ToReturn = ToReturn + " ββββββ"
ToReturn = ToReturn + "\n" + " 0123"
return ToReturn
def column_depths(self) -> list[int]:
"""Calculates how 'deep' the available space on each column goes, from the top down."""
# record the depth of every column
column_depths: list[int] = [0, 0, 0, 0]
column_collisions: list[bool] = [
False,
False,
False,
False,
]
# In this sense, "depth" is the number of squares that are clear, to be clear
for ri in range(0, len(self.board)): # for every row
for ci in range(
0, len(self.board[0])
): # for every column (use first row to know how many columns there are)
if (
column_collisions[ci] == False and self.board[ri][ci] == False
): # if column X has not been recorded yet and the column in this row is not occupied, increment the depth
column_depths[ci] = column_depths[ci] + 1
else: # we hit a floor!
column_collisions[ci] = True
return column_depths
def over(self) -> bool:
"""Determines the game is over (if all cols in top row are occupied)."""
return self.board[0] == [1, 1, 1, 1]
def drop(self, column: int) -> float:
"""Drops a single block into the column, returns the reward of doing so."""
if column < 0 or column > 3:
raise InvalidDropException(
"Invalid move! Column to drop in must be 0, 1, 2, or 3."
)
reward_before: float = self.score_plus()
cds: list[int] = self.column_depths()
if cds[column] == 0:
raise InvalidDropException(
"Unable to drop on column " + str(column) + ", it is already full!"
)
self.board[cds[column] - 1][column] = True
reward_after: float = self.score_plus()
return reward_after - reward_before
def score(self) -> int:
ToReturn: int = 0
for row in self.board:
for col in row:
if col:
ToReturn = ToReturn + 1
return ToReturn
def score_plus(self) -> float:
# start at score
ToReturn: float = float(self.score())
# penalize for standard deviation
stdev: float = statistics.pstdev(self.column_depths())
ToReturn = ToReturn - (stdev * 2)
return ToReturn
def randomize(self) -> float:
"""Sets the board to a random setup."""
# first, clear all values
for ri in range(0, len(self.board)):
for ci in range(0, len(self.board[0])):
self.board[ri][ci] = False
# drop a random number in each column
for ci in range(0, 4):
random_drops: int = random.randint(0, 4)
for _ in range(0, random_drops):
self.drop(ci)
# if all 16 are filled up, delete one
if self.score() == 16:
self.board[0][random.randint(0, 3)] = (
False # turn off a random square in the top row
)
|