cmake_minimum_required(VERSION 3.24)
project(nethack_mcp VERSION 0.1.0 LANGUAGES C CXX)
include(CTest)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
option(NETHACK_BUILD_ENGINE "Build the pinned NetHack library" ON)
include(FetchContent)
# Declare these before loading libmw. Its current CMake files declare the
# same projects without pins, so the application's declarations must win.
FetchContent_Declare(
httplib
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
GIT_TAG 278c2979e8c68468960c3073e28e1c51b098d6a4
GIT_SHALLOW FALSE)
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG 57cb5fb7a8ff30079751728234623230535a5c92
GIT_SHALLOW FALSE)
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG aa391dc0a56f8409e2e7aca6e7c9a9d766d44ce8
GIT_SHALLOW FALSE)
FetchContent_Declare(
libmw
GIT_REPOSITORY https://github.com/MetroWind/libmw.git
GIT_TAG 00f857d93fda0f0eb84bb8ab540b58063f1c91b2
GIT_SHALLOW FALSE)
FetchContent_Declare(
tomlplusplus
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
GIT_TAG v3.4.0
GIT_SHALLOW FALSE)
set(LIBMW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(LIBMW_BUILD_URL ON CACHE BOOL "" FORCE)
set(LIBMW_BUILD_HTTP_SERVER ON CACHE BOOL "" FORCE)
set(LIBMW_BUILD_SQLITE ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(libmw)
FetchContent_MakeAvailable(tomlplusplus)
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED COMPONENTS Crypto)
if(NETHACK_BUILD_ENGINE)
FetchContent_Declare(
lua
URL https://www.lua.org/ftp/lua-5.4.9.tar.gz
URL_HASH SHA256=2335b6c582a52654f94612bf10d2f4672805d05329aa6568b1d8cd9e5c6fb8e6
DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
# The upstream archive has no CMakeLists.txt; MakeAvailable fetches it,
# and the static library target is defined here.
FetchContent_MakeAvailable(lua)
set(NETHACK_LUA_INCLUDE_DIR "${lua_SOURCE_DIR}/src")
add_library(nethack_lua STATIC
"${NETHACK_LUA_INCLUDE_DIR}/lapi.c"
"${NETHACK_LUA_INCLUDE_DIR}/lcode.c"
"${NETHACK_LUA_INCLUDE_DIR}/lctype.c"
"${NETHACK_LUA_INCLUDE_DIR}/ldebug.c"
"${NETHACK_LUA_INCLUDE_DIR}/ldo.c"
"${NETHACK_LUA_INCLUDE_DIR}/ldump.c"
"${NETHACK_LUA_INCLUDE_DIR}/lfunc.c"
"${NETHACK_LUA_INCLUDE_DIR}/lgc.c"
"${NETHACK_LUA_INCLUDE_DIR}/llex.c"
"${NETHACK_LUA_INCLUDE_DIR}/lmem.c"
"${NETHACK_LUA_INCLUDE_DIR}/lobject.c"
"${NETHACK_LUA_INCLUDE_DIR}/lopcodes.c"
"${NETHACK_LUA_INCLUDE_DIR}/lparser.c"
"${NETHACK_LUA_INCLUDE_DIR}/lstate.c"
"${NETHACK_LUA_INCLUDE_DIR}/lstring.c"
"${NETHACK_LUA_INCLUDE_DIR}/ltable.c"
"${NETHACK_LUA_INCLUDE_DIR}/ltm.c"
"${NETHACK_LUA_INCLUDE_DIR}/lundump.c"
"${NETHACK_LUA_INCLUDE_DIR}/lvm.c"
"${NETHACK_LUA_INCLUDE_DIR}/lzio.c"
"${NETHACK_LUA_INCLUDE_DIR}/lauxlib.c"
"${NETHACK_LUA_INCLUDE_DIR}/lbaselib.c"
"${NETHACK_LUA_INCLUDE_DIR}/lcorolib.c"
"${NETHACK_LUA_INCLUDE_DIR}/ldblib.c"
"${NETHACK_LUA_INCLUDE_DIR}/liolib.c"
"${NETHACK_LUA_INCLUDE_DIR}/lmathlib.c"
"${NETHACK_LUA_INCLUDE_DIR}/loadlib.c"
"${NETHACK_LUA_INCLUDE_DIR}/loslib.c"
"${NETHACK_LUA_INCLUDE_DIR}/lstrlib.c"
"${NETHACK_LUA_INCLUDE_DIR}/ltablib.c"
"${NETHACK_LUA_INCLUDE_DIR}/lutf8lib.c"
"${NETHACK_LUA_INCLUDE_DIR}/linit.c")
target_compile_features(nethack_lua PRIVATE c_std_99)
target_compile_definitions(nethack_lua PRIVATE LUA_USE_LINUX)
target_include_directories(nethack_lua PUBLIC
"${NETHACK_LUA_INCLUDE_DIR}")
target_link_libraries(nethack_lua PUBLIC m ${CMAKE_DL_LIBS})
set_target_properties(nethack_lua PROPERTIES
POSITION_INDEPENDENT_CODE ON)
FetchContent_Declare(
nethack
GIT_REPOSITORY https://github.com/NetHack/NetHack.git
GIT_TAG c94fd5225beef48143244bfb7bc42682aad58741
GIT_SHALLOW FALSE)
FetchContent_MakeAvailable(nethack)
set(NETHACK_WORK_DIR
"${CMAKE_BINARY_DIR}/nethack-work"
CACHE PATH "Isolated NetHack build directory")
set(NETHACK_LIBRARY "${NETHACK_WORK_DIR}/src/libnh.a")
set(NETHACK_COMPAT_LIBRARY
"${NETHACK_WORK_DIR}/src/libnethack.a")
set(NETHACK_RUNTIME_DIR
"${NETHACK_WORK_DIR}/playground")
set(NETHACK_BUILD_MARKER
"${NETHACK_WORK_DIR}/.nethack-library-built")
file(MAKE_DIRECTORY "${NETHACK_WORK_DIR}/include")
add_custom_command(
OUTPUT "${NETHACK_BUILD_MARKER}"
BYPRODUCTS
"${NETHACK_COMPAT_LIBRARY}"
"${NETHACK_RUNTIME_DIR}/nhdat"
"${NETHACK_RUNTIME_DIR}/symbols"
"${NETHACK_RUNTIME_DIR}/license"
"${NETHACK_RUNTIME_DIR}/sysconf"
COMMAND "${CMAKE_COMMAND}" -E rm -rf "${NETHACK_WORK_DIR}"
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${nethack_SOURCE_DIR}" "${NETHACK_WORK_DIR}"
COMMAND "${CMAKE_COMMAND}"
"-DNETHACK_SOURCE_DIR=${NETHACK_WORK_DIR}"
"-DNETHACK_LUA_INCLUDE_DIR=${NETHACK_LUA_INCLUDE_DIR}"
"-DNETHACK_LUA_LIBRARY=$<TARGET_FILE:nethack_lua>"
"-DNETHACK_COMPILER=${CMAKE_C_COMPILER}"
"-DNETHACK_ARCHIVER=${CMAKE_AR}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_nethack.cmake"
DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_nethack.cmake"
nethack_lua
VERBATIM)
add_custom_target(nethack_engine_build
DEPENDS "${NETHACK_BUILD_MARKER}")
add_library(nethack_lib STATIC IMPORTED GLOBAL)
set_target_properties(nethack_lib PROPERTIES
IMPORTED_LOCATION "${NETHACK_COMPAT_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES
"${NETHACK_WORK_DIR}/include"
INTERFACE_LINK_LIBRARIES nethack_lua)
add_dependencies(nethack_lib nethack_engine_build)
add_library(NetHack::libnethack ALIAS nethack_lib)
if(BUILD_TESTING)
add_executable(nethack_engine_probe src/engine_probe.cpp)
target_compile_options(nethack_engine_probe PRIVATE
-Wall -Wextra -Wpedantic)
target_compile_definitions(nethack_engine_probe PRIVATE
NETHACK_PROBE_RUNTIME_DIR="${NETHACK_RUNTIME_DIR}")
target_include_directories(nethack_engine_probe PRIVATE
"${NETHACK_WORK_DIR}/include")
target_link_libraries(nethack_engine_probe PRIVATE nethack_lib)
add_test(NAME nethack_engine_probe COMMAND nethack_engine_probe)
set_tests_properties(nethack_engine_probe PROPERTIES
WORKING_DIRECTORY "${NETHACK_RUNTIME_DIR}"
TIMEOUT 10)
endif()
else()
set(NETHACK_RUNTIME_DIR "${CMAKE_CURRENT_BINARY_DIR}")
endif()
set(NETHACK_MCP_SOURCES
src/config_file.cpp
src/engine_process.cpp
src/game_manager.cpp
src/game_record_store.cpp
src/game_session.cpp
src/identity.cpp
src/main.cpp
src/mcp_server.cpp
src/observation_store.cpp
src/protocol.cpp
src/game_http_server.cpp)
set(STATIC_FILES
"${CMAKE_CURRENT_SOURCE_DIR}/web/AGENT.md"
"${CMAKE_CURRENT_SOURCE_DIR}/web/copy_prompt.js"
"${CMAKE_CURRENT_SOURCE_DIR}/web/home.html"
"${CMAKE_CURRENT_SOURCE_DIR}/web/home.css"
"${CMAKE_CURRENT_SOURCE_DIR}/web/local_time.js"
"${CMAKE_CURRENT_SOURCE_DIR}/web/index.html"
"${CMAKE_CURRENT_SOURCE_DIR}/web/viewer.css"
"${CMAKE_CURRENT_SOURCE_DIR}/web/viewer.js"
"${CMAKE_CURRENT_SOURCE_DIR}/web/kreative_square.ttf")
set(EMBEDDED_ASSETS_SOURCE
"${CMAKE_CURRENT_BINARY_DIR}/generated/embedded_assets.cpp")
add_custom_command(
OUTPUT "${EMBEDDED_ASSETS_SOURCE}"
COMMAND "${CMAKE_COMMAND}"
"-DOUTPUT=${EMBEDDED_ASSETS_SOURCE}"
"-DWEB_ROOT=${CMAKE_CURRENT_SOURCE_DIR}/web"
"-DSTATIC_FILES=${STATIC_FILES}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed_assets.cmake"
DEPENDS ${STATIC_FILES} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed_assets.cmake"
VERBATIM)
list(APPEND NETHACK_MCP_SOURCES
"${EMBEDDED_ASSETS_SOURCE}")
if(NETHACK_BUILD_ENGINE)
list(APPEND NETHACK_MCP_SOURCES
src/engine_worker.cpp
src/window_adapter.cpp)
else()
list(APPEND NETHACK_MCP_SOURCES src/engine_worker_stub.cpp)
endif()
add_executable(nethack_mcp ${NETHACK_MCP_SOURCES})
target_compile_features(nethack_mcp PRIVATE cxx_std_23)
target_compile_options(nethack_mcp PRIVATE -Wall -Wextra -Wpedantic)
target_include_directories(nethack_mcp PRIVATE include)
target_compile_definitions(nethack_mcp PRIVATE
NETHACK_RUNTIME_DIR="${NETHACK_RUNTIME_DIR}")
target_link_libraries(nethack_mcp PRIVATE
mw::mw
mw::http-server
mw::sqlite
OpenSSL::Crypto
nlohmann_json::nlohmann_json
tomlplusplus::tomlplusplus
Threads::Threads)
if(NETHACK_BUILD_ENGINE)
target_include_directories(nethack_mcp PRIVATE
"${NETHACK_WORK_DIR}/include")
target_compile_definitions(nethack_mcp PRIVATE NETHACK_HAS_ENGINE=1)
target_link_libraries(nethack_mcp PRIVATE nethack_lib)
endif()