Мы все еще находимся в блокировке из-за Covid-19, и я использую время для реализации текстовой шахматной игры с моим 12-летним. В этой серии я буду делиться тем, что мы сделали, и как проект развивался.
Мы остановились в 3-й день с действительным ходом, проверяющие тестовые набор для общих сценариев (целевой квадрат вне границ или занятых куском с одинаковым цветом), а в ладьях.
Сегодня мы решили внедрить перемещение ладья на доске.
Мы определили этот интерфейс:
def move(board, piece, target_rank, target_file): pass
Когда мы рассмотрели то, что нам понадобится, чтобы реализовать его, мы поняли, что проверка, если будет действительный ход, будет недостаточно, мы должны были бы знать, где находилась действительная часть. А поскольку IS_Valid_Move
Вычисляет это местоположение как часть проверки проверки – оно имело гораздо больше смысла объединить их вместо реализации второго метода в дополнение к методу проверки.
Если честно, я знал, что это то, что мы бы в конечном итоге делаем, но я позволил ему понять эту часть самостоятельно, это часть процесса обучения. Дополнительное преимущество заключается в том, что он показал ему ценность проведения тестов – после того, как мы изменили метод, чтобы вернуть что -то еще, мы внесли несколько небольших изменений в существующих тестах, и когда они пройдут, мы могли бы быть уверенными, что не вызвали никакой регрессии в функциональность.
Вот что мы изменили:
def find_valid_move(board, piece, target_rank, target_file): # out of bounds if target_rank < 0 or target_rank > 7: return -1, -1 if target_file < 0 or target_file > 7: return -1, -1 # piece with same color is in the target square if board[target_rank][target_file][0] == piece[0]: return -1, -1 if piece in ("WR", "BR"): return find_valid_rook_move(board, piece, target_rank, target_file) return -1, -1 def find_valid_rook_move(board, piece, target_rank, target_file): found_rank = -1 found_file = -1 for i in range(8): if board[target_rank][i] == piece: found_rank = target_rank found_file = i break if found_rank == -1: for i in range(8): if board[i][target_file] == piece: found_rank = i found_file = target_file break if found_rank < 0 or found_file < 0: return -1, -1 if found_rank == target_rank: start_file = min(target_file+1, found_file+1) end_file = max(target_file, found_file) for i in range(start_file, end_file): if board[target_rank][i] != EMPTY: return -1, -1 else: # found_file == target_file start_rank = min(target_rank+1, found_rank+1) end_rank = max(target_rank, found_rank) for i in range(start_rank, end_rank): if board[i][target_file] != EMPTY: return -1, -1 return found_rank, found_file
Это та же самая логика, только мы возвращаем (-1, -1), когда не найдено допустимого, и (supd_rank, found_file), если мы нашли допустимую часть.
Далее мы реализовали переехать
Логика следующим образом:
def move(board, piece, target_rank, target_file): found_rank, found_file = find_valid_move(board, piece, target_rank, target_file) if (found_rank, found_file) != (-1, -1): board[target_rank][target_file] = piece board[found_rank][found_file] = EMPTY return True return False
Далее мы сделали небольшой ручной тест, чтобы увидеть это двигаться
Работал, как и ожидалось:
board = create_board() board[1][0] = EMPTY result = move(board, "WR", 5, 0) print_board(board)
И удивительный выход был:
---- ---- ---- ---- ---- ---- ---- ---- 8 | BR | BN | BB | BQ | BK | BB | BN | BR | ---- ---- ---- ---- ---- ---- ---- ---- 7 | BP | BP | BP | BP | BP | BP | BP | BP | ---- ---- ---- ---- ---- ---- ---- ---- 6 | WR | | | | | | | | ---- ---- ---- ---- ---- ---- ---- ---- 5 | WR | | | | | | | | ---- ---- ---- ---- ---- ---- ---- ---- 4 | WR | | | | | | | | ---- ---- ---- ---- ---- ---- ---- ---- 3 | WR | | | | | | | | ---- ---- ---- ---- ---- ---- ---- ---- 2 | | WP | WP | WP | WP | WP | WP | WP | ---- ---- ---- ---- ---- ---- ---- ---- 1 | | WN | WB | WQ | WK | WB | WN | WR | ---- ---- ---- ---- ---- ---- ---- ---- a b c d e f g h
Не было никакого способа, которым 12 лет выяснили бы это, но я понял, что произошло сразу, и я должен был знать лучше. Упс Я инициализировал доску, используя:
board = [[EMPTY]*8]*8
Который создает один список и дублирует его 8 раз. Когда мы что-то изменили в ранг 5, он изменил его во всех средних строках (другие строки были заменены строками, которые содержали кусочки). Очень неправильно. Не делай этого.
12 лет были некоторые проблемы с пониманием того, что пошло не так и почему, поэтому я создал этот простой пример, чтобы объяснить:
sub_list = [1,2,3] test_list = [sub_list]*3 print(test_list) sub_list[0] = 5 print(test_list)
И он рода пошел “ООО« Ооооххх, да … это изменит товар во всех списках одновременно » Поэтому я понял, что он понял. Выход:
[[1, 2, 3], [1, 2, 3], [1, 2, 3]] [[5, 2, 3], [5, 2, 3], [5, 2, 3]]
Как только мы исправим это, это работало правильно. 12во написал следующий тест:
def test_move_rook(): board = create_board() board[1][0] = EMPTY result = move(board, "WR", 5, 0) assert result print("test_move_rook passed")
Неплохо! Однако я спросил его, если бы это нашло бы проблему, которую мы видели, когда мы провели тест вручную. Он немного подумал об этом и сказал «нет». Я добавил ОБЕСПЕЧЕНИЕ_BOOD
бит к тесту:
def test_move_rook(): board = create_board() board[1][0] = EMPTY expected_board = create_board() expected_board[1][0] = EMPTY expected_board[0][0] = EMPTY expected_board[5][0] = "WR" result = move(board, "WR", 5, 0) assert result assert board == expected_board print("test_move_rook passed")
Я знал, что это придет Но я не хотел добавлять его в микс слишком рано … Теперь было время: что произойдет, если есть две достоверные произведения? 12во начинал сожалеть, что не примет мое первоначальное представление о том, чтобы оперить время, когда мы двигали кусок от а также цель Вместо того, чтобы рассчитать возможные места, но я не собирался сдаваться сейчас.
Он сказал, что, когда есть две подходящие части, игроки указывают ранг или файл произведения, но чтобы сделать вещи чуть проще, я предложил нам указать ранг и файл. Когда он видел, что мой совет обычно звучит, он решил принять эту идею.
Мы изменили find_valid_rook_move
метод следующим образом:
- Мы добавили
Source_rank
иsource_file
Чтобы включить указание который Товар, который мы хотели переместить. - Вместо того, чтобы остановить, как только мы нашли подходящую часть, мы добавили его в список
found_pieces
- Если не было найдено, возвращайте -1, -1
- Если мы нашли более одного куска, попробуйте найти тот, который соответствует данному
Source_rank
иsource_file
Отказ - Если есть только одно соответствие, используйте его.
- Затем продолжите и проверьте, не будет ли другого блокировки.
def find_valid_rook_move(board, piece, target_rank, target_file, source_rank=-1, source_file=-1): found_pieces = [] for i in range(8): if board[target_rank][i] == piece: found_rank = target_rank found_file = i found_pieces.append((found_rank, found_file)) for i in range(8): if board[i][target_file] == piece: found_rank = i found_file = target_file found_pieces.append((found_rank, found_file)) if len(found_pieces) == 0: return -1, -1 # don't know which item to choose found_rank = -1 found_file = -1 if (len(found_pieces) > 1): if source_rank < 0 or source_file < 0: return -1, -1 for rank, file in found_pieces: if rank == source_rank and file == source_file: found_rank = rank found_file = file else: found_rank, found_file = found_pieces[0] if (found_rank, found_file) == (-1, -1): return -1, -1 if found_rank == target_rank: start_file = min(target_file+1, found_file+1) end_file = max(target_file, found_file) for i in range(start_file, end_file): if board[target_rank][i] != EMPTY: return -1, -1 else: # found_file == target_file start_rank = min(target_rank+1, found_rank+1) end_rank = max(target_rank, found_rank) for i in range(start_rank, end_rank): if board[i][target_file] != EMPTY: return -1, -1 return found_rank, found_file
И добавил тест:
def test_double_move_rook(): board = create_board() board[1][0] = EMPTY board[0][7] = EMPTY board[5][0] = "WR" result = move(board, "WR", 3, 0) assert not result expected_board = create_board() expected_board[1][0] = EMPTY expected_board[0][7] = EMPTY expected_board[5][0] = "WR" expected_board[3][0] = "WR" expected_board[0][0] = EMPTY result = move(board, "WR", 3, 0, 0, 0) assert result assert board == expected_board print("test_double_move_rook passed")
Есть еще один сценарий, однако мы должны проверить, и я уже знаю, что это не пройдет … Что произойдет, если на доске есть две подходящие части, но один из них заблокирован? Я позволил 12во написать тест, а затем попытаться понять, почему он не удался.
... board = create_board() board[0][7] = EMPTY board[5][0] = "WR" expected_board = create_board() expected_board[0][7] = EMPTY expected_board[3][0] = "WR" expected_board[0][0] = "WR" result = move(board, "WR", 3, 0) assert result assert board == expected_board
Мы добавили Печать
Команды в разных местах в find_valid_rook_move
И мы обнаружили, что с тех пор, как мы проверяем достоверность после Выбирая кусок, мы приходили к выводу у нас не было достаточно информации (то есть Source_rank
и source_file
слишком рано).
Мы переключились вокруг логики, чтобы найти Valid_pieces
Перед проверкой нам нужна Source_rank
и source_file
, и это сработало (12 лет внес эти изменения после того, как я прошел его через логику 👏):
def find_valid_rook_move(board, piece, target_rank, target_file, source_rank=-1, source_file=-1): found_pieces = [] for i in range(8): if board[target_rank][i] == piece: found_rank = target_rank found_file = i found_pieces.append((found_rank, found_file)) for i in range(8): if board[i][target_file] == piece: found_rank = i found_file = target_file found_pieces.append((found_rank, found_file)) if len(found_pieces) == 0: return -1, -1 valid_pieces = [] for found_rank, found_file in found_pieces: is_valid = True if found_rank == target_rank: start_file = min(target_file+1, found_file+1) end_file = max(target_file, found_file) for i in range(start_file, end_file): if board[target_rank][i] != EMPTY: is_valid = False break else: # found_file == target_file start_rank = min(target_rank+1, found_rank+1) end_rank = max(target_rank, found_rank) for i in range(start_rank, end_rank): if board[i][target_file] != EMPTY: is_valid = False break if is_valid: valid_pieces.append((found_rank, found_file)) # don't know which item to choose found_rank = -1 found_file = -1 if len(valid_pieces) > 1: if source_rank < 0 or source_file < 0: return -1, -1 for rank, file in valid_pieces: if rank == source_rank and file == source_file: found_rank = rank found_file = file elif len(valid_pieces) == 1: found_rank, found_file = valid_pieces[0] return found_rank, found_file
Теперь мы все настроем с шаблоном для реализации перемещения всех остальных произведений, поэтому я надеюсь, что мы сможем пройти через следующие быстрее.
Присоединяйтесь к нам в следующий раз!
Окончательный код на сегодняшний день (включая модифицированные тесты):
EMPTY = " " def print_board(board): row_number = 8 print(" ", end="") print(" ----"*8) for row in reversed(board): print(row_number, end=" ") row_number -= 1 for cell in row: print("| {} ".format(cell), end="") print("|") print(" ", end="") print(" ----"*8) print(" ", end="") for letter in ['a','b','c','d','e','f','g','h']: print(" {} ".format(letter), end="") print("") def create_board(): board = [] board.append(["WR","WN","WB","WQ","WK","WB","WN","WR"]) board.append(["WP","WP","WP","WP","WP","WP","WP","WP"]) for i in range(2, 6): board.append([EMPTY]*8) board.append(["BP","BP","BP","BP","BP","BP","BP","BP"]) board.append(["BR","BN","BB","BQ","BK","BB","BN","BR"]) return board def find_valid_move(board, piece, target_rank, target_file, source_rank=-1, source_file=-1): # out of bounds if target_rank < 0 or target_rank > 7: return -1, -1 if target_file < 0 or target_file > 7: return -1, -1 # piece with same color is in the target cell if board[target_rank][target_file][0] == piece[0]: return -1, -1 if piece in ("WR", "BR"): return find_valid_rook_move(board, piece, target_rank, target_file, source_rank, source_file) return -1, -1 def find_valid_rook_move(board, piece, target_rank, target_file, source_rank=-1, source_file=-1): found_pieces = [] for i in range(8): if board[target_rank][i] == piece: found_rank = target_rank found_file = i found_pieces.append((found_rank, found_file)) for i in range(8): if board[i][target_file] == piece: found_rank = i found_file = target_file found_pieces.append((found_rank, found_file)) if len(found_pieces) == 0: return -1, -1 valid_pieces = [] for found_rank, found_file in found_pieces: is_valid = True if found_rank == target_rank: start_file = min(target_file+1, found_file+1) end_file = max(target_file, found_file) for i in range(start_file, end_file): if board[target_rank][i] != EMPTY: is_valid = False break else: # found_file == target_file start_rank = min(target_rank+1, found_rank+1) end_rank = max(target_rank, found_rank) for i in range(start_rank, end_rank): if board[i][target_file] != EMPTY: is_valid = False break if is_valid: valid_pieces.append((found_rank, found_file)) # don't know which item to choose found_rank = -1 found_file = -1 if len(valid_pieces) > 1: if source_rank < 0 or source_file < 0: return -1, -1 for rank, file in valid_pieces: if rank == source_rank and file == source_file: found_rank = rank found_file = file elif len(valid_pieces) == 1: found_rank, found_file = valid_pieces[0] return found_rank, found_file def move(board, piece, target_rank, target_file, source_rank=-1, source_file=-1): found_rank, found_file = find_valid_move(board, piece, target_rank, target_file, source_rank, source_file) if (found_rank, found_file) != (-1, -1): board[target_rank][target_file] = piece board[found_rank][found_file] = EMPTY return True return False # ----------------------- tests ------------------- def test_move_rook(): board = create_board() board[1][0] = EMPTY expected_board = create_board() expected_board[1][0] = EMPTY expected_board[0][0] = EMPTY expected_board[5][0] = "WR" result = move(board, "WR", 5, 0) assert result assert board == expected_board print("test_move_rook passed") def test_double_move_rook(): board = create_board() board[1][0] = EMPTY board[0][7] = EMPTY board[5][0] = "WR" result = move(board, "WR", 3, 0) assert not result expected_board = create_board() expected_board[1][0] = EMPTY expected_board[0][7] = EMPTY expected_board[5][0] = "WR" expected_board[3][0] = "WR" expected_board[0][0] = EMPTY result = move(board, "WR", 3, 0, 0, 0) assert result assert board == expected_board board = create_board() board[0][7] = EMPTY board[5][0] = "WR" expected_board = create_board() expected_board[0][7] = EMPTY expected_board[3][0] = "WR" expected_board[0][0] = "WR" result = move(board, "WR", 3, 0) assert result assert board == expected_board print("test_double_move_rook passed") def test_is_rank_valid(): board = create_board() result = find_valid_move(board, "xx", 22, 4) assert result == (-1, -1) result = find_valid_move(board, "xx", -2020, 4) assert result == (-1, -1) print("test_is_rank_valid passed") def test_is_file_valid(): board = create_board() result = find_valid_move(board, "xx", 0, 22) assert result == (-1, -1) result = find_valid_move(board, "xx", 0, -2020) assert result == (-1, -1) print("test_is_file_valid passed") def test_is_target_square_taken(): board = create_board() result = find_valid_move(board, "Wx", 0, 1) assert result == (-1, -1) print("test_is_target_square_taken passed") def test_rook_is_blocked(): board = create_board() result = find_valid_rook_move(board, "BR", 5, 0) assert result == (-1, -1) board[7][5] = EMPTY result = find_valid_rook_move(board, "BR", 7, 5) assert result == (-1, -1) print("test_rook_is_blocked passed") def test_rook_is_not_blocked(): board = create_board() board[1][0] = EMPTY result = find_valid_rook_move(board, "WR", 2, 0) assert result == (0,0) board[0][1] = EMPTY result = find_valid_rook_move(board, "WR", 0, 1) assert result == (0,0) print("test_rook_is_not_blocked passed") def run_tests(): test_is_rank_valid() test_is_file_valid() test_is_target_square_taken() test_rook_is_blocked() test_rook_is_not_blocked() test_move_rook() test_double_move_rook() run_tests()
Оригинал: “https://dev.to/rinaarts/writing-a-chess-game-in-python-day-4-1o84”