def test_enter_block_circle(self): """ enter_block_circle()のテストコード 確認事項 1. 4番サークルにカラーブロックが置かれている場合、6番に進入すること 2. 6番サークルに黒ブロックが置かれている場合、6番に進入すること 3. 上記2条件に当てはまらない場合、4番に進入すること """ # 確認事項1.のテスト for bonus in range(1, 8 + 1): for black in range(1, 8 + 1): if black == 4 or bonus == black: continue solver = BlockCirclesSolver(bonus, black, 4, True) self.assertEqual((2, 0), solver.enter_block_circle()) # 確認事項2.のテスト for bonus in range(1, 8 + 1): for color in range(1, 8 + 1): if bonus == 6 or color == 6: continue solver = BlockCirclesSolver(bonus, 6, color, True) self.assertEqual((2, 0), solver.enter_block_circle()) # 確認事項3.のテスト for bonus in range(1, 8 + 1): for black in range(1, 8 + 1): for color in range(1, 8 + 1): # ブロックビンゴのルールによる制約 if bonus == black or black == color: # 例外的な2条件を省くための制約 if color == 4 or black == 6: continue solver = BlockCirclesSolver(bonus, black, color, True) self.assertEqual((1, 0), solver.enter_block_circle())
def test_enter_block_circle_right(self): """ enter_block_circle()のテストコード Rコース版 確認事項 1. 5番サークルにカラーブロックが置かれている場合、8番に進入すること 2. 8番サークルに黒ブロックが置かれている場合、8番に進入すること 3. 上記2条件に当てはまらない場合、5番に進入すること """ # 確認事項1. のテスト for bonus in range(1, 8 + 1): for black in range(1, 8 + 1): if black == 5 or bonus == black: continue solver = BlockCirclesSolver(bonus, black, 5, False) self.assertEqual((2, 2), solver.enter_block_circle()) # 確認事項2. のテスト for bonus in range(1, 8 + 1): for color in range(1, 8 + 1): if bonus == 8 or color == 8: continue solver = BlockCirclesSolver(bonus, 8, color, False) self.assertEqual((2, 2), solver.enter_block_circle()) # 確認事項3. のテスト for bonus in range(1, 8 + 1): for black in range(1, 8 + 1): for color in range(1, 8 + 1): # ブロックビンゴのルールによる制約 if bonus == black or black == color: # 例外的な2条件を省くための制約 if color == 5 or black == 8: continue solver = BlockCirclesSolver(bonus, black, color, False) self.assertEqual((1, 2), solver.enter_block_circle())
def test_subset_of_tracks(self): """ subset_of_tracks()のテストコード 確認事項 1. リストに含まれない要素が指定されたときはNoneを返すこと 2. 部分集合の先頭は、始点を含まないこと 3. 部分集合の末尾は、終点を含むこと 4. 始点が終点よりも大きい場合の部分集合は、元のリストから(終点→始点の場合の部分集合)を引いた差集合になっていること 5. 4.の条件を除いて部分集合は、元のリストの順序を保った状態の部分集合となっていること """ original = [i + 1 for i in range(0, 5)] # [1, 2, 3, 4, 5] solver = BlockCirclesSolver(1, 2, 3, True) # 引数の値は適当 # 確認事項1.のテスト self.assertIsNone(solver.subset_of_tracks(0, 3, original)) self.assertIsNone(solver.subset_of_tracks(2, 6, original)) # 確認事項2.のテスト self.assertTrue(1 not in solver.subset_of_tracks(1, 3, original)) # 確認事項3.のテスト self.assertTrue(3 in solver.subset_of_tracks(1, 3, original)) # 確認事項4.のテスト subtrahend = solver.subset_of_tracks(2, 4, original) # [3, 4] subset = solver.subset_of_tracks(4, 2, original) # [5, 1, 2] self.assertEqual(set(original) - set(subtrahend), set(subset)) # 確認事項5.のテスト start = original.index(2) + 1 goal = original.index(5) + 1 self.assertEqual(original[start:goal], solver.subset_of_tracks(2, 5, original))
def __init__(self, bonus, black, color, is_left=True): """ ブロックサークル内の黒ブロックを運搬する経路を計算するための情報を登録する。 Parameters ---------- bonus : int ボーナスサークル番号 black : int ブロックサークル内の黒ブロックが置かれているサークル番号 color : int ブロックサークル内のカラーブロックが置かれているサークル番号 is_left : bool コース設定のためのフラグ """ self.is_left = is_left # インスタンス生成 self.block_circles_solver = BlockCirclesSolver(bonus, black, color, is_left) self.block_circles_coordinate = BlockCirclesCoordinate( is_left, bonus, color, black) # 経路を計算 route_tmp = self.block_circles_solver.solve() # 経路の軸の相違を吸収 self.reverse_route = route_tmp self.route = list(map(lambda x: (x[1], x[0]), route_tmp)) """ 機体の向きの表現方法 左上 右上 0 1 2 +---+---+-- 0 | | → | (1, 0)の機体 => (1, 0) +---+---+-- 1 | ↑ | | (0, 1)の機体 => (0, -1) +---+---+-- 2 | | | 左下 右下 """ # 機体の向きを初期化 if self.is_left: self.direction = (1, 0) else: self.direction = (-1, 0)
def test_path_to_catch_block(self): """ path_to_catch_block()のテストコード 確認事項 1. 求めた経路内にカラーブロックが置かれたサークルは存在しないこと 2. 求めた経路内に進入サークルは含まれていないこと """ for bonus in range(1, 8 + 1): for black in range(1, 8 + 1): for color in range(1, 8 + 1): # ブロックビンゴのルールによる制約 if bonus == black or black == color: continue solver = BlockCirclesSolver(bonus, black, color, True) enter = solver.enter_block_circle() path = solver.path_to_catch_block( enter, BlockCirclesTracks(solver.coordinate)) # 確認事項1.のテスト self.assertTrue(solver.coordinate.get(color) not in path) # 確認事項2.のテスト self.assertTrue(enter not in path)
def test_path_to_bonus_circle(self): """ path_to_bonus_circle()のテストコード 確認事項 1. 求めた経路内に黒ブロックが置かれたサークルは存在しないこと 2. 求めた経路の末尾を除いて、カラーブロックが置かれたサークルは存在しないこと """ for bonus in range(1, 8 + 1): for black in range(1, 8 + 1): for color in range(1, 8 + 1): # ブロックビンゴのルールによる制約 if bonus == black or black == color: continue solver = BlockCirclesSolver(bonus, black, color, True) path = solver.path_to_bonus_circle( BlockCirclesTracks(solver.coordinate)) # 確認事項1.のテスト self.assertTrue(solver.coordinate.get(black) not in path) # 確認事項2.のテスト self.assertTrue( solver.coordinate.get(color) not in path[0:-1])
def solve(self, is_left): """ solve()のテストコード 確認事項 1. 求めた経路の先頭は、進入サークルの座標になっていること 2. 求めた経路の末尾は、ボーナスサークルの座標になっていること 3. 求めた経路の末尾を除いて、カラーブロックが置かれたサークルは存在しないこと """ for bonus in range(1, 8 + 1): for black in range(1, 8 + 1): for color in range(1, 8 + 1): # ブロックビンゴのルールによる制約 if bonus == black or black == color: continue solver = BlockCirclesSolver(bonus, black, color, is_left) enter = solver.enter_block_circle() path = solver.solve() # 確認事項1.のテスト self.assertEqual(enter, path[0]) # 確認事項2.のテスト self.assertEqual(solver.coordinate.get(bonus), path[-1]) # 確認事項3.のテスト self.assertTrue( solver.coordinate.get(color) not in path[0:-1])
class BlackBlockCommands(): def __init__(self, bonus, black, color, is_left): """ ブロックサークル内の黒ブロックを運搬する経路を計算するための情報を登録する。 Parameters ---------- bonus : int ボーナスサークル番号 black : int ブロックサークル内の黒ブロックが置かれているサークル番号 color : int ブロックサークル内のカラーブロックが置かれているサークル番号 is_left : bool コース設定のためのフラグ """ self.is_left = is_left # インスタンス生成 self.block_circles_solver = BlockCirclesSolver(bonus, black, color, is_left) self.block_circles_coordinate = BlockCirclesCoordinate(is_left, bonus, color, black) # 経路を計算 route_tmp = self.block_circles_solver.solve() # 経路の軸の相違を吸収 self.reverse_route = route_tmp self.route = list(map(lambda x: (x[1], x[0]), route_tmp)) """ 機体の向きの表現方法 左上 右上 0 1 2 +---+---+-- 0 | | → | (1, 0)の機体 => (1, 0) +---+---+-- 1 | ↑ | | (0, 1)の機体 => (0, -1) +---+---+-- 2 | | | 左下 右下 """ # 機体の向きを初期化 if self.is_left: self.direction = (1, 0) else: self.direction = (-1, 0) def gen_commands(self): """ 運搬経路からコマンドを生成する Returning --------- コマンドの文字列 """ commands = "" # ブロックビンゴエリアへの侵入先を決定 if self.is_left: tmp_trans = list(self.block_circles_coordinate.get(4)) # 座標系の相違を吸収 tmp_trans[0], tmp_trans[1] = tmp_trans[1], tmp_trans[0] if list(self.route[0]) == tmp_trans: commands += Instructions.ENTER_BINGO_AREA_L4 else: commands += Instructions.ENTER_BINGO_AREA_L6 else: tmp_trans = list(self.block_circles_coordinate.get(5)) # 座標系の相違を吸収 tmp_trans[0], tmp_trans[1] = tmp_trans[1], tmp_trans[0] if list(self.route[0]) == tmp_trans: commands += Instructions.ENTER_BINGO_AREA_R5 else: commands += Instructions.ENTER_BINGO_AREA_R8 current_coordinate = self.route[0] for i in range(1, len(self.route)): commands += self.coordinate_to_command(current_coordinate, self.route[i], self.direction) current_coordinate = self.route[i] # 黒ブロックを配置するコマンドを追加する commands = self.put_to_command(commands) return commands.replace(Instructions.STRAIGHT * 2, Instructions.STRAIGHT_STRAIGHT) def coordinate_to_command(self, robot_coor, next_coor, direction): """ 現在の座標と次の座標から動作を計算 Parameters ---------- robot_coor : tuple 機体の座標 next_coor : tuple 次のの座標(隣の座標でないといけない) direction : tuple 機体の向き Returning --------- tmp_commands : str 一時コマンドの文字列 """ # 値のチェック if len(robot_coor) != 2: raise ValueError("robot_coor is invalid!") if len(next_coor) != 2: raise ValueError("next_coor is invalid!") if len(direction) != 2: raise ValueError("direction is invalid!") # 斜め移動や隣以外のブロックサークルへ移動するコマンドはコマンドは作成できない if abs(robot_coor[0] - next_coor[0]) + abs(robot_coor[1] - next_coor[1]) != 1: raise ValueError("Next coordinate value is in valid!") tmp_commands = "" # 機体の向きを設定 next_direction = (next_coor[0] - robot_coor[0], next_coor[1] - robot_coor[1]) tmp_commands += self.direction_to_command(direction, next_direction) # 機体の向きを更新 self.direction = next_direction # ブロックサークル間移動 tmp_commands += Instructions.STRAIGHT return tmp_commands def direction_to_command(self, robot_direction, movement_direction): """ 機体の方向と進行方向からコマンドを計算 Parameters ---------- robot_direction : tuple 機体の向き movement_direction : tuple 移動方向 Returning --------- command : str """ # 値のチェック if len(robot_direction) != 2: raise ValueError("robot_direction is invalid!") if len(movement_direction) != 2: raise ValueError("movement_direction is invalid!") # 機体の向きと移動方向が一致している場合 if robot_direction == movement_direction: return "" # 90度回転 direction = self.detect_direction(robot_direction, movement_direction) if direction == "r": return Instructions.SPIN_RIGHT if direction == "l": return Instructions.SPIN_LEFT # 180度右回転 if robot_direction[0] == movement_direction[0]: return Instructions.SPIN180 if robot_direction[1] == movement_direction[1]: return Instructions.SPIN180 return "" def detect_direction(self, robot_direction, movement_direction): """ 機体の向きから回転方向を判定する Parameters ---------- robot_direction : tuple 回転前の機体向き movement_direction : tuple 回転後の機体の向き Returning --------- direction : str 右に90度回転時 "r" 左に90度回転時 "l" 回転しない or 180度回転時 "" """ # インデックスが増える方向に回転 => 右回転 dx = (1, 0, -1, 0) dy = (0, 1, 0, -1) # 回転前のインデックスを取得 for pre_index in range(len(dx)): tmp = (dx[pre_index], dy[pre_index]) if robot_direction == tmp: break # 回転後のインデックスを取得 for next_index in range(len(dx)): tmp = (dx[next_index], dy[next_index]) if movement_direction == tmp: break # 右に90度回転 if ((pre_index + 1) % 4) == next_index: return 'r' # 左に90度回転 if ((next_index + 1) % 4) == pre_index: return 'l' # 回転しない or 180度回転 return '' def put_to_command(self, commands): """ ブロックサークル間移動後に走行体がブロックを設置するコマンドを追加する。 :param commands: ブロックサークル間移動のコマンド """ # 末尾のコマンドを削除する commands = commands[:-1] # ブロックサークル間の黒線まで移動するコマンドを追加する commands += Instructions.PREPARE_TO_PUT # 黒線からブロックを設置するコマンドを追加する commands += Instructions.PUT return commands