""" Nom/Prénom: Heredero/Louis """ from typing import Optional # UTILITY FUNCTIONS # Those functions are provided as helpers, you do not have to use them # The displayBoardSequence function will be used to visualize the results of your algorithms during the evaluation def printPiece(piece:tuple[tuple[int,int], tuple[int,int]]) -> None: print(piece[0][1], piece[1][1]) print(piece[0][0], piece[1][0]) def printBoard(board:list[list[int, int]]) -> None: if len(board) <= 0 or len(board[0]) <= 0: print("Empty board -> skipping printing") return None for i in range(len(board)-1): if len(board[i]) != len(board[i+1]): print("Board is not a rectangle --> skipping printing") return None for y in range(len(board[0])-1, -1, -1): line = "" for x in range(len(board)): line += str(board[x][y],) print(line) def placeAt(loc, board, piece): for x in range(2): for y in range(2): if piece[x][y] == 0: continue board[loc[0] + x][loc[1] + y] = piece[x][y] def displayBoardSequence(board_width:int, board_height:int, pieces:list[tuple[tuple[int,int], tuple[int,int]]], placements:list[list[int, int]]): """Display a sequence of move on the board by placing the blocs at the given locations ==> /!\ This is an unsafe function that simply place the 1 ones of the given blocs at the given placements, it will crash while trying to place blocs outside the board ==> /!\ it does not check the validity of the placement => It's main use is for debugging """ board = [None] * board_width for i in range(board_width): board[i] = [0] * board_height for i in range(len(pieces)): print( "----", i, "----") print("Piece to place") printPiece(pieces[i]) print("Board state after placing piece @ "+ str(placements[i])) placeAt(placements[i], board, pieces[i]) printBoard(board) """ Explications: Une approche possible consiste à représenter la partie sous la forme d'un arbre dans lequel les nœuds sont les états de la grille et les arêtes sont les coups possibles (i.e. placement d'une pièce) Ainsi, l'état initial serait celui de la grille vide, et chaque "niveau" de l'arbre représenterait une pièce différente pouvant être posée On peut ensuite parcourir l'arbre en largeur (BFS) ce qui nous garantit de trouver une solution optimale en premier. On peut également le parcourir en profondeur (DFS) jusqu'à trouver une solution, puis en parcourant le reste de l'arbre à la recherche d'une meilleure solution. Cela permettrait également d'appliquer de la mémoïsation (principe de programmation dynamique) afin d'optimiser la recherche Afin d'accélérer la recherche nous pouvons appliquer les optimisations suivantes: - élagage des branches inintéressantes : dès qu'une pièce laisse un espace vide sous elle, il devient impossible de le remplir - tri des branches selon la hauteur de la pièce une fois posée : prioriser les branches plaçant les pièces le plus bas possible - combinaisons de pièces en macro-blocs : combiner les pièces pouvant s'assembler (p.ex. coin et carré) """ def find_y(board: list[list[int]], height: int, piece: tuple[tuple[int,int], tuple[int,int]], x: int) -> int: y_max = height - 2 if piece[0][1] == 0 and piece[1][1] == 0: y_max = height - 1 y_min = 0 if piece[0][0] == 0 and piece[1][0] == 0: y_min = -1 lowest = 0 for y in range(y_max, y_min - 1, -1): valid = True for dy in range(2): for dx in range(2): if piece[dx][dy] == 1 and board[x + dx][y + dy] == 1: valid = False break if not valid: break if valid: lowest = y else: break return lowest def copy_board(board: list[list[int]]) -> list[list[int]]: new_board: list[list[int]] = [ row.copy() for row in board ] return new_board def playMinitris( board_width: int, board_height: int, board: list[list[int]], pieces: list[tuple[tuple[int,int], tuple[int,int]]], path: list[tuple[int, int]] ) -> Optional[list[tuple[int, int]]]: if len(pieces) == 0: for y in range(board_height): row = [] for x in range(board_width): row.append(board[x][y]) if row != [row[0]] * board_width: return None return path first_piece: tuple[tuple[int, int], tuple[int, int]] = pieces[0] rest: list[tuple[tuple[int, int], tuple[int, int]]] = pieces[1:] x_min = 0 if first_piece[0][0] == 0 and first_piece[0][1] == 0: x_min = -1 x_max = board_width - 2 if first_piece[1][0] == 0 and first_piece[1][1] == 0: x_max = board_width - 1 for x in range(x_min, x_max + 1): y = find_y(board, board_height, first_piece, x) new_board = copy_board(board) pos = (x, y) path2 = path + [pos] placeAt(pos, new_board, first_piece) # If space below piece if first_piece[0][1] == 1 and first_piece[1][1] and first_piece[0][0] != first_piece[1][0]: if new_board[x][y] == 0 or new_board[x + 1][y] == 0: return None res: Optional[list[tuple[int, int]]] = playMinitris(board_width, board_height, new_board, rest, path2) if res is not None: return res return None # This is the function to fill def playMinitrisFastAndWell( board_width: int, board_height: int, pieces: list[tuple[tuple[int,int], tuple[int,int]]] ) -> list[list[int, int]]: board: list[list[int]] = [ [0] * board_height for _ in range(board_width) ] result: Optional[list[tuple[int, int]]] = playMinitris( board_width, board_height, board, pieces, [] ) if result is None: return [] return list(map(list, result)) if __name__ == '__main__': displayBoardSequence(3, 4, [((1, 0), (0, 0)), ((1, 0), (0, 0)), ((1, 0), (0, 0))], [(0, 0), (1, 0), (2, 0)]) displayBoardSequence(3, 4, [((1, 0), (0, 0)), ((1, 1), (0, 1)), ((1, 1), (0, 0))], [(1, 0), (0, 0), (2, 0)]) w = 3 h = 4 pieces = [((1, 0), (0, 0)), ((1, 0), (0, 0)), ((1, 0), (0, 0))] seq = playMinitrisFastAndWell(w, h, pieces) displayBoardSequence(w, h, pieces, seq)