From 65f195fecf488f5bbd1fc285ade7105fbec4548d Mon Sep 17 00:00:00 2001 From: Simon Pirkelmann Date: Sat, 11 Sep 2021 01:44:03 +0200 Subject: [PATCH] implemented level generator --- gauss-turing/game/level_generator.py | 124 +++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 gauss-turing/game/level_generator.py diff --git a/gauss-turing/game/level_generator.py b/gauss-turing/game/level_generator.py new file mode 100644 index 0000000..84fe683 --- /dev/null +++ b/gauss-turing/game/level_generator.py @@ -0,0 +1,124 @@ +from gauss_turing import Program, Board, Command, Robot +import numpy as np +import json + +levels = { + 0: lambda cmds: len(cmds) <= 3, + 1: lambda cmds: len(cmds) == 3 +} + +def rate_level(robot_path, cmds, board): + path_length = len(set(robot_path)) + n_cmds = len(cmds) + n_cmd_colors = len(set([tuple(c.color) for c in cmds])) + + difficulty = (path_length - 1) * (n_cmds + n_cmd_colors) + + # place coins on the robot path to create a solution + if difficulty > 0: + n_coins = np.random.randint(1, min(path_length, 5)) - 1 + + # put one coin on last tile visited + coins = [robot_path[-1]] + + # distribute other coins randomly on the path + # path without first and list tile + unique_tiles = list(set(robot_path) - {robot_path[-1]} - {robot_path[0]}) + + for _ in range(n_coins): + c = np.random.randint(0, len(unique_tiles)) + new_coin = unique_tiles.pop(c) + coins.append(new_coin) + pass + else: + coins = [] + + return difficulty, coins + +def generate_level(dimx, dimy, max_steps=100): + n_cmds = np.random.randint(2, 6) + assert n_cmds <= 5 + + # generate random board without any coins + board = Board(dimx, dimy, n_coins=0) + + cmds = [] + actions = list(sorted(Command.valid_actions - {'-'})) + # generate random commands + for i in range(n_cmds): + action = np.random.choice(actions) + color = Board.valid_colors[np.random.randint(len(Board.valid_colors))] + cmds.append(Command(action, color)) + + # generate robot at random position + rx = np.random.randint(0, dimx-1) + ry = np.random.randint(0, dimy-1) + orientation = np.random.choice(['>','v','<','^']) + r = Robot(rx, ry, orientation) + + prg = Program(r, board, cmds) + continue_running = True + state = 'running' + prg_counter_old = prg.prg_counter + + robot_path = [(r.x, r.y)] + step = 0 + + while continue_running and step < max_steps: + #print(f"prg_counter = {prg.prg_counter} - robot: {r} - state: {state}") + state = prg.step(state, check_victory=False) + + robot_path.append((r.x, r.y)) + + stuck = prg.prg_counter == prg_counter_old + + prg_counter_old = prg.prg_counter + + if state == 'game_over' or stuck: + continue_running = False + + step += 1 + + last_pos = robot_path[-1] + if not ((0 <= last_pos[0] < dimx) and (0 <= last_pos[1] < dimy)): + # remove last entry of path if robot leaves the board + robot_path.pop(-1) + + difficulty, coins = rate_level(robot_path, cmds, board) + + # put coins on the board + for coin in coins: + board.tiles[coin[1], coin[0]]['star'] = True + n_coins = len(coins) + + + return difficulty, board, n_coins, set(robot_path), (rx, ry, orientation), cmds + +if __name__ == "__main__": + np.random.seed(2) + + levels = {} + + for i in range(100): + diff, board, n_coins, robot_path, init_robot_pos, solution = generate_level(7, 4) + if diff > 0: + print("difficulty: ", diff, "n_coins: ", n_coins, "path length: ", len(robot_path)) + if diff in levels: + if n_coins > levels[diff]['n_coins'] and len(robot_path) > levels[diff]['path_length']: + levels[diff] = {'board': board, 'init_robot_pos': init_robot_pos, 'solution': solution, + 'n_coins': n_coins, 'path_length': len(robot_path)} + else: + levels[diff] = {'board': board, 'init_robot_pos': init_robot_pos, 'solution': solution, + 'n_coins': n_coins, 'path_length': len(robot_path)} + + level_info = {} + for l, data in levels.items(): + np.save(f'levels/{l}.npy', data['board'].tiles) + sol = [(cmd.action, tuple(map(int, cmd.color))) for cmd in data['solution']] + level_info[l] = {'init_robot_pos': data['init_robot_pos'], 'solution': sol, + 'n_coins': int(data['n_coins']), 'path_length': int(data['path_length']), + 'file': f'levels/{l}.npy'} + + with open('levels/level_info.json', 'w') as f: + json.dump(level_info, f, indent=4) + pass \ No newline at end of file