Skip to content

Instantly share code, notes, and snippets.

@stenno
Created February 4, 2019 20:12
Show Gist options
  • Save stenno/d6389c8e745ec06b87011ce1b4e96223 to your computer and use it in GitHub Desktop.
Save stenno/d6389c8e745ec06b87011ce1b4e96223 to your computer and use it in GitHub Desktop.
low level API for nethack in node.js
// https://nethackwiki.com/wiki/Vt_tiledata
const pty = require("node-pty");
const AnsiTerminal = require("node-ansiterminal").AnsiTerminal;
const AnsiParser = require("node-ansiparser");
const WAITING_DELAY = 100; // a short delay to capture all neccessary data
// we monkeypatch AnsiTerminal for vt_tiledata support
class TiledataTerminal extends AnsiTerminal {
constructor(cols, rows, scrollLength) {
super(cols, rows, scrollLength);
const old_inst_c = this.inst_c;
this.waiting = false;
this.current_window = 0;
this.inst_c = (collected, params, flag) => {
if (flag === "z") {
this.last_char = ''; // monkeypatching intensifies
this._rem_c = '';
this.wrap = false;
this.TILE(params);
} else {
old_inst_c.bind(this)(collected, params, flag);
}
}
this.inst_c = this.inst_c.bind(this);
}
getWindow(number) {
return this.screen.buffer.reduce((acc, row) => [...acc, ...row.cells.filter(cell => cell.tiles_window === number)], []);
}
TILE([check, command, arg1, arg2]) {
// lazy here
const handlers = [
this.handleStartGlyph,
this.handleEndGlyph,
this.handleSelectWindow,
this.handleWait
];
handlers[command].bind(this)(arg1, arg2);
}
handleStartGlyph(number, effect) {
this.screen.buffer[this.cursor.row].cells[this.cursor.col].glyph = number;
this.screen.buffer[this.cursor.row].cells[this.cursor.col].effect = effect;
this.screen.buffer[this.cursor.row].cells[this.cursor.col].tiles_window = this.current_window;
}
handleEndGlyph(_) {
// TBI
}
handleSelectWindow(number) {
this.current_window = number;
}
handleWait() {
this.waiting = true;
}
resetWait() {
this.waiting = false;
}
}
const terminal = new TiledataTerminal(80, 24, 500);
const parser = new AnsiParser(terminal);
const shell = "bash";
const ptyProcess = pty.spawn(shell, [], {
name: "xterm",
cols: 80,
rows: 24,
env: process.env
});
const promisifiedOnce = async (emitter, event) => {
return new Promise((resolve, reject) => emitter.once(event, data => resolve(data)));
};
// super nasty, pls don't kill me
// at some point i should figure out the window indices
const getAllWindows = () => [0,1,2,3,4,5,6, undefined].map(x => [x, terminal.getWindow(x)]);
ptyProcess.on("data", data => {
parser.parse(data);
console.log("data", data.length, terminal.waiting);
if (terminal.waiting) {
// we don't expect any more data here unless we send some more
terminal.current_window = 0;
terminal.waiting = false;
setTimeout(() => ptyProcess.emit("tiledata_waiting"), WAITING_DELAY);
}
});
ptyProcess.once("tiledata_waiting", () => {
console.log("we are waiting for the first time now");
doSomeThings().then(x => console.log("done...")).then(() =>
console.log(getAllWindows().map(([idx, window]) => [idx, window.length]))
);
});
const doSomeThings = async() => {
try {
await doInput(" ");
console.log(terminal.toString());
await doInput(" ");
console.log(terminal.toString());
await doInput("#version\n");
console.log(terminal.toString());
} catch (e) {
// yolo
}
};
const doInput = async (input) => {
ptyProcess.write(input);
return promisifiedOnce(ptyProcess, "tiledata_waiting");
};
ptyProcess.write("ssh nethack@eu.hardfought.org\nlstenno\nnotmypassword\nv");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment