diff --git a/.github/workflows/cs102.yml b/.github/workflows/cs102.yml index 4509ac4..8e1e8be 100644 --- a/.github/workflows/cs102.yml +++ b/.github/workflows/cs102.yml @@ -7,10 +7,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8.6 + - name: Set up Python 3.10.7 uses: actions/setup-python@v2 with: - python-version: '3.8.6' + python-version: '3.10.7' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..b15c5a4 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +sudoku.py \ No newline at end of file diff --git a/.idea/Chzh.iml b/.idea/Chzh.iml new file mode 100644 index 0000000..8e5446a --- /dev/null +++ b/.idea/Chzh.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..cc2518d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..adbc062 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 1673962365428 + + + + + + + + file://$PROJECT_DIR$/main.py + 8 + + + + + \ No newline at end of file diff --git a/homework02/sudoku.py b/homework02/sudoku.py index df78ab1..87139b0 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): @@ -42,7 +43,16 @@ def group(values: tp.List[T], n: int) -> tp.List[tp.List[T]]: >>> group([1,2,3,4,5,6,7,8,9], 3) [[1, 2, 3], [4, 5, 6], [7, 8, 9]] """ - pass + t: tp.List[T] = [] + res = [] + length = len(values) + for i in range(length): + if i % n == 0 and i != 0: + res.append(t) + t = [] + t.append(values[i]) + res.append(t) + return res def get_row(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: @@ -55,7 +65,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 +78,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 + res = [] + for arr in grid: + res.append(arr[pos[1]]) + return res def get_block(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: @@ -82,10 +95,16 @@ 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 + res = [] + r = pos[0] // 3 * 3 + c = pos[1] // 3 * 3 + for i in range(r, r + 3): + for j in range(c, c + 3): + res.append(grid[i][j]) + return res -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]]) -> tp.Tuple[int, int]: """Найти первую свободную позицию в пазле >>> find_empty_positions([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']]) @@ -95,7 +114,13 @@ 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 + if grid != 0: + for i in range(len(grid)): + for j in range(len(grid[i])): + if not grid[i][j].isdigit(): + r, c = i, j + return r, c + return -1, -1 def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.Set[str]: @@ -109,11 +134,20 @@ def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) - >>> values == {'2', '5', '9'} True """ - pass + res = [] + for arr in range(1, 10): + s = str(arr) + if ( + s not in get_block(grid, pos) + and s not in get_col(grid, pos) + and s not in get_row(grid, pos) + ): + res.append(s) + return set(res) def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: - """ Решение пазла, заданного в grid """ + """Решение пазла, заданного в grid""" """ Как решать Судоку? 1. Найти свободную позицию 2. Найти все возможные значения, которые могут находиться на этой позиции @@ -125,13 +159,36 @@ 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 = find_empty_positions(grid) # type: tp.Tuple[int, int] + if empty[0] < 0: + return grid + possible = find_possible_values(grid, empty) # type: tp.Set[str] + if not possible: + return None + for i in possible: + grid[empty[0]][empty[1]] = i + if solve(grid): + return grid + grid[empty[0]][empty[1]] = "." + return None def check_solution(solution: tp.List[tp.List[str]]) -> bool: - """ Если решение solution верно, то вернуть True, в противном случае False """ + """Если решение solution верно, то вернуть True, в противном случае False""" # TODO: Add doctests with bad puzzles - pass + g = solution + for i in range(9): + if len(get_row(g, (i, 0))) != len(set(get_row(g, (i, 0)))) or len( + get_col(g, (0, i)) + ) != len(set(get_col(g, (0, i)))): + return False + if i == 0 or i == 3 or i == 6: + for j in range(0, 9, 3): + if len(get_block(g, (i, j))) != len(set(get_block(g, (i, j)))): + return False + if "." in g[i]: + return False + return True def generate_sudoku(N: int) -> tp.List[tp.List[str]]: @@ -156,7 +213,15 @@ def generate_sudoku(N: int) -> tp.List[tp.List[str]]: >>> check_solution(solution) True """ - pass + grid = [["."] * 9 for _ in range(9)] + solve(grid) + count, N = 0, min(81, N) + while count < 81 - N: + i, j = random.randint(0, 8), random.randint(0, 8) + if grid[i][j] != ".": + grid[i][j] = "." + count += 1 + return grid if __name__ == "__main__": diff --git a/homework03/life.py b/homework03/life.py index 7aef0b6..0ef18e3 100644 --- a/homework03/life.py +++ b/homework03/life.py @@ -2,9 +2,6 @@ import random import typing as tp -import pygame -from pygame.locals import * - Cell = tp.Tuple[int, int] Cells = tp.List[int] Grid = tp.List[Cells] @@ -15,7 +12,7 @@ def __init__( self, size: tp.Tuple[int, int], randomize: bool = True, - max_generations: tp.Optional[float] = float("inf"), + max_generations: float = float("inf"), ) -> None: # Размер клеточного поля self.rows, self.cols = size @@ -29,46 +26,80 @@ def __init__( self.generations = 1 def create_grid(self, randomize: bool = False) -> Grid: - # Copy from previous assignment - pass + if randomize: + grid = [[random.randint(0, 1) for _ in range(self.cols)] for _ in range(self.rows)] + else: + grid = [[0 for _ in range(self.cols)] for _ in range(self.rows)] + + return grid def get_neighbours(self, cell: Cell) -> Cells: - # Copy from previous assignment - pass + neighbours = [] + y_borders = [max(0, cell[0] - 1), min(self.rows, cell[0] + 2)] + x_borders = [max(0, cell[1] - 1), min(self.cols, cell[1] + 2)] + for i in range(*y_borders): + neighbours += self.curr_generation[i][x_borders[0] : x_borders[1]] + neighbours.remove(self.curr_generation[cell[0]][cell[1]]) + return neighbours def get_next_generation(self) -> Grid: - # Copy from previous assignment - pass + new_generation = self.create_grid(randomize=False) + for i in range(self.rows): + for j in range(self.cols): + n_neighbours = sum(self.get_neighbours((i, j))) + if self.curr_generation[i][j] and 2 <= n_neighbours <= 3: + new_generation[i][j] = 1 + elif not self.curr_generation[i][j] and n_neighbours == 3: + new_generation[i][j] = 1 + + return new_generation def step(self) -> None: """ Выполнить один шаг игры. """ - pass + self.generations += 1 + self.prev_generation = self.curr_generation + self.curr_generation = self.get_next_generation() @property def is_max_generations_exceeded(self) -> bool: """ Не превысило ли текущее число поколений максимально допустимое. """ - pass + return self.generations >= self.max_generations @property def is_changing(self) -> bool: """ Изменилось ли состояние клеток с предыдущего шага. """ - pass + return self.prev_generation != self.curr_generation or self.generations == 1 @staticmethod def from_file(filename: pathlib.Path) -> "GameOfLife": """ Прочитать состояние клеток из указанного файла. """ - pass + try: + with filename.open() as f: + grid = list(map(lambda x: list(map(int, list(x))), f.read().split())) + loaded_game = GameOfLife((len(grid), len(grid[0])), randomize=False) + loaded_game.curr_generation = grid + + except (ValueError, IndexError): + print("Something wrong with file") + return GameOfLife((13, 13)) + except Exception as error: + print(error) + return GameOfLife((13, 13)) + else: + return loaded_game def save(self, filename: pathlib.Path) -> None: """ Сохранить текущее состояние клеток в указанный файл. """ - pass + with filename.open("w") as f: + for cells in self.curr_generation: + f.write("".join(map(str, cells)) + "\n") diff --git a/homework03/life_console.py b/homework03/life_console.py index ddeb9ef..79ab954 100644 --- a/homework03/life_console.py +++ b/homework03/life_console.py @@ -1,22 +1,71 @@ import curses +import pygame from life import GameOfLife from ui import UI class Console(UI): - def __init__(self, life: GameOfLife) -> None: + def __init__(self, life: GameOfLife, speed=10) -> None: super().__init__(life) + self.speed = speed def draw_borders(self, screen) -> None: - """ Отобразить рамку. """ - pass + """Отобразить рамку.""" + screen.addstr(f"+{'-' * self.life.cols}+\n") + for i in range(self.life.rows): + screen.addstr("|") + screen.addstr(i + 1, self.life.cols + 1, "|\n") + screen.addstr(f"+{'-' * self.life.cols}+\n") def draw_grid(self, screen) -> None: - """ Отобразить состояние клеток. """ - pass + """Отобразить состояние клеток.""" + for row in range(self.life.rows): + for col in range(self.life.cols): + if self.life.curr_generation[row][col]: + screen.move(row + 1, col + 1) + screen.addstr("*") def run(self) -> None: + clock = pygame.time.Clock() + screen = curses.initscr() - # PUT YOUR CODE HERE - curses.endwin() + curses.noecho() + curses.cbreak() + screen.nodelay(True) + curses.curs_set(False) + try: + running = True + pause = False + while running: + key = screen.getch() + while key != -1: + if key == 113: + running = False + elif key == 32: + pause = not pause + key = screen.getch() + screen.clear() + self.draw_borders(screen) + self.draw_grid(screen) + if not pause: + self.life.step() + screen.refresh() + clock.tick(self.speed) + + if not self.life.is_changing or self.life.is_max_generations_exceeded: + running = False + curses.echo() + curses.nocbreak() + curses.endwin() + + except Exception as err: + print(f"{err}\n{type(err)}") + curses.echo() + curses.nocbreak() + curses.endwin() + + +if __name__ == "__main__": + life = Console(GameOfLife((32, 32))) + life.run() diff --git a/homework03/life_gui.py b/homework03/life_gui.py index 1126b29..c7d054d 100644 --- a/homework03/life_gui.py +++ b/homework03/life_gui.py @@ -7,15 +7,72 @@ 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 = self.cell_size * self.life.cols + self.height = self.cell_size * self.life.rows + + # Создание нового окна + self.screen = pygame.display.set_mode((self.width, self.height)) + + # Вычисляем количество ячеек по вертикали и горизонтали + 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 + """ + Отрисовка списка клеток с закрашиванием их в соответствующе цвета. + """ + color = (0, 255, 0) + for i in range(self.life.rows): + for j in range(self.life.cols): + if self.life.curr_generation[i][j]: + pygame.draw.rect( + self.screen, + color, + pygame.Rect( + self.cell_size * j, + self.cell_size * i, + self.cell_size, + self.cell_size, + ), + ) 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 + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + self.screen.fill(pygame.Color("white")) + + self.draw_grid() + self.draw_lines() + self.life.step() + + pygame.display.flip() + clock.tick(self.speed) + + if not self.life.is_changing or self.life.is_max_generations_exceeded: + running = False + pygame.quit() + + +if __name__ == "__main__": + life = GUI(GameOfLife((32, 32))) + life.run() diff --git a/homework03/life_proto.py b/homework03/life_proto.py index c6d6010..7ad76d8 100644 --- a/homework03/life_proto.py +++ b/homework03/life_proto.py @@ -30,32 +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) running = True while running: for event in pygame.event.get(): - if event.type == QUIT: + if event.type == pygame.QUIT: running = False - self.draw_lines() + self.screen.fill(pygame.Color("white")) - # Отрисовка списка клеток - # Выполнение одного шага игры (обновление состояния ячеек) - # PUT YOUR CODE HERE + self.draw_grid() + self.draw_lines() + self.grid = self.get_next_generation() pygame.display.flip() clock.tick(self.speed) @@ -79,13 +79,34 @@ def create_grid(self, randomize: bool = False) -> Grid: out : Grid Матрица клеток размером `cell_height` х `cell_width`. """ - pass + if randomize: + grid = [ + [random.randint(0, 1) for _ in range(self.cell_width)] + for _ in range(self.cell_height) + ] + else: + grid = [[0 for _ in range(self.cell_width)] for _ in range(self.cell_height)] + + return grid def draw_grid(self) -> None: """ Отрисовка списка клеток с закрашиванием их в соответствующе цвета. """ - pass + color = (0, 255, 0) + for i in range(self.cell_height): + for j in range(self.cell_width): + if self.grid[i][j]: + pygame.draw.rect( + self.screen, + color, + pygame.Rect( + self.cell_size * j, + self.cell_size * i, + self.cell_size, + self.cell_size, + ), + ) def get_neighbours(self, cell: Cell) -> Cells: """ @@ -105,7 +126,13 @@ def get_neighbours(self, cell: Cell) -> Cells: out : Cells Список соседних клеток. """ - pass + neighbours = [] + y_borders = [max(0, cell[0] - 1), min(self.cell_height, cell[0] + 2)] + x_borders = [max(0, cell[1] - 1), min(self.cell_width, cell[1] + 2)] + for i in range(*y_borders): + neighbours += self.grid[i][x_borders[0] : x_borders[1]] + neighbours.remove(self.grid[cell[0]][cell[1]]) + return neighbours def get_next_generation(self) -> Grid: """ @@ -116,4 +143,18 @@ def get_next_generation(self) -> Grid: out : Grid Новое поколение клеток. """ - pass + new_generation = self.create_grid(randomize=False) + for i in range(self.cell_height): + for j in range(self.cell_width): + n_neighbours = sum(self.get_neighbours((i, j))) + if self.grid[i][j] and 2 <= n_neighbours <= 3: + new_generation[i][j] = 1 + elif not self.grid[i][j] and n_neighbours == 3: + new_generation[i][j] = 1 + + return new_generation + + +if __name__ == "__main__": + life = GameOfLife() + life.run() diff --git a/homework03/test.py b/homework03/test.py new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py new file mode 100644 index 0000000..5596b44 --- /dev/null +++ b/main.py @@ -0,0 +1,16 @@ +# This is a sample Python script. + +# Press Shift+F10 to execute it or replace it with your code. +# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. + + +def print_hi(name): + # Use a breakpoint in the code line below to debug your script. + print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint. + + +# Press the green button in the gutter to run the script. +if __name__ == '__main__': + print_hi('PyCharm') + +# See PyCharm help at https://www.jetbrains.com/help/pycharm/