Skip to content

Commit c792122

Browse files
authored
Added matrix based color game algorithm (#12400)
* Added matrix based color game * updating DIRECTORY.md --------- Co-authored-by: Miranda13 <Miranda13@users.noreply.github.com>
1 parent fc33c50 commit c792122

File tree

2 files changed

+285
-0
lines changed

2 files changed

+285
-0
lines changed

DIRECTORY.md

+1
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@
794794
* [Cramers Rule 2X2](matrix/cramers_rule_2x2.py)
795795
* [Inverse Of Matrix](matrix/inverse_of_matrix.py)
796796
* [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py)
797+
* [Matrix Based Game](matrix/matrix_based_game.py)
797798
* [Matrix Class](matrix/matrix_class.py)
798799
* [Matrix Equalization](matrix/matrix_equalization.py)
799800
* [Matrix Multiplication Recursion](matrix/matrix_multiplication_recursion.py)

matrix/matrix_based_game.py

+284
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
"""
2+
Matrix-Based Game Script
3+
=========================
4+
This script implements a matrix-based game where players interact with a grid of
5+
elements. The primary goals are to:
6+
- Identify connected elements of the same type from a selected position.
7+
- Remove those elements, adjust the matrix by simulating gravity, and reorganize empty
8+
columns.
9+
- Calculate and display the score based on the number of elements removed in each move.
10+
11+
Functions:
12+
-----------
13+
1. `find_repeat`: Finds all connected elements of the same type.
14+
2. `increment_score`: Calculates the score for a given move.
15+
3. `move_x`: Simulates gravity in a column.
16+
4. `move_y`: Reorganizes the matrix by shifting columns leftward when a column becomes
17+
empty.
18+
5. `play`: Executes a single move, updating the matrix and returning the score.
19+
20+
Input Format:
21+
--------------
22+
1. Matrix size (`lines`): Integer specifying the size of the matrix (N x N).
23+
2. Matrix content (`matrix`): Rows of the matrix, each consisting of characters.
24+
3. Number of moves (`movs`): Integer indicating the number of moves.
25+
4. List of moves (`movements`): A comma-separated string of coordinates for each move.
26+
27+
(0,0) position starts from first left column to last right, and below row to up row
28+
29+
30+
Example Input:
31+
---------------
32+
4
33+
RRBG
34+
RBBG
35+
YYGG
36+
XYGG
37+
2
38+
0 1,1 1
39+
40+
Example (0,0) = X
41+
42+
Output:
43+
--------
44+
The script outputs the total score after processing all moves.
45+
46+
Usage:
47+
-------
48+
Run the script and provide the required inputs as prompted.
49+
50+
"""
51+
52+
53+
def validate_matrix_size(size: int) -> None:
54+
"""
55+
>>> validate_matrix_size(-1)
56+
Traceback (most recent call last):
57+
...
58+
ValueError: Matrix size must be a positive integer.
59+
"""
60+
if not isinstance(size, int) or size <= 0:
61+
raise ValueError("Matrix size must be a positive integer.")
62+
63+
64+
def validate_matrix_content(matrix: list[str], size: int) -> None:
65+
"""
66+
Validates that the number of elements in the matrix matches the given size.
67+
68+
>>> validate_matrix_content(['aaaa', 'aaaa', 'aaaa', 'aaaa'], 3)
69+
Traceback (most recent call last):
70+
...
71+
ValueError: The matrix dont match with size.
72+
>>> validate_matrix_content(['aa%', 'aaa', 'aaa'], 3)
73+
Traceback (most recent call last):
74+
...
75+
ValueError: Matrix rows can only contain letters and numbers.
76+
>>> validate_matrix_content(['aaa', 'aaa', 'aaaa'], 3)
77+
Traceback (most recent call last):
78+
...
79+
ValueError: Each row in the matrix must have exactly 3 characters.
80+
"""
81+
print(matrix)
82+
if len(matrix) != size:
83+
raise ValueError("The matrix dont match with size.")
84+
for row in matrix:
85+
if len(row) != size:
86+
msg = f"Each row in the matrix must have exactly {size} characters."
87+
raise ValueError(msg)
88+
if not all(char.isalnum() for char in row):
89+
raise ValueError("Matrix rows can only contain letters and numbers.")
90+
91+
92+
def validate_moves(moves: list[tuple[int, int]], size: int) -> None:
93+
"""
94+
>>> validate_moves([(1, 2), (-1, 0)], 3)
95+
Traceback (most recent call last):
96+
...
97+
ValueError: Move is out of bounds for a matrix.
98+
"""
99+
for move in moves:
100+
x, y = move
101+
if not (0 <= x < size and 0 <= y < size):
102+
raise ValueError("Move is out of bounds for a matrix.")
103+
104+
105+
def parse_moves(input_str: str) -> list[tuple[int, int]]:
106+
"""
107+
>>> parse_moves("0 1, 1 1")
108+
[(0, 1), (1, 1)]
109+
>>> parse_moves("0 1, 1 1, 2")
110+
Traceback (most recent call last):
111+
...
112+
ValueError: Each move must have exactly two numbers.
113+
>>> parse_moves("0 1, 1 1, 2 4 5 6")
114+
Traceback (most recent call last):
115+
...
116+
ValueError: Each move must have exactly two numbers.
117+
"""
118+
moves = []
119+
for pair in input_str.split(","):
120+
parts = pair.strip().split()
121+
if len(parts) != 2:
122+
raise ValueError("Each move must have exactly two numbers.")
123+
x, y = map(int, parts)
124+
moves.append((x, y))
125+
return moves
126+
127+
128+
def find_repeat(
129+
matrix_g: list[list[str]], row: int, column: int, size: int
130+
) -> set[tuple[int, int]]:
131+
"""
132+
Finds all connected elements of the same type from a given position.
133+
134+
>>> find_repeat([['A', 'B', 'A'], ['A', 'B', 'A'], ['A', 'A', 'A']], 0, 0, 3)
135+
{(1, 2), (2, 1), (0, 0), (2, 0), (0, 2), (2, 2), (1, 0)}
136+
>>> find_repeat([['-', '-', '-'], ['-', '-', '-'], ['-', '-', '-']], 1, 1, 3)
137+
set()
138+
"""
139+
140+
column = size - 1 - column
141+
visited = set()
142+
repeated = set()
143+
144+
if (color := matrix_g[column][row]) != "-":
145+
146+
def dfs(row_n: int, column_n: int) -> None:
147+
if row_n < 0 or row_n >= size or column_n < 0 or column_n >= size:
148+
return
149+
if (row_n, column_n) in visited:
150+
return
151+
visited.add((row_n, column_n))
152+
if matrix_g[row_n][column_n] == color:
153+
repeated.add((row_n, column_n))
154+
dfs(row_n - 1, column_n)
155+
dfs(row_n + 1, column_n)
156+
dfs(row_n, column_n - 1)
157+
dfs(row_n, column_n + 1)
158+
159+
dfs(column, row)
160+
161+
return repeated
162+
163+
164+
def increment_score(count: int) -> int:
165+
"""
166+
Calculates the score for a move based on the number of elements removed.
167+
168+
>>> increment_score(3)
169+
6
170+
>>> increment_score(0)
171+
0
172+
"""
173+
return int(count * (count + 1) / 2)
174+
175+
176+
def move_x(matrix_g: list[list[str]], column: int, size: int) -> list[list[str]]:
177+
"""
178+
Simulates gravity in a specific column.
179+
180+
>>> move_x([['-', 'A'], ['-', '-'], ['-', 'C']], 1, 2)
181+
[['-', '-'], ['-', 'A'], ['-', 'C']]
182+
"""
183+
184+
new_list = []
185+
186+
for row in range(size):
187+
if matrix_g[row][column] != "-":
188+
new_list.append(matrix_g[row][column])
189+
else:
190+
new_list.insert(0, matrix_g[row][column])
191+
for row in range(size):
192+
matrix_g[row][column] = new_list[row]
193+
return matrix_g
194+
195+
196+
def move_y(matrix_g: list[list[str]], size: int) -> list[list[str]]:
197+
"""
198+
Shifts all columns leftward when an entire column becomes empty.
199+
200+
>>> move_y([['-', 'A'], ['-', '-'], ['-', 'C']], 2)
201+
[['A', '-'], ['-', '-'], ['-', 'C']]
202+
"""
203+
204+
empty_columns = []
205+
206+
for column in range(size - 1, -1, -1):
207+
if all(matrix_g[row][column] == "-" for row in range(size)):
208+
empty_columns.append(column)
209+
210+
for column in empty_columns:
211+
for col in range(column + 1, size):
212+
for row in range(size):
213+
matrix_g[row][col - 1] = matrix_g[row][col]
214+
for row in range(size):
215+
matrix_g[row][-1] = "-"
216+
217+
return matrix_g
218+
219+
220+
def play(
221+
matrix_g: list[list[str]], pos_x: int, pos_y: int, size: int
222+
) -> tuple[list[list[str]], int]:
223+
"""
224+
Processes a single move, updating the matrix and calculating the score.
225+
226+
>>> play([['R', 'G'], ['R', 'G']], 0, 0, 2)
227+
([['G', '-'], ['G', '-']], 3)
228+
"""
229+
230+
same_colors = find_repeat(matrix_g, pos_x, pos_y, size)
231+
232+
if len(same_colors) != 0:
233+
for pos in same_colors:
234+
matrix_g[pos[0]][pos[1]] = "-"
235+
for column in range(size):
236+
matrix_g = move_x(matrix_g, column, size)
237+
238+
matrix_g = move_y(matrix_g, size)
239+
240+
return (matrix_g, increment_score(len(same_colors)))
241+
242+
243+
def process_game(size: int, matrix: list[str], moves: list[tuple[int, int]]) -> int:
244+
"""Processes the game logic for the given matrix and moves.
245+
246+
Args:
247+
size (int): Size of the game board.
248+
matrix (List[str]): Initial game matrix.
249+
moves (List[Tuple[int, int]]): List of moves as (x, y) coordinates.
250+
251+
Returns:
252+
int: The total score obtained.
253+
>>> process_game(3, ['aaa', 'bbb', 'ccc'], [(0, 0)])
254+
6
255+
"""
256+
257+
game_matrix = [list(row) for row in matrix]
258+
total_score = 0
259+
260+
for move in moves:
261+
pos_x, pos_y = move
262+
game_matrix, score = play(game_matrix, pos_x, pos_y, size)
263+
total_score += score
264+
265+
return total_score
266+
267+
268+
if __name__ == "__main__":
269+
import doctest
270+
271+
doctest.testmod(verbose=True)
272+
try:
273+
size = int(input("Enter the size of the matrix: "))
274+
validate_matrix_size(size)
275+
print(f"Enter the {size} rows of the matrix:")
276+
matrix = [input(f"Row {i+1}: ") for i in range(size)]
277+
validate_matrix_content(matrix, size)
278+
moves_input = input("Enter the moves (e.g., '0 0, 1 1'): ")
279+
moves = parse_moves(moves_input)
280+
validate_moves(moves, size)
281+
score = process_game(size, matrix, moves)
282+
print(f"Total score: {score}")
283+
except ValueError as e:
284+
print(f"{e}")

0 commit comments

Comments
 (0)