diff --git a/.github/workflows/cs102.yml b/.github/workflows/cs102.yml index 4509ac4..cb286a1 100644 --- a/.github/workflows/cs102.yml +++ b/.github/workflows/cs102.yml @@ -10,7 +10,7 @@ jobs: - name: Set up Python 3.8.6 uses: actions/setup-python@v2 with: - python-version: '3.8.6' + python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/homework02/sudoku.py b/homework02/sudoku.py index df78ab1..df02ea2 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -1,11 +1,12 @@ import pathlib +import random import typing as tp T = tp.TypeVar("T") def read_sudoku(path: tp.Union[str, pathlib.Path]) -> tp.List[tp.List[str]]: - """ Прочитать Судоку из указанного файла """ + """Прочитать Судоку из указанного файла""" path = pathlib.Path(path) with path.open() as f: puzzle = f.read() @@ -19,7 +20,7 @@ def create_grid(puzzle: str) -> tp.List[tp.List[str]]: def display(grid: tp.List[tp.List[str]]) -> None: - """Вывод Судоку """ + """Вывод Судоку""" width = 2 line = "+".join(["-" * (width * 3)] * 3) for row in range(9): @@ -38,11 +39,18 @@ def group(values: tp.List[T], n: int) -> tp.List[tp.List[T]]: Сгруппировать значения values в список, состоящий из списков по n элементов >>> group([1,2,3,4], 2) - [[1, 2], [3, 4]] + [[1, 2], [3, 4]]) >>> group([1,2,3,4,5,6,7,8,9], 3) [[1, 2, 3], [4, 5, 6], [7, 8, 9]] """ - pass + i = 0 + j = n + s = [] + while j <= len(values): + s.append(values[i:j]) + i = j + j += n + return s def get_row(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: @@ -55,7 +63,7 @@ def get_row(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str >>> get_row([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']], (2, 0)) ['.', '8', '9'] """ - pass + return grid[pos[0]] def get_col(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: @@ -68,7 +76,10 @@ def get_col(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str >>> get_col([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']], (0, 2)) ['3', '6', '9'] """ - pass + post = [] + for i in range(len(grid)): + post.append(grid[i][pos[1]]) + return post def get_block(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: @@ -82,10 +93,14 @@ def get_block(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[s >>> get_block(grid, (8, 8)) ['2', '8', '.', '.', '.', '5', '.', '7', '9'] """ - pass + square = [] + for i in range(pos[0] // 3 * 3, pos[0] // 3 * 3 + 3): + for j in range(pos[1] // 3 * 3, pos[1] // 3 * 3 + 3): + square.append(grid[i][j]) + return square -def find_empty_positions(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.Tuple[int, int]]: +def find_empty_positions(grid: tp.List[tp.List[str]]): """Найти первую свободную позицию в пазле >>> find_empty_positions([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']]) @@ -95,7 +110,14 @@ def find_empty_positions(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.Tuple[in >>> find_empty_positions([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']]) (2, 0) """ - pass + x = -1 + for i in range(len(grid)): + x += 1 + y = 0 + for j in range(len(grid[i])): + if grid[i][j] == ".": + return x, y + y += 1 def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.Set[str]: @@ -109,11 +131,13 @@ def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) - >>> values == {'2', '5', '9'} True """ - pass + full = set(get_row(grid, pos) + get_col(grid, pos) + get_block(grid, pos)) + possible_values = set("123456789") - full + return possible_values -def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: - """ Решение пазла, заданного в grid """ +def solve(grid: tp.List[tp.List[str]]): + """Решение пазла, заданного в grid""" """ Как решать Судоку? 1. Найти свободную позицию 2. Найти все возможные значения, которые могут находиться на этой позиции @@ -125,13 +149,33 @@ def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: >>> solve(grid) [['5', '3', '4', '6', '7', '8', '9', '1', '2'], ['6', '7', '2', '1', '9', '5', '3', '4', '8'], ['1', '9', '8', '3', '4', '2', '5', '6', '7'], ['8', '5', '9', '7', '6', '1', '4', '2', '3'], ['4', '2', '6', '8', '5', '3', '7', '9', '1'], ['7', '1', '3', '9', '2', '4', '8', '5', '6'], ['9', '6', '1', '5', '3', '7', '2', '8', '4'], ['2', '8', '7', '4', '1', '9', '6', '3', '5'], ['3', '4', '5', '2', '8', '6', '1', '7', '9']] """ - pass + empty_positions = find_empty_positions(grid) + if empty_positions is None: + return grid + else: + possible_values = find_possible_values(grid, empty_positions) + for i in possible_values: + grid[empty_positions[0]][empty_positions[1]] = i + next = solve(grid) + if next: + return next + grid[empty_positions[0]][empty_positions[1]] = "." def check_solution(solution: tp.List[tp.List[str]]) -> bool: - """ Если решение solution верно, то вернуть True, в противном случае False """ + """Если решение solution верно, то вернуть True, в противном случае False""" # TODO: Add doctests with bad puzzles - pass + all = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] + for i in range(0, len(solution)): + for j in range(0, len(solution)): + pos = i, j + if ( + not sorted(get_row(solution, pos)) == all + or not sorted(get_col(solution, pos)) == all + or not sorted(get_block(solution, pos)) == all + ): + return False + return True def generate_sudoku(N: int) -> tp.List[tp.List[str]]: @@ -156,7 +200,24 @@ def generate_sudoku(N: int) -> tp.List[tp.List[str]]: >>> check_solution(solution) True """ - pass + grid = [["." for i in range(9)] for i in range(9)] + print(N) + if N > 81: + N = 81 + print(grid) + grid = solve(grid) + print(grid) + k = 0 + while k != 81 - N: + i = random.randint(0, 8) + j = random.randint(0, 8) + if grid[i][j] == ".": + k = k + else: + grid[i][j] = "." + k += 1 + print(grid) + return grid if __name__ == "__main__": @@ -168,3 +229,8 @@ def generate_sudoku(N: int) -> tp.List[tp.List[str]]: print(f"Puzzle {fname} can't be solved") else: display(solution) + if check_solution(solution): + print("Solution is correct") + else: + print("LoL") + display(generate_sudoku(1)) diff --git a/homework03/life.py b/homework03/life.py index 7aef0b6..a95dcf8 100644 --- a/homework03/life.py +++ b/homework03/life.py @@ -30,45 +30,127 @@ def __init__( def create_grid(self, randomize: bool = False) -> Grid: # Copy from previous assignment - pass + if randomize: + return [[random.randint(0, 1) for i in range(self.rows)] for i in range(self.cols)] + else: + return [[0 for i in range(self.rows)] for i in range(self.cols)] def get_neighbours(self, cell: Cell) -> Cells: # Copy from previous assignment - pass + height, width = cell + cells = [] + if height == 0 and width == 0: + cells.append(self.curr_generation[0][1]) + cells.append(self.curr_generation[1][0]) + cells.append(self.curr_generation[1][1]) + elif height == 0 and width == self.cols - 1: + cells.append(self.curr_generation[0][width - 1]) + cells.append(self.curr_generation[1][width]) + cells.append(self.curr_generation[1][width - 1]) + elif height == self.rows - 1 and width == 0: + cells.append(self.curr_generation[height - 1][0]) + cells.append(self.curr_generation[height - 1][1]) + cells.append(self.curr_generation[height][1]) + elif height == self.rows - 1 and width == self.cols - 1: + cells.append(self.curr_generation[height - 1][width - 1]) + cells.append(self.curr_generation[height][width - 1]) + cells.append(self.curr_generation[height - 1][width]) + elif height == 0: + cells.append(self.curr_generation[0][width - 1]) + cells.append(self.curr_generation[0][width + 1]) + cells.append(self.curr_generation[1][width]) + cells.append(self.curr_generation[1][width - 1]) + cells.append(self.curr_generation[1][width + 1]) + elif width == 0: + cells.append(self.curr_generation[height - 1][0]) + cells.append(self.curr_generation[height + 1][0]) + cells.append(self.curr_generation[height][1]) + cells.append(self.curr_generation[height - 1][1]) + cells.append(self.curr_generation[height + 1][1]) + elif height == self.rows - 1: + cells.append(self.curr_generation[height][width - 1]) + cells.append(self.curr_generation[height][width + 1]) + cells.append(self.curr_generation[height - 1][width]) + cells.append(self.curr_generation[height - 1][width - 1]) + cells.append(self.curr_generation[height - 1][width + 1]) + elif width == self.cols - 1: + cells.append(self.curr_generation[height - 1][width]) + cells.append(self.curr_generation[height + 1][width]) + cells.append(self.curr_generation[height][width - 1]) + cells.append(self.curr_generation[height - 1][width - 1]) + cells.append(self.curr_generation[height + 1][width - 1]) + else: + cells.append(self.curr_generation[height][width - 1]) + cells.append(self.curr_generation[height][width + 1]) + cells.append(self.curr_generation[height - 1][width]) + cells.append(self.curr_generation[height + 1][width]) + cells.append(self.curr_generation[height - 1][width - 1]) + cells.append(self.curr_generation[height - 1][width + 1]) + cells.append(self.curr_generation[height + 1][width + 1]) + cells.append(self.curr_generation[height + 1][width - 1]) + return cells def get_next_generation(self) -> Grid: # Copy from previous assignment - pass + next_grid = [[0 for i in range(self.cols)] for i in range(self.rows)] + for i in range(self.rows): + for j in range(self.cols): + if self.curr_generation[i][j] == 1 and ( + sum(self.get_neighbours((i, j))) == 2 or sum(self.get_neighbours((i, j))) == 3 + ): + next_grid[i][j] = 1 + elif self.curr_generation[i][j] == 0 and sum(self.get_neighbours((i, j))) == 3: + next_grid[i][j] = 1 + else: + next_grid[i][j] = 0 + return next_grid def step(self) -> None: """ Выполнить один шаг игры. """ - pass + self.prev_generation = self.curr_generation + self.curr_generation = self.get_next_generation() + self.generations += 1 @property def is_max_generations_exceeded(self) -> bool: """ Не превысило ли текущее число поколений максимально допустимое. """ - pass + if self.max_generations == self.generations: + return True + else: + return False @property def is_changing(self) -> bool: """ Изменилось ли состояние клеток с предыдущего шага. """ - pass + if self.curr_generation == self.prev_generation: + return False + else: + return True @staticmethod def from_file(filename: pathlib.Path) -> "GameOfLife": """ Прочитать состояние клеток из указанного файла. """ - pass + f = open(filename, "r").readlines() + grid = [] + for i in range(len(f)): + r = list(map(int, f[i].split())) + grid.append(r) + life = GameOfLife(size=(len(grid), len(grid[0])), randomize=False) + life.curr_generation = grid + return life def save(self, filename: pathlib.Path) -> None: """ Сохранить текущее состояние клеток в указанный файл. """ - pass + with open(filename, "w") as f: + for i in self.curr_generation: + f.write("".join([str(j) for j in i]) + "\n") diff --git a/homework03/life_console.py b/homework03/life_console.py index ddeb9ef..e5a90b8 100644 --- a/homework03/life_console.py +++ b/homework03/life_console.py @@ -9,14 +9,32 @@ def __init__(self, life: GameOfLife) -> None: super().__init__(life) def draw_borders(self, screen) -> None: - """ Отобразить рамку. """ - pass + """Отобразить рамку.""" + screen.border("|", "|", "-", "-", "+", "+", "+", "+") def draw_grid(self, screen) -> None: - """ Отобразить состояние клеток. """ - pass + """Отобразить состояние клеток.""" + for i in range(self.life.rows): + for j in range(self.life.cols): + if self.life.curr_generation[i][j] == 0: + screen.addch(i + 1, j + 1, " ") + else: + screen.addch(i + 1, j + 1, "#") def run(self) -> None: screen = curses.initscr() # PUT YOUR CODE HERE - curses.endwin() + while True: + self.draw_borders(screen) + self.draw_grid(screen) + screen.refresh() + self.life.step() + if screen.getch() == 32: # space + curses.endwin() + break + + +if __name__ == "__main__": + life = GameOfLife((100, 100), max_generations=50) + ui = Console(life) + ui.run() diff --git a/homework03/life_gui.py b/homework03/life_gui.py index 1126b29..19b8bf5 100644 --- a/homework03/life_gui.py +++ b/homework03/life_gui.py @@ -7,15 +7,89 @@ class GUI(UI): def __init__(self, life: GameOfLife, cell_size: int = 10, speed: int = 10) -> None: super().__init__(life) + self.cell_size = cell_size + self.width = life.rows * cell_size + self.height = life.cols * cell_size + self.screen_size = self.width, self.height + self.screen = pygame.display.set_mode(self.screen_size) + self.cell_width = self.width // self.cell_size + self.cell_height = self.height // self.cell_size + self.speed = speed def draw_lines(self) -> None: - # Copy from previous assignment - pass + for x in range(0, self.width, self.cell_size): + pygame.draw.line(self.screen, pygame.Color("black"), (x, 0), (x, self.height)) + for y in range(0, self.height, self.cell_size): + pygame.draw.line(self.screen, pygame.Color("black"), (0, y), (self.width, y)) def draw_grid(self) -> None: - # Copy from previous assignment - pass + for i in range(self.cell_height): + for j in range(self.cell_width): + if self.life.curr_generation[i][j] == 1: + pygame.draw.rect( + self.screen, + pygame.Color("green"), + ( + j * self.cell_size + 1, + i * self.cell_size + 1, + self.cell_size - 1, + self.cell_size - 1, + ), + ) + else: + pygame.draw.rect( + self.screen, + pygame.Color("white"), + ( + j * self.cell_size + 1, + i * self.cell_size + 1, + self.cell_size - 1, + self.cell_size - 1, + ), + ) + + def myself(self, height, width) -> None: + col = height // self.cell_size + row = width // self.cell_size + if self.life.curr_generation[row][col] == 0: + self.life.curr_generation[row][col] = 1 + else: + self.life.curr_generation[row][col] = 0 def run(self) -> None: - # Copy from previous assignment - pass + pygame.init() + clock = pygame.time.Clock() + pygame.display.set_caption("Game of Life") + self.screen.fill(pygame.Color("white")) + running = True + pause = False + while running: + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_SPACE: + pause = not pause + elif event.type == pygame.MOUSEBUTTONDOWN: + height, width = event.pos + self.myself(height, width) + self.draw_grid() + self.draw_lines() + pygame.display.flip() + elif event.type == QUIT: + running = False + if not self.life.is_changing: + running = False + elif self.life.is_max_generations_exceeded: + running = False + if not pause: + self.life.step() + self.draw_lines() + self.draw_grid() + pygame.display.flip() + clock.tick(self.speed) + pygame.quit() + + +if __name__ == "__main__": + game = GameOfLife(size=(100, 100), randomize=True) + gui = GUI(life=game, cell_size=10, speed=10) + gui.run() diff --git a/homework03/life_proto.py b/homework03/life_proto.py index c6d6010..a816204 100644 --- a/homework03/life_proto.py +++ b/homework03/life_proto.py @@ -30,33 +30,32 @@ def __init__( self.speed = speed def draw_lines(self) -> None: - """ Отрисовать сетку """ + """Отрисовать сетку""" for x in range(0, self.width, self.cell_size): pygame.draw.line(self.screen, pygame.Color("black"), (x, 0), (x, self.height)) for y in range(0, self.height, self.cell_size): pygame.draw.line(self.screen, pygame.Color("black"), (0, y), (self.width, y)) def run(self) -> None: - """ Запустить игру """ + """Запустить игру""" pygame.init() clock = pygame.time.Clock() pygame.display.set_caption("Game of Life") self.screen.fill(pygame.Color("white")) - # Создание списка клеток - # PUT YOUR CODE HERE - + # Добавил поле + self.grid = self.create_grid(randomize=True) + self.draw_grid(self.grid) running = True while running: for event in pygame.event.get(): if event.type == QUIT: running = False self.draw_lines() - # Отрисовка списка клеток # Выполнение одного шага игры (обновление состояния ячеек) - # PUT YOUR CODE HERE - + self.grid = self.get_next_generation() + self.draw_grid(self.grid) pygame.display.flip() clock.tick(self.speed) pygame.quit() @@ -79,13 +78,42 @@ def create_grid(self, randomize: bool = False) -> Grid: out : Grid Матрица клеток размером `cell_height` х `cell_width`. """ - pass - - def draw_grid(self) -> None: + if randomize: + return [ + [random.randint(0, 1) for i in range(self.cell_width)] + for i in range(self.cell_height) + ] + else: + return [[0 for i in range(self.cell_width)] for i in range(self.cell_height)] + + def draw_grid(self, grid: list[list[int]]) -> None: """ Отрисовка списка клеток с закрашиванием их в соответствующе цвета. """ - pass + for i in range(self.cell_height): + for j in range(self.cell_width): + if grid[i][j] == 1: + pygame.draw.rect( + self.screen, + pygame.Color("green"), + ( + j * self.cell_size + 1, + i * self.cell_size + 1, + self.cell_size - 1, + self.cell_size - 1, + ), + ) + else: + pygame.draw.rect( + self.screen, + pygame.Color("white"), + ( + j * self.cell_size + 1, + i * self.cell_size + 1, + self.cell_size - 1, + self.cell_size - 1, + ), + ) def get_neighbours(self, cell: Cell) -> Cells: """ @@ -105,7 +133,58 @@ def get_neighbours(self, cell: Cell) -> Cells: out : Cells Список соседних клеток. """ - pass + height, width = cell + cells = [] + if height == 0 and width == 0: + cells.append(self.grid[0][1]) + cells.append(self.grid[1][0]) + cells.append(self.grid[1][1]) + elif height == 0 and width == self.cell_width - 1: + cells.append(self.grid[0][width - 1]) + cells.append(self.grid[1][width]) + cells.append(self.grid[1][width - 1]) + elif height == self.cell_height - 1 and width == 0: + cells.append(self.grid[height - 1][0]) + cells.append(self.grid[height - 1][1]) + cells.append(self.grid[height][1]) + elif height == self.cell_height - 1 and width == self.cell_width - 1: + cells.append(self.grid[height - 1][width - 1]) + cells.append(self.grid[height][width - 1]) + cells.append(self.grid[height - 1][width]) + elif height == 0: + cells.append(self.grid[0][width - 1]) + cells.append(self.grid[0][width + 1]) + cells.append(self.grid[1][width]) + cells.append(self.grid[1][width - 1]) + cells.append(self.grid[1][width + 1]) + elif width == 0: + cells.append(self.grid[height - 1][0]) + cells.append(self.grid[height + 1][0]) + cells.append(self.grid[height][1]) + cells.append(self.grid[height - 1][1]) + cells.append(self.grid[height + 1][1]) + elif height == self.cell_height - 1: + cells.append(self.grid[height][width - 1]) + cells.append(self.grid[height][width + 1]) + cells.append(self.grid[height - 1][width]) + cells.append(self.grid[height - 1][width - 1]) + cells.append(self.grid[height - 1][width + 1]) + elif width == self.cell_width - 1: + cells.append(self.grid[height - 1][width]) + cells.append(self.grid[height + 1][width]) + cells.append(self.grid[height][width - 1]) + cells.append(self.grid[height - 1][width - 1]) + cells.append(self.grid[height + 1][width - 1]) + else: + cells.append(self.grid[height][width - 1]) + cells.append(self.grid[height][width + 1]) + cells.append(self.grid[height - 1][width]) + cells.append(self.grid[height + 1][width]) + cells.append(self.grid[height - 1][width - 1]) + cells.append(self.grid[height - 1][width + 1]) + cells.append(self.grid[height + 1][width + 1]) + cells.append(self.grid[height + 1][width - 1]) + return cells def get_next_generation(self) -> Grid: """ @@ -116,4 +195,20 @@ def get_next_generation(self) -> Grid: out : Grid Новое поколение клеток. """ - pass + next_grid = [[0 for i in range(self.cell_width)] for i in range(self.cell_height)] + for i in range(self.cell_height): + for j in range(self.cell_width): + if self.grid[i][j] == 1 and ( + sum(self.get_neighbours((i, j))) == 2 or sum(self.get_neighbours((i, j))) == 3 + ): + next_grid[i][j] = 1 + elif self.grid[i][j] == 0 and sum(self.get_neighbours((i, j))) == 3: + next_grid[i][j] = 1 + else: + next_grid[i][j] = 0 + return next_grid + + +if __name__ == "__main__": + game = GameOfLife(width=640, height=480, cell_size=10, speed=10) + game.run()