diff --git a/examples/python/buffers.py b/examples/python/buffers.py index 2ad199d68..a184f34ca 100755 --- a/examples/python/buffers.py +++ b/examples/python/buffers.py @@ -2,8 +2,11 @@ ##################################################################### # This script presents different buffers and formats. -# OpenCV is used here to display images, install it or remove any -# references to cv2 +# +# Note: OpenCV is used here to display images, install it: +# pip install opencv-python +# or remove any references to cv2. +# # Configuration is loaded from "../../scenarios/basic.cfg" file. # number of episodes are played. # Random combination of buttons is chosen for every action. @@ -70,13 +73,17 @@ # Enables labeling of in game objects labeling. game.set_labels_buffer_enabled(True) + # See also labels_buffer.py example for more explanations. - # Enables buffer with top down map of he current episode/level . + # Enables buffer with top down map of the current episode/level . game.set_automap_buffer_enabled(True) game.set_automap_mode(vzd.AutomapMode.OBJECTS) game.set_automap_rotate(False) game.set_automap_render_textures(False) + # There is also audio buffer which is not present here. + # See audio_buffer.py example for more explanations. + game.set_render_hud(True) game.set_render_minimal_hud(False) diff --git a/examples/python/record_episodes.py b/examples/python/record_episodes.py index 23dbc965d..6e60c518d 100755 --- a/examples/python/record_episodes.py +++ b/examples/python/record_episodes.py @@ -45,17 +45,20 @@ while not game.is_episode_finished(): s = game.get_state() + a = choice(actions) r = game.make_action(choice(actions)) print(f"State #{s.number}") + print("Action:", a) print("Game variables:", s.game_variables[0]) print("Reward:", r) print("=====================") - print("Episode", i, "finished.") - print("total reward:", game.get_total_reward()) + print(f"Episode {i} finished. Saved to file episode{i}_rec.lmp") + print("Total reward:", game.get_total_reward()) print("************************\n") +game.new_episode() # This is currently required to stop and save the previous recording. game.close() # New render settings for replay @@ -76,21 +79,24 @@ game.replay_episode(f"episode{i}_rec.lmp") while not game.is_episode_finished(): + # Get a state s = game.get_state() - # Use advance_action instead of make_action. + # Use advance_action instead of make_action to proceed game.advance_action() + # Retrieve the last actions and the reward + a = game.get_last_action() r = game.get_last_reward() - # game.get_last_action is not supported and don't work for replay at the moment. print(f"State #{s.number}") + print("Action:", a) print("Game variables:", s.game_variables[0]) print("Reward:", r) print("=====================") print("Episode", i, "finished.") - print("total reward:", game.get_total_reward()) + print("Total reward:", game.get_total_reward()) print("************************") game.close() diff --git a/examples/python/scenarios.py b/examples/python/scenarios.py index 90a5cf59d..73f5e18ab 100755 --- a/examples/python/scenarios.py +++ b/examples/python/scenarios.py @@ -10,7 +10,7 @@ # To see the scenario description go to "../../scenarios/README.md" ##################################################################### -import itertools as it +import itertools import os from argparse import ArgumentParser from random import choice @@ -48,7 +48,7 @@ # Creates all possible actions depending on how many buttons there are. actions_num = game.get_available_buttons_size() actions = [] - for perm in it.product([False, True], repeat=actions_num): + for perm in itertools.product([False, True], repeat=actions_num): actions.append(list(perm)) episodes = 10 diff --git a/scenarios/predict_position.cfg b/scenarios/predict_position.cfg index aa66eef57..0bc456715 100644 --- a/scenarios/predict_position.cfg +++ b/scenarios/predict_position.cfg @@ -9,7 +9,7 @@ doom_skill = 1 living_reward = -0.001 # Rendering options -screen_resolution = RES_800X450 +screen_resolution = RES_320X240 screen_format = CRCGCB render_hud = false render_crosshair = false diff --git a/tests/manual_test_seed.py b/tests/manual_test_seed.py new file mode 100755 index 000000000..c86681b0d --- /dev/null +++ b/tests/manual_test_seed.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3ch + +# Tests ViZDoom seed option. +# This test can be run as Python script or via PyTest + +import itertools +import os +import random + +import cv2 +import numpy as np + +import vizdoom as vzd + + +def test_seed(repeats=10, tics=8, audio_buffer=False, seed=1993): + scenarios_to_skip = [ + # "deadly_corridor.cfg", + # "defend_the_center.cfg", + # "deathmatch.cfg", + # "health_gathering.cfg", + # "health_gathering_supreme.cfg", + # "deathmatch.cfg", + # Multiplayer scenarios + "cig.cfg", + "multi_duel.cfg", + "multi.cfg", + "oblige.cfg", + ] + configs = [ + file + for file in os.listdir(vzd.scenarios_path) + if file.endswith(".cfg") and file not in scenarios_to_skip + ] + print(configs) + game = vzd.DoomGame() + + for config in configs: + print(config) + initial_states = [] + states_after_action = [] + + game = vzd.DoomGame() + game.load_config(config) + game.set_window_visible(False) + + # Creates all possible actions depending on how many buttons there are. + actions_num = game.get_available_buttons_size() + actions = [] + for perm in itertools.product([False, True], repeat=actions_num): + actions.append(list(perm)) + + # Enable all buffers + buffers = ["screen_buffer", "depth_buffer", "labels_buffer", "automap_buffer"] + game.set_depth_buffer_enabled(True) + game.set_labels_buffer_enabled(True) + game.set_automap_buffer_enabled(True) + game.set_objects_info_enabled(True) + game.set_sectors_info_enabled(True) + game.set_audio_buffer_enabled(audio_buffer) + if audio_buffer: + buffers.append("audio_buffer") + + game.set_screen_format(vzd.ScreenFormat.BGR24) + + game.init() + + for i in range(repeats): + game.set_seed(1993) + random.seed(seed) + # game.init() + game.new_episode() + + initial_states.append(game.get_state()) + if i % 2 == 0: + game.make_action(random.choice(actions), tics=tics) + else: + action = random.choice(actions) + for _ in range(tics): + game.make_action(action, tics=1) + + game.make_action(random.choice(actions), tics=tics) + states_after_action.append(game.get_state()) + + # game.close() + + for s1, s2 in zip(initial_states[:-1], initial_states[1:]): + assert s1.tic == s2.tic + assert np.array_equal(s1.game_variables, s2.game_variables) + + if not np.array_equal(s1.screen_buffer, s2.screen_buffer): + print("Initial states are not equal") + print(f"s1: {s1.tic}, {s1.game_variables}") + print(f"s2: {s2.tic}, {s2.game_variables}") + print(np.all(s1.screen_buffer == s2.screen_buffer)) + print(np.array_equal(s1.screen_buffer, s2.screen_buffer)) + cv2.imshow("s1", s1.screen_buffer) + cv2.imshow("s2", s2.screen_buffer) + cv2.imshow("s1 - s2", s1.screen_buffer - s2.screen_buffer) + cv2.waitKey(int(10000)) + + for b in buffers: + if not np.array_equal(getattr(s1, b), getattr(s2, b)): + print("Initial states are not equal") + cv2.imshow("s1", getattr(s1, b)) + cv2.imshow("s2", getattr(s2, b)) + cv2.imshow("s1 - s2", getattr(s1, b) - getattr(s2, b)) + cv2.waitKey(int(10000)) + + # assert np.array_equal(getattr(s1, b), getattr(s2, b)) + + for s1, s2 in zip(states_after_action[:-1], states_after_action[1:]): + assert s1.tic == s2.tic + assert np.array_equal(s1.game_variables, s2.game_variables) + + if not np.array_equal(s1.screen_buffer, s2.screen_buffer): + print("States after action are not equal") + print(f"s1: {s1.tic}, {s1.game_variables}") + print(f"s2: {s2.tic}, {s2.game_variables}") + print(np.all(s1.screen_buffer == s2.screen_buffer)) + print(np.array_equal(s1.screen_buffer, s2.screen_buffer)) + cv2.imshow("s1", s1.screen_buffer) + cv2.imshow("s2", s2.screen_buffer) + cv2.imshow("s1 - s2", s1.screen_buffer - s2.screen_buffer) + cv2.waitKey(int(10000)) + + for b in buffers: + if not np.array_equal(getattr(s1, b), getattr(s2, b)): + print("States after action are not equal") + cv2.imshow("s1", getattr(s1, b)) + cv2.imshow("s2", getattr(s2, b)) + cv2.imshow("s1 - s2", getattr(s1, b) - getattr(s2, b)) + cv2.waitKey(int(10000)) + + # assert np.array_equal(getattr(s1, b), getattr(s2, b)) + + +if __name__ == "__main__": + test_seed() diff --git a/tests/test_configs.py b/tests/test_configs.py index df03ba669..ec512a58d 100755 --- a/tests/test_configs.py +++ b/tests/test_configs.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Tests for ViZDoom enums and related methods. +# Tests ViZDoom load_config method and all the config files from the scenario directory. # This test can be run as Python script or via PyTest import os @@ -9,10 +9,21 @@ def test_load_config(): + print("Testing load_config() and default scenarios ...") + for file in os.listdir(vzd.scenarios_path): if file.endswith(".cfg"): - vzd.DoomGame().load_config(os.path.join(vzd.scenarios_path, file)) - vzd.DoomGame().load_config(file) + game = vzd.DoomGame() + + # Both should work + game.load_config(os.path.join(vzd.scenarios_path, file)) + game.load_config(file) + + w = game.get_screen_width() + h = game.get_screen_height() + assert ( + w == 320 and h == 240 + ), f"Config file {file} is using non-default screen resolution: {w}x{h} instead 320x240." if __name__ == "__main__": diff --git a/tests/test_enums.py b/tests/test_enums.py index bfc902aef..2647eec0d 100755 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -24,30 +24,30 @@ def _test_enums(enum_name, func_name): ] all_values_names = [v.name for v in all_values] - # set test + # set_X function test set_func(all_values) - get_buttons_names = [v.name for v in get_func()] - assert all_values_names == get_buttons_names + get_values_names = [v.name for v in get_func()] + assert all_values_names == get_values_names - # add test + # add_X function test clear_func() for i, v in enumerate(all_values): add_func(v) get_values_names = [v.name for v in get_func()] assert all_values_names[: i + 1] == get_values_names - # again set test + # Check if set function overwrites previous values set_func(all_values) get_values_names = [v.name for v in get_func()] assert all_values_names == get_values_names - # multiple adds + # Multiple add_X functions test for i, v in enumerate(all_values): add_func(v) get_values_names = [v.name for v in get_func()] assert all_values_names == get_values_names - # multiple in set + # Test duplicated values in set_X function set_func(all_values + all_values) get_values_names = [v.name for v in get_func()] assert all_values_names == get_values_names diff --git a/tests/test_game_args.py b/tests/test_game_args.py index d592dc083..9d89d28f8 100755 --- a/tests/test_game_args.py +++ b/tests/test_game_args.py @@ -7,6 +7,7 @@ def test_game_args(): + print("Testing setting custom game arguments...") game = vzd.DoomGame() game.set_window_visible(False) @@ -16,12 +17,12 @@ def test_game_args(): args_all = args1 + " " + args2 game.set_game_args(args_all) - assert game.get_game_args() == args_all + assert game.get_game_args() == args_all, "Game args not set correctly." game.clear_game_args() game.add_game_args(args1) game.add_game_args(args2) - assert game.get_game_args() == args_all + assert game.get_game_args() == args_all, "Game args not set correctly." if __name__ == "__main__": diff --git a/tests/test_get_state.py b/tests/test_get_state.py index c6cdcc713..85d253099 100755 --- a/tests/test_get_state.py +++ b/tests/test_get_state.py @@ -18,16 +18,17 @@ def _test_get_state( num_iterations=10, num_states=20, mem_eta_mb=0, - depthBuffer=False, - labelsBuffer=False, - automapBuffer=False, - objectsInfo=False, - sectorsInfo=False, - audioBuffer=False, + depth_buffer=False, + labels_buffer=False, + automap_buffer=False, + objects_info=False, + sectors_info=False, + audio_buffer=False, + seed=1993, ): print("Testing get_state() ...") - random.seed(1993) + random.seed(seed) buttons = [ vzd.Button.MOVE_FORWARD, @@ -46,21 +47,21 @@ def _test_get_state( game.set_episode_timeout(num_states) game.set_available_buttons(buttons) - game.set_depth_buffer_enabled(depthBuffer) - game.set_labels_buffer_enabled(labelsBuffer) - game.set_automap_buffer_enabled(automapBuffer) - game.set_objects_info_enabled(objectsInfo) - game.set_sectors_info_enabled(sectorsInfo) - game.set_audio_buffer_enabled(audioBuffer) + game.set_depth_buffer_enabled(depth_buffer) + game.set_labels_buffer_enabled(labels_buffer) + game.set_automap_buffer_enabled(automap_buffer) + game.set_objects_info_enabled(objects_info) + game.set_sectors_info_enabled(sectors_info) + game.set_audio_buffer_enabled(audio_buffer) buffers = ["screen_buffer"] - if depthBuffer: + if depth_buffer: buffers.append("depth_buffer") - if labelsBuffer: + if labels_buffer: buffers.append("labels_buffer") - if automapBuffer: + if automap_buffer: buffers.append("automap_buffer") - if audioBuffer: + if audio_buffer: buffers.append("audio_buffer") # This fixes "BiquadFilter_setParams: Assertion `gain > 0.00001f' failed" issue # or "no audio in buffer" issue caused by a bug in OpenAL version 1.19. @@ -97,13 +98,16 @@ def _test_get_state( max_vals = {b: -np.inf for b in buffers} for s, bs_copy in zip(states, buffers_copies): for b in buffers: - assert np.array_equal(getattr(s, b), bs_copy[b]) + assert np.array_equal( + getattr(s, b), bs_copy[b] + ), f"Buffer {b} is not equal with its copy" min_vals[b] = min(min_vals[b], np.min(bs_copy[b])) max_vals[b] = max(max_vals[b], np.max(bs_copy[b])) for b in buffers: - print(f"Buffer {b} min: {min_vals[b]}, max: {max_vals[b]}") - assert min_vals[b] != max_vals[b] + assert ( + min_vals[b] != max_vals[b] + ), f"Buffer {b} min: {min_vals[b]}, max: {max_vals[b]} are equal, buffer is empty" # Save and load states via pickle - confirms that states and all sub-objects (labels, lines, objects) are picklable. with open("tmp_states.pkl", "wb") as f: @@ -114,7 +118,9 @@ def _test_get_state( # Compare loaded states with their copies - to confirm that pickling doesn't mutate states. for s, s_copy in zip(states, pickled_states): - assert pickle.dumps(s) == pickle.dumps(s_copy) + assert pickle.dumps(s) == pickle.dumps( + s_copy + ), "Pickled state is not equal with its original object after save and load" del pickled_states os.remove("tmp_states.pkl") @@ -133,7 +139,9 @@ def _test_get_state( prev_mem = mem prev_len = len(states) elif prev_len == len(states): - assert abs(prev_mem - mem) < mem_eta_mb + assert ( + abs(prev_mem - mem) < mem_eta_mb + ), f"Memory leak detected: with {len(states)} states saved, after episode {i + 1} / {num_iterations}: {mem} MB used, expected ~{prev_mem} +/- {mem_eta_mb} MB" def test_get_state(num_iterations=10, num_states=20): @@ -142,12 +150,12 @@ def test_get_state(num_iterations=10, num_states=20): num_iterations=num_iterations, num_states=num_states, mem_eta_mb=0, - depthBuffer=True, - labelsBuffer=True, - automapBuffer=True, - objectsInfo=True, - sectorsInfo=True, - audioBuffer=False, # Turned off by default, because it fails on some systems without audio backend and OpenAL installed + depth_buffer=True, + labels_buffer=True, + automap_buffer=True, + objects_info=True, + sectors_info=True, + audio_buffer=False, # Turned off by default, because it fails on some systems without audio backend and OpenAL installed ) diff --git a/tests/test_labels_buffer.py b/tests/test_labels_buffer.py index bffee7470..3ec2fb836 100755 --- a/tests/test_labels_buffer.py +++ b/tests/test_labels_buffer.py @@ -35,6 +35,7 @@ def check_label(labels_buffer, label): def test_labels_buffer(): + print("Testing labels buffer ...") game = vzd.DoomGame() game.load_config(os.path.join(vzd.scenarios_path, "deathmatch.cfg"))