From 0c32b1d1afaefaf4ff9c16b07c5474ba1feea55d Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Sun, 20 Jul 2025 12:15:51 +0100 Subject: [PATCH 01/19] Some initial experimental code - not for merging --- CMakeLists.txt | 15 ++++++++--- include/nletypes.h | 2 -- nle/env/base.py | 5 +++- nle/nethack/nethack.py | 3 +++ pixels.py | 9 +++++++ src/nle.c | 9 +++++++ third_party/converter/stripgfx.c | 2 +- third_party/converter/stripgfx.h | 2 +- win/rl/pynethack.cc | 5 ---- win/rl/tile2rgb.c | 44 ++++++++++++++++++++++++++++++++ 10 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 pixels.py create mode 100644 win/rl/tile2rgb.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bfdcc61d..33a7c8c51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,9 @@ message(STATUS "Building nle backend version: ${CMAKE_NLE_VERSION}") set(CMAKE_POSITION_INDEPENDENT_CODE ON) +add_compile_options(-Wno-deprecated-non-prototype) +add_compile_options(-Wno-unused-variable) + # We use this to decide where the root of the nle/ package is. Normally it # shouldn't be needed, but sometimes (e.g. when using setuptools) we are # generating some of the files outside of the original package path. @@ -61,8 +64,7 @@ add_compile_definitions( HACKDIR="${HACKDIR}" DEFAULT_WINDOW_SYS="rl" DLB - NOCWD_ASSUMPTIONS - NLE_USE_TILES) + NOCWD_ASSUMPTIONS) set(NLE_SRC ${nle_SOURCE_DIR}/src) set(NLE_INC ${nle_SOURCE_DIR}/include) @@ -94,7 +96,10 @@ file( "sys/unix/unixmain.c" "sys/unix/unixres.c" "win/tty/*.c" - "win/rl/winrl.cc") + "win/rl/winrl.cc" + "win/rl/tile2rgb.c" + "win/share/tiletext.c" + "win/share/tilemap.c") # static version of bzip2 library add_library( @@ -120,7 +125,9 @@ target_include_directories( nethack PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${NLE_INC_GEN} /usr/local/include ${CMAKE_CURRENT_SOURCE_DIR}/third_party/libtmt - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/bzip2) + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/bzip2 + ${CMAKE_CURRENT_SOURCE_DIR}/win/share + ${NLE_SRC_GEN}) # target_link_directories(nethack PUBLIC /usr/local/lib) # Careful with -DMONITOR_HEAP: Ironically, it fails to fclose FILE* heaplog. diff --git a/include/nletypes.h b/include/nletypes.h index e1edefdfc..824d41df1 100644 --- a/include/nletypes.h +++ b/include/nletypes.h @@ -46,8 +46,6 @@ #define NLE_BL_CONDITION 25 /* condition bit mask */ #define NLE_BL_ALIGN 26 -/* #define NLE_USE_TILES 1 */ /* Set in CMakeLists.txt. */ - /* NetHack defines boolean as follows: typedef xchar boolean; (global.h:80) typedef schar xchar; (global.h:73) diff --git a/nle/env/base.py b/nle/env/base.py index 3cbbbb0f1..ed15eee18 100644 --- a/nle/env/base.py +++ b/nle/env/base.py @@ -151,7 +151,7 @@ class NLE(gym.Env): # but NetHack doesn't have any. Set it to 42, because # that is always the answer to life, the universe and # everything. - metadata = {"render_modes": ["human", "ansi", "full"], "render_fps": 42} + metadata = {"render_modes": ["human", "ansi", "full", "pixel"], "render_fps": 42} class StepStatus(enum.IntEnum): """Specifies the status of the terminal state. @@ -548,6 +548,9 @@ def render(self): chars = self.last_observation[self._observation_keys.index("chars")] # TODO: Why return a string here but print in the other branches? return "\n".join([line.tobytes().decode("utf-8") for line in chars]) + + if mode == "pixel": + return self.nethack.pixel_render() return "\nInvalid render mode: " + mode diff --git a/nle/nethack/nethack.py b/nle/nethack/nethack.py index 868295be0..a344134aa 100644 --- a/nle/nethack/nethack.py +++ b/nle/nethack/nethack.py @@ -324,3 +324,6 @@ def in_normal_game(self): def how_done(self): return self._pynethack.how_done() + + def pixel_render(self): + return np.zeros((336,1264,3),dtype=np.int8) diff --git a/pixels.py b/pixels.py new file mode 100644 index 000000000..5ba5ba87c --- /dev/null +++ b/pixels.py @@ -0,0 +1,9 @@ +import gymnasium as gym +import nle + +env=gym.make("NetHack-v0", render_mode="pixel") +env2=gym.wrappers.AddRenderObservation(env, render_only=False, render_key="pixel", obs_key="glyphs") + +obs=env2.reset() + +print(obs) \ No newline at end of file diff --git a/src/nle.c b/src/nle.c index 67276dc84..54028e13c 100644 --- a/src/nle.c +++ b/src/nle.c @@ -31,6 +31,7 @@ #endif extern int unixmain(int, char **); +extern int init_tiles(const char *[], int); signed char vt_char_color_extract(TMTCHAR *c) @@ -402,6 +403,14 @@ nle_start(nle_obs *obs, FILE *ttyrec, nle_settings *settings_p) /* Initialise the level generation RNG */ nle_init_lgen_rng(); + /* TEMP: load the tiles */ + char *tilefiles[] = { + "monsters.txt", + "objects.txt", + "other.txt" + }; + init_tiles(tilefiles, 3); + nle->stack = create_fcontext_stack(STACK_SIZE); nle->generatorcontext = make_fcontext(nle->stack.sptr, nle->stack.ssize, mainloop); diff --git a/third_party/converter/stripgfx.c b/third_party/converter/stripgfx.c index a7a108e93..d8e757b98 100644 --- a/third_party/converter/stripgfx.c +++ b/third_party/converter/stripgfx.c @@ -344,7 +344,7 @@ static unsigned char dec_graphics[MAXPCHARS] = { /* clang-format on */ -void populate_gfx_arrays() { +void populate_gfx_arrays(void) { int i; diff --git a/third_party/converter/stripgfx.h b/third_party/converter/stripgfx.h index 4088e2bbb..61d7058db 100644 --- a/third_party/converter/stripgfx.h +++ b/third_party/converter/stripgfx.h @@ -1,7 +1,7 @@ #ifndef INCLUDED_stripgfx_h #define INCLUDED_stripgfx_h -void populate_gfx_arrays(); +void populate_gfx_arrays(void); unsigned char strip_gfx(unsigned char inchar, int use_dec); #endif /* !INCLUDED_stripgfx_h */ diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index 13894a60c..5930b579c 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -25,7 +25,6 @@ extern "C" { #undef min #undef max -#ifdef NLE_USE_TILES extern short glyph2tile[]; /* in tile.c (made from tilemap.c) */ /* Copy from dungeon.c. Necessary to add tile.c. @@ -53,7 +52,6 @@ on_level(d_level *lev1, d_level *lev2) && lev1->dlevel == lev2->dlevel); } /* End of copy from dungeon.c */ -#endif namespace py = pybind11; using namespace py::literals; @@ -629,13 +627,10 @@ PYBIND11_MODULE(_pynethack, m) py::vectorize([](int glyph) { return glyph_is_swallow(glyph); })); mn.def("glyph_is_warning", py::vectorize([](int glyph) { return glyph_is_warning(glyph); })); - -#ifdef NLE_USE_TILES mn.attr("glyph2tile") = py::memoryview::from_buffer(glyph2tile, /*shape=*/{ MAX_GLYPH }, /*strides=*/{ sizeof(glyph2tile[0]) }, /*readonly=*/true); -#endif py::class_(mn, "permonst", "The permonst struct.") .def( diff --git a/win/rl/tile2rgb.c b/win/rl/tile2rgb.c new file mode 100644 index 000000000..13536e1b7 --- /dev/null +++ b/win/rl/tile2rgb.c @@ -0,0 +1,44 @@ +/* Converts the tile text descriptions in monsters.txt, objects.txt, and + other.txt into RGB pixels */ + +#include +#include "hack.h" +#include "tile.h" + +#define NUM_TILES 1082 /* TODO figure out how to not hard code this */ + +/* defined in tile.c, a generated file */ +extern short glyph2tile[]; +extern int total_tiles_used; + +typedef struct tile_s { + pixel tile[TILE_Y][TILE_X]; +} tile_t; + +tile_t tileset[NUM_TILES]; + +/* Basically want to open the files, read the pixels and be done with it */ + +int init_tiles(const char *filenames[], int filecount) { + + memset(tileset, 0, sizeof(tileset)); + + pixel tile[TILE_Y][TILE_X]; + tile_t *tile_ptr = tileset; + + for(int f=0; f Date: Thu, 14 Aug 2025 19:19:59 +0100 Subject: [PATCH 02/19] more updates - not for merging --- CMakeLists.txt | 13 ++++-------- nle/nethack/nethack.py | 3 +++ src/nle.c | 9 --------- util/CMakeLists.txt | 14 +++++++++++-- win/rl/nletiletxt.c | 11 ++++++++++ win/rl/pynethack.cc | 46 +++++++++++++++++++++++++++++++++++++----- win/rl/tile2rgb.c | 23 +++++++++------------ win/rl/tile2rgb.h | 14 +++++++++++++ 8 files changed, 95 insertions(+), 38 deletions(-) create mode 100644 win/rl/nletiletxt.c create mode 100644 win/rl/tile2rgb.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 33a7c8c51..5e9b007ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,10 +96,7 @@ file( "sys/unix/unixmain.c" "sys/unix/unixres.c" "win/tty/*.c" - "win/rl/winrl.cc" - "win/rl/tile2rgb.c" - "win/share/tiletext.c" - "win/share/tilemap.c") + "win/rl/winrl.cc") # static version of bzip2 library add_library( @@ -125,9 +122,7 @@ target_include_directories( nethack PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${NLE_INC_GEN} /usr/local/include ${CMAKE_CURRENT_SOURCE_DIR}/third_party/libtmt - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/bzip2 - ${CMAKE_CURRENT_SOURCE_DIR}/win/share - ${NLE_SRC_GEN}) + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/bzip2) # target_link_directories(nethack PUBLIC /usr/local/lib) # Careful with -DMONITOR_HEAP: Ironically, it fails to fclose FILE* heaplog. @@ -162,8 +157,8 @@ pybind11_add_module( $) target_link_libraries(_pynethack PUBLIC nethackdl) set_target_properties(_pynethack PROPERTIES CXX_STANDARD 14) -target_include_directories(_pynethack PUBLIC ${NLE_INC_GEN}) -add_dependencies(_pynethack util) # For pm.h. +target_include_directories(_pynethack PUBLIC ${NLE_INC_GEN} ${NLE_WIN}/share) +#add_dependencies(_pynethack util tile) # For pm.h. # ttyrec converter library add_library( diff --git a/nle/nethack/nethack.py b/nle/nethack/nethack.py index a344134aa..0163837e2 100644 --- a/nle/nethack/nethack.py +++ b/nle/nethack/nethack.py @@ -325,5 +325,8 @@ def in_normal_game(self): def how_done(self): return self._pynethack.how_done() + def setup_tiles(self): + return self._pynethack.setup_tiles() + def pixel_render(self): return np.zeros((336,1264,3),dtype=np.int8) diff --git a/src/nle.c b/src/nle.c index 54028e13c..67276dc84 100644 --- a/src/nle.c +++ b/src/nle.c @@ -31,7 +31,6 @@ #endif extern int unixmain(int, char **); -extern int init_tiles(const char *[], int); signed char vt_char_color_extract(TMTCHAR *c) @@ -403,14 +402,6 @@ nle_start(nle_obs *obs, FILE *ttyrec, nle_settings *settings_p) /* Initialise the level generation RNG */ nle_init_lgen_rng(); - /* TEMP: load the tiles */ - char *tilefiles[] = { - "monsters.txt", - "objects.txt", - "other.txt" - }; - init_tiles(tilefiles, 3); - nle->stack = create_fcontext_stack(STACK_SIZE); nle->generatorcontext = make_fcontext(nle->stack.sptr, nle->stack.ssize, mainloop); diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 8f0aa828e..7b2861cb9 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -54,11 +54,13 @@ add_custom_command( DEPENDS makedefs OUTPUT ${NLE_INC_GEN}/pm.h COMMAND $ ARGS -p) +add_custom_target(generate_pm_h DEPENDS ${NLE_INC_GEN}/pm.h) add_custom_command( DEPENDS tilemap OUTPUT ${NLE_SRC_GEN}/tile.c COMMAND $) +add_custom_target(generate_tile_c DEPENDS ${NLE_SRC_GEN}/tile.c) add_custom_command( OUTPUT ${NLE_UTIL_GEN}/dgn_parser.c ${NLE_UTIL_GEN}/dgn_comp.h @@ -90,8 +92,16 @@ add_executable(tilemap ${NLE_WIN}/share/tilemap.c) target_include_directories(tilemap PUBLIC ${NLE_INC} ${NLE_INC_GEN}) add_dependencies(tilemap util) -add_library(tile OBJECT ${NLE_SRC_GEN}/tile.c) -target_include_directories(tile PUBLIC ${NLE_INC} ${NLE_INC_GEN}) +file( + GLOB + NETHACK_TILE_SRC + ${NLE_WIN}/rl/tile2rgb.c + ${NLE_WIN}/rl/nletiletxt.c + ${NLE_WIN}/share/tiletext.c +) +add_library(tile OBJECT ${NETHACK_TILE_SRC} ${NLE_SRC_GEN}/tile.c) +target_include_directories(tile PUBLIC ${NLE_INC} ${NLE_INC_GEN} ${NLE_WIN}/share) +add_dependencies(tile generate_pm_h generate_tile_c) # NOTE: util is dependent on these two add_dependencies(lev_comp util) diff --git a/win/rl/nletiletxt.c b/win/rl/nletiletxt.c new file mode 100644 index 000000000..87c4865d1 --- /dev/null +++ b/win/rl/nletiletxt.c @@ -0,0 +1,11 @@ +/* Copied from NetHack's /util/Makefile.utl + +This is the secondary compilation of tilemap.c with the TILETEXT +directive set. + +*/ + +#define TILETEXT +#include "../share/tilemap.c" + +/*nletiletxt.c*/ \ No newline at end of file diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index 5930b579c..e7b99a94c 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -18,6 +18,7 @@ extern "C" { extern "C" { #include "nledl.h" +#include "tile2rgb.h" } // Undef name clashes between NetHack and Python. @@ -26,6 +27,7 @@ extern "C" { #undef max extern short glyph2tile[]; /* in tile.c (made from tilemap.c) */ +extern int total_tiles_used; /* also in tile.c */ /* Copy from dungeon.c. Necessary to add tile.c. Can't add dungeon.c itself as it pulls in too much. */ @@ -153,6 +155,12 @@ class Nethack if (ttyrec_) { fclose(ttyrec_); } + if (tileset) { + free(tileset); + } + if (rendered_tiles) { + free(rendered_tiles); + } } void @@ -361,6 +369,31 @@ class Nethack strncpy(settings_.wizkit, wizkit.c_str(), sizeof(settings_.wizkit)); } + boolean + setup_tiles() + { + int tiles_read = 0; + const char *tilefiles[] = { + "./win/share/monsters.txt", + "./win/share/objects.txt", + "./win/share/other.txt" + }; + + tileset = (tile_t *) calloc(sizeof(tile_t), (size_t) total_tiles_used); + // TODO - handle memory allocation failure for tileset + if(tileset) { + tiles_read = init_tiles(tilefiles, 3, tileset); + + if(tiles_read == 3) { + return true; + } + } + + // TODO Error Handling + printf("Unable to open tile file %s\n", tilefiles[tiles_read]); + return false; + } + private: void reset(FILE *ttyrec) @@ -390,6 +423,8 @@ class Nethack nledl_ctx *nle_ = nullptr; std::FILE *ttyrec_ = nullptr; nle_settings settings_; + tile_t *tileset = nullptr; + tile_t *rendered_tiles = nullptr; }; PYBIND11_MODULE(_pynethack, m) @@ -429,7 +464,8 @@ PYBIND11_MODULE(_pynethack, m) .def("get_seeds", &Nethack::get_seeds) .def("in_normal_game", &Nethack::in_normal_game) .def("how_done", &Nethack::how_done) - .def("set_wizkit", &Nethack::set_wizkit); + .def("set_wizkit", &Nethack::set_wizkit) + .def("setup_tiles", &Nethack::setup_tiles); py::module mn = m.def_submodule( "nethack", "Collection of NetHack constants and functions"); @@ -629,8 +665,8 @@ PYBIND11_MODULE(_pynethack, m) py::vectorize([](int glyph) { return glyph_is_warning(glyph); })); mn.attr("glyph2tile") = py::memoryview::from_buffer(glyph2tile, /*shape=*/{ MAX_GLYPH }, - /*strides=*/{ sizeof(glyph2tile[0]) }, - /*readonly=*/true); + /*strides=*/{ sizeof(glyph2tile[0]) }, + /*readonly=*/true); py::class_(mn, "permonst", "The permonst struct.") .def( @@ -689,7 +725,7 @@ PYBIND11_MODULE(_pynethack, m) "Argument should be between 0 and MAXMCLASSES (" + std::to_string(MAXMCLASSES) + ") but got " + std::to_string(let)); - return &def_monsyms[let]; + return &def_monsyms[(int)let]; }, py::return_value_policy::reference) .def_static( @@ -700,7 +736,7 @@ PYBIND11_MODULE(_pynethack, m) "Argument should be between 0 and MAXOCLASSES (" + std::to_string(MAXOCLASSES) + ") but got " + std::to_string(olet)); - return &def_oc_syms[olet]; + return &def_oc_syms[(int)olet]; }, py::return_value_policy::reference) .def_readonly("sym", &class_sym::sym) diff --git a/win/rl/tile2rgb.c b/win/rl/tile2rgb.c index 13536e1b7..20620e2e2 100644 --- a/win/rl/tile2rgb.c +++ b/win/rl/tile2rgb.c @@ -3,25 +3,21 @@ #include #include "hack.h" -#include "tile.h" -#define NUM_TILES 1082 /* TODO figure out how to not hard code this */ +#include "tile2rgb.h" /* defined in tile.c, a generated file */ extern short glyph2tile[]; extern int total_tiles_used; -typedef struct tile_s { - pixel tile[TILE_Y][TILE_X]; -} tile_t; - -tile_t tileset[NUM_TILES]; - /* Basically want to open the files, read the pixels and be done with it */ -int init_tiles(const char *filenames[], int filecount) { +int init_tiles(const char *filenames[], int filecount, tile_t *tileset) { - memset(tileset, 0, sizeof(tileset)); + if(!tileset) { + // function was called without memory being allocated + return 0; + } pixel tile[TILE_Y][TILE_X]; tile_t *tile_ptr = tileset; @@ -29,16 +25,17 @@ int init_tiles(const char *filenames[], int filecount) { for(int f=0; f Date: Sat, 16 Aug 2025 17:39:58 +0100 Subject: [PATCH 03/19] test getter --- win/rl/pynethack.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index e7b99a94c..acc493c94 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -394,6 +394,10 @@ class Nethack return false; } + tile_t *get_tile_set() { + return tileset; + } + private: void reset(FILE *ttyrec) @@ -667,6 +671,10 @@ PYBIND11_MODULE(_pynethack, m) py::memoryview::from_buffer(glyph2tile, /*shape=*/{ MAX_GLYPH }, /*strides=*/{ sizeof(glyph2tile[0]) }, /*readonly=*/true); + mn.attr("tileset") = + py::memoryview::from_buffer(get_tile_set(), /*shape=*/{ total_tiles_used }, + /*strides=*/{ sizeof(tile_s) }, + /*readonly=*/true); py::class_(mn, "permonst", "The permonst struct.") .def( From 1afe829cc12ce5776d8b8d03be5fa5a9ed5c550f Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 15 Dec 2025 18:57:35 +0000 Subject: [PATCH 04/19] test tileset from python --- testframes.py | 12 ++++++++++++ win/rl/pynethack.cc | 35 ++++++++++++++++++----------------- win/rl/tile2rgb.c | 8 ++++++-- win/rl/tile2rgb.h | 2 +- 4 files changed, 37 insertions(+), 20 deletions(-) create mode 100644 testframes.py diff --git a/testframes.py b/testframes.py new file mode 100644 index 000000000..fe97d2304 --- /dev/null +++ b/testframes.py @@ -0,0 +1,12 @@ +# import matplotlib.pyplot as plt +import numpy as np + +from nle import _pynethack + +nh = _pynethack.Nethack(".", ".", "", False) +nh.setup_tiles() + +frame = np.zeros(16 * 16 * 3, dtype=np.uint8) +print(frame) +nh.get_tileset(frame) +print(frame) diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index acc493c94..95dc633de 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -5,6 +5,7 @@ #include #include +#include // "digit" is declared in both Python's longintrepr.h and NetHack's extern.h. #define digit nethack_digit @@ -29,6 +30,8 @@ extern "C" { extern short glyph2tile[]; /* in tile.c (made from tilemap.c) */ extern int total_tiles_used; /* also in tile.c */ +namespace fs = std::filesystem; + /* Copy from dungeon.c. Necessary to add tile.c. Can't add dungeon.c itself as it pulls in too much. */ @@ -158,9 +161,6 @@ class Nethack if (tileset) { free(tileset); } - if (rendered_tiles) { - free(rendered_tiles); - } } void @@ -370,19 +370,19 @@ class Nethack } boolean - setup_tiles() + setup_tileset() { int tiles_read = 0; const char *tilefiles[] = { - "./win/share/monsters.txt", - "./win/share/objects.txt", - "./win/share/other.txt" + "/Users/stephenoman/Development/nle/win/share/monsters.txt", + "/Users/stephenoman/Development/nle/win/share/objects.txt", + "/Users/stephenoman/Development/nle/win/share/other.txt" }; - tileset = (tile_t *) calloc(sizeof(tile_t), (size_t) total_tiles_used); + this->tileset = (tile_t *) calloc(sizeof(tile_t), (size_t) total_tiles_used); // TODO - handle memory allocation failure for tileset if(tileset) { - tiles_read = init_tiles(tilefiles, 3, tileset); + tiles_read = init_tileset(tilefiles, 3, tileset); if(tiles_read == 3) { return true; @@ -394,8 +394,13 @@ class Nethack return false; } - tile_t *get_tile_set() { - return tileset; + void get_tileset(py::array_t frame) { + auto buffer = frame.mutable_unchecked<1>(); + uint8_t *pixel_rgb = (uint8_t *) this->tileset; + + for(pybind11::ssize_t i = 0; i < buffer.shape(0); ++i) { + buffer(i) = pixel_rgb[i]; + } } private: @@ -428,7 +433,6 @@ class Nethack std::FILE *ttyrec_ = nullptr; nle_settings settings_; tile_t *tileset = nullptr; - tile_t *rendered_tiles = nullptr; }; PYBIND11_MODULE(_pynethack, m) @@ -469,7 +473,8 @@ PYBIND11_MODULE(_pynethack, m) .def("in_normal_game", &Nethack::in_normal_game) .def("how_done", &Nethack::how_done) .def("set_wizkit", &Nethack::set_wizkit) - .def("setup_tiles", &Nethack::setup_tiles); + .def("setup_tiles", &Nethack::setup_tileset) + .def("get_tileset", &Nethack::get_tileset); py::module mn = m.def_submodule( "nethack", "Collection of NetHack constants and functions"); @@ -671,10 +676,6 @@ PYBIND11_MODULE(_pynethack, m) py::memoryview::from_buffer(glyph2tile, /*shape=*/{ MAX_GLYPH }, /*strides=*/{ sizeof(glyph2tile[0]) }, /*readonly=*/true); - mn.attr("tileset") = - py::memoryview::from_buffer(get_tile_set(), /*shape=*/{ total_tiles_used }, - /*strides=*/{ sizeof(tile_s) }, - /*readonly=*/true); py::class_(mn, "permonst", "The permonst struct.") .def( diff --git a/win/rl/tile2rgb.c b/win/rl/tile2rgb.c index 20620e2e2..3b88c697c 100644 --- a/win/rl/tile2rgb.c +++ b/win/rl/tile2rgb.c @@ -12,7 +12,7 @@ extern int total_tiles_used; /* Basically want to open the files, read the pixels and be done with it */ -int init_tiles(const char *filenames[], int filecount, tile_t *tileset) { +int init_tileset(const char *filenames[], int filecount, tile_t *tileset) { if(!tileset) { // function was called without memory being allocated @@ -23,6 +23,10 @@ int init_tiles(const char *filenames[], int filecount, tile_t *tileset) { tile_t *tile_ptr = tileset; for(int f=0; f Date: Fri, 16 Jan 2026 09:21:03 +0000 Subject: [PATCH 05/19] Complete functions to get the tileset and the frame from the glyphs --- nle/nethack/nethack.py | 12 ++++-- win/rl/pynethack.cc | 83 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/nle/nethack/nethack.py b/nle/nethack/nethack.py index 0163837e2..f45b936a6 100644 --- a/nle/nethack/nethack.py +++ b/nle/nethack/nethack.py @@ -324,9 +324,15 @@ def in_normal_game(self): def how_done(self): return self._pynethack.how_done() - + def setup_tiles(self): return self._pynethack.setup_tiles() - + def pixel_render(self): - return np.zeros((336,1264,3),dtype=np.int8) + return np.zeros((336, 1264, 3), dtype=np.int8) + + def get_tileset(self, buffer): + return self._pynethack.get_tileset(buffer) + + def get_frame(self, buffer): + return self._pynethack.get_frame(buffer) diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index 95dc633de..e9b6595a3 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -390,17 +391,87 @@ class Nethack } // TODO Error Handling - printf("Unable to open tile file %s\n", tilefiles[tiles_read]); + std::cout << "Unable to open tile file " << tilefiles[tiles_read] << std::endl; return false; } + // Get the tileset as a numpy array of shape passed in as 'frame'. + // This method is for testing the initialization of the tileset only. void get_tileset(py::array_t frame) { - auto buffer = frame.mutable_unchecked<1>(); + auto buffer = frame.mutable_unchecked<3>(); uint8_t *pixel_rgb = (uint8_t *) this->tileset; - for(pybind11::ssize_t i = 0; i < buffer.shape(0); ++i) { - buffer(i) = pixel_rgb[i]; + pybind11::size_t tile_rows = buffer.shape(0) / TILE_Y; + pybind11::size_t tile_cols = buffer.shape(1) / TILE_X; + + if(tile_rows * tile_cols > (pybind11::size_t) total_tiles_used) { + // TODO Error Handling + printf("Requested more tiles than available in tileset\n"); + return; + } + + for(pybind11::ssize_t tile_row = 0; tile_row < tile_rows; tile_row++) { + for(pybind11::ssize_t tile_col = 0; tile_col < tile_cols; tile_col++) { + for(pybind11::ssize_t y = 0; y < TILE_Y; y++) { + memcpy(&buffer((tile_row * TILE_Y) + y, + (tile_col * TILE_X), 0), + pixel_rgb, TILE_X * 3 * sizeof(uint8_t)); + pixel_rgb += TILE_X * 3; + } + } + } + } + + void get_frame(py::array_t frame) { + auto buffer = frame.mutable_unchecked<3>(); + + pybind11::size_t tile_rows = buffer.shape(0) / TILE_Y; + if(tile_rows != ROWNO) { + // TODO Error Handling + std::cout << "Frame height incorrect. Got " << tile_rows << ", expected " << ROWNO << std::endl; + return; + } + pybind11::size_t tile_cols = (buffer.shape(1) / TILE_X); + if(tile_cols != COLNO - 1) { // Usually COLNO is 80, but only 79 columns are used. + // TODO Error Handling + std::cout << "Frame width incorrect. Got " << tile_cols << ", expected " << COLNO - 1 << std::endl; + return; } + pybind11::size_t channels = buffer.shape(2); + if(channels != 3) { + // TODO Error Handling + std::cout << "Frame channels incorrect. Got " << channels << ", expected 3" << std::endl; + return; + } + + for(pybind11::ssize_t tile_row = 0; tile_row < tile_rows; tile_row++) { + for(pybind11::ssize_t tile_col = 0; tile_col < tile_cols; tile_col++) { + // figure out which tile to copy from the glyph at this position + short int glyph = obs_.glyphs[(tile_row * (COLNO - 1)) + tile_col]; + + // only update the tile if the glyph has changed since last time + if(glyph == prev_glyphs[(tile_row * (COLNO - 1)) + tile_col]) { + continue; + } + int tile_index = glyph2tile[glyph]; + + tile_t *tile_data = &(tileset[tile_index]); + if(!tile_data) { + // TODO Error Handling + printf("No tile data for glyph %d at position (%d,%d)\n", glyph, tile_row, tile_col); + return; + } + + for(pybind11::ssize_t pixel_row = 0; pixel_row < TILE_Y; pixel_row++) { + memcpy(&buffer((tile_row * TILE_Y) + pixel_row, + (tile_col * TILE_X), 0), + &(tile_data->tile[pixel_row]), (size_t)TILE_Y * sizeof(pixel)); + } + } + } + + // store glyphs for faster tile rendering next time this is called + std::copy(obs_.glyphs, obs_.glyphs + ROWNO * (COLNO - 1), prev_glyphs); } private: @@ -433,6 +504,7 @@ class Nethack std::FILE *ttyrec_ = nullptr; nle_settings settings_; tile_t *tileset = nullptr; + short prev_glyphs[ROWNO * (COLNO - 1)] = { 0 }; }; PYBIND11_MODULE(_pynethack, m) @@ -474,7 +546,8 @@ PYBIND11_MODULE(_pynethack, m) .def("how_done", &Nethack::how_done) .def("set_wizkit", &Nethack::set_wizkit) .def("setup_tiles", &Nethack::setup_tileset) - .def("get_tileset", &Nethack::get_tileset); + .def("get_tileset", &Nethack::get_tileset) + .def("get_frame", &Nethack::get_frame); py::module mn = m.def_submodule( "nethack", "Collection of NetHack constants and functions"); From 769e23ed4e3734aa1d992c7c167f8eb6c271d705 Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Fri, 30 Jan 2026 08:37:49 +0000 Subject: [PATCH 06/19] Move tile file path control to Python --- nle/env/base.py | 16 +++++- nle/nethack/nethack.py | 11 ++-- pixels.py | 114 +++++++++++++++++++++++++++++++++++++++-- testframes.py | 15 ++++-- win/rl/pynethack.cc | 73 ++++++++++++-------------- win/rl/tile2rgb.h | 2 + 6 files changed, 175 insertions(+), 56 deletions(-) diff --git a/nle/env/base.py b/nle/env/base.py index ed15eee18..eb844da27 100644 --- a/nle/env/base.py +++ b/nle/env/base.py @@ -332,6 +332,17 @@ def __init__( self.action_space = gym.spaces.Discrete(len(self.actions)) + if render_mode == "pixel": + # Pre-setup reference tilemap for pixel rendering + tile_paths = [ + "./win/share/monsters.txt", + "./win/share/objects.txt", + "./win/share/other.txt", + ] + if not self.nethack.setup_tiles(tile_paths): + raise RuntimeError("Failed to setup tilemap for pixel rendering.") + self.rendered_frame = np.zeros((336, 1264, 3), dtype=np.uint8) + def _get_observation(self, observation): return { key: observation[i] @@ -548,9 +559,10 @@ def render(self): chars = self.last_observation[self._observation_keys.index("chars")] # TODO: Why return a string here but print in the other branches? return "\n".join([line.tobytes().decode("utf-8") for line in chars]) - + if mode == "pixel": - return self.nethack.pixel_render() + self.nethack.draw_frame(buffer=self.rendered_frame) + return self.rendered_frame return "\nInvalid render mode: " + mode diff --git a/nle/nethack/nethack.py b/nle/nethack/nethack.py index f45b936a6..2e34222f3 100644 --- a/nle/nethack/nethack.py +++ b/nle/nethack/nethack.py @@ -325,14 +325,11 @@ def in_normal_game(self): def how_done(self): return self._pynethack.how_done() - def setup_tiles(self): - return self._pynethack.setup_tiles() - - def pixel_render(self): - return np.zeros((336, 1264, 3), dtype=np.int8) + def setup_tiles(self, tile_paths): + return self._pynethack.setup_tiles(tile_paths) def get_tileset(self, buffer): return self._pynethack.get_tileset(buffer) - def get_frame(self, buffer): - return self._pynethack.get_frame(buffer) + def draw_frame(self, buffer): + return self._pynethack.draw_frame(buffer) diff --git a/pixels.py b/pixels.py index 5ba5ba87c..33b4fef22 100644 --- a/pixels.py +++ b/pixels.py @@ -1,9 +1,113 @@ import gymnasium as gym -import nle +from PIL import Image -env=gym.make("NetHack-v0", render_mode="pixel") -env2=gym.wrappers.AddRenderObservation(env, render_only=False, render_key="pixel", obs_key="glyphs") +import nle # noqa: F401 -obs=env2.reset() +env = gym.make("NetHack-v0", render_mode="pixel") +env = gym.wrappers.AddRenderObservation( + env, render_only=False, render_key="pixel", obs_key="glyphs" +) +env.unwrapped.seed(1234, 5678, False, 1) -print(obs) \ No newline at end of file + +frames = [] + +obs = env.reset() +frame = obs[0]["pixel"] +img = Image.fromarray(frame, "RGB") +frames.append(img) + +NORTH = 0 +WEST = 3 +SOUTH = 2 +EAST = 1 + +# Get out of starting room +steps = [EAST, EAST, NORTH, NORTH, WEST, WEST, WEST, WEST, WEST] +# Go to room two +steps += [ + SOUTH, + SOUTH, + SOUTH, + SOUTH, + SOUTH, + WEST, + SOUTH, + WEST, + SOUTH, + SOUTH, + SOUTH, + SOUTH, + SOUTH, + WEST, + SOUTH, + WEST, + WEST, +] +# Traverse room two +steps += [WEST, WEST, WEST, WEST, NORTH, NORTH, NORTH, NORTH] +# Go to room three +steps += [ + WEST, + NORTH, + NORTH, + WEST, + WEST, + NORTH, + NORTH, + WEST, + WEST, + NORTH, + NORTH, + WEST, + WEST, + WEST, + WEST, + WEST, + WEST, + WEST, + WEST, + NORTH, +] +# Traverse room three +steps += [NORTH, NORTH] +# Go back towards room two and kill the monster +steps += [SOUTH, SOUTH, SOUTH, EAST, EAST, EAST, EAST, EAST] +# Continue to room two +steps += [ + EAST, + EAST, + EAST, + SOUTH, + SOUTH, + EAST, + EAST, + SOUTH, + SOUTH, + EAST, + EAST, + SOUTH, + SOUTH, + EAST, + SOUTH, + SOUTH, +] +# Traverse room two to other exit +steps += [WEST, WEST, WEST, WEST, WEST, WEST, WEST, WEST, WEST, NORTH] +# Go to room four +steps += [NORTH, NORTH, NORTH, NORTH] + +for action in range(len(steps)): + obs = env.step(steps[action]) + env.unwrapped.nethack.draw_frame(frame) + img = Image.fromarray(frame, "RGB") + frames.append(img) + +print(f"Saving animation with {len(frames)} frames...") +frames[0].save( + "nethack_tiles_animation.gif", + save_all=True, + append_images=frames[1:], + duration=400, + loop=0, +) diff --git a/testframes.py b/testframes.py index fe97d2304..465fd81be 100644 --- a/testframes.py +++ b/testframes.py @@ -1,12 +1,21 @@ # import matplotlib.pyplot as plt import numpy as np +from PIL import Image from nle import _pynethack nh = _pynethack.Nethack(".", ".", "", False) -nh.setup_tiles() -frame = np.zeros(16 * 16 * 3, dtype=np.uint8) -print(frame) +tile_paths = [ + "/Users/stephenoman/Development/nle/win/share/monsters.txt", + "/Users/stephenoman/Development/nle/win/share/objects.txt", + "/Users/stephenoman/Development/nle/win/share/other.txt", +] +nh.setup_tiles(tile_paths) + +frame = np.zeros((432, 640, 3), dtype=np.uint8) nh.get_tileset(frame) print(frame) + +img = Image.fromarray(frame, "RGB") +img.save("tileset.png") diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index e9b6595a3..9b237ba0b 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -371,19 +371,20 @@ class Nethack } boolean - setup_tileset() + setup_tileset(std::array tilefiles) { int tiles_read = 0; - const char *tilefiles[] = { - "/Users/stephenoman/Development/nle/win/share/monsters.txt", - "/Users/stephenoman/Development/nle/win/share/objects.txt", - "/Users/stephenoman/Development/nle/win/share/other.txt" - }; this->tileset = (tile_t *) calloc(sizeof(tile_t), (size_t) total_tiles_used); + // TODO - handle memory allocation failure for tileset if(tileset) { - tiles_read = init_tileset(tilefiles, 3, tileset); + const char *tilefile_ptrs[3] = { + tilefiles[0].c_str(), + tilefiles[1].c_str(), + tilefiles[2].c_str() + }; + tiles_read = init_tileset(tilefile_ptrs, 3, tileset); if(tiles_read == 3) { return true; @@ -415,37 +416,20 @@ class Nethack for(pybind11::ssize_t y = 0; y < TILE_Y; y++) { memcpy(&buffer((tile_row * TILE_Y) + y, (tile_col * TILE_X), 0), - pixel_rgb, TILE_X * 3 * sizeof(uint8_t)); - pixel_rgb += TILE_X * 3; + pixel_rgb, TILE_X * TILE_Z * sizeof(uint8_t)); + pixel_rgb += TILE_X * TILE_Z; } } } } - void get_frame(py::array_t frame) { - auto buffer = frame.mutable_unchecked<3>(); - - pybind11::size_t tile_rows = buffer.shape(0) / TILE_Y; - if(tile_rows != ROWNO) { - // TODO Error Handling - std::cout << "Frame height incorrect. Got " << tile_rows << ", expected " << ROWNO << std::endl; - return; - } - pybind11::size_t tile_cols = (buffer.shape(1) / TILE_X); - if(tile_cols != COLNO - 1) { // Usually COLNO is 80, but only 79 columns are used. - // TODO Error Handling - std::cout << "Frame width incorrect. Got " << tile_cols << ", expected " << COLNO - 1 << std::endl; - return; - } - pybind11::size_t channels = buffer.shape(2); - if(channels != 3) { - // TODO Error Handling - std::cout << "Frame channels incorrect. Got " << channels << ", expected 3" << std::endl; - return; - } + void draw_frame(py::array_t frame) { + auto buffer = checked_conversion(frame, { ROWNO * TILE_Y, (COLNO - 1) * TILE_X, TILE_Z }); + + int frame_width = (COLNO - 1) * TILE_X * TILE_Z; - for(pybind11::ssize_t tile_row = 0; tile_row < tile_rows; tile_row++) { - for(pybind11::ssize_t tile_col = 0; tile_col < tile_cols; tile_col++) { + for(int tile_row = 0; tile_row < ROWNO; tile_row++) { + for(int tile_col = 0; tile_col < (COLNO - 1); tile_col++) { // figure out which tile to copy from the glyph at this position short int glyph = obs_.glyphs[(tile_row * (COLNO - 1)) + tile_col]; @@ -458,14 +442,20 @@ class Nethack tile_t *tile_data = &(tileset[tile_index]); if(!tile_data) { // TODO Error Handling - printf("No tile data for glyph %d at position (%d,%d)\n", glyph, tile_row, tile_col); - return; + printf("No tile data for glyph %d at position (%ld,%ld)\n", glyph, tile_row, tile_col); + continue; } - for(pybind11::ssize_t pixel_row = 0; pixel_row < TILE_Y; pixel_row++) { - memcpy(&buffer((tile_row * TILE_Y) + pixel_row, - (tile_col * TILE_X), 0), - &(tile_data->tile[pixel_row]), (size_t)TILE_Y * sizeof(pixel)); + uint8_t * frame_tile = buffer + (tile_row * frame_width * TILE_Y) + (tile_col * TILE_X * TILE_Z); + if(!frame_tile) { + // TODO Error Handling + printf("No frame tile pointer for glyph %d at position (%ld,%ld)\n", glyph, tile_row, tile_col); + continue; + } + + for(int pixel_row = 0; pixel_row < TILE_Y; pixel_row++) { + memcpy(frame_tile + (pixel_row * frame_width), + &(tile_data->tile[pixel_row][0]), TILE_X * TILE_Z * sizeof(uint8_t)); } } } @@ -492,6 +482,11 @@ class Nethack /* Once the seeds have been used, prevent them being reused. */ settings_.initial_seeds.use_init_seeds = false; settings_.initial_seeds.use_lgen_seed = false; + + if(tileset) { + // reset previous glyphs to force full redraw on first draw_frame call + std::fill(std::begin(prev_glyphs), std::end(prev_glyphs), 0); + } if (obs_.done) throw std::runtime_error("NetHack done right after reset"); @@ -547,7 +542,7 @@ PYBIND11_MODULE(_pynethack, m) .def("set_wizkit", &Nethack::set_wizkit) .def("setup_tiles", &Nethack::setup_tileset) .def("get_tileset", &Nethack::get_tileset) - .def("get_frame", &Nethack::get_frame); + .def("draw_frame", &Nethack::draw_frame); py::module mn = m.def_submodule( "nethack", "Collection of NetHack constants and functions"); diff --git a/win/rl/tile2rgb.h b/win/rl/tile2rgb.h index d7b3455be..e36b43d31 100644 --- a/win/rl/tile2rgb.h +++ b/win/rl/tile2rgb.h @@ -5,6 +5,8 @@ #include "tile.h" +#define TILE_Z 3 /* RGB */ + typedef struct tile_s { pixel tile[TILE_Y][TILE_X]; } tile_t; From ade8553f8fa73a6ce7553c5aea7462570c53dc53 Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Tue, 3 Feb 2026 20:07:56 +0000 Subject: [PATCH 07/19] Install tile descriptors into hackdir --- CMakeLists.txt | 7 ++++++- nle/env/base.py | 9 ++------- nle/nethack/nethack.py | 8 +++++++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 67416c9dd..06b03b6eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,7 +178,7 @@ pybind11_add_module( target_link_libraries(_pynethack PUBLIC nethackdl) set_target_properties(_pynethack PROPERTIES CXX_STANDARD 14) target_include_directories(_pynethack PUBLIC ${NLE_INC_GEN} ${NLE_WIN}/share) -#add_dependencies(_pynethack util tile) # For pm.h. +# add_dependencies(_pynethack util tile) # For pm.h. # ttyrec converter library add_library( @@ -204,3 +204,8 @@ target_link_libraries(_pyconverter PUBLIC converter) set_target_properties(_pyconverter PROPERTIES CXX_STANDARD 14) target_include_directories( _pyconverter PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/third_party/converter) + +set(TILE_FILES "win/share/monsters.txt" "win/share/objects.txt" + "win/share/other.txt") + +install(FILES ${TILE_FILES} DESTINATION ${INSTDIR}/tiles) diff --git a/nle/env/base.py b/nle/env/base.py index eb844da27..5a1394a1b 100644 --- a/nle/env/base.py +++ b/nle/env/base.py @@ -333,13 +333,8 @@ def __init__( self.action_space = gym.spaces.Discrete(len(self.actions)) if render_mode == "pixel": - # Pre-setup reference tilemap for pixel rendering - tile_paths = [ - "./win/share/monsters.txt", - "./win/share/objects.txt", - "./win/share/other.txt", - ] - if not self.nethack.setup_tiles(tile_paths): + # Pre-load reference tilemap for pixel rendering + if not self.nethack.setup_tiles(): raise RuntimeError("Failed to setup tilemap for pixel rendering.") self.rendered_frame = np.zeros((336, 1264, 3), dtype=np.uint8) diff --git a/nle/nethack/nethack.py b/nle/nethack/nethack.py index 2e34222f3..3bf71ab41 100644 --- a/nle/nethack/nethack.py +++ b/nle/nethack/nethack.py @@ -325,7 +325,13 @@ def in_normal_game(self): def how_done(self): return self._pynethack.how_done() - def setup_tiles(self, tile_paths): + def setup_tiles(self, tile_paths=None): + if tile_paths is None: + tile_paths = [ + os.path.join(HACKDIR, "tiles", "monsters.txt"), + os.path.join(HACKDIR, "tiles", "objects.txt"), + os.path.join(HACKDIR, "tiles", "other.txt"), + ] return self._pynethack.setup_tiles(tile_paths) def get_tileset(self, buffer): From 22a09130bd38cc706e161eaa0cee7a3dd9583ad9 Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Sat, 14 Feb 2026 12:28:21 +0000 Subject: [PATCH 08/19] Expose tile dimensions to Nethack python --- nle/env/base.py | 2 +- nle/nethack/__init__.py | 2 ++ nle/nethack/nethack.py | 10 ++++++++++ win/rl/pynethack.cc | 4 ++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/nle/env/base.py b/nle/env/base.py index 5a1394a1b..8c7a77f34 100644 --- a/nle/env/base.py +++ b/nle/env/base.py @@ -336,7 +336,7 @@ def __init__( # Pre-load reference tilemap for pixel rendering if not self.nethack.setup_tiles(): raise RuntimeError("Failed to setup tilemap for pixel rendering.") - self.rendered_frame = np.zeros((336, 1264, 3), dtype=np.uint8) + self.rendered_frame = np.zeros(nethack.TILE_RENDER_SHAPE, dtype=np.uint8) def _get_observation(self, observation): return { diff --git a/nle/nethack/__init__.py b/nle/nethack/__init__.py index 6623e27b7..8bf53f6f4 100644 --- a/nle/nethack/__init__.py +++ b/nle/nethack/__init__.py @@ -5,6 +5,8 @@ Nethack, NETHACKOPTIONS, DUNGEON_SHAPE, + TILE_SHAPE, + TILE_RENDER_SHAPE, BLSTATS_SHAPE, MESSAGE_SHAPE, INV_SIZE, diff --git a/nle/nethack/nethack.py b/nle/nethack/nethack.py index 3bf71ab41..b175d0760 100644 --- a/nle/nethack/nethack.py +++ b/nle/nethack/nethack.py @@ -14,6 +14,16 @@ DLPATH = os.path.join(os.path.dirname(_pynethack.__file__), "libnethack.so") DUNGEON_SHAPE = (_pynethack.nethack.ROWNO, _pynethack.nethack.COLNO - 1) +TILE_SHAPE = ( + _pynethack.nethack.TILE_Y, + _pynethack.nethack.TILE_X, + _pynethack.nethack.TILE_Z, +) +TILE_RENDER_SHAPE = ( + DUNGEON_SHAPE[0] * TILE_SHAPE[0], + DUNGEON_SHAPE[1] * TILE_SHAPE[1], + TILE_SHAPE[2], +) BLSTATS_SHAPE = (_pynethack.nethack.NLE_BLSTATS_SIZE,) MESSAGE_SHAPE = (_pynethack.nethack.NLE_MESSAGE_SIZE,) PROGRAM_STATE_SHAPE = (_pynethack.nethack.NLE_PROGRAM_STATE_SIZE,) diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index 9b237ba0b..2764787d5 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -598,6 +598,10 @@ PYBIND11_MODULE(_pynethack, m) mn.attr("NHW_MENU") = py::int_(NHW_MENU); mn.attr("NHW_TEXT") = py::int_(NHW_TEXT); + mn.attr("TILE_X") = py::int_(TILE_X); + mn.attr("TILE_Y") = py::int_(TILE_Y); + mn.attr("TILE_Z") = py::int_(TILE_Z); + // Cannot include wintty.h as it redefines putc etc. // MAXWIN is #defined as 20 there. mn.attr("MAXWIN") = py::int_(20); From 5859aa1784a235f8d622388bd79d6db50ac8a437 Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 16 Feb 2026 10:01:47 +0000 Subject: [PATCH 09/19] Tidy up tile setup code --- win/rl/pynethack.cc | 33 +++++++++++++++------------------ win/rl/tile2rgb.c | 15 +++++++++++---- win/rl/tile2rgb.h | 2 +- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index 2764787d5..45c5ec393 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -373,27 +373,24 @@ class Nethack boolean setup_tileset(std::array tilefiles) { - int tiles_read = 0; - this->tileset = (tile_t *) calloc(sizeof(tile_t), (size_t) total_tiles_used); + if(!this->tileset) { + throw std::runtime_error("Unable to allocate memory for tileset"); + } - // TODO - handle memory allocation failure for tileset - if(tileset) { - const char *tilefile_ptrs[3] = { - tilefiles[0].c_str(), - tilefiles[1].c_str(), - tilefiles[2].c_str() - }; - tiles_read = init_tileset(tilefile_ptrs, 3, tileset); - - if(tiles_read == 3) { - return true; - } + const char *tilefile_ptrs[3] = { + tilefiles[0].c_str(), + tilefiles[1].c_str(), + tilefiles[2].c_str() + }; + int tiles_read = init_rgb_tileset(tilefile_ptrs, 3, tileset); + + if(tiles_read == 3) { + return true; + } else { + std::cout << "Unable to open tile file " << tilefiles[tiles_read] << std::endl; + return false; } - - // TODO Error Handling - std::cout << "Unable to open tile file " << tilefiles[tiles_read] << std::endl; - return false; } // Get the tileset as a numpy array of shape passed in as 'frame'. diff --git a/win/rl/tile2rgb.c b/win/rl/tile2rgb.c index 3b88c697c..70a77afc4 100644 --- a/win/rl/tile2rgb.c +++ b/win/rl/tile2rgb.c @@ -10,9 +10,16 @@ extern short glyph2tile[]; extern int total_tiles_used; -/* Basically want to open the files, read the pixels and be done with it */ - -int init_tileset(const char *filenames[], int filecount, tile_t *tileset) { +/* +Basically want to open the files, read the pixels and be done with it. +Returns the number of files read sucessfully, so 0 == failure. +*/ +int init_rgb_tileset(const char *filenames[], int filecount, tile_t *tileset) { + + if(!filenames || filecount <= 0) { + // no files to read, return 0 + return 0; + } if(!tileset) { // function was called without memory being allocated @@ -29,7 +36,7 @@ int init_tileset(const char *filenames[], int filecount, tile_t *tileset) { */ if(!fopen_text_file(filenames[f], "r")) { /* can't read the tiles, throw the problem back */ - printf("init_tiles: unable to open %s\n", filenames[f]); + fprintf(stderr, "init_tiles: unable to open %s\n", filenames[f]); return f; } diff --git a/win/rl/tile2rgb.h b/win/rl/tile2rgb.h index e36b43d31..59dd30459 100644 --- a/win/rl/tile2rgb.h +++ b/win/rl/tile2rgb.h @@ -11,6 +11,6 @@ typedef struct tile_s { pixel tile[TILE_Y][TILE_X]; } tile_t; -int init_tileset(const char *[], int, tile_t *); +int init_rgb_tileset(const char *[], int, tile_t *); #endif /* TILE2RGB */ \ No newline at end of file From 239a098e984aaa44400e340212c92a48be0e6724 Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 16 Feb 2026 20:02:12 +0000 Subject: [PATCH 10/19] Update error handling for frame rendering --- win/rl/pynethack.cc | 51 ++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index 45c5ec393..a4ac2c28f 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -373,9 +373,9 @@ class Nethack boolean setup_tileset(std::array tilefiles) { - this->tileset = (tile_t *) calloc(sizeof(tile_t), (size_t) total_tiles_used); - if(!this->tileset) { - throw std::runtime_error("Unable to allocate memory for tileset"); + tileset = (tile_t *) calloc(sizeof(tile_t), (size_t) total_tiles_used); + if(!tileset) { + throw std::runtime_error("Unable to allocate memory for tileset."); } const char *tilefile_ptrs[3] = { @@ -385,29 +385,36 @@ class Nethack }; int tiles_read = init_rgb_tileset(tilefile_ptrs, 3, tileset); - if(tiles_read == 3) { - return true; - } else { - std::cout << "Unable to open tile file " << tilefiles[tiles_read] << std::endl; - return false; - } + if(tiles_read != 3) { + throw std::runtime_error("Unable to open tile file " + tilefiles[tiles_read] + + " for reading. Check that the file exists and is readable."); + } + + return true; } // Get the tileset as a numpy array of shape passed in as 'frame'. // This method is for testing the initialization of the tileset only. void get_tileset(py::array_t frame) { + + if(!tileset) { + throw std::runtime_error("get_tileset() called but the tileset has not been initialized."); + } + auto buffer = frame.mutable_unchecked<3>(); - uint8_t *pixel_rgb = (uint8_t *) this->tileset; pybind11::size_t tile_rows = buffer.shape(0) / TILE_Y; pybind11::size_t tile_cols = buffer.shape(1) / TILE_X; if(tile_rows * tile_cols > (pybind11::size_t) total_tiles_used) { - // TODO Error Handling - printf("Requested more tiles than available in tileset\n"); + throw std::runtime_error("Requested more tiles than available in tileset (available: " + + std::to_string(total_tiles_used) + ", requested: " + + std::to_string(tile_rows * tile_cols) + ")."); return; } + uint8_t *pixel_rgb = (uint8_t *) tileset; + for(pybind11::ssize_t tile_row = 0; tile_row < tile_rows; tile_row++) { for(pybind11::ssize_t tile_col = 0; tile_col < tile_cols; tile_col++) { for(pybind11::ssize_t y = 0; y < TILE_Y; y++) { @@ -421,6 +428,11 @@ class Nethack } void draw_frame(py::array_t frame) { + + if(!tileset) { + throw std::runtime_error("draw_frame() called but the tileset has not been initialized."); + } + auto buffer = checked_conversion(frame, { ROWNO * TILE_Y, (COLNO - 1) * TILE_X, TILE_Z }); int frame_width = (COLNO - 1) * TILE_X * TILE_Z; @@ -436,19 +448,14 @@ class Nethack } int tile_index = glyph2tile[glyph]; - tile_t *tile_data = &(tileset[tile_index]); - if(!tile_data) { - // TODO Error Handling - printf("No tile data for glyph %d at position (%ld,%ld)\n", glyph, tile_row, tile_col); + // Check tile_index is within bounds of the tileset. If not, log and skip this tile. + if(tile_index < 0 || tile_index >= total_tiles_used) { + fprintf(stderr, "Invalid tile index %d for glyph %d at position (%ld,%ld)\n", tile_index, glyph, tile_row, tile_col); continue; } - uint8_t * frame_tile = buffer + (tile_row * frame_width * TILE_Y) + (tile_col * TILE_X * TILE_Z); - if(!frame_tile) { - // TODO Error Handling - printf("No frame tile pointer for glyph %d at position (%ld,%ld)\n", glyph, tile_row, tile_col); - continue; - } + tile_t *tile_data = &(tileset[tile_index]); + uint8_t *frame_tile = buffer + (tile_row * frame_width * TILE_Y) + (tile_col * TILE_X * TILE_Z); for(int pixel_row = 0; pixel_row < TILE_Y; pixel_row++) { memcpy(frame_tile + (pixel_row * frame_width), From eddf6550006f59ebd1c465371891c62711f9d29c Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 23 Feb 2026 14:01:36 +0000 Subject: [PATCH 11/19] Update test suite for new tile functionality --- nle/tests/test_tiles.py | 96 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 nle/tests/test_tiles.py diff --git a/nle/tests/test_tiles.py b/nle/tests/test_tiles.py new file mode 100644 index 000000000..42d866c8a --- /dev/null +++ b/nle/tests/test_tiles.py @@ -0,0 +1,96 @@ +import gymnasium as gym +import numpy as np +import pytest + +import nle + + +class TestTileset: + # Test that the tile files can be read successfully + # with the default paths & that the tileset is correctly + # generated. + def test_tile_setup_repo(self): + nh = nle.nethack.Nethack() + nh.setup_tiles() + tileset = np.zeros((432, 640, 3), dtype=np.uint8) + nh.get_tileset(tileset) + + assert tileset[0][0][0] == 71 + assert tileset[431][638][2] == 108 + + # Test that invalid tile paths raise an error. + def test_tile_setup_invalid_path(self): + nh = nle.nethack.Nethack() + with pytest.raises(RuntimeError): + nh.setup_tiles( + [ + "invalid/path/monsters.txt", + "invalid/path/objects.txt", + "invalid/path/other.txt", + ] + ) + + # Stupid test but am doing it anyway :-) + # Test that the tileset cannot be retrieved if the frame is too big + def test_tileset_too_large(self): + nh = nle.nethack.Nethack() + nh.setup_tiles() + tileset = np.zeros((1000, 1000, 3), dtype=np.uint8) + with pytest.raises(RuntimeError): + nh.get_tileset(tileset) + + # Alternatively, test that the tileset can be retrieved if the frame is too small. + def test_tileset_too_small(self): + nh = nle.nethack.Nethack() + nh.setup_tiles() + tileset = np.zeros((100, 100, 3), dtype=np.uint8) + nh.get_tileset(tileset) + + assert tileset[0][0][0] == 71 + assert tileset[99][99][2] == 0 + + +class TestDrawingFrame: + def test_drawing_frame_before_tileset_setup(self): + nh = nle.nethack.Nethack() + frame = np.zeros(nle.nethack.TILE_RENDER_SHAPE, dtype=np.uint8) + with pytest.raises(RuntimeError): + nh.draw_frame(frame) + + def test_drawing_frame_with_invalid_frame_size(self): + nh = nle.nethack.Nethack() + nh.setup_tiles() + frame = np.zeros((100, 100, 3), dtype=np.uint8) + with pytest.raises(ValueError): + nh.draw_frame(frame) + + +class TestTileObservations: + def test_observation_contains_pixels(self): + env = gym.make("NetHack-v0", render_mode="pixel") + env = gym.wrappers.AddRenderObservation( + env, render_only=False, render_key="pixel", obs_key="glyphs" + ) + obs = env.reset() + + assert "pixel" in obs[0] + + # Test that the observation contains the correct pixel data for the starting location of the hero. + def test_hero_pixel_values(self): + env = gym.make("NetHack-v0", render_mode="pixel") + env = gym.wrappers.AddRenderObservation( + env, render_only=False, render_key="pixel", obs_key="glyphs" + ) + env.unwrapped.seed(1234, 5678, False, 1) + + obs = env.reset() + + # The monk hero should be at location (7,51) with this seed. + assert obs[0]["chars"][7][51] == ord("@") + assert obs[0]["glyphs"][7][51] == 333 + + # The pixel location corresponding to (7,51) is (7*16, 51*16) = (112, 816). + # Pick out the R values of the piles a few rows down to check that the correct tiles are being rendered. + assert obs[0]["pixel"][115][822][0] == 145 + assert obs[0]["pixel"][115][823][0] == 255 + assert obs[0]["pixel"][115][824][0] == 145 From eecf7db00f5b0c1cc4b3ee897fad3d0cf0d5886d Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 23 Feb 2026 15:01:23 +0000 Subject: [PATCH 12/19] Add documentation for tile usage --- dat/nle/nethack_tiles_animation.gif | Bin 0 -> 42575 bytes dat/nle/tileset.png | Bin 0 -> 86150 bytes doc/nle/source/index.rst | 1 + doc/nle/source/tiles.rst | 109 ++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 dat/nle/nethack_tiles_animation.gif create mode 100644 dat/nle/tileset.png create mode 100644 doc/nle/source/tiles.rst diff --git a/dat/nle/nethack_tiles_animation.gif b/dat/nle/nethack_tiles_animation.gif new file mode 100644 index 0000000000000000000000000000000000000000..57606117f418d125c761a03928f2446e6da42d34 GIT binary patch literal 42575 zcmd?RWmptoyDmD^&`7s{fJk?T(hbtm-KBI%cXtgndzhCr~99WT549_1R-HkDYw?H zy^A7XF&=KM+xVD7r~3!9n98R4CSkI zG#@Uux*aT!bhLb3=?TT8R_$#4w$Y!;Vlvv#`P%r4BPc+#xm%73~Tf5dMxJ$#*H}M8QYC` z!ENY9g6R3~Mxr>7X){Th*lsgfo}qj*MVWtZGgVENX)8@j$8IZK*SdTw!_afDR2dM4 zE)KBFup7@pAPg%5S=MJWNI3M)m*u|1A%x_)t?NO=y-u@14`$+7}nZk6)Hpa+SgQi}ILkdept5Ui>dn6t|=7PiX~N@Dh5u(G=WSVdLu z6|Az3o3o;-Y2CM?y6tG+p{D1S*|F|DRfS{iF!`cm{TK+w1j!F!wufX{FPfLP>nMpM zpco!7Wf$+0!&w)>kX_#|cdG!Y6 z+TpktLm>Cn+a*s7=GI+%2dHZ% zh3*)&u7*8vZe5S!IP%^|LS?^%*cAVCpU?8L6w&mH?s&pSFw(Qo|}CO2<~d2Wus4L(`vd^i65 z?)XsYpxQs2&FkyWAFGiZw-=k4fnU#e23Le}DlnegA4!k+*7;Ct>STT7v@pb3r z-RtaoKEyzFh%WzeH zig(IgAzKra5bAab&`3)sM)K%;dbRWVbFa~xfD1{I`tgX!DKlDhyM9?;%w+bkO*$en z1x3}A42Sz=cc@np2pH%cJjQz0u7O?FOdHQ4EZY|lfyxTNMq+5w5(%*Fya4Ef{zR$8j4I`d z)QTFKz8WQ*-&L8qJZsVy&dLPEAS_O8-C0-eBJK1LfCFs!xxys$lQc7L`jv8-@MQT& z7&A|4yE2S89U#|duhG=5Rn0$L@eyks;<(3j@%;p1n~uX(C_4-b;e)-*vp4(0&4aohq8ZkvMy_+Qst4o z`k=3;PzGPBwVaZEoa=dUfy$u^v;NFb#(7iiXB+XMg+}H(*m-l8T{+Ej^2OyV{v3`i zyUCE)#q|^GGD*7qmzhI`wnvR6b-dN?ZUkCCnOfTBMyk2`^xqoMI<>FzC3t>}{oLc} zR=a{#;~Olse11z@bkca}yW>akV}Ya@qQWY_H?%Up^y5wO$VkHyo!R)2S z2Wk?^7L%P9?;nqHC3xkU6V}XqWCNbW`(bS~vB%f)sdC%$4qGN*c;_o7xY~e}ttKOM z>NQ4BEMdc2k;d6Q`Z#VD?aJE$?5#~FL=7M!&29b;x4T~U@5i8>*Q5fAdU=#(OnEGc3p$EVQLovt`hY>@~d3Zpp-H z;x5SDR?X38Eg)`!nceJZb}X<`O>;mY{@4x6jsi_YZ+pX#N%rF+P;1M0*oCh&FblI6({L!82y!i6S%gYUH zLHeqjVwr(bPTv6O>y9k-<)_HR8zpWdRTxkA$Z_e}(BrYo6s(_FK(wD*IbY63bBrGs z$OR!L8J~10b<7Pof8VqDdZ~?PT=#*uM?CH5YWCgjLLC+7VdS%v+6N@Z?IdVlo7kNY z1n<5Bj63EQRd4oR(0|?1*zQGrp0KUXG zBdA3#ahf-`1Y>W0CSP716D*#5k9dg&d5P3-f6Jh?QWk(=ix=$$h|Tol698aw0=~%w zUT3(FL+}P`neN` zQ2Uzgi67fsDAu=-Z>WJpSsoeNel?i@I&YKGolxa0+yXDRLV-Y~7JCeF2HC7o=`16q zxiCX{T#Xk#4Zy&{Ct-bQ;j!m|I*cKjyT*p75k?mgxhD!PKzj?_NWM(Z2^|JuXxIl5 z#a9;*JE-2pnaUH*5ssu$ffo@00v4f+-m!OCLD6&Jpdv1tb;gvUCq#9OnP`kTwv2hu zX!n!o4APjAsOZxEXvl7Kc@$$>3Rf~?w7Dc3KOOrge37?^cul*)?Pww$j3P}9!d+Rh zoq}=Qwy{0(ag(%$M5i%qq+H9ATtcU!Lk*m`5@M6K@e>=O)5Y<#jPdhP@t+71W*Xv0 z1-aJnd4~H#IEuMck?|_e5{^j|(_0b{QWN%Dg^Y`NFIyAscyt~VCjKN%0th8Nf5!(L zNJLiPLuE=riB7^ENW$1l64MmQ4GlQY3Ls?S7kMX`x*-78h>x614tgg*OO`_KlR_1p zLYJMw+Lpp@m%@gg$}y14n4N6lmW**1m8kC)*B8aoDlhT@E$wZ8YVTa?d)+j7g)~JW zkvTd~!SaAk3!(1Q_~ki{XY=ukXbBrp>4pmFdV1*wd+DF&(#X!z_MRjXx8d5hWjMBF zSZ4>A(o4Abh}$4PqtVO6lg@OY&qPbh1iqJftsvocnHfeV`#L()BkkEsJOA)asoeKc zNj{k|n^{6AxwSmelLGK0RJPGCe zkaC*RKwi8#EkarL^f~PVveEN7?j<>m=(%;5IT6{i)k3ecH?!#*bBWSG8J5{Bo4HeD zdBZ|^Guhd5(F*f=d2wmEjgcUnySJcCz5Fet{N3#Q{kHrs^Z7@Y`Nw1hCqe~hdIje` z1sB-`S8WA1^98q;1psm|qA(a)9}M&bqk+H}?O?10FwPYim%I>PxR6l4kl4476jVsw zUP!r6NOe_6LtaEHTtu&5#Nb=R2r6Q3FJfINV!tZlBroO`F6PxQ=JzcY1QiRn7mF?w zKfNjzCoho{E|JzRk@YQ+2bCzcmnbiks9u$*k(X)+mul&k>iCxGf=cz3YtV-o+4bHreB`nTb>0f2ep^yE|lkAm4nG)MZ&NWeOQ?< z3<`q5+F_Lou<9#VEqO(~a7CkjMYC^3E2yHqy`poWqWh|%hrF^^xbnSz*!AIC<5CaMhH4)r@b|9H?rcy=rNpYWb>amAragxO!8+dfT^p7gW99Uj1dE z`sk|qn7rmhxaLg1=G?dD0#tL=UURchb9+?-pr}O@sYN!Z1^U&Z<Cx`HOSC7d@B8a4B;)bLVmi?#R{K*QC=p8!xE3nLuvv7A zk~VB9Bl9_p4iB6#JWqQXn1IEswK-Juf9ftdxJi|o9p`~q)d@ymaFsy|=0sVO@JtV4 zGdh4?{?%;IQUnlDfNuy8v{q+}H>wTo30mxvAE@V3Zj&(V!}^(^4QON3LjoWoqdCoS zY=?u$UKuOu@&eu?iwJNgnTm%@)jMQtu?UMJj&c$$bSd3bjyIsA#dVL+2==Zakzz3> zTwKfp20s#qQF@8Hf5lqgwoI_&7eYYnv0$O^Hof?~KlAKPCkfo<8}5M=8+n(?X_kxT zZvDda-OsJ;KQo@k1$Fd}0co9}>&*iLS(zGdbrXOijs}dDy(aCDlYCU1s2BHY6yyAB9#i5_t9h?8Uf?Dy=g@VjJAe<3ih2vy?{GAp;S@ZZZTT7u3Ajq zqv&riz{|OKFKo2Y#k1;=YU!`U%?{#Bp*1l!>rRz~^+t8rZaAF#q*cQy5(MPlisZ13 zP;vci?1gAPHLs$CNY3Dm0UZWIR{MbUP#mQL6q%h;a8~3=m_V zaF+|J7|I+-Yuk>;Je3ZIWU$)#ejzB5Vd(ty^Oe>T0V<0gtOiYJ68JR7=f#HYj)&dh zv925|-F`eXP8ChnbMKsgk{e%Velu&uK$i7nX_I&tjQ&O6S&+aBpr;;q~B>v^EL>X>T0Z z@u6%AE0Ccc_jXS-P@!lz+C^eKA3F+Yq<>QM3wR2T0ozZ(bn!$-?5CMtR2jeVe5{)e zK&Z1E%Kr({`Jx-_T*nZH*~~Tz^jK@p8~g=45n*WIroVz;5)HqoGAEZL00SO1#`(T| z=zGW%+9aA3B$brP^YP07joUWzQxpcZTF~156t+%H^={IxB@p^#yIYWqOTAkv6jP2Y zy;Wg6gI+ zv1nZOn{(ebdXvzB^tjnBMFWXQB>lPBb&q8bk&S6NY1{yz2)s_dT2Y!iAq7uSFGLkx zp`+nEB*$1W$4vck$UZZU`&u@`mE4bAosQESLo02q+F4mRY5s8lUibl+0c?M80C?8; z?Ew9?YZ53xLV3$oHE?e%8neBlX;82n3LzOYE)-z#As$C004w@c$@^Gz)gsGZR>Let zo_2w6F&xf=%1){{RXqC3+>bTQhJoVh!lMF8CR9>u5Ua#h&l^$K7M?fSdc*7sP>f^o*Eq92l%Deh< zWC96A&%~zkJKcEldwjx0QiHo0%^%$+-d_=3WD3T=AiNlOiqe4O!k!pX9#zrZ@t}_p zgb#6sbzO?zMqpqu>JFHi$;YB7#PDV(6(Rryi5lZt)q*8axMWUp6;6v(a)Wz?jFVF{QF{wkez-(Z{`4o0N20$+e{NpPsE-5#Go?bF@D z4i+y%3Y*DtO+N_bGsfmA{lg>(c|pJ= z7~v?=G$hTb#We5*dKj7KgZ-9_uqWF6D^@u6wr1*7+Yr;Jvv_%PU!?pf+9>8~ZxVlcsBc$d|_L`;zw zNCZxq;RpaGAFR8734e7@Nm&pQnBQ_u?( zPt0u(mT}gV;D8Rqb0+WO<##}AdeS5suKAA8Z{$xMO@nvF%uuaIG22Xc?jI6xsC{PJ zS&GiCmgg(JKQBYkIYVi%x`P4{Gx6;5<5{V;6A4s-b$GuF&P&B?VLEPd-mpn^_;J&E zx!XH76chBo1h4XPYqNk>B~gv#I`nAes}V;a+s~`juNz|z_x>O; z_Bodc7bw`7dly{R-hGD3UE zvudb;YiLj#O~8Dy!};6(mYzV-e766CqldXIoMuVE1cO9qf?N{sU(+=Z3hKEk4Q8@w zPUn8AGmml;dqzBzO}{;ou4$ta@7v^%P$Xp$?>|?2xZr2OlcAz1aJ1MH7#jbxMc{C? zKm3VTMUI!nr@>;5%!K6D(~@zB1EgEb>U164;C4s7BGzJiJSUcFVnXHZR`g|~CG)|x z*uv$--VUlSZgAjr@!pfwfQiSOE5QI9C{6GonLAC0%_kWWNlcx78Z+QiG!iKSRqusx z8vTo$P-NWW)j&l%`J~84&5Y(=ETE{MIA(kg^XQIGC}a-H={ggUWtXiR4q_*yCCR`+ zdAcxxJV81HAsj906scxwt7K`a?5#w}Zker^+w@(_7}bQm^=uB%N8M~?{sHTFyCNot zzG=PCOpfqqxlOLRmAAPse>c-se%Q%gn!4vQ6Jt_1zrAhJd0{JVY@)C|Qwc!Bd#@~A zCv#6a&y{%};u%eDpNmQcW-f<-?Cm5lNbkTsl|=Ki`&GW=mH?zqX0?i{>h*S0iH`Ft z2e)2e4oe+6R+4yKXLsh8MjCv}9>h88A&16~Ds5Kjy2j9KX+z$CVe8;dJE9nw|BijXLJa z=+is&GhN9QX{S$oR7TnpN_ZoUC8{JSO*$gag*~SAjQk5~H8n>(rUr2G9J9@DMVlw| za`Ng^6=7ZHvp=p31loUM#C?CWf39hCl<_K(>Bkay`Qh=3_o@HM@->5V>&l*f*m;T* zzOmb)W5L7bEz6-V=T&A6{cWRGHtcS@#gp*|J6Of}zU8G}2^9x54&)*m;FrcCd-*vD z4*$k4)c|V$so;u2_E*2erB;lSSEucbB}La+5`_C@44IVq&2AmNLK3Yej;LMzW2JOX zl=|GgdW+#SUbh)7Z_U!t0ye)0=}Xrg#aPKmTDHL{>xo$LVqt%bX{c6$7M^jFKDWbk zl|~mi@(^FqT&U%{d@)e-mxVgnmBDFJuWF-eudRt;5igFFhJZW7Y8F8j^^))|&><+x ze)Yq1YN>7}A8xDO3=5snF9bX`x%toFGfogq$Yg0>zHvMapY{yY=t=lz{bn^}WVG>S zY=R$lpKE<$Z&XBEkK-!bzCKOJ<#*VAlvfk6{MyTDAGOrv<6mZ+T;v&~)VD>5NEj2R ztk1^*p+7KY8c24jx^FIl_*n94cVf3F0N_P_;2IfV6@Z0BLleBpmP8YRM6V zI?Qt*a}q35PVkrcEX=v;hqe69I<1zI3H}C}`|5SpUq29Dd%Z+@`{a(9DyPxqUCuv^ zujA$|tj1E6y2Dt7c@-v^=|XYDKn;HxUyF(SrFi~TNdS;S{_sX-|L?z%X9AM>KCayT zNf~yEqc^qOHo2rcz9n&O<^!2|N>ZZ!J`HwLQ-s6#eQ~Uka8&ANO@G+aa9{0?W;V$N^iTX*i>8ONnHFmpw?30KpW$SO)H8z@Tv8uI z9+HGn>Ya!WYO_uDRbFyiH|Je956`!r+QQ7G3fMt14N|eARSV6| zi!E%ZeH^k?q$Ki_!H5R3k>tq%F8uJqs6R?{LPA}iC9wt~w>;@Aj2yk?pd zA3?wZE)DM`Lp~*9h9Eu!@G=b)ad-tdnz{v#6{1;z2(yyG&vTy_+L!J%mA$XUZXC_s zYjRS_w8sYi(-Rl{AzW2)?*nsy;Cr}=+%SsfkwgUQLyWLFHFJ0y$H*X^lw2$uP9??t z+7@?1E{TxDS(z-kx%Rg<( zBEdmY@yl;C@Mx~vE^2EUJXS#MfZsXK`D7sPp+6kQ2SC+=Ws0d}m|r1GmLg^7Yoj+o zKgDUG-WrZi+DT++zT>~hs@xSeH!BgP%)d`V}Tb=PSt|wS7ChL*DiqbUc=D|_f>LHFupXR>+CG#3p_Xp z+xK){YH{DH?{a84GJf+4VW)5CN6m6iByX_7s@ zPtUqpe{9RUk~mnYi@ckCn(ycH6`==0@+SYa-|5#+JMOB_PA+%*{H``*JSt|YZ+wVW zgYLegng%1%%7eAAgtb>f&HEy$9Zk6ybS+}YWaq*u^>^1IsM&$*QMBhMYtdZEKw`VU z@Lubq*5lZ*A$ksz=rRU=!oBCliGANLij%6a_29f$V0KB$Q+D*tR9Sw7jWnFcT2}I$ zZy6|@3_Z)O91KF3wzK+tF1NC+KSucw&w;{$U!e!+)MZ(O` z^7<==GOdNXrZNYw6T5?|))3%9+JTdANm&PwBQooOK0T<9i)>EfD|X#>3Xl42Pq(UH!$wfR!UO_4eZrEMGhKswoJ-N2=0ayKl}9 zYPu}(n#x*li5-vME+F2dl=U8#vi9Q2v2r0|bkJA0im6n7`*5w3ORn0zTw?KIY~8(T zV5Z2hYH+Gvv}$Ogm$GVje8P|$Vac~squ!ChfM=8msjGgRjo@efCoVd%hKXkd!)GHx z2TQo!GTlSprgbw}zNdfOzJ4`h8mV$PquFB6)NKx_;%~6zk%shN;&q-cnYMJ{zH9JQ z{IRUNcvm&v*O@&0V-^4UCqX|p?S_-yS4#J`4D?RUCNxX-Pzm@5>uhj=(q7 zvqD!l7hX53*AmMGcX!7y>Lq>>5db-^QRlV6a)MV6kfGAkFPq4KQvR(Ab|RpL$M}Z1 z|E;Y=zh4!C396hfhv!2jH^q4x^cPWVFAH%3SmO#&D8K*|8gPVQ<(lM0rX<-_Z8(OS zJmMWPw{$r2HzCB+g$J2}{6N8P(Q0Al5c_&@B5s#xM7@lCMjPT*`J~_%mgY)xvUSE= zvX(KiE2p@%ANY+E<6pX4T+Ov~myCQ$7#pPRD?@1;`0nVcQn4{{Bq%QdoOm6jWkS2x zD$k~Q|rIbus7L$2qv=0Y%8JB6-T4M1M&oZN6KVQj2`|Oo{-XG$*`9)pkO!p zJZQSn`WRonZ&5}wCw;>Rnd@BTRe*Eg`^ky^DbkOg_$Ns*aZl*BqCd(KPv}vf6>l;} z--$^@jO8EaLc|Xg#{7tR!TRpo^qF>}OX@rx*eJ_VW~Azo%Q=|G zB%zoe)HttCOs}+N%h4MphcsmUthDt?)$JQC6M>37x8G>c|K6G*R^##9agE2|a;xXO zxxSCh$pcOcH?MvABYJeZz8zev-_&X`4b#o^Gei-}Y#rmPcCCt0?Q0*Zd5x`Z)Ijl~ z>)fMbrH{p{j>7n>y#Ob!Xr=G$(DFG9`ZPHtB{hoB&#n=s(EVRJ{V2CG)WR5Tz0x~UG9nO@<%1sR} zAuTmx*GLH+YJ7vsLEVH;p@_fn8L`893EeE3L2|!xWCndc|WELOJ5YZp(I=%wf0P;BfwwIXB zo~Rdy`_MjSZYb{q@%bSBRUuL?{8<>6c_arp2%l6$hjqSIc&zBqXNFa;#jwryJ6cAe zeuvGslxdL+4ume3meIqxpe?0LQ9KCY{KPBskMu|)4;|fX$4fKWSf&km?RJV&ax`wU zn&8t|5vegs>EZmIXBB5Ko7ekz^t-+4>pa4m2^1wDd0V%T1+U%Q|z}%JzNKV>7bkdKfO*dayW z6w+GBf7?PR7b4<^k`u5DXr&Zbe0Cwd$e0rDJZdOzi%(KYJD#HuFqYg z-A(b+b9z6`ez77fIYdm9zk0aAd2-v3B4gd4{_)pl55Jb$nUSbH-!e?{S5spGDgwNf zUPb#Mu~qz@S6N&Gy=f2b-cz>xKPbT5RuxoF@yPjwEM05HGF(&B7sI|YZ{16n$!b8UxMTXVUseN z1<#Px_Josa$c={)FnF)QXD3O8o`^1UYlx3R1OZWu`jCQXeD0t%MB)TN(`ZhOU23Ey zy^De%AzHOZ@k>U#>+yoFq>mEC&b=Rn;Ptkyc(_RHs(H?fL4GN~wlYm4;FYlDV7QuM zmju1K0kcYJlAK}ql(i01A-$fdFoMU9-7^FtvX_)_9=xSJ_QI61(qjxp2}Sh%4DicG zrBf<8RpHPgS*Gn>10%gFqSACgk(6!ga+LucrXn~quo)^ z^{qk%;v;-fJ?>4_WNeBfN@nijU(!>)DDd8=GBrHQxc#X|+T}VmI!=l~K{`;QH!;CKO33|HM;Eg9B}UhK2lJasU8g4`3W%`iCIMUsy+fr)Q(#$^S+COZez-;$IPS zxcJw+|Cji;ysl&@ne|@$ORzqa!{7iH|GGUMEkN_dc&Iq_NG(?;m086%84#SK%E1mV z?aKNfXtgQtVIJnwHKyvu>2pMMFJjFmVNO0xU;3f8vx9kl$Ga-+-rpAdvs=C{bg}=$ z0AjXouD;X6OqCvPg_>u@(^dRrYU5r5V?Nej&Yc4XkD+k6QHg0a!Xq{z)S7dgcW+h) z6F!MBKyV1IHaoE1^YuJj#CH8k6;SULf`I;dl32B~O+Exj45tQDw4NSN$;g?&XA^+^ znY!4!7NJ+cKaP;vb8LEEcV4$|L0)Ed-*o*PMz#w~FwXn=^X?d%@N>W!-zs$w+B(B( z05Ut+Gz85)L(dP_T#ih;(#m!vZYUwrz$T>68gkzk)p;-wv|SHMEF8QMx(qm&2XaM zQdW1nbt}s#L#Qa7R7h_l-n{v;D8aa$3=(PEJH4ssIH3T^RXRbZ%k}T}DbluVlX)cX z7h$_yWSLN&YQ`-#uv-#OrNC&JT&=fPmUa|n4a)AGXM&VqC~ATtX<>WN$_(G_(iaim z_pK@#+x1JG^=4o!<$^Kx2Q_{=SrgSA7(@7VJ>(Vm^-atjj`e-Q6!;Bq6l3w5s`M-D zi`+aZpe-!+9Zszw8JNyVp)rei&DF4LypH1bA-vAcK@pC|frSdZuC)CQyf-;l*SI}# z7+82+K$h@Vy%-x5tnVb*u(;ImpDUNVqt;jE`p{zEshPi1c3t~X*yZ|gfGpLJ`{Np{ zle>dG65VZ>yFvL>nP}xT>(JVsDC^kMjV0@GZ!raT6Ip7p7ZZvcV(ydnT-Y8{&!1xx z_dhhq%NSsC@b{QDe(~62PAOi+bKbPTC~e-pCeL#b?+v!s^WTdmT*O*H58%~*iRXl( z(ZXf(cyHhRT{a&=B&B#G|4TN{6+@W)3kKz=#v;3y%`@5Pyobx?pGOKa-BK#L+sY#% z+Y%cM5?c3_pn+8#6`Sk_Ab6z-jd0ogFhsw__o32^>3F%-WQG`vPL)B0BVBi%0bjLD zyLaru$k30S0> z;S5Ky4`wqpYx13HzlXqQl06wJ9bTa*Z7vUbuqhi~F4U0Bd5&BQ9u7qteB?iR?QyU> zOrcgoCEV^cyYx#o|McwabhjmwWQo%3`umrW!N*Xl0F!+Yl$n+Sk~eBUZ}7IM^%qbC z=Yoh1ZB`9Fj5M0QM%+6yH3jdLtcF=>3r>cUCQPp?QhLr!M~XbcEsD~ygHA;=YJAU^ z;Aq}8^W`0!OR*Fr&e}*2hI@{TC~$8h>6s_vX0kL6x@C$yKT~n48uO)EniesmdAc@x z^y5^WqE@Rkeb=_Fbn^_o5-F36d2^6z#6VdZDF(V}&MSU;NH$WQon@k%>%2{Z^LDv) zieh@I(B3xFg&Si#Ed@v4wp3m?hN-~sDeQ?<{tTHNG)O~{xr88JpScL; zI~jd~&SH+50_zP~<{pTnJ^e%637IP;0@GMx^x-q?mU1}eF?WpT# zEWWNAz)f6o`2Z-WWNYJ{ykr|-|LoIHn|oC0HnNJN>@p_q=8!Vf)?4{%lu6*Q@;Omz z*xAtf)8Vrz)*44XMcq)trVnf)Y|RsW5ud+LplA9w&ppSq-Cm&7%y_)y_|bqg-Cc>* ztJr7Oxl16;UqDnZi2>Aavug36+F}ihG+|?mpX6cn*tGBKxdh@zeXVQhaz{SnDN@dD zW}>vy7kdn}33cm&&dc6NUelj{tPnflbOgWzR7Cb`L~bEpdRqL!o=xup+7sHOu3JC@ z#htHHN1lxMX8-Q8uMm6Tsm}O6;j$9J{}}ziDYk;@a9<%M6L;*#L+FEVx=@5GH%buW zABYGO4%9!EPhn7J*V_A3I+BBW&wmA24}_wj;ew+Zph`%XDa>q_sixx*QrhHsS9p8( zZp7-uj0<~FuJumRFp1~@P-$u98BX3*JlBGm&Rz>jJJ1JFuI;nwazD~2v;VsOS*aC4 z)Gf7=gx|MI(=+bFpfUrvP2^#WLP448>EuFo`72Xf=tV zNRVHPWTm=Lj{&53o5v!x$tQc`+qb~yW*|n~U`Cv@4Oec^?nW%_XiHHtFA%-hn}BPe zBvm2f1${iR#wER{sQEm7ia|a}N}9>C?v}@k6mc8am}fOQsvA~Gb; zdVQ}f$fKU|NxlVea7Q`-|FW#`0kB*g5uH(-G0#u3zYGD1-aP}srcAFGoN+!6(23y! zGbxyFquU0WR5QWkvdyd7nUDlw?P{eo^<)*5B{<;Xl>Bw_6pP$^51l|x;1FX(-y>7U zvXTjqWnu3^)GoB!-u+97I{V^op4-u!Qxi`&IeVn9@4v z+kPwHj=c(wH%FHh)m`{@jIX)?*Ph2cl^~ItQaM>HSQCn0q){S)jpI%`(MN&Ly~hlO z1pQbKFsTPNV{R-4Fk|CRdYBtnc|MZmi=K`QZ!?(vMt6FJ2!>mH&>va+|Hk}>lgi_w zYK!_p2;jS2TE7*2jG>f@!Md!Y|B%e6Da<>7A3T`JYyb*V(Av2;M$O$kUf0WB3o9A_ehJQ2PzPaMp7;bkE*gfnhJKD&O{p)U*(L~7? z*Y3i~p)(HVV*&m)9!Hen7FBn6#k;MKTZ8K4R zTYfW1me$8Ip5utoBK5iOKv9ZDuY75`Hva5n0*1-}UYa30p=FvVdt0fb5y*}Xq(>}d zo$chiH4l}B?G`E^w(bPx z*y}Su3VdUrHW>6tL5PULfRT4_!;VrsC_+DGmM|)tWxN#TsxQ&ed7K6*}f)7znp(} zKOH*Lz&G^w-9*MR;xDQ-+r9DTK4-&cwAudOZ{wrkjMAP^8R9a()|cyXadaX9ukmWk z-lwskj^>bUSa;^}(GgR&uh~qL{yTRQ<#!gH){Pe^C93{xTiTnea46^&==gECPy=T- zsNTN3JYCx;GI`K-eeq+Z&nQQT9D9+wAhD7*Kv#5uonJWP_`g1hibiTGm(d_k6G|{I`x`pvv z#`1J=A}OW%iOgkaC5g`1dId>bu2IFogtR?DsX`~an`r{0f*Hn{V&$cAeENFHX8Ic0 z`6)Lta$9NGXxk>yzIoYY0S*{G+o^Wk1KT;~3(@pR!VEr^*|O($B~jeiF@>5DAM~IF z^t6M}0_n%?<|Tt{DnA& zRiRUD`qCF$iNA0*)M_AKrNWcdpw?lj5-QflZinZ0VzU_8zaX>;`6|DCMTlql#o7U439wHt8lOgh6>-qQfZFBb~1}|#0{#w+=SlR{!E1-~5 zdm0Mf_n|;I!E*0asa%$zo9eHYFq?sbmPWj%Y_wROf>gJsv3vlG9*e^VYP26Jz0b(2 zgfQ`NjZ)7>MgGl@Dt%O2F{M)$-8#yl!O}T*1f4SD&kJ!PEgaIR$eB-_D!gh|-eG3~ zKP(2l3hknLNt;jE%B@+L^yI&KeOr#h9py@^=;i75Wa>_Qch}XAUH^`P2X|uES7)bp zGh-{aN4J2CC)5GR6J92-Q80E_f-%mYB>7o4((3pQQi7603x7j?woz?-hf>40;DDAVAd(@(%5$P9C)uk%!P z!C|<@P*6D0@3o0I;w!i+h2{_5;u7$0Y7@y&TvXv)c^9+yQA9kdooMRCa6@k(VwS@GV}@19t_&Ek1HQwimUjOJ0O+#)Zi?Wz^g#M64%YOPKbsT6QK z`l{`}gX$1yjKGKvB#}&=bh=>fh4*SOZm?}qr5d~d>DR91XB?@vBKnYLkh!N2CT6g& z0v9|Px5>O{4rdzQfeVF+ZQy5Fvs;_-WAVLX|zEI!jk9orMw>_+w{8_*6 zVO2#E_s4Jo!<+45tcVjhlvN&5q8P|;bBK0sfOEsKe)%MqMR0* z-i9>VqJsUEX6)xstl!oA#B$K*LUC0c;k;m zPXw3BFIW5LNvh;(eOrM>mTu?07O1!E4Zgz%=C*Mn3`vpjIE%J7s+WMnHH@nS?)Qo- z&}PB+iaR0Kv8^nthfD00p@6-6#`&WIoxL_x_J$r1!b1d%(-wbnWA^X_@~Z1?`YxA%MR zKd4c2%sxjSy|rK4YRdIpGHh)9T>cJ6_k}o_kMrSD*Tyv_zvg$Z6Or-4neqU!*Jp>8 z(ecJ4;#W~;M=E1A51Frj;9X(CBtV3^jF36UdoU>+wia06so0Q72noHLNg(B3`kFuO z)p5{h6C2rQD=_RiAG&3bG z&KGC-sBR=U?4ekkCJ>OHAS6Gf_LDUVS_s!F`(r}Ps8Wew3ZI<-NH+y*8Xh%L1b0Wi zOj;Z2O>fiUn8{Iymtx0Nkd4Vz0a(8IadA-KC~tSYK~E_OjplcFe9 zJ>u+tL^c+(x1hb%R`G4)OKc28jIGUfspgg=O7?wqjSBs-kF` zxkIBrM}R-Q@jbw%1n#$g<9m!W{Q!Cw#z%ihnU76+LvH{$!VCeFK1?hwqp=K^!hvus zHf3lpmVgI??X+h`XIx_N`YAK8?1M*y;f6 zp61ccRINg^laj#~>$y6Wnd&Q=mLtp#+m+V`_r3O)UzyTci+ykTxLJ{yP7#satg<$V zMd9p!b*{hNRol<$Nn z)+9#7LGjEBL_;)7JSJzYg@87039r~;vPnZgAYP8uz>-^oDbRgA-DNwpqOx@ha_uj) z?&sy05YX+YP5o%TUd)Z>9Ec3&X!?U6uS*o;?NJ5GRbO_>eq(#YS<1)axo#QrbyPf;f^y7E7yPXfgI6iJRG`=O=gt-@qYbglnJM22bMB zAqwAun>(`0M?bX|?+)$9M$rCQ#w_^ zyw^?cHWMm{%Xx3iyP;$PdXH%je_nNaqL6^bax{my(u5Jb4yc8OpUp@kwCiC~j_wzljXwLQ~$Za1s zcg>!D9K3h?`M|iS=ZDqu=PPMTS!c(yt(<$+Rdhbi8&moG_hJq29UZ*L4SX5Cy!hpO zVQu_|(cc%#P=HECQU*cAwHSgxFa=f%K(v^%YvD{F8wn5^SO6aW1CZ=S3-}DJ2k`xT z)USjK1MpV8Hc;-J=`zO)OrNjEU3$wFL<Q|HE zpWz(XZ%t6bC~nce^Ll0~6HiM^_J<{r0|#Be8v;j$a|lXjd_ozI6}mCq>3z!@k}LXb z(!oT~G6=-2H1ZS(=a5L1Gst@|l*I84p;4$`jJU(C9b(dF=PuvuAf5JSIEQ`+=SE4T zU32vkCWqe0d)oA)MYuoewOAxc^d^%Gp5p%#q(A3(NHux@k+Nk>&!>K zCt%Te_BZ*GQpwYunWx(`{B81G^QmVJa|TU5RR`L>i{RrVKk0}+sjzU%J1oZ9fcjJjsXPxt%Q#?i2`IJ$J{x_-Q&qa;h^Bm4CzmtltA z|HYU6AE@Va9+AXaE6>!|W|o3h69-p=cQR)fgA8v*q(P5{OHH3uQrj2?QH8bAJrX;3 zY=(vn)T<~SW=6OK@6T|K5h_xDf~*wPSf1_(W+0rymPJumxg{}yuRG#HlH_u0ULqya zE=#iJ^p1rJ#o=sGl9tDunIZ;m7^B)lOyEY&rUct6G@=R2nr`dkZSDif1?Oh-!pSx= zdG>IM@*jZO%@o)?I--pAglxg}1)DS0Ie@1T7b!L8PI0Ht;6 zJdu3o*flG|?bLmPNgfhA-8)SHvp}J9swNerQXlr2#%G!3^}? zZ|wMA>tD_lNEMUM#pbTtANPjf(pGiK!WT@B=Tp$oR5UMQw`A=syA%7IuyIvwF1#F5;^4aLmmv~nF zQ4UD*fl{h)iIty}O_ho!^J&eK0ZBfzyvn&*qyRz?56xjolnhAnA<#q^R!~$K)UwSX zEH0W4wPLV7%`f?qnad%3zTs}|dhP<#a?%bSh2-6l`Y4t_hk0Qqt>FxjDAt5C=cjcE zx6NfFkV!t+G>Z5M)5msiYt=^imSD%mNfD;pd14*S{nL=Zefn|QCg#|YDq?~yZk=7#S^>>^6fnr0vz^6|gq1f zBZ6-W{*%;m8n(!NorYgDP?T&!AzW1-I_M_bfFPY z&35kf&O#cKByv{0%Wito#XZsTR-{RgK*K4U>&eo|vg$#RBZP1k^hbKxXmJW@Gx=!PRZs3?qbXAbo57xikNBJ1JlDZ-oC z2Fq=WP#u|8`A4aIGrNvr;HGCFv1+2d+Cjd{k7eofWV`yYcRxAK9|TOUqu23$luA>Z zyL@_|*5muhfG^v7-BxEze{*?$EiqpNCAf1RC)4dpZpAnM{`p2Uf7iF^bd{d36UmkZ zhjE``bC!GfAJBfq9=_p&;=S+u_4|(n+Ur9h36wl%AFOw|Zs4zNN$>d0J0oS$_)FWy z)Q+K~V=xR-VX=AdH&)6G#^_8ZFG;eFLuw6(-)d%5kZ&OZUcX#HrZk&WgXFK@#<^=UrIwZTi~C@Pby4R@o!LlB zO1@1{mIhOh?@|lq#W|YzElASI4a-A`xYb6%ROZqN5S0IfGQ2@$1g;DO{_zs#!WfKs zgGDljqwc5Rha_bvaa}qV2qSabsWa~f1Z!+}^iB##(g9GP1$D#LP$sYA9)4uQ#_N0v z+bwi?w}OeZ8_`^MXQwtMOKB4cLlKR;atMX0_E++n_UhGI#q=1LQ`~bUO$YgGpBV19*uL!X?MRdEf!taf47ou%OHE z?fuFX31Fs=640L?z=iD0vAFqbw9AQnS8XHJemTw70#DcFcZuHuM6xchmrw$dzoO2o3yfJ=8O zUZYMbWYiqaJu3i@Y9X71{Rr{2{7sQU3ac@ph1_YOVurBDa^*1T`gp#G=NTQ>tx`4E zlSnQxoJL!jII47#(2FEa`HI_|W~?0UW!i-X<=SNPO(jzeYQt%kkSP4-&t?%8+v z#@poBdzbgCi z{xCgm@yp(3W+D5aQrDNWzL(Sy^FY+xgY#2`{;%V4-C~Y_W8}8QQFp-L$4{p$HnuOW zk3%npJ(B{_#Y2pdDqIsa6wC|;f4sBw%;1a1HcS@Cyg4Y96%|7WZSzb4kT>VG90Jag zWsD*ef=EPi8KozLqYdch`+R+AV{WneMRqyvXVlydwFx*8j|;fgQQV6F8)L?lLXuSm z$cw!+wCD1X?%bYKVLO~WRJ;`1t#~s`nJuIzw;wsazb}(6D%L6;u#W3mlLzeWMSCHHSW5YPsH7~Kf$LVv3`;# zqcS3WF}0&!qLR;?%IR)1nW^Qd-Zhf4KUV#BCJ{zI?$PDn7{or7bn^3b{V;-#ty>Z; z@qByy8MW^jzTV-+7);85Pt5a+`RfYWr`k6S>JAnl1Uu6ez#wkLc%LQ#aF)8X_p#Cp z+A{*kn_*UFV~H-&PaBQ|sti3Q5cl0@lx{b-#UUMD9~~?B-~7m}mR_2#Q3+SM;&^&~ zULH>H3-jh5`iB|+d;$M}O!r|TMQ{cxp4r zG7<$gZ_YVCSszKgt+a#pCf33EB~hIk?M|Do%emS2tPOf> zZjf5xC36|qsb7(}J)m-(Y7eAjr|4`wR_wRGXH7`p>1I4o(k34eC0zS=Wj(s@VAo&Y z>ACVaJ3>?9@(1Q@o!c|3P1(R;v7&&~iY%%sB z1&&ux{N{Tmei$;?dD6H)FAMIRGX`MN3l~V^nE-ZQ$}q2d(-VIx;$S?@*3~eg5@BQM zgJ-3%V0QS>N(>RF%|^5vE1P+s(2rIL>0dnz$R{ZA&v-|Ergf47CDg^_6`+WfuN-;{ zABbnXAB8Wc&eETZu7L21AlV#ACopggk(=67%(_d=))=W#tQaL``r{pSNUNAJlw8zn4TSsa zJ>6$iv)@!7{Ds2;vK9z{tc3)a6gvJ5|J>GnaOA>A1UAL@KoUw=sjogcWuC+1yw8%M7~F~1}&7qqVo57W+7AhfZ(`D z^bKMln4aA33-zQuT9Ro0VW;kU9;rGP&%eQ8p#$@FZeZg6_cxENe`3A(5c+AL z{lLy<4*t#wL%3NA<3fvz8Gh=N2lXE(FFAltAom_kin8 zk8G(_q(=-#0%vdmc3k~a4x;^`G&CJ9w>d$^|2!^}0l%B>w|;l#AL9&|1Udl`u=xZE zdc&}YnFS<1nZ69gBQVo6kqCN%iH~L0$U#zwipfC^=@d?~lu5xS69yx>$caq1gpgok zxeP4l=~vXmYfub=1R)5HYneta4lR`gkduK@glEFnDEE#DT8V}TvrI@0(#fHqq<<{x zUNr}|v1Jxec(%8YBrPSHqyM!`qQORvq*7tFrU&w$t3y5&hTmGe+<0$$^7STW#qa8S1IPCyklr1Q89yYHuYcBNrCJ=)cCfwDF8R?9mG$P6X%ZesDv`!nbkyJfW^O;&<;%PVU42VP* zWwM#WV$aT2BQ~9Jql4c5uYbiI;O+c9*uuY7mHB;D&tL2K*MJ8~gIs@8o%FA^j^EYJ z{k4vNjjxyHZ)*Vmwbo$)tmF4pxk$L?e}u71g`%@Cd;JV!C&4pX97;4Fhy+|J7Rd4) z&!Q2~9I2 z2yplRc07nzS(E$Y^4q6k>-@m;@+YtWG)!G1FDzGU7?c407eM(xW@-83&qp4q|I^Zv z0DFv11h9I05`X%%qb$7YR)Ih>b*|$-EiGmbGLv4)1C|zb07nZM3qxZ2A@x5kEyv)U z2CyU=9$da^Sg`zkqGE&SUF@Ht$&!*zd7+~IyoWdVP%hnjW}37{M`AgiS3CkllQK5} zOUtWv3hEL~>{P@`ANf9)C3foZGUhX42FwSioTh_#;D5TM#r%ZsMMmehlbwlE@d@!R z-!EMYqk%^OVpreJ1GiU<1Mgj5M}Na)gq*z`T#?wp$yoKrYt~^5M%%+$3pw7USPS~3 zR%U8R;Z#-tr4&3ji6D&HW(vERB+C%N9<^f}{jILdEVOOB)htYCGQuE|YERcZn3Q>^ z&{5><9&?P$4_&agAhU>tqXM6oMWTi%HDjE%xSTwt$#`=lKH+|sqoA`d#BS(+~!Ma>Rp;-cBgF+AfaEwFv9 zvzr=rh_hReZI`tR&xMZIL>J2ZDmHZkoKR1H+X?l5-8K(oYnee2fNOMi$OJwZ&&aCJ za}G#slW|4Eo(`XE3`Mi52+GdY1GN+AH@)V5_M3_$opLFb%Fz2yn16zaGr6MWVcNW0+>h1W;c^oO?RUectDtHFEC(^#yOjpP{53Rg?Tq$QHO{AmF$yQEa-oU^d+$eE zP{%^t$(J49cpPhXhm(RZ7vI%w{*UbatPLA=~$}yIANF_8?!Dvj8c1uIrM860JJ%K zjJ+C+WmE=_<_sHz#r)`>Wr<`4?E(Uv!-x+F+=AZi5oe~dEH)@vui7!#sM#`4>UiqGk9K; z?`T3WDRlE8Uv~#h0?@_JbAxewT1-L-O2V1EDdn+OHR+Y9*20*Xsqz8s;!MFKG)Wug zNIpBN^>Fp^2>MW=b!--2mU%D6i2DO&S@CCGvLB*EMyM}`>Bt4|Tv5bGvJXDmz zX~kioDUFb|Oj3t_E6TX5yjvW{1aD(WvVv1-q*I-svj+-|XTowEC%;(%Q+tbTU*Ei; z0_Z(V{o-uxN#V)-km+G|n7}Pj0P0{xwObfVqraQRZR%t6%guBM5FzpZAImxb0_y+1 zv@DBMK7acrKi)&Qu3de9w#I%UK&0Jsm$o5#W<@#gQtEi8z?^3ypwl~ScdEv7rCYrJ ze5G%uROQ9}AKRw~>@fGQo9y|DXly%Ve&2A=jUec3!U-N2ARAZ!@k!~skR#sD`JO~v zHY?tg;t{Yg4)xhL;S|~_>(R7HL(E~!c00zAd%3zB5%&V9m}9Q0BFv-7P&rNQ1?tnl zNly^XMM+8}XCLe|l7#5PZ%Jk{dF$iu=%?le*;=NUmz=X^n!+5w(OnjDXi_E zBeb{eJd-?Nk*-iL)BM}*Y1YrBwyUkDe{))q?<_k8vNSo!z5bxeP$|JJV3UBP6pxsQ5#Gexscrm?kw{UwHB8{uk{He$8~!<0|#QnNy%*5#BW#r`eh zNSIRL-pxgbvuPL}%8KJ^nu~7ttxS-1XV3ravlCAL{PuEH$0tW9K$ZD=MSFg{@hTe2 zxVz(Aa%(J?G9=(>yYYwjRd_LZhL;{|E5W|kgRlJ`xPMxn2nMEG9qwP+=USfB+zfdB zhU%1IdLr<}^$*55_EmrKD{GTL><{8zOQDtRjE$K*mB>S2t^ z=W5{;;$>@*#Ogbyk(BTqW(Z65j%l<&SVRK!7F-7&jIrlw9`05lb;GdOJc^CDEK2-I;zUa;1Vm}GQ;+nZz&cw>)z-YIKpP@5eF@cLca8^AEIU*-8M_O58C?5EBN3$ zPWw)>phF)-sk0z$W!$QJ)>hYb7WbjV=(qu2=X>f--rg=we7?S`5$;=M#P9Fdy&@Xi zc4?%(-CWmv)A!fzhX?>A_561C^IxcdH;83Jes-Z$fRG4qEj>X8urUA?5Ihi1s)V$n zZ9Yz61hPow8d!mh43lX2JOZ@ zHKdTJO(+7V*+H+`V*D@chCjTCrloo;1s4z!83Bsd29--lxjh-#Gdn^alB2R55XO;^+ zI7x;*zF#3ruc#{t`FvVGF4l4KTUU$lTK3e6ahK9R;C1-;me%2V+bNsp?6mGyGXFz= zhs!U%+j}AW3%JmtZqSaz%NWh4BVZsYQ}^Q%y$+atRm4aUnblVp|Mn zH-f+yJB*1E9@2%;_xU0%lH>=3!7<8&*qe{kM`SJ2*oaMHHUQ8*|s)C&-uW6yI&kaJ`N(~qRV_1NUgav|beWoj{e zT%7O}qbOs8)PWrvn0=zihcZ_RR=bQq8ZP#7#Vzl5h%$<6T|l;gl4zaeuSK-dI(-#S!a#0zgwME{qAE^wmP1j@ubK7;;Q$E5Z>l|dArHh z#PShP5O&yNWsxk4tlZuc$z0BDEj50uQT_?}f!J zd|TPig!cRe=h}-0{Z7pl&x+|zIjg(DEIH3!Xr(H;4q4}YzcoUXeZOXe!ruOHl>eSJ z)9X7ApJtRku&;a`_2{cZeX$sY($O1YuBF>ke4?>@@34O?9KV#6&uN+n(Yr|+PnccS zG-Ggd+0c6?S0(tK6rog|cb}HGWzH~g=WN7*TH&>U&am7u3IY6KQ zh~+o`U;yQRa5jmE1v7v*>M`bD(mX)Tt!yDISTu~}Y%WIBF+$TZ462ctTS{qs#(QMm z4W$$LVwSk?+B9IQFxh0Tajz5(#X^Nb`CPl&a*f<4<#tOx8arC>r+rbyoTuEgbv9E0 zJoaan+D$GSY!(Y=Rf`>-UDN%eEj7y(1Vpn33c@^wJp`DPMh}{bBnM+y?7L($nYGY^ zfUud$w%gkic~ZgjyzM}aNr{GeS`N_{>luW3UmkCVkmF<}@iM(#$JfJ^?%<7XTUhJF zLQgWDadnP|3h;``>d?8~JKZ0zGiFy-L;bQ>-w+LijnI3kpe?-!Y&6R2ygci(|B34H z8NHntzD1B7Xm?$Ps+$Gr6KTm;!sLOiLdmN%O{}qKI7}VTWn@hq$wx|&$&@{e792q` zjDAO{vT!9lx1lu@x{|VGnU9$qERl2X8&^R%0kX*fcV6!lx(g|3K7|s9MwrIS{+OkO z3R9Fa%JBD6MSHPgXr%SzW} z8ZaNE_u~NX(H;(4zQ&v_JIovRg1taNY?dJ`MtO)Dt{yqN6B=hk%}^vk-OgSjZ|lQR z`q0~ltxPr&0WL4N5aB`$mius3+{=u#W38;plHWJ0U#8KkZYLSAujy`|XRGzRlIJlR zpvOyxr>WDF)Vs>*+d|MJJ1T3ZDWV;l?N~<-be97bN~_l4FR|CoQEQZG4c+98y6PUoM*B-K~x+R-~ zO#Zb)sS@)-g-Dj zO2csETLX{{_V><8;%Z$i_G+pwU(p+Uu-acVwf9M<7VnV6*v3ouywZ3S9EV8?e!Ajg z@#Gw9v$S}^2aJM&0*2*FocLQ%1(q?n7^|TPshU`0mz1PFk;NH;sbk5UQ-h$ldKiOo zOv+55pEwhGWZ1ebRwGKOJ=P=ny&IXMX-TNeB6!zY8DjHpa+vr%upDAB+lc(N$v_3Y zMOpb>B@-hRkSf#jw?MYPnv6R`i7-IP^s~wMFc^39XOmH^ki;R<{qsnadW&EgXfjGw zLPhWZfO4bFFpNHy3jioHPX;R$%4=Pay?j@p>#aRL(quncWz-ji0r-RG8c0UrhND7E zlg%za*_72w9bTu?cY8d|)Vm;#$UP%~Rw|0d?O_WEGRFvK7&?YoNoc+80{v^3mrhG)ZnP8M@3Vi} z#?#~P>6-U^KJ}*Ip39g{ll|6AsQop2b-VAE1AH}Hy6eu4%lF0a8}e0NbYESbpYH!3 z*_0e>o>W%Yd7)OeK>iwC)Rznp0h!f$QF8ckUUL?cO~FN)mY z=RB#lT@grjoT_u#AT)%WeS|c)HQ|20(5zm0!Ck}0Veigs&~1XsZ4~pC^(8`&npt_Q zwcavZi-FP!=6N)OnZh64{JpjDeTm-_!0X$NG@zT0<^cxu|L)T6znP`hk~`#}^FQr; z5{xr+dgk@R#OF&JDhEmeBkK6)+_gcgz<1)h-!ID@mPUhrSA zNqyl+HS3=hg!v8-=KWU%DeC>Jf{cd$RY4;Es36-TIT-%H15ROn0z$U{5bTX*F98HQ z34mbV8!rVF`ydhQBZWT^>_RL^1Un+uOZ($|i^~FTh{D&Z#bEc-q^^n<8t*UgECl)%M>KI(QXOrs&^LHp-j%sj2p zA48(R=ch_;FmHSTZjOcr#bdu}Z}}7*6~vDW#5`UfEZk6Rg^8S^j#b>A4VGOzG_I^c#x?}7a0w5iga#(d&ptwRq ztHFda*lQsq+Olh*6jl*yKzPBqi5h*_(Ar<&1;T6Ipw~s~(HsNV8!>{@WiX3JZ8jTm zcaY%)cLa+_mBcA0$bG@}f{XL|KHO`yQf<2FI+Tw*DGnVOKvuvuEA3 zh09viymtYv?!xrtsOiS-u(#}IGhnG3;KF06^#om%=G)2}@HV|squ_1Ma=k2R@y4Vn zZ5~g%&u#NMtAnL+uY{Yq;jnp>z3tQVB~$y!{3UzWx3kN`AmZJ9j-DGh-??96Tt;*C z5>V%G^$~FAFoB{ZD--$_Ed7f5@u?MW^$)r(R<%wg;jaziGiou6u&i*nN-kt|mNXH- z>Bv#}0`Kz;(zjSQOt5w1H@sr+ORF3Add(v^z|BC=SR_em$TZ2oUbQ=^p6}QA)(hk2 z-6@)dQurH_aJnz@p!`^m={baw`n*#*y62*6gOcac*>bGsv`;iw(eq)5v9I$AQ$OAL z*eVI(pDz z9?oUh{syL2M3X6hbDq@hUAgY_WyP$f{jn;Yt|&1a?bX=^wo!WN(dokZW*4cnScR6K zA8fWeJvucj$e}LP*lNvMD3YM6dR_}$RMXxUazLKs63A7i?M;HVC!+2;H!u9el}SX? zD|w>sO`$1wq>YIlZetXS)l}cqxwZ1T!1mN>;akfgW^Z`@)%aq=339&5Pu^-WX7AD>ytruO4<-OQJsR?GD^eM zrYKd zEXYUJCP$0S%O*dJd58Tr;crxm*zpUmscWqVT~wt z65UHH?k?rB&-EC$IPB9aK1#1-`DEJJiW|~nVlXgX?UysfVcPLvEs9KnoAKEAA;MRE<@m{3bH=j zc-@ncyvY#PK9(1LJUu)$bbqtgVSp6{^3T}o|7;>3V5QZyrSk!o?ch+OBsO4CaD7fZ~ z|2PHyB)*^nst~$=m~%;wJo8Cm;<5nBY#1CAL2{e0a%dGU6N?lINhEFz0+_?Hr^Cb> z!3?Or$9(Kp^^+*oy=H(Wezp+brVa9D(vR-)VFWe}Adtu-a=%%5C%q zR*waM@wV0fA5y#J$IMtu+UNN|1YqRc$)wK literal 0 HcmV?d00001 diff --git a/dat/nle/tileset.png b/dat/nle/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..e46e17f25ffd4bb1d6fb037f75b5ab8ef8d70460 GIT binary patch literal 86150 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⪻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 literal 0 HcmV?d00001 diff --git a/doc/nle/source/index.rst b/doc/nle/source/index.rst index 776bd8357..f58bec644 100644 --- a/doc/nle/source/index.rst +++ b/doc/nle/source/index.rst @@ -16,6 +16,7 @@ resembles the one used by people when playing the game. :caption: Getting Started getting_started + tiles .. toctree:: diff --git a/doc/nle/source/tiles.rst b/doc/nle/source/tiles.rst new file mode 100644 index 000000000..69cb3c9c6 --- /dev/null +++ b/doc/nle/source/tiles.rst @@ -0,0 +1,109 @@ +Using NetHack's tiles for image observations +============================================ + +Tiles +***** + +NetHack as we know and love is a text based game with characters representing the dungeon +levels and objects. The RL Environment represents these in an observation numpy array: + +.. code-block:: python + + obs[0]["chars"] + +Each character is also associated with a unique glyph id, which is represented in the "glyph" observation: + +.. code-block:: python + + obs[0]["glyph"] + +NetHack also contains a set of tile descriptor files which can be used to generate +the equivalent RGB values so that the game can be rendered as an image-based display. + +The source for the descriptor files are here: + +.. code-block:: console + + win/share/monsters.txt + win/share/objects.txt + win/share/other.txt + +When converted to RGB, the full set of tiles looks like this: + +.. image:: https://github.com/NetHack-LE/nle/raw/main/dat/nle/tileset.png + :alt: NetHack tileset + :align: center +\ + +Installation +************ + +The tile descriptor files are included in the distribution and are installed in the +`nethackdir/tiles` directory. + + +Initialisation +************** + +To get NLE to render the tiles as an observation set, you must set the render_mode to +"pixel" when the environment is created. For example: + +.. code-block:: python + + env = gym.make("NetHack-v0", render_mode="pixel") + +The next step is to add the Gymnasium RenderObservationWrapper to the environment. This +ensures that every time the envrionment is rendered, the observations will include the +RGB tile observations automatically. + +.. code-block:: python + + env = gym.wrappers.AddRenderObservation( + env, render_only=False, render_key="pixel", obs_key="glyphs") + +RGB observations +**************** + +The RGB tiles representing the underlying dungeon can be accessed using the "pixel" +key in the observations dictionary. + +.. code-block:: python + + rgb_frame = obs[0]["pixel"] + +This frame is a 3D numpy array, each 2D slice represents all the pixels in the +rendered game screen, and the 3rd dimension represents the RGB values for each pixel. + +Example +******* + +Here's a short example of how to set up the environment to use the tile-based RGB observations: + +(Note that you need to install the Pillow library to run this example, which can be done with `pip install Pillow`) + +.. code-block:: python + + import gymnasium as gym + from PIL import Image + import nle + + env = gym.make("NetHack-v0", render_mode="pixel") + env = gym.wrappers.AddRenderObservation( + env, render_only=False, render_key="pixel", obs_key="glyphs") + env.unwrapped.seed(1234, 5678, False, 1) + + obs, _ = env.reset() # each reset generates a new dungeon + rgb_frame = obs["pixel"] + + # Convert the RGB frame to an image and display it + img = Image.fromarray(rgb_frame) + img.show() + + +Here's an animated example of what the tile-based rendering looks like +after a few steps in the environment: + +.. image:: https://github.com/NetHack-LE/nle/raw/main/dat/nle/nethack_tiles_animation.gif + :alt: NetHack tileset + :align: center +\ From d968a6a15805188437f5977cbcbcbe86ae2602fe Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 23 Feb 2026 15:02:48 +0000 Subject: [PATCH 13/19] Add script to generate animated tile gif --- nle/scripts/pixels.py | 113 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 nle/scripts/pixels.py diff --git a/nle/scripts/pixels.py b/nle/scripts/pixels.py new file mode 100644 index 000000000..5125d9388 --- /dev/null +++ b/nle/scripts/pixels.py @@ -0,0 +1,113 @@ +import gymnasium as gym +from PIL import Image + +import nle # noqa: F401 + +env = gym.make("NetHack-v0", render_mode="pixel") +# env = gym.wrappers.AddRenderObservation( +# env, render_only=False, render_key="pixel", obs_key="glyphs" +# ) +env.unwrapped.seed(1234, 5678, False, 1) + + +frames = [] + +obs = env.reset() +frame = obs[0]["pixel"] +img = Image.fromarray(frame, "RGB") +frames.append(img) + +NORTH = 0 +WEST = 3 +SOUTH = 2 +EAST = 1 + +# Get out of starting room +steps = [EAST, EAST, NORTH, NORTH, WEST, WEST, WEST, WEST, WEST] +# Go to room two +steps += [ + SOUTH, + SOUTH, + SOUTH, + SOUTH, + SOUTH, + WEST, + SOUTH, + WEST, + SOUTH, + SOUTH, + SOUTH, + SOUTH, + SOUTH, + WEST, + SOUTH, + WEST, + WEST, +] +# Traverse room two +steps += [WEST, WEST, WEST, WEST, NORTH, NORTH, NORTH, NORTH] +# Go to room three +steps += [ + WEST, + NORTH, + NORTH, + WEST, + WEST, + NORTH, + NORTH, + WEST, + WEST, + NORTH, + NORTH, + WEST, + WEST, + WEST, + WEST, + WEST, + WEST, + WEST, + WEST, + NORTH, +] +# Traverse room three +steps += [NORTH, NORTH] +# Go back towards room two and kill the monster +steps += [SOUTH, SOUTH, SOUTH, EAST, EAST, EAST, EAST, EAST] +# Continue to room two +steps += [ + EAST, + EAST, + EAST, + SOUTH, + SOUTH, + EAST, + EAST, + SOUTH, + SOUTH, + EAST, + EAST, + SOUTH, + SOUTH, + EAST, + SOUTH, + SOUTH, +] +# Traverse room two to other exit +steps += [WEST, WEST, WEST, WEST, WEST, WEST, WEST, WEST, WEST, NORTH] +# Go to room four +steps += [NORTH, NORTH, NORTH, NORTH] + +for action in range(len(steps)): + obs = env.step(steps[action]) + env.unwrapped.nethack.draw_frame(frame) + img = Image.fromarray(frame, "RGB") + frames.append(img) + +print(f"Saving animation with {len(frames)} frames...") +frames[0].save( + "nethack_tiles_animation.gif", + save_all=True, + append_images=frames[1:], + duration=400, + loop=0, +) From a7ac1f78ece93d2566851ec0a8d36122b3df32dd Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 23 Feb 2026 15:04:55 +0000 Subject: [PATCH 14/19] Remove temporary scaffolding files --- pixels.py | 113 -------------------------------------------------- testframes.py | 21 ---------- 2 files changed, 134 deletions(-) delete mode 100644 pixels.py delete mode 100644 testframes.py diff --git a/pixels.py b/pixels.py deleted file mode 100644 index 33b4fef22..000000000 --- a/pixels.py +++ /dev/null @@ -1,113 +0,0 @@ -import gymnasium as gym -from PIL import Image - -import nle # noqa: F401 - -env = gym.make("NetHack-v0", render_mode="pixel") -env = gym.wrappers.AddRenderObservation( - env, render_only=False, render_key="pixel", obs_key="glyphs" -) -env.unwrapped.seed(1234, 5678, False, 1) - - -frames = [] - -obs = env.reset() -frame = obs[0]["pixel"] -img = Image.fromarray(frame, "RGB") -frames.append(img) - -NORTH = 0 -WEST = 3 -SOUTH = 2 -EAST = 1 - -# Get out of starting room -steps = [EAST, EAST, NORTH, NORTH, WEST, WEST, WEST, WEST, WEST] -# Go to room two -steps += [ - SOUTH, - SOUTH, - SOUTH, - SOUTH, - SOUTH, - WEST, - SOUTH, - WEST, - SOUTH, - SOUTH, - SOUTH, - SOUTH, - SOUTH, - WEST, - SOUTH, - WEST, - WEST, -] -# Traverse room two -steps += [WEST, WEST, WEST, WEST, NORTH, NORTH, NORTH, NORTH] -# Go to room three -steps += [ - WEST, - NORTH, - NORTH, - WEST, - WEST, - NORTH, - NORTH, - WEST, - WEST, - NORTH, - NORTH, - WEST, - WEST, - WEST, - WEST, - WEST, - WEST, - WEST, - WEST, - NORTH, -] -# Traverse room three -steps += [NORTH, NORTH] -# Go back towards room two and kill the monster -steps += [SOUTH, SOUTH, SOUTH, EAST, EAST, EAST, EAST, EAST] -# Continue to room two -steps += [ - EAST, - EAST, - EAST, - SOUTH, - SOUTH, - EAST, - EAST, - SOUTH, - SOUTH, - EAST, - EAST, - SOUTH, - SOUTH, - EAST, - SOUTH, - SOUTH, -] -# Traverse room two to other exit -steps += [WEST, WEST, WEST, WEST, WEST, WEST, WEST, WEST, WEST, NORTH] -# Go to room four -steps += [NORTH, NORTH, NORTH, NORTH] - -for action in range(len(steps)): - obs = env.step(steps[action]) - env.unwrapped.nethack.draw_frame(frame) - img = Image.fromarray(frame, "RGB") - frames.append(img) - -print(f"Saving animation with {len(frames)} frames...") -frames[0].save( - "nethack_tiles_animation.gif", - save_all=True, - append_images=frames[1:], - duration=400, - loop=0, -) diff --git a/testframes.py b/testframes.py deleted file mode 100644 index 465fd81be..000000000 --- a/testframes.py +++ /dev/null @@ -1,21 +0,0 @@ -# import matplotlib.pyplot as plt -import numpy as np -from PIL import Image - -from nle import _pynethack - -nh = _pynethack.Nethack(".", ".", "", False) - -tile_paths = [ - "/Users/stephenoman/Development/nle/win/share/monsters.txt", - "/Users/stephenoman/Development/nle/win/share/objects.txt", - "/Users/stephenoman/Development/nle/win/share/other.txt", -] -nh.setup_tiles(tile_paths) - -frame = np.zeros((432, 640, 3), dtype=np.uint8) -nh.get_tileset(frame) -print(frame) - -img = Image.fromarray(frame, "RGB") -img.save("tileset.png") From bd16502ef570fd85d0582fa25f166929a490e86a Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 23 Feb 2026 15:56:24 +0000 Subject: [PATCH 15/19] fix CMakeLists.txt lint issues --- util/CMakeLists.txt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 7b2861cb9..4f0194b67 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -92,15 +92,11 @@ add_executable(tilemap ${NLE_WIN}/share/tilemap.c) target_include_directories(tilemap PUBLIC ${NLE_INC} ${NLE_INC_GEN}) add_dependencies(tilemap util) -file( - GLOB - NETHACK_TILE_SRC - ${NLE_WIN}/rl/tile2rgb.c - ${NLE_WIN}/rl/nletiletxt.c - ${NLE_WIN}/share/tiletext.c -) +file(GLOB NETHACK_TILE_SRC ${NLE_WIN}/rl/tile2rgb.c ${NLE_WIN}/rl/nletiletxt.c + ${NLE_WIN}/share/tiletext.c) add_library(tile OBJECT ${NETHACK_TILE_SRC} ${NLE_SRC_GEN}/tile.c) -target_include_directories(tile PUBLIC ${NLE_INC} ${NLE_INC_GEN} ${NLE_WIN}/share) +target_include_directories(tile PUBLIC ${NLE_INC} ${NLE_INC_GEN} + ${NLE_WIN}/share) add_dependencies(tile generate_pm_h generate_tile_c) # NOTE: util is dependent on these two From 4ba6f5a0524c898cc88b3f4554055cb3288c6de0 Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 23 Feb 2026 16:07:57 +0000 Subject: [PATCH 16/19] Remove unneeded namespace --- nle/scripts/pixels.py | 6 +++--- win/rl/pynethack.cc | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/nle/scripts/pixels.py b/nle/scripts/pixels.py index 5125d9388..33b4fef22 100644 --- a/nle/scripts/pixels.py +++ b/nle/scripts/pixels.py @@ -4,9 +4,9 @@ import nle # noqa: F401 env = gym.make("NetHack-v0", render_mode="pixel") -# env = gym.wrappers.AddRenderObservation( -# env, render_only=False, render_key="pixel", obs_key="glyphs" -# ) +env = gym.wrappers.AddRenderObservation( + env, render_only=False, render_key="pixel", obs_key="glyphs" +) env.unwrapped.seed(1234, 5678, False, 1) diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index a4ac2c28f..aa23d029f 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -31,8 +31,6 @@ extern "C" { extern short glyph2tile[]; /* in tile.c (made from tilemap.c) */ extern int total_tiles_used; /* also in tile.c */ -namespace fs = std::filesystem; - /* Copy from dungeon.c. Necessary to add tile.c. Can't add dungeon.c itself as it pulls in too much. */ From 7aaaa80140b07444cde996f9b21f7c374e3cc9e5 Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 23 Feb 2026 17:52:00 +0000 Subject: [PATCH 17/19] Fix c and c++ lint issues --- win/rl/pynethack.cc | 128 ++++++++++++++++++++++++++------------------ win/rl/tile2rgb.c | 77 +++++++++++++------------- win/rl/tile2rgb.h | 4 +- 3 files changed, 116 insertions(+), 93 deletions(-) diff --git a/win/rl/pynethack.cc b/win/rl/pynethack.cc index aa23d029f..e4d1bbcf6 100644 --- a/win/rl/pynethack.cc +++ b/win/rl/pynethack.cc @@ -2,7 +2,6 @@ #include #include #include -#include #include #include @@ -28,7 +27,7 @@ extern "C" { #undef min #undef max -extern short glyph2tile[]; /* in tile.c (made from tilemap.c) */ +extern short glyph2tile[]; /* in tile.c (made from tilemap.c) */ extern int total_tiles_used; /* also in tile.c */ /* Copy from dungeon.c. Necessary to add tile.c. @@ -371,32 +370,36 @@ class Nethack boolean setup_tileset(std::array tilefiles) { - tileset = (tile_t *) calloc(sizeof(tile_t), (size_t) total_tiles_used); - if(!tileset) { - throw std::runtime_error("Unable to allocate memory for tileset."); + tileset = + (tile_t *) calloc(sizeof(tile_t), (size_t) total_tiles_used); + if (!tileset) { + throw std::runtime_error( + "Unable to allocate memory for tileset."); } - const char *tilefile_ptrs[3] = { - tilefiles[0].c_str(), - tilefiles[1].c_str(), - tilefiles[2].c_str() - }; + const char *tilefile_ptrs[3] = { tilefiles[0].c_str(), + tilefiles[1].c_str(), + tilefiles[2].c_str() }; int tiles_read = init_rgb_tileset(tilefile_ptrs, 3, tileset); - if(tiles_read != 3) { - throw std::runtime_error("Unable to open tile file " + tilefiles[tiles_read] + - " for reading. Check that the file exists and is readable."); - } - + if (tiles_read != 3) { + throw std::runtime_error("Unable to open tile file " + + tilefiles[tiles_read] + + " for reading. Check that the file " + "exists and is readable."); + } + return true; } // Get the tileset as a numpy array of shape passed in as 'frame'. // This method is for testing the initialization of the tileset only. - void get_tileset(py::array_t frame) { - - if(!tileset) { - throw std::runtime_error("get_tileset() called but the tileset has not been initialized."); + void + get_tileset(py::array_t frame) + { + if (!tileset) { + throw std::runtime_error("get_tileset() called but the tileset " + "has not been initialized."); } auto buffer = frame.mutable_unchecked<3>(); @@ -404,18 +407,21 @@ class Nethack pybind11::size_t tile_rows = buffer.shape(0) / TILE_Y; pybind11::size_t tile_cols = buffer.shape(1) / TILE_X; - if(tile_rows * tile_cols > (pybind11::size_t) total_tiles_used) { - throw std::runtime_error("Requested more tiles than available in tileset (available: " + - std::to_string(total_tiles_used) + ", requested: " + - std::to_string(tile_rows * tile_cols) + ")."); + if (tile_rows * tile_cols > (pybind11::size_t) total_tiles_used) { + throw std::runtime_error( + "Requested more tiles than available in tileset (available: " + + std::to_string(total_tiles_used) + ", requested: " + + std::to_string(tile_rows * tile_cols) + ")."); return; } uint8_t *pixel_rgb = (uint8_t *) tileset; - for(pybind11::ssize_t tile_row = 0; tile_row < tile_rows; tile_row++) { - for(pybind11::ssize_t tile_col = 0; tile_col < tile_cols; tile_col++) { - for(pybind11::ssize_t y = 0; y < TILE_Y; y++) { + for (pybind11::ssize_t tile_row = 0; tile_row < tile_rows; + tile_row++) { + for (pybind11::ssize_t tile_col = 0; tile_col < tile_cols; + tile_col++) { + for (pybind11::ssize_t y = 0; y < TILE_Y; y++) { memcpy(&buffer((tile_row * TILE_Y) + y, (tile_col * TILE_X), 0), pixel_rgb, TILE_X * TILE_Z * sizeof(uint8_t)); @@ -425,45 +431,60 @@ class Nethack } } - void draw_frame(py::array_t frame) { - - if(!tileset) { - throw std::runtime_error("draw_frame() called but the tileset has not been initialized."); + void + draw_frame(py::array_t frame) + { + if (!tileset) { + throw std::runtime_error("draw_frame() called but the tileset " + "has not been initialized."); } - auto buffer = checked_conversion(frame, { ROWNO * TILE_Y, (COLNO - 1) * TILE_X, TILE_Z }); - - int frame_width = (COLNO - 1) * TILE_X * TILE_Z; + auto buffer = checked_conversion( + frame, { ROWNO * TILE_Y, (COLNO - 1) * TILE_X, TILE_Z }); + + int frame_width = (COLNO - 1) * TILE_X * TILE_Z; - for(int tile_row = 0; tile_row < ROWNO; tile_row++) { - for(int tile_col = 0; tile_col < (COLNO - 1); tile_col++) { - // figure out which tile to copy from the glyph at this position - short int glyph = obs_.glyphs[(tile_row * (COLNO - 1)) + tile_col]; + for (int tile_row = 0; tile_row < ROWNO; tile_row++) { + for (int tile_col = 0; tile_col < (COLNO - 1); tile_col++) { + // figure out which tile to copy from the glyph at this + // position + short int glyph = + obs_.glyphs[(tile_row * (COLNO - 1)) + tile_col]; - // only update the tile if the glyph has changed since last time - if(glyph == prev_glyphs[(tile_row * (COLNO - 1)) + tile_col]) { + // only update the tile if the glyph has changed since last + // time + if (glyph + == prev_glyphs[(tile_row * (COLNO - 1)) + tile_col]) { continue; } int tile_index = glyph2tile[glyph]; - // Check tile_index is within bounds of the tileset. If not, log and skip this tile. - if(tile_index < 0 || tile_index >= total_tiles_used) { - fprintf(stderr, "Invalid tile index %d for glyph %d at position (%ld,%ld)\n", tile_index, glyph, tile_row, tile_col); + // Check tile_index is within bounds of the tileset. If not, + // log and skip this tile. + if (tile_index < 0 || tile_index >= total_tiles_used) { + fprintf(stderr, + "Invalid tile index %d for glyph %d at position " + "(%ld,%ld)\n", + tile_index, glyph, tile_row, tile_col); continue; } tile_t *tile_data = &(tileset[tile_index]); - uint8_t *frame_tile = buffer + (tile_row * frame_width * TILE_Y) + (tile_col * TILE_X * TILE_Z); + uint8_t *frame_tile = buffer + + (tile_row * frame_width * TILE_Y) + + (tile_col * TILE_X * TILE_Z); - for(int pixel_row = 0; pixel_row < TILE_Y; pixel_row++) { + for (int pixel_row = 0; pixel_row < TILE_Y; pixel_row++) { memcpy(frame_tile + (pixel_row * frame_width), - &(tile_data->tile[pixel_row][0]), TILE_X * TILE_Z * sizeof(uint8_t)); + &(tile_data->tile[pixel_row][0]), + TILE_X * TILE_Z * sizeof(uint8_t)); } } } // store glyphs for faster tile rendering next time this is called - std::copy(obs_.glyphs, obs_.glyphs + ROWNO * (COLNO - 1), prev_glyphs); + std::copy(obs_.glyphs, obs_.glyphs + ROWNO * (COLNO - 1), + prev_glyphs); } private: @@ -484,9 +505,10 @@ class Nethack /* Once the seeds have been used, prevent them being reused. */ settings_.initial_seeds.use_init_seeds = false; settings_.initial_seeds.use_lgen_seed = false; - - if(tileset) { - // reset previous glyphs to force full redraw on first draw_frame call + + if (tileset) { + // reset previous glyphs to force full redraw on first draw_frame + // call std::fill(std::begin(prev_glyphs), std::end(prev_glyphs), 0); } @@ -748,8 +770,8 @@ PYBIND11_MODULE(_pynethack, m) py::vectorize([](int glyph) { return glyph_is_warning(glyph); })); mn.attr("glyph2tile") = py::memoryview::from_buffer(glyph2tile, /*shape=*/{ MAX_GLYPH }, - /*strides=*/{ sizeof(glyph2tile[0]) }, - /*readonly=*/true); + /*strides=*/{ sizeof(glyph2tile[0]) }, + /*readonly=*/true); py::class_(mn, "permonst", "The permonst struct.") .def( @@ -808,7 +830,7 @@ PYBIND11_MODULE(_pynethack, m) "Argument should be between 0 and MAXMCLASSES (" + std::to_string(MAXMCLASSES) + ") but got " + std::to_string(let)); - return &def_monsyms[(int)let]; + return &def_monsyms[(int) let]; }, py::return_value_policy::reference) .def_static( @@ -819,7 +841,7 @@ PYBIND11_MODULE(_pynethack, m) "Argument should be between 0 and MAXOCLASSES (" + std::to_string(MAXOCLASSES) + ") but got " + std::to_string(olet)); - return &def_oc_syms[(int)olet]; + return &def_oc_syms[(int) olet]; }, py::return_value_policy::reference) .def_readonly("sym", &class_sym::sym) diff --git a/win/rl/tile2rgb.c b/win/rl/tile2rgb.c index 70a77afc4..1d354cc5b 100644 --- a/win/rl/tile2rgb.c +++ b/win/rl/tile2rgb.c @@ -1,8 +1,8 @@ -/* Converts the tile text descriptions in monsters.txt, objects.txt, and +/* Converts the tile text descriptions in monsters.txt, objects.txt, and other.txt into RGB pixels */ -#include #include "hack.h" +#include #include "tile2rgb.h" @@ -10,43 +10,44 @@ extern short glyph2tile[]; extern int total_tiles_used; -/* +/* Basically want to open the files, read the pixels and be done with it. Returns the number of files read sucessfully, so 0 == failure. */ -int init_rgb_tileset(const char *filenames[], int filecount, tile_t *tileset) { - - if(!filenames || filecount <= 0) { - // no files to read, return 0 - return 0; - } - - if(!tileset) { - // function was called without memory being allocated - return 0; - } - - pixel tile[TILE_Y][TILE_X]; - tile_t *tile_ptr = tileset; - - for(int f=0; f Date: Mon, 9 Mar 2026 19:46:16 +0000 Subject: [PATCH 18/19] Merge conflict edit --- CMakeLists.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 250410b7b..3078be921 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,13 +47,6 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_compile_options(-Wno-deprecated-non-prototype) add_compile_options(-Wno-unused-variable) -# We use this to decide where the root of the nle/ package is. Normally it -# shouldn't be needed, but sometimes (e.g. when using setuptools) we are -# generating some of the files outside of the original package path. -set(PYTHON_SRC_PARENT - ${nle_SOURCE_DIR} - CACHE STRING "Directory containing the nle package files") - set(HACKDIR "$ENV{HOME}/nethackdir.nle" CACHE STRING "Configuration files for nethack") From 6d32c9e4bfc135499fee5116fca133ef012e6fe7 Mon Sep 17 00:00:00 2001 From: Stephen Oman Date: Mon, 9 Mar 2026 19:56:36 +0000 Subject: [PATCH 19/19] Switch to using target_compile_options to reduce noisy warnings from NetHack --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3078be921..48a45e4b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,10 @@ set_target_properties(tmt PROPERTIES C_STANDARD 11) add_library(nethack SHARED ${NETHACK_SRC}) add_dependencies(nethack util dat) set_target_properties(nethack PROPERTIES CXX_STANDARD 14 SUFFIX ".so") +target_compile_options( + nethack + PRIVATE -Wno-deprecated-non-prototype + PRIVATE -Wno-unused-variable) target_include_directories( nethack PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${NLE_INC_GEN} /usr/local/include