diff --git a/search/__pycache__/main.cpython-38.pyc b/search/__pycache__/main.cpython-38.pyc index 616e48299948de696d98b0d9e0253aa967bc760b..39cf0f0c5ea04dbc9c928500fcf177d8a05a2557 100644 Binary files a/search/__pycache__/main.cpython-38.pyc and b/search/__pycache__/main.cpython-38.pyc differ diff --git a/search/__pycache__/movement_logic.cpython-38.pyc b/search/__pycache__/movement_logic.cpython-38.pyc index 73f5474c9ead21cb9510b416712b3b8c3be0f9bc..fd26de4a44dce1583ac4a901083de447674a94d6 100644 Binary files a/search/__pycache__/movement_logic.cpython-38.pyc and b/search/__pycache__/movement_logic.cpython-38.pyc differ diff --git a/search/__pycache__/search_algo.cpython-38.pyc b/search/__pycache__/search_algo.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a78ee17d6e28f7e9f68cc5ee5a115a10aa34ce4f Binary files /dev/null and b/search/__pycache__/search_algo.cpython-38.pyc differ diff --git a/search/__pycache__/search_algorithm.cpython-38.pyc b/search/__pycache__/search_algorithm.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59ce8da52be11782436d43c6965409b689989642 Binary files /dev/null and b/search/__pycache__/search_algorithm.cpython-38.pyc differ diff --git a/search/main.py b/search/main.py index 882801548ed93688cc6ad1a6b39cc551cdb3e14f..5f9abbd3848ed45715689cb07dae1f81b7031f76 100644 --- a/search/main.py +++ b/search/main.py @@ -8,32 +8,33 @@ This script contains the entry point to the program (the code in import sys import json - -# If you want to separate your code into separate files, put them -# inside the `search` directory (like this one and `util.py`) and -# then import from them like this: from search.movement_logic import * -from search.util import print_board, print_slide, print_swing +from search.search_algo import check_if_piece_hit_target, make_board, update_state +from search.util import print_board # Constant's definition. + + TYPE = 0 ROW = 1 COLUMN = 2 - A_WIN = 1 B_WIN = 2 DRAW = 0 - MAX_DISTANCE = 99 +FIRST_CHAR = 0 + +upperDictPieces = {} +lowerDictPieces ={} +targetDict = {} -# Why don't we just make the pieces and blocks global then? -# No need to ever pass them around -dictPieces = {} setBlocks = set() -dictTargets = {} def main(): + # define global variable + global upperDictPieces, lowerDictPieces, targetDict, setBlocks + print("a") try: with open(sys.argv[1]) as file: data = json.load(file) @@ -42,8 +43,6 @@ def main(): sys.exit(1) parse_input(data) - print(dictPieces) - # So basically it is heavily implied to treat the game as a state-based search problem. # We are also told in question 3 of the design report to discuss the time and space # requirements, and the connection with the branching factor and search tree depth. @@ -56,15 +55,13 @@ def main(): # ALGORITHM GOES HERE # Add starting targets - - # Algorithm start - - # TODO: - # Find and print a solution to the board configuration described - # by `data`. - # Why not start by trying to print this configuration out using the - # `print_board` helper function? (See the `util.py` source code for - # usage information). + for piece in upperDictPieces: + find_target(piece) + # keep moving until all the piece met its target + while targetDict: + upperDictPieces = update_state(upperDictPieces, lowerDictPieces, setBlocks, targetDict) + targetDict = check_if_piece_hit_target(upperDictPieces, targetDict) + print(upperDictPieces) def parse_input(data): @@ -79,6 +76,7 @@ def parse_input(data): # We can put the code to read the file NOT in the try/except statement because # if the file couldn't be read the process would end anyway + global upperDictPieces, lowerDictPieces, setBlocks initialPiecesUpper = data["upper"] initialPiecesLower = data["lower"] initialBlocks = data["block"] @@ -97,7 +95,7 @@ def parse_input(data): else: nums = nums + 1 keyWrite = "S" + str(nums) - dictPieces[keyWrite] = (piece[ROW], piece[COLUMN]) + upperDictPieces[keyWrite] = (piece[ROW], piece[COLUMN]) # parse the Lower player's token nump, numr, nums = 0, 0, 0 @@ -111,7 +109,7 @@ def parse_input(data): else: nums = nums + 1 keyWrite = "s" + str(nums) - dictPieces[keyWrite] = (piece[ROW], piece[COLUMN]) + lowerDictPieces[keyWrite] = (piece[ROW], piece[COLUMN]) # parse the block object for block in initialBlocks: @@ -158,41 +156,39 @@ def find_target(piece_key): """ This function changes the value of the key given in the target dictionary to the closest enemy piece it can beat + XUAN: by separated the upper to lower piece, we dont need to search for the whole dictionary every time we compare. :param piece_key: key of the piece to find a target for. We should only work with upper player's pieces :return: null """ - currentPos = dictPieces[piece_key] + global targetDict # Set the target - target = "" if piece_key[TYPE] == "R": target = "s" elif piece_key[TYPE] == "S": target = "p" - elif piece_key[TYPE] == "P": - target = "r" - # If we haven't set a target by now, we're dealing with the lower player pieces else: - return + target = "r" + # Now we check which target is closest # Start with dummy information targetPiece = "" targetDist = MAX_DISTANCE - for key in dictPieces: - # XUAN: unfortunately i believe this logical operation will always be FALSE - # e.g: 'r' != 'r1' - if key[TYPE] == target: - distance = distance_between(dictPieces[piece_key], dictPieces[key]) + for key in lowerDictPieces: + if key[FIRST_CHAR] == target: + distance = distance_between(lowerDictPieces[key], lowerDictPieces[key]) + # if the distance between this key and the query key is less than the least distance. + # as well as the key is not targeted by any other key. if distance < targetDist: targetPiece = key targetDist = distance + # Now add target to dictionary + # case where no key that match criteria. if targetDist == MAX_DISTANCE: - dictTargets[piece_key] = () + targetDict[piece_key] = () else: - # Xuan: does this 'variable' has any value? since it is out side its scope. - dictTargets[piece_key] = dictPieces[targetPiece] - return + targetDict[piece_key] = lowerDictPieces[targetPiece] # Situation 1: If you plan the route ahead without knowing any other piece route, @@ -217,14 +213,13 @@ def find_target(piece_key): # | | | | | | # '-._.-'-._.-'-._.-'-._.-'-._.-' - # Situation 2 (B are blocks) # I'm not sure if they will throw something this tricky at us # but this should be solvable # .-'-._.-'-._.-'-._.-'-._.-'-. # | | | | | | # .-'-._.-'-._.-'-._.-'-._.-'-._.-'-. - # | | | | | | | + # | | | | | | | # .-'-._.-'-._.-'-._.-'-._.-'-._.-'-._.-'-. # | | | | | | | | # .-'-._.-'-._.-'-._.-'-._.-'-._.-'-._.-'-._.-'-. @@ -239,4 +234,4 @@ def find_target(piece_key): # | | | | | | | # '-._.-'-._.-'-._.-'-._.-'-._.-'-._.-' # | | | | | | - # '-._.-'-._.-'-._.-'-._.-'-._.-' \ No newline at end of file + # '-._.-'-._.-'-._.-'-._.-'-._.-' diff --git a/search/movement_logic.py b/search/movement_logic.py index 0943875610d76ca053af1b879ac208641ebcfbef..16017e5facaeec9ab1a05fa9fef3001c859520b4 100644 --- a/search/movement_logic.py +++ b/search/movement_logic.py @@ -146,10 +146,10 @@ Swing action logic: '-._.-'-._.-' '-._.-' """ + def swing_to_tile_1(token, x): position = get_relative_position(token, x) - if position == LEFT: new_postion = slide_down_left(slide_left(token)) @@ -168,8 +168,8 @@ def swing_to_tile_1(token, x): if position == DOWN_RIGHT: new_postion = slide_right(slide_down_right(token)) - #if the position of the token after the action complete is out of board, return the token position in - #stead + # if the position of the token after the action complete is out of board, return the token position in + # stead if compare_tile(new_postion, x): return token @@ -204,6 +204,7 @@ def swing_to_tile_2(token, x): return new_postion + def swing_to_tile_3(token, x): position = get_relative_position(token, x) @@ -295,7 +296,6 @@ def check_within_board(tile): return True - def distance_between(Upper_token, Lower_token): """ :argument Upper_token - compose of (row, column) @@ -308,6 +308,5 @@ def distance_between(Upper_token, Lower_token): """ dx = abs(Upper_token[0] - Lower_token[0]) dy = abs(Upper_token[1] - Lower_token[1]) - - return dx + max(0, (dy - dx) / 2) - + result = dx + max(0, (dy - dx) / 2) + return result diff --git a/search/search_algo.py b/search/search_algo.py new file mode 100644 index 0000000000000000000000000000000000000000..f55d69a3409706c9b357825c076e7c9c263e6447 --- /dev/null +++ b/search/search_algo.py @@ -0,0 +1,154 @@ +from search.movement_logic import slide_right, slide_left, slide_up_left, slide_up_right, slide_down_left, \ + slide_down_right, compare_tile, distance_between +from search.util import print_board + +BLOCK = "" +POSITION_CLOSEST_TO_TARGET = 0 + + +def make_board(lowerPieces, upperPieces, setBlocks): + """ + create a board of the current game -> can do a position look up. + :param upperPieces: dictionary contain all the upper piece and its location + :param lowerPieces: dictionary contain all the lower piece and its location + :param setBlocks: all the block + :return: the dictionary represent the board + """ + board = {} + for piece in lowerPieces: + board[lowerPieces[piece]] = piece + + for piece in upperPieces: + board[upperPieces[piece]] = piece + + for block in setBlocks: + board[block] = BLOCK + return board + + +def get_stronger_piece(piece_type): + """ + Stronger piece is base on the type, even if the piece are from a player. + :param piece_type: the type of the piece that we are interested + :return: the type of the piece that stronger than the input piece + """ + if piece_type == 'R': + return 'p' + if piece_type == 'S': + return 'r' + return 's' + + +def add_slide_action(upperDict, piece, board): + """ + add all the valid slide action to a list + :param board: dictionary contain all piece and block + :param upperDict: contain detail about upper piece + :param piece: key of the interested piece + :return: un sorted list of all position as a result of slide action + """ + list = [] + + # check the right tile + appending_list = add_if_valid(list, piece, slide_right(upperDict[piece]), upperDict[piece], board) + + # check the left tile + appending_list = add_if_valid(list, piece, slide_left(upperDict[piece]), upperDict[piece], board) + + # check the up_left + appending_list = add_if_valid(list, piece, slide_up_left(upperDict[piece]), upperDict[piece], board) + + # check the up_right + appending_list = add_if_valid(list, piece, slide_up_right(upperDict[piece]), upperDict[piece], board) + + # check the down_left + appending_list = add_if_valid(list, piece, slide_down_left(upperDict[piece]), upperDict[piece], board) + + # check the down_right + appending_list = add_if_valid(list, piece, slide_down_right(upperDict[piece]), upperDict[piece], board) + + return appending_list + + +def add_if_valid(position_list, piece, new_position, piece_position, board): + """ + check if the move is valid. + :param position_list: list contain all added slide action from this turn + :param piece: the interested upper piece + :param new_position: position result of a slide action + :param piece_position: initial position of the piece. + :param board: dictionary contain all piece and block + :return: the list + """ + + # check if the new position is result of a out of board action + if compare_tile(new_position, piece_position): + return position_list + + if new_position in board.keys(): + # check if the tile is occupied by a block + if board[new_position] == BLOCK: + return position_list + + # check if the new position is occupied by piece that stronger + elif board[new_position] == get_stronger_piece(piece): + return position_list + + # add the new position and return + position_list.append(new_position) + return position_list + + +def make_priority_list_of_action(upperDict, piece, targetDict, board): + """ + compile all possible action this piece can under go. + sort them base on the result distance relative to the piece's target + :param upperDict: use to read the position of the piece + :param piece: key of the piece + :param targetDict: dictionary contain the target of every upper piece + :param board: the dictionary of board + :return: the sorted list of position result from an action + """ + # add all the adjacent move to queue + position_list = add_slide_action(upperDict, piece, board) + + # sort the list base on the how close it is to target + position_list.sort(key=(lambda x: distance_between(x, targetDict[piece]))) + + return position_list + + +def update_state(upperPieces, lowerPieces, setBlocks, targetDict): + """ + move the piece in a way that bring all piece closer to its target + # currently only in away that is work for one piece. + :param upperPieces: dictionary contain all the upper piece + :param lowerPieces: dictionary contain all the lower piece + :param setBlocks: dictionary contain all the block on the board + :param targetDict: map each piece to a target + :return: the updated upper piece + """ + # create the board in order to support faster look up + board = make_board(lowerPieces, upperPieces, setBlocks) + print_board(board) + + # right now, i assume there only 1 piece, there will be more code combine all the queue. + for piece in upperPieces: + position_list = make_priority_list_of_action(upperPieces,piece, targetDict, board) + upperPieces[piece] = position_list[POSITION_CLOSEST_TO_TARGET] + + return upperPieces + + +def check_if_piece_hit_target(upperPiece, targetDict): + """ + remove the target from the target dictionary if the upper piece is at its target location + :param upperPiece: contain all upper piece + :param targetDict: map upper piece to its lower target + :return: the updated target dictionary + """ + for piece in upperPiece: + if targetDict and compare_tile(upperPiece[piece], targetDict[piece]): + del targetDict[piece] + + return targetDict \ No newline at end of file diff --git a/search/search_algorithm.py b/search/search_algorithm.py index 3e544625827b782e54eda59c8ac1db3014bd0797..25a65da05d1e627d546f53093fbf0bf8755bcb41 100644 --- a/search/search_algorithm.py +++ b/search/search_algorithm.py @@ -2,76 +2,114 @@ logic: -> prioritise the action which take the token closer to its target """ -from search.movement_logic import slide_right, slide_left, slide_up_left, slide_up_right, slide_down_left, \ - slide_down_right, distance_between +from search.main import * +from search.util import print_board +board = {} -def add_to_queue(record, queue, token_position, target): + +def add_to_queue(token, state, targetDict): """ - :argument record: the tile that current token already pass by - :param queue: tile a wait to be process. - :param token_position: current position - :param target: where the token want to go - :return: - - -> check the adjcent tile and weight them {prioritise the tile close to the target first} - -> append them to the queue in that order + -> check the adjacent tile for potential move. + -> add them into a priority queue + :param targetDict: + :param state: run + :param token: the one that we want to choose an action for + :return: the list sort base on how close it is to the token's target """ - #add all the adjcent move to queue - appending_list = add_adjcent_action(record, token_position, target) + # add all the adjacent move to queue + appending_list = add_adjacent_action(token, state) + + # sort the list base on the how close it is to target + appending_list.sort() + appending_list.sort(key=(lambda x: distance_between(x, targetDict[token]))) - #sort the list base on the how close it is to target - appending_list.sort(key = (lambda x: distance_between(x, target))) - #add the list to the head to the queue - appending_list.extend(queue) return appending_list -def add_if_new_tile(record, list, tile, target): +def add_if_allowed(list, token, token_new_position, token_position): """ - handle check if the tile has been seen by the token before - we only add after we expand the node - :param record: log all move of the token while moving toward the target. + handle check if the tile has sit on top of a block. + or if it is sit on top on a token would beat it :param list: the temporary list to append to while await to process - :param tile: the adjcent tile of a token + :param token: the adjcent tile of a token + :param token_new_position: :return: the list so the local change can be pass to higher scope """ - if not tile in record or (record[tile] != target): - list.append(tile) + if compare_tile(token_new_position, token_position): + return list + if token_new_position == token_new_position not in board.keys() or \ + board[token_new_position] != "" or \ + board[token_new_position] != get_stronger_token(token): + list.append(token_new_position) return list -def add_adjcent_action(record, token_position, target): +def add_adjacent_action(token, state): """ - create a abstraction to hide implementation detail on how to add adjcent move - :param record: ensure add the move we have not seen - :param token: current position of the token - :param target: where the token want to get to + create a abstraction to hide implementation detail on how to add adjacent move. + check if the any slide action is allowed. + :param token:token that we want to find a direction to :return: the queue """ appending_list = [] # check the right tile - appending_list = add_if_new_tile(record, appending_list, slide_right(token_position), target) + appending_list = add_if_allowed(appending_list, token, slide_right(state[token]), state[token]) # check the left tile - appending_list = add_if_new_tile(record, appending_list, slide_left(token_position), target) + appending_list = add_if_allowed(appending_list, token, slide_left(state[token]), state[token]) # check the up_left - appending_list = add_if_new_tile(record, appending_list, slide_up_left(token_position), target) + appending_list = add_if_allowed(appending_list, token, slide_up_left(state[token]), state[token]) # check the up_uight - appending_list = add_if_new_tile(record, appending_list, slide_up_right(token_position), target) + appending_list = add_if_allowed(appending_list, token, slide_up_right(state[token]), state[token]) # check the down_left - appending_list = add_if_new_tile(record, appending_list, slide_down_left(token_position), target) + appending_list = add_if_allowed(appending_list, token, slide_down_left(state[token]), state[token]) # check the down_right - appending_list = add_if_new_tile(record, appending_list, slide_down_right(token_position), target) + appending_list = add_if_allowed(appending_list, token, slide_down_right(state[token]), state[token]) return appending_list +def get_stronger_token(token): + if token[FIRST_CHAR] == 'R': + return 'p' + if token[FIRST_CHAR] == 'S': + return 'r' + return 's' + + +def update_state(state, setblocks, targetDict): + + make_board(state, setblocks) + + print("\n\n") + print_board(board) + for token in state: + if token[0] in ('s', 'p', 'r'): + print("continue") + continue + + potential_new_position = add_to_queue(token, state, targetDict) + state[token] = potential_new_position[0] + print(state[token]) + return state + +def make_board(state, setblocks): + global board + board = {} + for piece in state: + + board[state[piece]] = piece + + for block in setblocks: + board[block] = '' + + diff --git a/search/test.py b/search/test.py index 1fa07dd88fddfd83e75e5a6d6bce10b177c3f40e..e715455e1f581633e7dfd05ab0b2b417abc6c03f 100644 --- a/search/test.py +++ b/search/test.py @@ -1 +1 @@ -print('r' == 'r1') \ No newline at end of file +print('R' in ('R', 's'))