+++ /dev/null
- NETHACK GENERAL PUBLIC LICENSE
- (Copyright 1989 M. Stephenson)
-
- (Based on the BISON general public license,
- copyright 1988 Richard M. Stallman)
-
- Everyone is permitted to copy and distribute verbatim copies of this
- license, but changing it is not allowed. You can also use this wording to
- make the terms for other programs.
-
- The license agreements of most software companies keep you at the mercy of
-those companies. By contrast, our general public license is intended to give
-everyone the right to share NetHack. To make sure that you get the rights we
-want you to have, we need to make restrictions that forbid anyone to deny you
-these rights or to ask you to surrender the rights. Hence this license
-agreement.
-
- Specifically, we want to make sure that you have the right to give away
-copies of NetHack, that you receive source code or else can get it if you
-want it, that you can change NetHack or use pieces of it in new free
-programs, and that you know you can do these things.
-
- To make sure that everyone has such rights, we have to forbid you to
-deprive anyone else of these rights. For example, if you distribute copies
-of NetHack, you must give the recipients all the rights that you have. You
-must make sure that they, too, receive or can get the source code. And you
-must tell them their rights.
-
- Also, for our own protection, we must make certain that everyone finds out
-that there is no warranty for NetHack. If NetHack is modified by someone
-else and passed on, we want its recipients to know that what they have is
-not what we distributed.
-
- Therefore we (Mike Stephenson and other holders of NetHack copyrights) make
-the following terms which say what you must do to be allowed to distribute or
-change NetHack.
-
-
- COPYING POLICIES
-
- 1. You may copy and distribute verbatim copies of NetHack source code as
-you receive it, in any medium, provided that you keep intact the notices on
-all files that refer to copyrights, to this License Agreement, and to the
-absence of any warranty; and give any other recipients of the NetHack
-program a copy of this License Agreement along with the program.
-
- 2. You may modify your copy or copies of NetHack or any portion of it, and
-copy and distribute such modifications under the terms of Paragraph 1 above
-(including distributing this License Agreement), provided that you also do the
-following:
-
- a) cause the modified files to carry prominent notices stating that you
- changed the files and the date of any change; and
-
- b) cause the whole of any work that you distribute or publish, that in
- whole or in part contains or is a derivative of NetHack or any part
- thereof, to be licensed at no charge to all third parties on terms
- identical to those contained in this License Agreement (except that you
- may choose to grant more extensive warranty protection to some or all
- third parties, at your option)
-
- c) You may charge a distribution fee for the physical act of
- transferring a copy, and you may at your option offer warranty protection
- in exchange for a fee.
-
- 3. You may copy and distribute NetHack (or a portion or derivative of it,
-under Paragraph 2) in object code or executable form under the terms of
-Paragraphs 1 and 2 above provided that you also do one of the following:
-
- a) accompany it with the complete machine-readable source code, which
- must be distributed under the terms of Paragraphs 1 and 2 above; or,
-
- b) accompany it with full information as to how to obtain the complete
- machine-readable source code from an appropriate archive site. (This
- alternative is allowed only for noncommercial distribution.)
-
-For these purposes, complete source code means either the full source
-distribution as originally released over Usenet or updated copies of the
-files in this distribution used to create the object code or executable.
-
- 4. You may not copy, sublicense, distribute or transfer NetHack except as
-expressly provided under this License Agreement. Any attempt otherwise to
-copy, sublicense, distribute or transfer NetHack is void and your rights to
-use the program under this License agreement shall be automatically
-terminated. However, parties who have received computer software programs
-from you with this License Agreement will not have their licenses terminated
-so long as such parties remain in full compliance.
-
-
-Stated plainly: You are permitted to modify NetHack, or otherwise use parts
-of NetHack, provided that you comply with the conditions specified above;
-in particular, your modified NetHack or program containing parts of NetHack
-must remain freely available as provided in this License Agreement. In
-other words, go ahead and share NetHack, but don't try to stop anyone else
-from sharing it farther.
+++ /dev/null
-# NetHack
-This is the ACTUAL NetHack game, originally written in 1982 and one of the longest-standing open source collaborations. This isn't a wrapper around the binary NetHack, but the game itself compiled into [WebAssembly](https://webassembly.org/) (WASM) using [emscripten](https://emscripten.org/). This module should run [anywhere WebAssembly is supported](https://developer.mozilla.org/en-US/docs/WebAssembly#Browser_compatibility) including node.js and modern browsers.
-
-Since NetHack typically uses the [TTY](https://en.wikipedia.org/wiki/Computer_terminal#Text_terminals) to show depictions of the game and that won't work for WebAssembly, you are required to implement the graphics portion of NetHack to make this work. This allows a wide variety of UIs to be created, both text and graphics based as well as using keyboard and mouse to control the game. The API for implementing graphics is described below.
-
-## Install
-
-``` sh
-npm install nethack
-```
-
-## API
-The main module returns a setup function: `startNethack(uiCallback, moduleOptions)`.
-* `uiCallback(name, ... args)` - Your callback function that will handle rendering NetHack on the screen of your choice. The `name` argument is one of the UI functions of the [NetHack Window Interface](https://github.com/NetHack/NetHack/blob/NetHack-3.7/doc/window.doc) and the `args` are corresponding to the window interface function that is being called. You are required to return the correct type of data for the function that is implemented. The `uiCallback` may be an `async` function.
-* `moduleOptions` - An optional [emscripten Module object](https://emscripten.org/docs/api_reference/module.html) for configuring the WASM that will be run.
- * `Module.arguments` - Of note is the [arguments property](https://emscripten.org/docs/api_reference/module.html#Module.arguments) which gets passed to NetHack as its [command line parameters](https://nethackwiki.com/wiki/Options).
-
-There are a number of auxilary functions and variables that may help with building your applications. All of these are under `globalThis.nethackOptions`. Use `console.log(globalThis.nethackOptions)` for a full list of options. Some worth mentioning are:
-* `globalThis.nethackOptions.helpers` - Helper functions that are useful for NetHack windowing ports
- * `globalThis.nethackOptions.mapglyphHelper` - Converts an integer glyph into a character to be displayed. Useful if you are using ASCII characters for representing NetHack (as opposed to tiles). Interface is `mapglyphHelper(glyph, x, y, mgflags)` and will typically be called as part of the `shim_print_glyph` function.
-* `globalThis.nethackOptions.constants` - A Object full of constants that are `#define`'d in NetHack's C code. Useful for translating to / from numbers in the APIs and return values.
-
-## Example
-``` js
-let nethackStart = require("nethack");
-
-nethackStart(doGraphics);
-
-let winCount = 0;
-async function doGraphics(name, ... args) {
- console.log(`shim graphics: ${name} [${args}]`);
-
- switch(name) {
- case "shim_create_nhwindow":
- winCount++;
- console.log("creating window", args, "returning", winCount);
- return winCount;
- case "shim_yn_function":
- case "shim_message_menu":
- return 121; // return 'y' to all questions
- case "shim_nhgetch":
- case "shim_nh_poskey":
- return 0; // simulates a mouse click on "exit up the stairs"
- default:
- return 0;
- }
-}
-```
-
-## Other Notes
-* This module isn't small -- the WASM code is about 10MB. It may be slow to load over non-broadband connections. There are some emscripten build optimizations that may help with browser builds (such as dynamic loading), but those aren't currently part of this package. Pull requests are always welcome. :)
-* This hasn't been tested on browsers. If you get this to work on a browser, please let me know and I will add notes. Or if anyone wants to help setup automated browser testing, that would be supremely appreciated.
\ No newline at end of file
+++ /dev/null
-{
- "name": "@neth4ck/neth4ck",
- "version": "1.0.0",
- "lockfileVersion": 1
-}
+++ /dev/null
-{
- "name": "@neth4ck/neth4ck",
- "version": "1.0.2",
- "description": "The original NetHack rogue-like game built as a WebAssembly module",
- "main": "src/nethackShim.js",
- "scripts": {
- "test": "npm run build && node test/test.js",
- "clean": "rm ./build/nethack.js; rm ./build/nethack.wasm; true",
- "build": "mkdir build; cp ../../../targets/wasm/nethack.js ../../../targets/wasm/nethack.wasm ./build",
- "prepack": "npm run build"
- },
- "keywords": [
- "nethack",
- "rogue",
- "rogue-like",
- "roguelike",
- "dungeon",
- "dungeons",
- "game",
- "rpg",
- "dnd"
- ],
- "author": "Adam Powers <apowers@ato.ms>",
- "license": "SEE LICENSE IN LICENSE.md"
-}
+++ /dev/null
-const path = require("path");
-
-let Module;
-let userCallback;
-let savedOnRuntimeInitialized;
-
-// starts nethack
-function nethackStart(cb, inputModule = {}) {
- if (typeof cb !== "string" && typeof cb !== "function") {
- throw new TypeError("expected first argument to be 'Function' or 'String' representing global callback function name");
- }
-
- if (typeof inputModule !== "object") {
- throw new TypeError("expected second argument to be object");
- }
-
- let cbName;
- if (typeof cb === "function") {
- cbName = cb.name;
- if (cbName === "") {
- cbName = "__anonymousNetHackCallback";
- }
-
- if (globalThis[cbName] === undefined) {
- globalThis[cbName] = cb;
- } else if (globalThis[cbName] !== cb) {
- throw new Error(`'globalThis["${cbName}"]' is not the same as specified callback`);
- }
- }
-
- /* global globalThis */
- userCallback = globalThis[cbName];
- if (typeof userCallback !== "function") {
- throw new TypeError(`expected 'globalThis["${cbName}"]' to be a function`);
- }
- // if(userCallback.constructor.name !== "AsyncFunction") throw new TypeError(`expected 'globalThis["${cbName}"]' to be an async function`);
-
- // Emscripten Module config
- Module = inputModule;
- savedOnRuntimeInitialized = Module.onRuntimeInitialized;
- Module.onRuntimeInitialized = function(... args) {
- // after the WASM is loaded, add the shim graphics callback function
- Module.ccall(
- "shim_graphics_set_callback", // C function name
- null, // return type
- ["string"], // arg types
- [cbName], // arg values
- {async: true}, // options
- );
-
- // if the user had their own onRuntimeInitialized(), call it now
- if (savedOnRuntimeInitialized) {
- savedOnRuntimeInitialized(... args);
- }
- };
-
- // load and run the module
- let factory = require(path.join(__dirname, "../build/nethack.js"));
- factory(Module);
-}
-
-// TODO: ES6 'import' style module
-module.exports = nethackStart;
-
+++ /dev/null
-let nethackStart = require("../src/nethackShim.js");
-Error.stackTraceLimit = 20;
-
-// debugging to make sure the JavaScript event loop isn't blocked
-// const {performance} = require("perf_hooks");
-// let currentTime = 0;
-// let lastTime = 0;
-// setInterval(() => {
-// lastTime = currentTime;
-// currentTime = performance.now();
-// console.log("Time since last JavaScript loop:", currentTime-lastTime);
-// }, 10);
-
-let Module = {};
-let winCount = 0;
-
-/* global globalThis */
-nethackStart(async function (name, ... args) {
- switch(name) {
- case "shim_init_nhwindows":
- console.log("globalThis.nethackGlobal", globalThis.nethackGlobal);
- break;
- case "shim_create_nhwindow":
- winCount++;
- console.log("creating window", args, "returning", winCount);
- return winCount;
- case "shim_print_glyph":
- var x = args[1];
- var y = args[2];
- var glyph = args[3];
-
- var ret = globalThis.nethackGlobal.helpers.mapglyphHelper(glyph, x, y, 0);
- console.log(`GLYPH (${x},${y}): ${String.fromCharCode(ret.ch)}`);
- return;
- // case "shim_update_inventory":
- // globalThis.nethackGlobal.helpers.displayInventory();
- // return;
- case "shim_select_menu":
- return await selectMenu(...args);
- case "shim_yn_function":
- case "shim_message_menu":
- return 121; // 'y'
- case "shim_getmsghistory":
- return "";
- case "shim_nhgetch":
- case "shim_nh_poskey":
- return 0;
- default:
- console.log(`called doGraphics: ${name} [${args}]`);
- return 0;
- }
-}, Module);
-
-async function selectMenu(window, how, selected) {
- Module.setValue(selected, 0, "*");
- return -1;
-}
\ No newline at end of file
static char *shim_callback_name = NULL;
void shim_graphics_set_callback(char *cbName) {
if (shim_callback_name != NULL) free(shim_callback_name);
- shim_callback_name = strdup(cbName);
+ if(cbName && strlen(cbName) > 0) {
+ debugf("setting shim_callback_name: %s\n", cbName);
+ shim_callback_name = strdup(cbName);
+ } else {
+ debugf("un-setting shim_callback_name\n");
+ shim_callback_name = NULL;
+ }
/* TODO: free(shim_callback_name) during shutdown? */
}
void local_callback (const char *cb_name, const char *shim_name, void *ret_ptr, const char *fmt_str, void *args);
}
#endif /* __EMSCRIPTEN__ */
-enum win_types {
- WINSHIM_MESSAGE = 1,
- WINSHIM_MAP,
- WINSHIM_MENU,
- WINSHIM_EXT
-};
-
-#define VSTUB(name, args) \
-void name args { \
- printf ("Running " #name "...\n"); \
-}
-
-#define STUB(name, retval, args) \
-name args { \
- printf ("Running " #name "...\n"); \
- return retval; \
-}
-
-#define DECL(name, args) \
-void name args;
-
VDECLCB(shim_init_nhwindows,(int *argcp, char **argv), "vpp", P2V argcp, P2V argv)
VDECLCB(shim_player_selection,(void), "v")
VDECLCB(shim_askname,(void), "v")
jsArgs.push(val);
}
- decodeArgs(name, jsArgs);
-
// do the callback
let userCallback = globalThis[cbName];
runJsEventLoop(() => userCallback.call(this, name, ... jsArgs)).then((retVal) => {
}, 0);
});
- // make callback arguments friendly: convert numbers to strings where possible
- function decodeArgs(name, args) {
- // if (!globalThis.nethackGlobal.makeArgsFriendly) return;
-
- switch(name) {
- case "shim_create_nhwindow":
- args[0] = globalThis.nethackGlobal.constants["WIN_TYPE"][args[0]];
- break;
- case "shim_status_update":
- // which field is being updated?
- args[0] = globalThis.nethackGlobal.constants["STATUS_FIELD"][args[0]];
- // arg[1] is a string unless it is BL_CONDITION, BL_RESET, BL_FLUSH, BL_CHARACTERISTICS
- if(args[0] !== "BL_CONDITION" &&
- args[0] !== "BL_RESET" &&
- args[0] !== "BL_FLUSH" &&
- args[0] !== "BL_CHARACTERISTICS" &&
- args[1]) {
- args[1] = getArg(name, args[1], "s");
- } else {
- args[1] = getArg(name, args[1], "p");
- }
- break;
- case "shim_display_file":
- args[1] = !!args[1];
- case "shim_display_nhwindow":
- args[0] = decodeWindow(args[0]);
- args[1] = !!args[1];
- break;
- case "shim_getmsghistory":
- args[0] = !!args[0];
- break;
- case "shim_putmsghistory":
- args[1] = !!args[1];
- break;
- case "shim_status_enablefield":
- console.log("shim_status_enablefield arg 1:", args[1]);
- args[3] = !!args[3];
- break;
- case "shim_add_menu":
- args[0] = decodeWindow(args[0]);
- // args[1] = mapglyphHelper(args[1]);
- // args[5] = decodeAttr(args[5]);
- break;
- case "shim_putstr":
- args[0] = decodeWindow(args[0]);
- break;
- case "shim_select_menu":
- args[0] = decodeWindow(args[0]);
- args[1] = decodeSelected(args[1]);
- break;
- case "shim_clear_nhwindow":
- case "shim_destroy_nhwindow":
- case "shim_curs":
- case "shim_start_menu":
- case "shim_end_menu":
- case "shim_print_glyph":
- args[0] = decodeWindow(args[0]);
- break;
- }
- }
-
- function decodeWindow(winid) {
- let { WIN_MAP, WIN_INVEN, WIN_STATUS, WIN_MESSAGE } = globalThis.nethackGlobal.globals;
- switch(winid) {
- case WIN_MAP: return "WIN_MAP";
- case WIN_MESSAGE: return "WIN_MESSAGE";
- case WIN_STATUS: return "WIN_STATUS";
- case WIN_INVEN: return "WIN_INVEN";
- default: return winid;
- }
- // return winid;
- }
-
- function decodeSelected(how) {
- let { PICK_NONE, PICK_ONE, PICK_ANY } = globalThis.nethackGlobal.constants.MENU_SELECT;
- switch(how) {
- case PICK_NONE: return "PICK_NONE";
- case PICK_ONE: return "PICK_ONE";
- case PICK_ANY: return "PICK_ANY";
- default: return how;
- }
-
- }
-
function getArg(name, ptr, type) {
return (type === "o")?ptr:getPointerValue(name, getValue(ptr, "*"), type);
}