From 64e347c24800d11ac536c8e7d84b7580d4d59c1c Mon Sep 17 00:00:00 2001 From: Elizaveta Date: Thu, 17 Oct 2024 16:39:38 +0300 Subject: [PATCH 1/9] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20encrypt=5Fcaesar()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework01/caesar.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/homework01/caesar.py b/homework01/caesar.py index 41c69485..52d15555 100644 --- a/homework01/caesar.py +++ b/homework01/caesar.py @@ -1,6 +1,7 @@ def encrypt_caesar(plaintext: str, shift: int = 3) -> str: """ Encrypts plaintext using a Caesar cipher. + >>> encrypt_caesar("PYTHON") 'SBWKRQ' >>> encrypt_caesar("python") @@ -11,13 +12,22 @@ def encrypt_caesar(plaintext: str, shift: int = 3) -> str: '' """ ciphertext = "" - # PUT YOUR CODE HERE + for S in plaintext: + if "A" <= S <= "Z": + new_S = chr((ord(S) - ord("A") + shift) % 26 + ord("A")) + ciphertext += new_S + elif "a" <= S <= "z": + new_S = chr((ord(S) - ord("a") + shift) % 26 + ord("a")) + ciphertext += new_S + else: + ciphertext += S return ciphertext def decrypt_caesar(ciphertext: str, shift: int = 3) -> str: """ Decrypts a ciphertext using a Caesar cipher. + >>> decrypt_caesar("SBWKRQ") 'PYTHON' >>> decrypt_caesar("sbwkrq") @@ -28,5 +38,16 @@ def decrypt_caesar(ciphertext: str, shift: int = 3) -> str: '' """ plaintext = "" - # PUT YOUR CODE HERE - return plaintext \ No newline at end of file + for S in ciphertext: + if "A" <= S <= "Z": + new_S = chr((ord(S) - ord("A") - shift) % 26 + ord("A")) + plaintext += new_S + elif "a" <= S <= "z": + new_S = chr((ord(S) - ord("a") - shift) % 26 + ord("a")) + plaintext += new_S + else: + plaintext += S + return plaintext +if __name__ == "__main__": + print(encrypt_caesar(input())) + print(decrypt_caesar(input())) \ No newline at end of file From 2f189312a64530cddbd157f63e4d6a5cae8ff2c2 Mon Sep 17 00:00:00 2001 From: Elizaveta Date: Fri, 1 Nov 2024 17:42:44 +0300 Subject: [PATCH 2/9] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20encrypt=5Fvigenere()=20=D0=B8=20decrypt=5Fvigenere()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework01/vigenere.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/homework01/vigenere.py b/homework01/vigenere.py index ddff8602..64d563c9 100644 --- a/homework01/vigenere.py +++ b/homework01/vigenere.py @@ -9,7 +9,16 @@ def encrypt_vigenere(plaintext: str, keyword: str) -> str: 'LXFOPVEFRNHR' """ ciphertext = "" - # PUT YOUR CODE HERE + keyword_repeated = (keyword * (len(plaintext) // len(keyword) + 1))[:len(plaintext)] + + for p, k in zip(plaintext, keyword_repeated): + if p.isalpha(): + regist = ord('a') if p.islower() else ord('A') + new = chr((ord(p) - regist + ord(k.lower()) - ord('a')) % 26 + regist) + ciphertext += new + else: + ciphertext += p + return ciphertext @@ -24,5 +33,20 @@ def decrypt_vigenere(ciphertext: str, keyword: str) -> str: 'ATTACKATDAWN' """ plaintext = "" - # PUT YOUR CODE HERE - return plaintext \ No newline at end of file + keyword_repeated = (keyword * (len(ciphertext) // len(keyword) + 1))[:len(ciphertext)] + + for c, k in zip(ciphertext, keyword_repeated): + if c.isalpha(): + regist = ord('a') if c.islower() else ord('A') + new = chr((ord(c) - regist - (ord(k.lower()) - ord('a'))) % 26 + regist) + plaintext += new + else: + plaintext += c + + return plaintext + +encrypted = encrypt_vigenere(plaintext, keyword) +print(encrypted) + +decrypted = decrypt_vigenere(encrypted, keyword) +print(decrypted) \ No newline at end of file From b1b6c2c7875c4c28825b1eb9445af8d82a38f168 Mon Sep 17 00:00:00 2001 From: Elizaveta Date: Tue, 10 Dec 2024 21:38:37 +0300 Subject: [PATCH 3/9] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20is=5Fprime(n)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework01/rsa.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/homework01/rsa.py b/homework01/rsa.py index 91b14b02..bd769a9a 100644 --- a/homework01/rsa.py +++ b/homework01/rsa.py @@ -12,8 +12,12 @@ def is_prime(n: int) -> bool: >>> is_prime(8) False """ - # PUT YOUR CODE HERE - pass + if n < 2: + return False + for i in range(2, int(n ** 0.5) + 1): + if n % i == 0: + return False + return True def gcd(a: int, b: int) -> int: @@ -24,8 +28,9 @@ def gcd(a: int, b: int) -> int: >>> gcd(3, 7) 1 """ - # PUT YOUR CODE HERE - pass + while b != 0: + a, b = b, a % b + return abs(a) def multiplicative_inverse(e: int, phi: int) -> int: @@ -35,8 +40,20 @@ def multiplicative_inverse(e: int, phi: int) -> int: >>> multiplicative_inverse(7, 40) 23 """ - # PUT YOUR CODE HERE - pass + original_phi = phi + x0, x1 = 0, 1 + if phi == 1: + return 0 + + while e > 1: + q = e // phi + phi, e = e % phi, phi # Update e and phi + x0, x1 = x1 - q * x0, x0 # Update x0 and x1 + + if x1 < 0: + x1 += original_phi # Ensure the result is positive + + return x1 def generate_keypair(p: int, q: int) -> tp.Tuple[tp.Tuple[int, int], tp.Tuple[int, int]]: @@ -46,10 +63,10 @@ def generate_keypair(p: int, q: int) -> tp.Tuple[tp.Tuple[int, int], tp.Tuple[in raise ValueError("p and q cannot be equal") # n = pq - # PUT YOUR CODE HERE + n = p * q # phi = (p-1)(q-1) - # PUT YOUR CODE HERE + phi = (p - 1) * (q - 1) # Choose an integer e such that e and phi(n) are coprime e = random.randrange(1, phi) @@ -65,7 +82,7 @@ def generate_keypair(p: int, q: int) -> tp.Tuple[tp.Tuple[int, int], tp.Tuple[in # Return public and private keypair # Public key is (e, n) and private key is (d, n) - return ((e, n), (d, n)) + return (e, n), (d, n) def encrypt(pk: tp.Tuple[int, int], plaintext: str) -> tp.List[int]: @@ -73,7 +90,7 @@ def encrypt(pk: tp.Tuple[int, int], plaintext: str) -> tp.List[int]: key, n = pk # Convert each letter in the plaintext to numbers based on # the character using a^b mod m - cipher = [(ord(char) ** key) % n for char in plaintext] + cipher = [(ord(char)**key) % n for char in plaintext] # Return the array of bytes return cipher @@ -82,7 +99,7 @@ def decrypt(pk: tp.Tuple[int, int], ciphertext: tp.List[int]) -> str: # Unpack the key into its components key, n = pk # Generate the plaintext based on the ciphertext and key using a^b mod m - plain = [chr((char ** key) % n) for char in ciphertext] + plain = [chr((char**key) % n) for char in ciphertext] # Return the array of bytes as a string return "".join(plain) @@ -100,4 +117,4 @@ def decrypt(pk: tp.Tuple[int, int], ciphertext: tp.List[int]) -> str: print("".join(map(lambda x: str(x), encrypted_msg))) print("Decrypting message with public key ", public, " . . .") print("Your message is:") - print(decrypt(public, encrypted_msg)) \ No newline at end of file + print(decrypt(public, encrypted_msg)) From e89e681cbaaded2f6c4ebebeb222e6184206315b Mon Sep 17 00:00:00 2001 From: Elizaveta Date: Tue, 10 Dec 2024 22:19:46 +0300 Subject: [PATCH 4/9] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20is=5Fprime(),=20gcd(),=20multiplicative=5Finverse()=20?= =?UTF-8?q?=D0=B8=20generate=5Fkeypair()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...20\264\320\260\320\275\320\270\320\265.py" | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 "homework01/01\320\264\320\276\320\277\320\267\320\260\320\264\320\260\320\275\320\270\320\265.py" diff --git "a/homework01/01\320\264\320\276\320\277\320\267\320\260\320\264\320\260\320\275\320\270\320\265.py" "b/homework01/01\320\264\320\276\320\277\320\267\320\260\320\264\320\260\320\275\320\270\320\265.py" new file mode 100644 index 00000000..2534655c --- /dev/null +++ "b/homework01/01\320\264\320\276\320\277\320\267\320\260\320\264\320\260\320\275\320\270\320\265.py" @@ -0,0 +1,32 @@ +def encrypt_poly_shift(plaintext: str, odd_shift: int, even_shift: int) -> str: + alphabet = "абвгдежзийклмнопрстуфхцчшщъыьэюя" + alphabet_length = len(alphabet) + + encrypted_text = [] + + for index, char in enumerate(plaintext): + position = index + 1 + + if char.lower() in alphabet: + shift = odd_shift if position % 2 != 0 else even_shift + + original_index = alphabet.index(char.lower()) + + new_index = (original_index + shift) % alphabet_length + + encrypted_char = alphabet[new_index] + + if char.isupper(): + encrypted_text.append(encrypted_char.upper()) + else: + encrypted_text.append(encrypted_char) + else: + encrypted_text.append(char) + + return ''.join(encrypted_text) + +plaintext = input() +odd_shift = int(input()) +even_shift = int(input()) +encrypted = encrypt_poly_shift(plaintext, odd_shift, even_shift) +print(encrypted) \ No newline at end of file From 98cc7c4fe595fec9e886184e19ab5d1ef9090fd7 Mon Sep 17 00:00:00 2001 From: lezyonish Date: Sat, 11 Jan 2025 17:50:39 +0300 Subject: [PATCH 5/9] 11.01.2025 --- ...20\264\320\260\320\275\320\270\320\265.py" | 5 +- homework01/caesar.py | 20 ++- homework01/rsa.py | 4 +- homework01/vigenere.py | 15 +- homework02/sudoku.py | 161 ++++++++---------- 5 files changed, 91 insertions(+), 114 deletions(-) diff --git "a/homework01/01\320\264\320\276\320\277\320\267\320\260\320\264\320\260\320\275\320\270\320\265.py" "b/homework01/01\320\264\320\276\320\277\320\267\320\260\320\264\320\260\320\275\320\270\320\265.py" index 2534655c..98f07de1 100644 --- "a/homework01/01\320\264\320\276\320\277\320\267\320\260\320\264\320\260\320\275\320\270\320\265.py" +++ "b/homework01/01\320\264\320\276\320\277\320\267\320\260\320\264\320\260\320\275\320\270\320\265.py" @@ -23,10 +23,11 @@ def encrypt_poly_shift(plaintext: str, odd_shift: int, even_shift: int) -> str: else: encrypted_text.append(char) - return ''.join(encrypted_text) + return "".join(encrypted_text) + plaintext = input() odd_shift = int(input()) even_shift = int(input()) encrypted = encrypt_poly_shift(plaintext, odd_shift, even_shift) -print(encrypted) \ No newline at end of file +print(encrypted) diff --git a/homework01/caesar.py b/homework01/caesar.py index 52d15555..a94dc0be 100644 --- a/homework01/caesar.py +++ b/homework01/caesar.py @@ -14,11 +14,11 @@ def encrypt_caesar(plaintext: str, shift: int = 3) -> str: ciphertext = "" for S in plaintext: if "A" <= S <= "Z": - new_S = chr((ord(S) - ord("A") + shift) % 26 + ord("A")) - ciphertext += new_S + new_s = chr((ord(S) - ord("A") + shift) % 26 + ord("A")) + ciphertext += new_s elif "a" <= S <= "z": - new_S = chr((ord(S) - ord("a") + shift) % 26 + ord("a")) - ciphertext += new_S + new_s = chr((ord(S) - ord("a") + shift) % 26 + ord("a")) + ciphertext += new_s else: ciphertext += S return ciphertext @@ -40,14 +40,16 @@ def decrypt_caesar(ciphertext: str, shift: int = 3) -> str: plaintext = "" for S in ciphertext: if "A" <= S <= "Z": - new_S = chr((ord(S) - ord("A") - shift) % 26 + ord("A")) - plaintext += new_S + new_s = chr((ord(S) - ord("A") - shift) % 26 + ord("A")) + plaintext += new_s elif "a" <= S <= "z": - new_S = chr((ord(S) - ord("a") - shift) % 26 + ord("a")) - plaintext += new_S + new_s = chr((ord(S) - ord("a") - shift) % 26 + ord("a")) + plaintext += new_s else: plaintext += S return plaintext + + if __name__ == "__main__": print(encrypt_caesar(input())) - print(decrypt_caesar(input())) \ No newline at end of file + print(decrypt_caesar(input())) diff --git a/homework01/rsa.py b/homework01/rsa.py index bd769a9a..7556b45e 100644 --- a/homework01/rsa.py +++ b/homework01/rsa.py @@ -14,7 +14,7 @@ def is_prime(n: int) -> bool: """ if n < 2: return False - for i in range(2, int(n ** 0.5) + 1): + for i in range(2, int(n**0.5) + 1): if n % i == 0: return False return True @@ -90,7 +90,7 @@ def encrypt(pk: tp.Tuple[int, int], plaintext: str) -> tp.List[int]: key, n = pk # Convert each letter in the plaintext to numbers based on # the character using a^b mod m - cipher = [(ord(char)**key) % n for char in plaintext] + cipher = [(ord(char) ** key) % n for char in plaintext] # Return the array of bytes return cipher diff --git a/homework01/vigenere.py b/homework01/vigenere.py index 64d563c9..6fb2bf7c 100644 --- a/homework01/vigenere.py +++ b/homework01/vigenere.py @@ -9,12 +9,12 @@ def encrypt_vigenere(plaintext: str, keyword: str) -> str: 'LXFOPVEFRNHR' """ ciphertext = "" - keyword_repeated = (keyword * (len(plaintext) // len(keyword) + 1))[:len(plaintext)] + keyword_repeated = (keyword * (len(plaintext) // len(keyword) + 1))[: len(plaintext)] for p, k in zip(plaintext, keyword_repeated): if p.isalpha(): - regist = ord('a') if p.islower() else ord('A') - new = chr((ord(p) - regist + ord(k.lower()) - ord('a')) % 26 + regist) + regist = ord("a") if p.islower() else ord("A") + new = chr((ord(p) - regist + ord(k.lower()) - ord("a")) % 26 + regist) ciphertext += new else: ciphertext += p @@ -33,20 +33,21 @@ def decrypt_vigenere(ciphertext: str, keyword: str) -> str: 'ATTACKATDAWN' """ plaintext = "" - keyword_repeated = (keyword * (len(ciphertext) // len(keyword) + 1))[:len(ciphertext)] + keyword_repeated = (keyword * (len(ciphertext) // len(keyword) + 1))[: len(ciphertext)] for c, k in zip(ciphertext, keyword_repeated): if c.isalpha(): - regist = ord('a') if c.islower() else ord('A') - new = chr((ord(c) - regist - (ord(k.lower()) - ord('a'))) % 26 + regist) + regist = ord("a") if c.islower() else ord("A") + new = chr((ord(c) - regist - (ord(k.lower()) - ord("a"))) % 26 + regist) plaintext += new else: plaintext += c return plaintext + encrypted = encrypt_vigenere(plaintext, keyword) print(encrypted) decrypted = decrypt_vigenere(encrypted, keyword) -print(decrypted) \ No newline at end of file +print(decrypted) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index dc6fc07b..97039949 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -1,11 +1,11 @@ import pathlib import typing as tp +import random 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 +19,6 @@ 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): @@ -34,121 +33,94 @@ def display(grid: tp.List[tp.List[str]]) -> None: 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]] - >>> group([1,2,3,4,5,6,7,8,9], 3) - [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - """ - pass + return [values[i:i + n] for i in range(0, len(values), n)] def get_row(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: - """Возвращает все значения для номера строки, указанной в pos - >>> get_row([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']], (0, 0)) - ['1', '2', '.'] - >>> get_row([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']], (1, 0)) - ['4', '.', '6'] - >>> get_row([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']], (2, 0)) - ['.', '8', '9'] - """ - pass + row_idx = pos[0] + return grid[row_idx] def get_col(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: - """Возвращает все значения для номера столбца, указанного в pos - >>> get_col([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']], (0, 0)) - ['1', '4', '7'] - >>> get_col([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']], (0, 1)) - ['2', '.', '8'] - >>> get_col([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']], (0, 2)) - ['3', '6', '9'] - """ - pass + col_idx = pos[1] + return [grid[row_idx][col_idx] for row_idx in range(9)] def get_block(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: - """Возвращает все значения из квадрата, в который попадает позиция pos - >>> grid = read_sudoku('puzzle1.txt') - >>> get_block(grid, (0, 1)) - ['5', '3', '.', '6', '.', '.', '.', '9', '8'] - >>> get_block(grid, (4, 7)) - ['.', '.', '3', '.', '.', '1', '.', '.', '6'] - >>> get_block(grid, (8, 8)) - ['2', '8', '.', '.', '.', '5', '.', '7', '9'] - """ - pass + block_row, block_col = pos[0] // 3, pos[1] // 3 + block = [] + for i in range(block_row * 3, (block_row + 1) * 3): + for j in range(block_col * 3, (block_col + 1) * 3): + block.append(grid[i][j]) + return block def find_empty_positions(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.Tuple[int, int]]: - """Найти первую свободную позицию в пазле - >>> find_empty_positions([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']]) - (0, 2) - >>> find_empty_positions([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']]) - (1, 1) - >>> find_empty_positions([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']]) - (2, 0) - """ - pass + for row_idx in range(9): + for col_idx in range(9): + if grid[row_idx][col_idx] == '.': + return (row_idx, col_idx) + return None def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.Set[str]: - """Вернуть множество возможных значения для указанной позиции - >>> grid = read_sudoku('puzzle1.txt') - >>> values = find_possible_values(grid, (0,2)) - >>> values == {'1', '2', '4'} - True - >>> values = find_possible_values(grid, (4,7)) - >>> values == {'2', '5', '9'} - True - """ - pass + row_vals = set(get_row(grid, pos)) + col_vals = set(get_col(grid, pos)) + block_vals = set(get_block(grid, pos)) + + all_vals = set("123456789") + used_vals = row_vals | col_vals | block_vals + + return all_vals - used_vals def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: - """ Решение пазла, заданного в grid """ - """ Как решать Судоку? - 1. Найти свободную позицию - 2. Найти все возможные значения, которые могут находиться на этой позиции - 3. Для каждого возможного значения: - 3.1. Поместить это значение на эту позицию - 3.2. Продолжить решать оставшуюся часть пазла - >>> grid = read_sudoku('puzzle1.txt') - >>> 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_pos = find_empty_positions(grid) + if not empty_pos: + return grid + + row, col = empty_pos + possible_values = find_possible_values(grid, empty_pos) + + for value in possible_values: + grid[row][col] = value + solution = solve(grid) + if solution: + return solution + grid[row][col] = '.' + + return None def check_solution(solution: tp.List[tp.List[str]]) -> bool: - """ Если решение solution верно, то вернуть True, в противном случае False """ - # TODO: Add doctests with bad puzzles - pass + for i in range(9): + if len(set(solution[i])) != 9: + return False + if len(set(get_col(solution, (i, 0)))) != 9: + return False + + for i in range(3): + for j in range(3): + block = get_block(solution, (i * 3, j * 3)) + if len(set(block)) != 9: + return False + + return True def generate_sudoku(N: int) -> tp.List[tp.List[str]]: - """Генерация судоку заполненного на N элементов - >>> grid = generate_sudoku(40) - >>> sum(1 for row in grid for e in row if e == '.') - 41 - >>> solution = solve(grid) - >>> check_solution(solution) - True - >>> grid = generate_sudoku(1000) - >>> sum(1 for row in grid for e in row if e == '.') - 0 - >>> solution = solve(grid) - >>> check_solution(solution) - True - >>> grid = generate_sudoku(0) - >>> sum(1 for row in grid for e in row if e == '.') - 81 - >>> solution = solve(grid) - >>> check_solution(solution) - True - """ - pass + grid = [['.' for _ in range(9)] for _ in range(9)] + solution = solve(grid) + if not solution: + raise ValueError("Could not generate a valid solution.") + + positions = [(r, c) for r in range(9) for c in range(9)] + random.shuffle(positions) + + for r, c in positions[:81 - N]: + grid[r][c] = '.' + + return grid if __name__ == "__main__": @@ -159,4 +131,5 @@ def generate_sudoku(N: int) -> tp.List[tp.List[str]]: if not solution: print(f"Puzzle {fname} can't be solved") else: - display(solution) \ No newline at end of file + display(solution) + From f0e3af6a851a4d58467f50a5f2ef6c2a6f98eda9 Mon Sep 17 00:00:00 2001 From: lezyonish Date: Sat, 11 Jan 2025 21:25:50 +0300 Subject: [PATCH 6/9] 11.01 --- homework01/caesar.py | 1 + homework01/rsa.py | 49 +++++++------ homework01/vigenere.py | 75 +++++++++++++------ homework02/sudoku.py | 158 ++++++++++++++++++++++++----------------- 4 files changed, 172 insertions(+), 111 deletions(-) diff --git a/homework01/caesar.py b/homework01/caesar.py index a94dc0be..2ed7e154 100644 --- a/homework01/caesar.py +++ b/homework01/caesar.py @@ -53,3 +53,4 @@ def decrypt_caesar(ciphertext: str, shift: int = 3) -> str: if __name__ == "__main__": print(encrypt_caesar(input())) print(decrypt_caesar(input())) + diff --git a/homework01/rsa.py b/homework01/rsa.py index 7556b45e..92bea30e 100644 --- a/homework01/rsa.py +++ b/homework01/rsa.py @@ -12,12 +12,17 @@ def is_prime(n: int) -> bool: >>> is_prime(8) False """ - if n < 2: + count = 0 + for i in range(2, n - 1): + if abs(n) % i == 0: + count += 1 + break + if n <= 1: + count += 1 + if count == 0: + return True + else: return False - for i in range(2, int(n**0.5) + 1): - if n % i == 0: - return False - return True def gcd(a: int, b: int) -> int: @@ -28,9 +33,11 @@ def gcd(a: int, b: int) -> int: >>> gcd(3, 7) 1 """ - while b != 0: - a, b = b, a % b - return abs(a) + m = 0 + for i in range(1, max(a, b) + 1): + if a % i == 0 and b % i == 0 and i > m: + m = i + return m def multiplicative_inverse(e: int, phi: int) -> int: @@ -40,20 +47,14 @@ def multiplicative_inverse(e: int, phi: int) -> int: >>> multiplicative_inverse(7, 40) 23 """ - original_phi = phi - x0, x1 = 0, 1 - if phi == 1: - return 0 + d = 1 + if (e == 1) or (phi == 1): + d = 0 + else: + while (d * e) % phi != 1: + d += 1 - while e > 1: - q = e // phi - phi, e = e % phi, phi # Update e and phi - x0, x1 = x1 - q * x0, x0 # Update x0 and x1 - - if x1 < 0: - x1 += original_phi # Ensure the result is positive - - return x1 + return d def generate_keypair(p: int, q: int) -> tp.Tuple[tp.Tuple[int, int], tp.Tuple[int, int]]: @@ -61,11 +62,8 @@ def generate_keypair(p: int, q: int) -> tp.Tuple[tp.Tuple[int, int], tp.Tuple[in raise ValueError("Both numbers must be prime.") elif p == q: raise ValueError("p and q cannot be equal") - - # n = pq n = p * q - # phi = (p-1)(q-1) phi = (p - 1) * (q - 1) # Choose an integer e such that e and phi(n) are coprime @@ -82,7 +80,7 @@ def generate_keypair(p: int, q: int) -> tp.Tuple[tp.Tuple[int, int], tp.Tuple[in # Return public and private keypair # Public key is (e, n) and private key is (d, n) - return (e, n), (d, n) + return ((e, n), (d, n)) def encrypt(pk: tp.Tuple[int, int], plaintext: str) -> tp.List[int]: @@ -118,3 +116,4 @@ def decrypt(pk: tp.Tuple[int, int], ciphertext: tp.List[int]) -> str: print("Decrypting message with public key ", public, " . . .") print("Your message is:") print(decrypt(public, encrypted_msg)) + diff --git a/homework01/vigenere.py b/homework01/vigenere.py index 6fb2bf7c..8528a733 100644 --- a/homework01/vigenere.py +++ b/homework01/vigenere.py @@ -8,17 +8,37 @@ def encrypt_vigenere(plaintext: str, keyword: str) -> str: >>> encrypt_vigenere("ATTACKATDAWN", "LEMON") 'LXFOPVEFRNHR' """ + + if len(plaintext) > len(keyword): + diff = len(plaintext) - len(keyword) + for i in range(diff): + keyword += keyword[i] + nums = [] ciphertext = "" - keyword_repeated = (keyword * (len(plaintext) // len(keyword) + 1))[: len(plaintext)] - for p, k in zip(plaintext, keyword_repeated): - if p.isalpha(): - regist = ord("a") if p.islower() else ord("A") - new = chr((ord(p) - regist + ord(k.lower()) - ord("a")) % 26 + regist) - ciphertext += new + for i in range(len(keyword)): + if keyword[i].isalpha(): + if keyword[i].islower(): + nums.append((ord(keyword[i]) - ord("a")) % 26) + else: + nums.append((ord(keyword[i]) - ord("A")) % 26) else: - ciphertext += p + nums.append(0) + for i in range(len(nums)): + if plaintext[i].isalpha(): + if plaintext[i].islower(): + if ord(plaintext[i]) + nums[i] <= ord("z"): + ciphertext += chr(ord(plaintext[i]) + nums[i]) + else: + ciphertext += chr(ord(plaintext[i]) + nums[i] - 26) + else: + if ord(plaintext[i]) + nums[i] <= ord("Z"): + ciphertext += chr(ord(plaintext[i]) + nums[i]) + else: + ciphertext += chr(ord(plaintext[i]) + nums[i] - 26) + else: + ciphertext += plaintext[i] return ciphertext @@ -32,22 +52,35 @@ def decrypt_vigenere(ciphertext: str, keyword: str) -> str: >>> decrypt_vigenere("LXFOPVEFRNHR", "LEMON") 'ATTACKATDAWN' """ - plaintext = "" - keyword_repeated = (keyword * (len(ciphertext) // len(keyword) + 1))[: len(ciphertext)] - for c, k in zip(ciphertext, keyword_repeated): - if c.isalpha(): - regist = ord("a") if c.islower() else ord("A") - new = chr((ord(c) - regist - (ord(k.lower()) - ord("a"))) % 26 + regist) - plaintext += new + if len(ciphertext) > len(keyword): + diff = len(ciphertext) - len(keyword) + for i in range(diff): + keyword += keyword[i] + nums = [] + plaintext = "" + for i in range(len(ciphertext)): + if keyword[i].isalpha(): + if keyword[i].islower(): + nums.append((ord(keyword[i]) - ord("a")) % 26) + else: + nums.append((ord(keyword[i]) - ord("A")) % 26) else: - plaintext += c + nums.append(0) + for i in range(len(nums)): + if ciphertext[i].isalpha(): + if ciphertext[i].islower(): + if ord(ciphertext[i]) - nums[i] >= ord("a"): + plaintext += chr(ord(ciphertext[i]) - nums[i]) + else: + plaintext += chr(ord(ciphertext[i]) - nums[i] + 26) + else: + if ord(ciphertext[i]) - nums[i] >= ord("A"): + plaintext += chr(ord(ciphertext[i]) - nums[i]) + else: + plaintext += chr(ord(ciphertext[i]) - nums[i] + 26) + else: + plaintext += ciphertext[i] return plaintext - -encrypted = encrypt_vigenere(plaintext, keyword) -print(encrypted) - -decrypted = decrypt_vigenere(encrypted, keyword) -print(decrypted) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index 97039949..e9da7e85 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -1,11 +1,11 @@ import pathlib import typing as tp -import random 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,6 +19,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): @@ -33,94 +34,121 @@ def display(grid: tp.List[tp.List[str]]) -> None: def group(values: tp.List[T], n: int) -> tp.List[tp.List[T]]: - return [values[i:i + n] for i in range(0, len(values), n)] + """ + Сгруппировать значения values в список, состоящий из списков по n элементов + >>> group([1,2,3,4], 2) + [[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 def get_row(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: - row_idx = pos[0] - return grid[row_idx] + """Возвращает все значения для номера строки, указанной в pos + >>> get_row([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']], (0, 0)) + ['1', '2', '.'] + >>> get_row([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']], (1, 0)) + ['4', '.', '6'] + >>> get_row([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']], (2, 0)) + ['.', '8', '9'] + """ + pass def get_col(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: - col_idx = pos[1] - return [grid[row_idx][col_idx] for row_idx in range(9)] + """Возвращает все значения для номера столбца, указанного в pos + >>> get_col([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']], (0, 0)) + ['1', '4', '7'] + >>> get_col([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']], (0, 1)) + ['2', '.', '8'] + >>> get_col([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']], (0, 2)) + ['3', '6', '9'] + """ + pass def get_block(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: - block_row, block_col = pos[0] // 3, pos[1] // 3 - block = [] - for i in range(block_row * 3, (block_row + 1) * 3): - for j in range(block_col * 3, (block_col + 1) * 3): - block.append(grid[i][j]) - return block + """Возвращает все значения из квадрата, в который попадает позиция pos + >>> grid = read_sudoku('puzzle1.txt') + >>> get_block(grid, (0, 1)) + ['5', '3', '.', '6', '.', '.', '.', '9', '8'] + >>> get_block(grid, (4, 7)) + ['.', '.', '3', '.', '.', '1', '.', '.', '6'] + >>> get_block(grid, (8, 8)) + ['2', '8', '.', '.', '.', '5', '.', '7', '9'] + """ + pass def find_empty_positions(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.Tuple[int, int]]: - for row_idx in range(9): - for col_idx in range(9): - if grid[row_idx][col_idx] == '.': - return (row_idx, col_idx) - return None + """Найти первую свободную позицию в пазле + >>> find_empty_positions([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']]) + (0, 2) + >>> find_empty_positions([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']]) + (1, 1) + >>> find_empty_positions([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']]) + (2, 0) + """ + pass def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.Set[str]: - row_vals = set(get_row(grid, pos)) - col_vals = set(get_col(grid, pos)) - block_vals = set(get_block(grid, pos)) - - all_vals = set("123456789") - used_vals = row_vals | col_vals | block_vals - - return all_vals - used_vals + """Вернуть множество возможных значения для указанной позиции + >>> grid = read_sudoku('puzzle1.txt') + >>> values = find_possible_values(grid, (0,2)) + >>> values == {'1', '2', '4'} + True + >>> values = find_possible_values(grid, (4,7)) + >>> values == {'2', '5', '9'} + True + """ + pass def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: - empty_pos = find_empty_positions(grid) - if not empty_pos: - return grid - - row, col = empty_pos - possible_values = find_possible_values(grid, empty_pos) - - for value in possible_values: - grid[row][col] = value - solution = solve(grid) - if solution: - return solution - grid[row][col] = '.' - - return None + """ Решение пазла, заданного в grid """ + """ Как решать Судоку? + 1. Найти свободную позицию + 2. Найти все возможные значения, которые могут находиться на этой позиции + 3. Для каждого возможного значения: + 3.1. Поместить это значение на эту позицию + 3.2. Продолжить решать оставшуюся часть пазла + >>> grid = read_sudoku('puzzle1.txt') + >>> 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 def check_solution(solution: tp.List[tp.List[str]]) -> bool: - for i in range(9): - if len(set(solution[i])) != 9: - return False - if len(set(get_col(solution, (i, 0)))) != 9: - return False - - for i in range(3): - for j in range(3): - block = get_block(solution, (i * 3, j * 3)) - if len(set(block)) != 9: - return False - - return True + """ Если решение solution верно, то вернуть True, в противном случае False """ + # TODO: Add doctests with bad puzzles + pass def generate_sudoku(N: int) -> tp.List[tp.List[str]]: - grid = [['.' for _ in range(9)] for _ in range(9)] - solution = solve(grid) - if not solution: - raise ValueError("Could not generate a valid solution.") - - positions = [(r, c) for r in range(9) for c in range(9)] - random.shuffle(positions) - - for r, c in positions[:81 - N]: - grid[r][c] = '.' - - return grid + """Генерация судоку заполненного на N элементов + >>> grid = generate_sudoku(40) + >>> sum(1 for row in grid for e in row if e == '.') + 41 + >>> solution = solve(grid) + >>> check_solution(solution) + True + >>> grid = generate_sudoku(1000) + >>> sum(1 for row in grid for e in row if e == '.') + 0 + >>> solution = solve(grid) + >>> check_solution(solution) + True + >>> grid = generate_sudoku(0) + >>> sum(1 for row in grid for e in row if e == '.') + 81 + >>> solution = solve(grid) + >>> check_solution(solution) + True + """ + pass if __name__ == "__main__": From b64bd9cd5f0d0017234b23d7359450bd8af7537a Mon Sep 17 00:00:00 2001 From: lezyonish Date: Sun, 19 Jan 2025 00:57:06 +0300 Subject: [PATCH 7/9] o --- homework02/sudoku.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index e9da7e85..ded2f42f 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -160,4 +160,3 @@ def generate_sudoku(N: int) -> tp.List[tp.List[str]]: print(f"Puzzle {fname} can't be solved") else: display(solution) - From 426412d8d1adbbb6bb0f3c7be92c64da15cf3e6a Mon Sep 17 00:00:00 2001 From: lezyonish Date: Mon, 20 Jan 2025 21:15:46 +0300 Subject: [PATCH 8/9] 78788 --- homework02/sudoku.py | 14 +++++--------- homework02/test_sudoku.py | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/homework02/sudoku.py b/homework02/sudoku.py index ded2f42f..d53f10f1 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -5,7 +5,7 @@ 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,15 +19,11 @@ 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): - print( - "".join( - grid[row][col].center(width) + ("|" if str(col) in "25" else "") for col in range(9) - ) - ) + print("".join(grid[row][col].center(width) + ("|" if str(col) in "25" else "") for col in range(9))) if str(row) in "25": print(line) print() @@ -107,7 +103,7 @@ def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) - def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: - """ Решение пазла, заданного в grid """ + """Решение пазла, заданного в grid""" """ Как решать Судоку? 1. Найти свободную позицию 2. Найти все возможные значения, которые могут находиться на этой позиции @@ -122,7 +118,7 @@ def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: def check_solution(solution: tp.List[tp.List[str]]) -> bool: - """ Если решение solution верно, то вернуть True, в противном случае False """ + """Если решение solution верно, то вернуть True, в противном случае False""" # TODO: Add doctests with bad puzzles pass diff --git a/homework02/test_sudoku.py b/homework02/test_sudoku.py index 2aabb6a7..39180188 100644 --- a/homework02/test_sudoku.py +++ b/homework02/test_sudoku.py @@ -239,4 +239,4 @@ def test_generate_sudoku(self): self.assertEqual(expected_unknown, actual_unknown) solution = sudoku.solve(grid) solved = sudoku.check_solution(solution) - self.assertTrue(solved) \ No newline at end of file + self.assertTrue(solved) From a4ed4c2d89c0e7fbe9dc4455a4f6fe716ccab7a8 Mon Sep 17 00:00:00 2001 From: lezyonish Date: Tue, 21 Jan 2025 12:59:49 +0300 Subject: [PATCH 9/9] d --- homework01/caesar.py | 65 +++++++++++---------- homework01/rsa.py | 1 - homework01/vigenere.py | 1 - homework02/sudoku.py | 101 +++++++++++++++++++++++++++++++-- homework03/maze.py | 112 ++++++++++++++++++++++++++++--------- homework03/maze_gui.py | 12 ++-- homework04/life.py | 97 ++++++++++++++++++++++++++++---- homework04/life_console.py | 39 +++++++++++-- homework04/life_gui.py | 109 ++++++++++++++++++++++++++++++++++-- homework04/life_proto.py | 56 ++++++++++++++++--- 10 files changed, 493 insertions(+), 100 deletions(-) diff --git a/homework01/caesar.py b/homework01/caesar.py index 2ed7e154..aa886011 100644 --- a/homework01/caesar.py +++ b/homework01/caesar.py @@ -1,26 +1,30 @@ def encrypt_caesar(plaintext: str, shift: int = 3) -> str: """ Encrypts plaintext using a Caesar cipher. - >>> encrypt_caesar("PYTHON") - 'SBWKRQ' + "SBWKRQ" >>> encrypt_caesar("python") - 'sbwkrq' + "sbwkrq" >>> encrypt_caesar("Python3.6") - 'Sbwkrq3.6' + "Sbwkrq3.6" >>> encrypt_caesar("") - '' + "" """ ciphertext = "" - for S in plaintext: - if "A" <= S <= "Z": - new_s = chr((ord(S) - ord("A") + shift) % 26 + ord("A")) - ciphertext += new_s - elif "a" <= S <= "z": - new_s = chr((ord(S) - ord("a") + shift) % 26 + ord("a")) - ciphertext += new_s + for char in plaintext: + if char.isalpha(): + if char.islower(): + if ord(char) < ord("z") - shift + 1: + ciphertext += chr(ord(char) + shift) + else: + ciphertext += chr(ord(char) + shift - 26) + else: + if ord(char) < ord("Z") - shift + 1: + ciphertext += chr(ord(char) + shift) + else: + ciphertext += chr(ord(char) + shift - 26) else: - ciphertext += S + ciphertext += char return ciphertext @@ -29,28 +33,27 @@ def decrypt_caesar(ciphertext: str, shift: int = 3) -> str: Decrypts a ciphertext using a Caesar cipher. >>> decrypt_caesar("SBWKRQ") - 'PYTHON' + "PYTHON" >>> decrypt_caesar("sbwkrq") - 'python' + "python" >>> decrypt_caesar("Sbwkrq3.6") - 'Python3.6' + "Python3.6" >>> decrypt_caesar("") - '' + "" """ plaintext = "" - for S in ciphertext: - if "A" <= S <= "Z": - new_s = chr((ord(S) - ord("A") - shift) % 26 + ord("A")) - plaintext += new_s - elif "a" <= S <= "z": - new_s = chr((ord(S) - ord("a") - shift) % 26 + ord("a")) - plaintext += new_s + for char in ciphertext: + if char.isalpha(): + if char.islower(): + if ord(char) > ord("a") + shift - 1: + plaintext += chr(ord(char) - shift) + else: + plaintext += chr(ord(char) - shift + 26) + else: + if ord(char) > ord("A") + shift - 1: + plaintext += chr(ord(char) - shift) + else: + plaintext += chr(ord(char) - shift + 26) else: - plaintext += S + plaintext += char return plaintext - - -if __name__ == "__main__": - print(encrypt_caesar(input())) - print(decrypt_caesar(input())) - diff --git a/homework01/rsa.py b/homework01/rsa.py index 92bea30e..49f65c64 100644 --- a/homework01/rsa.py +++ b/homework01/rsa.py @@ -116,4 +116,3 @@ def decrypt(pk: tp.Tuple[int, int], ciphertext: tp.List[int]) -> str: print("Decrypting message with public key ", public, " . . .") print("Your message is:") print(decrypt(public, encrypted_msg)) - diff --git a/homework01/vigenere.py b/homework01/vigenere.py index 8528a733..7c35730f 100644 --- a/homework01/vigenere.py +++ b/homework01/vigenere.py @@ -83,4 +83,3 @@ def decrypt_vigenere(ciphertext: str, keyword: str) -> str: else: plaintext += ciphertext[i] return plaintext - diff --git a/homework02/sudoku.py b/homework02/sudoku.py index d53f10f1..3f0b13c8 100644 --- a/homework02/sudoku.py +++ b/homework02/sudoku.py @@ -1,4 +1,5 @@ import pathlib +import random import typing as tp T = tp.TypeVar("T") @@ -32,16 +33,23 @@ def display(grid: tp.List[tp.List[str]]) -> None: 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]] >>> group([1,2,3,4,5,6,7,8,9], 3) [[1, 2, 3], [4, 5, 6], [7, 8, 9]] """ + mas = [] + for i in range(len(values) // n): + mas.append(values[i * n : i * n + n]) + + return mas pass def get_row(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: """Возвращает все значения для номера строки, указанной в pos + >>> get_row([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']], (0, 0)) ['1', '2', '.'] >>> get_row([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']], (1, 0)) @@ -49,11 +57,13 @@ 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'] """ + return grid[pos[0]] pass def get_col(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: """Возвращает все значения для номера столбца, указанного в pos + >>> get_col([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']], (0, 0)) ['1', '4', '7'] >>> get_col([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']], (0, 1)) @@ -61,11 +71,17 @@ 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'] """ + mas = [] + + for i in range(len(grid)): + mas.append(grid[i][pos[1]]) + return mas pass def get_block(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]: """Возвращает все значения из квадрата, в который попадает позиция pos + >>> grid = read_sudoku('puzzle1.txt') >>> get_block(grid, (0, 1)) ['5', '3', '.', '6', '.', '.', '.', '9', '8'] @@ -74,11 +90,25 @@ 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'] """ + a = pos[0] // 3 * 3 + b = pos[1] // 3 * 3 + mas = [] + mas.append(grid[a][b]) + mas.append(grid[a][b + 1]) + mas.append(grid[a][b + 2]) + mas.append(grid[a + 1][b]) + mas.append(grid[a + 1][b + 1]) + mas.append(grid[a + 1][b + 2]) + mas.append(grid[a + 2][b]) + mas.append(grid[a + 2][b + 1]) + mas.append(grid[a + 2][b + 2]) + return mas pass def find_empty_positions(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.Tuple[int, int]]: """Найти первую свободную позицию в пазле + >>> find_empty_positions([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']]) (0, 2) >>> find_empty_positions([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']]) @@ -86,11 +116,16 @@ 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 + for i in range(len(grid)): + for j in range(len(grid[i])): + if grid[i][j] == ".": + return i, j + return None def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.Set[str]: """Вернуть множество возможных значения для указанной позиции + >>> grid = read_sudoku('puzzle1.txt') >>> values = find_possible_values(grid, (0,2)) >>> values == {'1', '2', '4'} @@ -99,7 +134,15 @@ def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) - >>> values == {'2', '5', '9'} True """ - pass + all = {"1", "2", "3", "4", "5", "6", "7", "8", "9"} + + var1 = get_block(grid, pos) + var2 = get_col(grid, pos) + var3 = get_row(grid, pos) + all = all.difference(var1) + all = all.difference(var2) + all = all.difference(var3) + return all def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: @@ -110,21 +153,54 @@ def solve(grid: tp.List[tp.List[str]]) -> tp.Optional[tp.List[tp.List[str]]]: 3. Для каждого возможного значения: 3.1. Поместить это значение на эту позицию 3.2. Продолжить решать оставшуюся часть пазла + >>> grid = read_sudoku('puzzle1.txt') >>> 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 + + def sudokusolver(grid: tp.List[tp.List[str]]) -> bool: + p = find_empty_positions(grid) + if p is None: + return True + possible_values = find_possible_values(grid, p) + for value in possible_values: + grid[p[0]][p[1]] = value + if sudokusolver(grid): # Recursively solve the rest of the puzzle + return True + grid[p[0]][p[1]] = "." # Backtrack + return False + + if sudokusolver(grid): # If solving is successful, return the grid + return grid + return None def check_solution(solution: tp.List[tp.List[str]]) -> bool: """Если решение solution верно, то вернуть True, в противном случае False""" # TODO: Add doctests with bad puzzles - pass + for i in range(len(solution)): + if type(solution[i]) != list: + return False + + for j in range(len(solution)): + if solution[i][j] == ".": + return False + pos = (i, j) + l1 = list(get_row(solution, pos)) + l2 = list(get_col(solution, pos)) + l3 = list(get_block(solution, pos)) + s1 = set(l1) + s2 = set(l2) + s3 = set(l3) + if len(s1) != len(l1) or len(s2) != len(l2) or len(s3) != len(l3): + return False + return True def generate_sudoku(N: int) -> tp.List[tp.List[str]]: """Генерация судоку заполненного на N элементов + >>> grid = generate_sudoku(40) >>> sum(1 for row in grid for e in row if e == '.') 41 @@ -144,7 +220,22 @@ def generate_sudoku(N: int) -> tp.List[tp.List[str]]: >>> check_solution(solution) True """ - pass + + mas: tp.List[tp.List[str]] = [["."] * 9 for _ in range(9)] + solved_grid = solve(mas) + if solved_grid is None: + raise ValueError("Sudoku puzzle could not be solved") + mas = solved_grid + if N < 81: + k = 81 - N + else: + k = 0 + for _ in range(k): + t, p = random.randint(0, 8), random.randint(0, 8) + while mas[t][p] == ".": + t, p = random.randint(0, 8), random.randint(0, 8) + mas[t][p] = "." + return mas if __name__ == "__main__": diff --git a/homework03/maze.py b/homework03/maze.py index dd654c45..e16641b1 100644 --- a/homework03/maze.py +++ b/homework03/maze.py @@ -9,22 +9,28 @@ def create_grid(rows: int = 15, cols: int = 15) -> List[List[Union[str, int]]]: return [["■"] * cols for _ in range(rows)] -def remove_wall( - grid: List[List[Union[str, int]]], coord: Tuple[int, int] -) -> List[List[Union[str, int]]]: +def remove_wall(grid: List[List[Union[str, int]]], coord: Tuple[int, int]) -> List[List[Union[str, int]]]: """ :param grid: :param coord: :return: """ - - pass + x, y, c, r = coord[0], coord[1], len(grid) - 1, len(grid[0]) - 1 + directions = ["up", "right"] + direction = choice(directions) + if direction == "up" and 0 <= x - 2 < c and 0 <= y < r: + grid[x - 1][y] = " " + else: + direction = "right" + if direction == "right" and 0 <= x < c and 0 <= y + 2 < r: + grid[x][y + 1] = " " + elif direction == "right" and 0 <= x - 2 < c and 0 <= y < r: + grid[x - 1][y] = " " + return grid -def bin_tree_maze( - rows: int = 15, cols: int = 15, random_exit: bool = True -) -> List[List[Union[str, int]]]: +def bin_tree_maze(rows: int = 15, cols: int = 15, random_exit: bool = True) -> List[List[Union[str, int]]]: """ :param rows: @@ -47,7 +53,9 @@ def bin_tree_maze( # выбрать второе возможное направление # 3. перейти в следующую клетку, сносим между клетками стену # 4. повторять 2-3 до тех пор, пока не будут пройдены все клетки - + while empty_cells: + x, y = empty_cells.pop(0) + remove_wall(grid, (x, y)) # генерация входа и выхода if random_exit: x_in, x_out = randint(0, rows - 1), randint(0, rows - 1) @@ -56,9 +64,7 @@ def bin_tree_maze( else: x_in, y_in = 0, cols - 2 x_out, y_out = rows - 1, 1 - grid[x_in][y_in], grid[x_out][y_out] = "X", "X" - return grid @@ -68,8 +74,12 @@ def get_exits(grid: List[List[Union[str, int]]]) -> List[Tuple[int, int]]: :param grid: :return: """ - - pass + exits = [] + for x, row in enumerate(grid): + for y, value in enumerate(row): + if value == "X": + exits.append((x, y)) + return exits def make_step(grid: List[List[Union[str, int]]], k: int) -> List[List[Union[str, int]]]: @@ -80,11 +90,19 @@ def make_step(grid: List[List[Union[str, int]]], k: int) -> List[List[Union[str, :return: """ - pass + directions = [(1, 0), (-1, 0), (0, 1), (0, -1)] + for x, row in enumerate(grid): + for y, cell in enumerate(row): + if cell == k: + for dx, dy in directions: + nx, ny = x + dx, y + dy + if 0 <= nx < len(grid) and 0 <= ny < len(grid[0]) and grid[nx][ny] == 0: + grid[nx][ny] = k + 1 + return grid def shortest_path( - grid: List[List[Union[str, int]]], exit_coord: Tuple[int, int] + grid: List[List[Union[str, int]]], exit_coord: Tuple[int, int] ) -> Optional[Union[Tuple[int, int], List[Tuple[int, int]]]]: """ @@ -92,7 +110,26 @@ def shortest_path( :param exit_coord: :return: """ - pass + k = 0 + x_out, y_out = exit_coord + while grid[x_out][y_out] == 0: + k += 1 + grid = make_step(grid, k) + path = [exit_coord] + k = int(grid[x_out][y_out]) + x, y = exit_coord + while grid[x][y] != 1 and k > 0: + for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: + nx, ny = x + dx, y + dy + if 0 <= nx < len(grid) and 0 <= ny < len(grid[0]) and grid[nx][ny] == k - 1: + path.append((nx, ny)) + x, y = nx, ny + k -= 1 + break + if len(path) != grid[exit_coord[0]][exit_coord[1]]: + grid[path[-1][0]][path[-1][1]] = " " + shortest_path(grid, exit_coord) + return path def encircled_exit(grid: List[List[Union[str, int]]], coord: Tuple[int, int]) -> bool: @@ -102,24 +139,49 @@ def encircled_exit(grid: List[List[Union[str, int]]], coord: Tuple[int, int]) -> :param coord: :return: """ + x, y = coord + rows, cols = len(grid), len(grid[0]) + + if (x in (0, rows - 1) and y in (0, cols - 1)) or (x - 1 == 0 and y + 1 == cols - 1): + return True - pass + if x == 0 and y in range(0, cols) and grid[x + 1][y] == "■": + return True + if x == rows - 1 and y in range(0, cols) and grid[x - 1][y] == "■": + return True + if y == 0 and x in range(0, rows) and grid[x][y + 1] == "■": + return True + if y == cols - 1 and x in range(0, rows) and grid[x][y - 1] == "■": + return True + return False def solve_maze( - grid: List[List[Union[str, int]]], + grid: List[List[Union[str, int]]], ) -> Tuple[List[List[Union[str, int]]], Optional[Union[Tuple[int, int], List[Tuple[int, int]]]]]: """ - :param grid: :return: """ - - pass + exits = get_exits(grid) + if len(exits) > 1: + if encircled_exit(grid, exits[0]) or encircled_exit(grid, exits[1]): + return grid, None + ngrid = deepcopy(grid) + x_in, y_in = exits[0] + grid[x_in][y_in] = 1 + for x, row in enumerate(grid): + for y, _ in enumerate(row): + if grid[x][y] == " " or grid[x][y] == "X": + grid[x][y] = 0 + path = shortest_path(grid, exits[1]) + return ngrid, path + path = exits + return grid, path def add_path_to_grid( - grid: List[List[Union[str, int]]], path: Optional[Union[Tuple[int, int], List[Tuple[int, int]]]] + grid: List[List[Union[str, int]]], path: Optional[Union[Tuple[int, int], List[Tuple[int, int]]]] ) -> List[List[Union[str, int]]]: """ @@ -127,7 +189,6 @@ def add_path_to_grid( :param path: :return: """ - if path: for i, row in enumerate(grid): for j, _ in enumerate(row): @@ -140,6 +201,7 @@ def add_path_to_grid( print(pd.DataFrame(bin_tree_maze(15, 15))) GRID = bin_tree_maze(15, 15) print(pd.DataFrame(GRID)) - _, PATH = solve_maze(GRID) - MAZE = add_path_to_grid(GRID, PATH) + NGRID, PATH = solve_maze(GRID) + MAZE = add_path_to_grid(NGRID, PATH) print(pd.DataFrame(MAZE)) + diff --git a/homework03/maze_gui.py b/homework03/maze_gui.py index 6c4780cb..26321237 100644 --- a/homework03/maze_gui.py +++ b/homework03/maze_gui.py @@ -1,7 +1,8 @@ import tkinter as tk +from tkinter import messagebox, ttk from typing import List -from tkinter import ttk, messagebox -from maze import bin_tree_maze, solve_maze, add_path_to_grid + +from maze import add_path_to_grid, bin_tree_maze, solve_maze def draw_cell(x, y, color, size: int = 10): @@ -16,9 +17,9 @@ def draw_maze(grid: List[List[str]], size: int = 10): for x, row in enumerate(grid): for y, cell in enumerate(row): if cell == " ": - color = 'White' + color = "White" elif cell == "■": - color = 'black' + color = "black" elif cell == "X": color = "red" draw_cell(y, x, color, size) @@ -41,7 +42,7 @@ def show_solution(): GRID = bin_tree_maze(N, M) window = tk.Tk() - window.title('Maze') + window.title("Maze") window.geometry("%dx%d" % (M * CELL_SIZE + 100, N * CELL_SIZE + 100)) canvas = tk.Canvas(window, width=M * CELL_SIZE, height=N * CELL_SIZE) @@ -51,4 +52,3 @@ def show_solution(): ttk.Button(window, text="Solve", command=show_solution).pack(pady=20) window.mainloop() - diff --git a/homework04/life.py b/homework04/life.py index 7aef0b62..a022c06d 100644 --- a/homework04/life.py +++ b/homework04/life.py @@ -1,6 +1,7 @@ import pathlib import random import typing as tp +from pprint import pprint as pp import pygame from pygame.locals import * @@ -17,6 +18,9 @@ def __init__( randomize: bool = True, max_generations: tp.Optional[float] = float("inf"), ) -> None: + """ + Задает класс "игра в жизнь" + """ # Размер клеточного поля self.rows, self.cols = size # Предыдущее поколение клеток @@ -29,46 +33,117 @@ def __init__( self.generations = 1 def create_grid(self, randomize: bool = False) -> Grid: - # Copy from previous assignment - pass + """ + Создание списка клеток. + + Клетка считается живой, если ее значение равно 1, в противном случае клетка + считается мертвой, то есть, ее значение равно 0. + + Parameters + ---------- + randomize : bool + Если значение истина, то создается матрица, где каждая клетка может + быть равновероятно живой или мертвой, иначе все клетки создаются мертвыми. + + Returns + ---------- + out : Grid + Матрица клеток размером `cell_height` х `cell_width`. + """ + grid = [[0] * self.cols for _ in range(self.rows)] + if randomize: + for i in range(self.rows): + for j in range(self.cols): + grid[i][j] = random.choice((0, 1)) + return grid def get_neighbours(self, cell: Cell) -> Cells: - # Copy from previous assignment - pass + """ + Вернуть список соседних клеток для клетки `cell`. + + Соседними считаются клетки по горизонтали, вертикали и диагоналям, + то есть, во всех направлениях. + + Parameters + ---------- + cell : Cell + Клетка, для которой необходимо получить список соседей. Клетка + представлена кортежем, содержащим ее координаты на игровом поле. + + Returns + ---------- + out : Cells + Список соседних клеток. + """ + row, col = cell + neighbours = [] + for i in range(row - 1, row + 2): + for j in range(col - 1, col + 2): + if 0 <= i < self.rows and 0 <= j < self.cols and cell != (i, j): + neighbours.append(self.curr_generation[i][j]) + return neighbours def get_next_generation(self) -> Grid: - # Copy from previous assignment - pass + """ + Получить следующее поколение клеток. + + Returns + ---------- + out : Grid + Новое поколение клеток. + """ + new_grid = [[0] * self.cols for _ in range(self.rows)] + for i, row in enumerate(new_grid): + for j, value in enumerate(row): + neighbours = self.get_neighbours((i, j)) + total_neighbours = sum(neighbours) + if self.curr_generation[i][j] == 0: + new_grid[i][j] = 1 if total_neighbours == 3 else 0 + else: + new_grid[i][j] = 1 if total_neighbours in [2, 3] else 0 + return new_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 + return self.generations >= self.max_generations if self.max_generations else False @property def is_changing(self) -> bool: """ Изменилось ли состояние клеток с предыдущего шага. """ - pass + return self.prev_generation != self.curr_generation @staticmethod def from_file(filename: pathlib.Path) -> "GameOfLife": """ Прочитать состояние клеток из указанного файла. """ - pass + grid = [] + with filename.open("r") as f: + for line in f: + line = line.strip() + if line: + row = [int(i) for i in line] + grid.append(row) + game = GameOfLife(size=(len(grid), len(grid[0]))) + game.curr_generation = grid + return game def save(self, filename: pathlib.Path) -> None: """ Сохранить текущее состояние клеток в указанный файл. """ - pass + with filename.open("w") as f: + f.writelines([str(row) + "\n" for row in self.curr_generation]) diff --git a/homework04/life_console.py b/homework04/life_console.py index ddeb9ef1..77c7e801 100644 --- a/homework04/life_console.py +++ b/homework04/life_console.py @@ -9,14 +9,43 @@ def __init__(self, life: GameOfLife) -> None: super().__init__(life) def draw_borders(self, screen) -> None: - """ Отобразить рамку. """ - pass + """Отобразить рамку.""" + screen.border(0) def draw_grid(self, screen) -> None: - """ Отобразить состояние клеток. """ - pass + """Отобразить состояние клеток.""" + height, width = screen.getmaxyx() + for i, row in enumerate(self.life.curr_generation): + for j, val in enumerate(row): + if 0 < i < height - 1 and 0 < j < width - 1: + ch = " " + if val == 1: + ch = "1" + screen.addch(i, j, ch) def run(self) -> None: screen = curses.initscr() - # PUT YOUR CODE HERE + curses.curs_set(0) + running = True + while running: + screen.clear() + + self.draw_borders(screen) + self.draw_grid(screen) + screen.refresh() + + self.life.step() + + if self.life.is_max_generations_exceeded: + screen.addstr(0, 0, "Max generations exceeded") + screen.refresh() + if not self.life.is_changing: + screen.addstr(0, 0, "Nothing changing") + screen.refresh() + + key = screen.getch() + + if key == ord("q"): + running = False + break curses.endwin() diff --git a/homework04/life_gui.py b/homework04/life_gui.py index 1126b291..e639c87c 100644 --- a/homework04/life_gui.py +++ b/homework04/life_gui.py @@ -5,17 +5,114 @@ 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.life.cols * self.cell_size + self.height = self.life.rows * self.cell_size + self.screen_size = self.width, self.height + self.screen = pygame.display.set_mode(self.screen_size) + self.speed = speed + + self.button_frame = pygame.Rect(self.width // 2 - 50, 0, 104, 54) + self.button = pygame.Rect(self.width // 2 - 48, 2, 100, 50) + self.error_frame = pygame.Rect(self.width // 2 - 132, self.height // 2 - 7, 304, 34) + self.error_button = pygame.Rect(self.width // 2 - 130, self.height // 2 - 5, 300, 30) + + pygame.font.init() + self.font = pygame.font.Font(None, 30) + + self.status = False 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, row in enumerate(self.life.curr_generation): + for j, val in enumerate(row): + if val == 1: + for x in range(i * self.cell_size, (i + 1) * self.cell_size): + pygame.draw.line( + self.screen, + pygame.Color("spring green"), + (j * self.cell_size, x), + ((j + 1) * self.cell_size, x), + ) + else: + for x in range(i * self.cell_size, (i + 1) * self.cell_size + 1): + pygame.draw.line( + self.screen, + pygame.Color("white"), + (j * self.cell_size, x), + ((j + 1) * self.cell_size, x), + ) 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 == QUIT: + running = False + + if event.type == pygame.MOUSEBUTTONDOWN: + mouse_pos = event.pos + if self.button.collidepoint(mouse_pos): + self.status = not self.status # смена паузы и продолжения игры + + if self.status: # если пауза, можно менять клетки + pos_x = mouse_pos[1] // self.cell_size + pos_y = mouse_pos[0] // self.cell_size + if 0 <= pos_x < self.life.rows and 0 <= pos_y < self.life.cols: + self.life.curr_generation[pos_x][pos_y] = 1 - self.life.curr_generation[pos_x][pos_y] + + self.screen.fill(pygame.Color("white")) # очистка экрана + self.draw_grid() + self.draw_lines() + + # кнопка паузы + button_color = pygame.Color("light steel blue") if self.status else (pygame.Color("lavender")) + pygame.draw.rect(self.screen, "indigo", self.button_frame, border_radius=15) + pygame.draw.rect(self.screen, button_color, self.button, border_radius=15) + button_text = self.font.render("Pause" if not self.status else "Resume", True, pygame.Color("indigo")) + if not self.status: + self.screen.blit(button_text, (self.button.x + 20, self.button.y + 15)) + else: + self.screen.blit(button_text, (self.button.x + 10, self.button.y + 15)) + + # сообщения об ошибках + if self.life.is_max_generations_exceeded: + pygame.draw.rect(self.screen, "black", self.error_frame, border_radius=0) + pygame.draw.rect(self.screen, "white", self.error_button, border_radius=0) + error_msg = self.font.render("Max generations exceeded", True, pygame.Color("red")) + self.screen.blit(error_msg, (self.width // 3, self.height // 2)) + self.status = True + if not self.life.is_changing: + error_msg = self.font.render("No changes", True, pygame.Color("red")) + self.screen.blit(error_msg, (self.width // 4, self.height // 2 + 30)) + + pygame.display.flip() + clock.tick(self.speed) + + if not self.status: + self.life.step() # если не пауза, поле меняется + + pygame.quit() + + +game = GameOfLife((30, 70), max_generations=100) +ui = GUI(game) +ui.run() diff --git a/homework04/life_proto.py b/homework04/life_proto.py index c6d60108..3009b2bf 100644 --- a/homework04/life_proto.py +++ b/homework04/life_proto.py @@ -10,9 +10,7 @@ class GameOfLife: - def __init__( - self, width: int = 640, height: int = 480, cell_size: int = 10, speed: int = 10 - ) -> None: + def __init__(self, width: int = 640, height: int = 480, cell_size: int = 10, speed: int = 10) -> None: self.width = width self.height = height self.cell_size = cell_size @@ -29,15 +27,17 @@ def __init__( # Скорость протекания игры self.speed = speed + self.grid = self.create_grid(randomize=True) + 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") @@ -51,7 +51,9 @@ def run(self) -> None: for event in pygame.event.get(): if event.type == QUIT: running = False + self.draw_grid() self.draw_lines() + self.grid = self.get_next_generation() # Отрисовка списка клеток # Выполнение одного шага игры (обновление состояния ячеек) @@ -79,13 +81,28 @@ def create_grid(self, randomize: bool = False) -> Grid: out : Grid Матрица клеток размером `cell_height` х `cell_width`. """ - pass + if randomize: + grid_random = [] + for i in range(self.width): + grid_random.append([random.randint(0, 1) for _ in range(self.height)]) + return grid_random + else: + return [[0] * self.width for _ in range(self.height)] def draw_grid(self) -> None: """ Отрисовка списка клеток с закрашиванием их в соответствующе цвета. """ - pass + for row_index, row in enumerate(self.grid): + for col_index, col in enumerate(row): + color = pygame.Color("green") if col == 1 else pygame.Color("white") + rect = pygame.Rect( + col_index * self.cell_size, + row_index * self.cell_size, + self.cell_size, + self.cell_size, + ) + pygame.draw.rect(self.screen, color, rect) def get_neighbours(self, cell: Cell) -> Cells: """ @@ -105,7 +122,13 @@ def get_neighbours(self, cell: Cell) -> Cells: out : Cells Список соседних клеток. """ - pass + row, col = cell + neighbours = [] + for i in range(row - 1, row + 2): + for j in range(col - 1, col + 2): + if 0 <= i < self.cell_height and 0 <= j < self.cell_width and (row, col) != (i, j): + neighbours.append(self.grid[i][j]) + return neighbours def get_next_generation(self) -> Grid: """ @@ -116,4 +139,19 @@ def get_next_generation(self) -> Grid: out : Grid Новое поколение клеток. """ - pass + new_grid = self.create_grid() + for i, row in enumerate(new_grid): + for j, value in enumerate(row): + neighbours = self.get_neighbours((i, j)) + total_neighbours = sum(neighbours) + if self.grid[i][j] == 0: + new_grid[i][j] = 1 if total_neighbours == 3 else 0 + else: + new_grid[i][j] = 1 if total_neighbours in [2, 3] else 0 + return new_grid + + +# game = GameOfLife() +# game.draw_grid() +# +# pygame.display.flip()