const sqlite = require('better-sqlite3'); const db = new sqlite('the_big_db.db', { verbose: console.log }); var sockets = new Map(); function interpret(context, player, command) { // interpret and execute a command entered by a player. // // context: the location the player has entered the command in from. // may be null if it's executing from a script? // player: the id of the player object who typed in the command. // may? be null if it's executing from a script? used for output. // so arguably // command: the full string typed in by the player const socket = sockets.get(player) const words = command.trim().split(' '); try { socket?.send(`interpreting and executing the received command: ${command}`); //for the moment we'll only allow statements of the form "go north" with implicit subject: the player const [first, second, third, ...rest] = words; const verb = findVerb(first, context, player); console.log(`executing verb ${verb} towards ${player} on ${player} ${second} ${third}`); executeVerb(player, verb, player, second, third, ...rest) } catch (error) { socket?.send(`got an error! ${error.mesage}`); } /* // if the second word is a verb, but the first word is also a verb, what do we do? if (second in verbs) { // for now, assume that if the second word is a verb, it's the verb we want. executeVerb(player, verbs.get(second), first, third, ...rest); } else { // if the second word is not a verb, then the first word is the verb, and the player is the implied subject. executeVerb(player, verbs.get(first), player, second, third, ...rest) } */ } function findVerb(word, location, actor) { // returns the number of a verb which matches the requested word. // check for verbs on actor let verb = findVerbOnObject(word, actor); if (verb) return verb; const actorContents = getAttribute(actor, "contents"); for (const obj of (actorContents || [])) { verb = findVerbOnObject(word, obj); if (verb) return verb; } const locationContents = getAttribute(location, "contents"); for (const obj of (locationContents || [])) { verb = findVerbOnObject(word, obj); if (verb) return verb; } verb = findVerbOnObject(word, location); if (verb) return verb; return undefined; } function findVerbOnObject(word, object) { // check for verbs on a single object, and its chain of parents. return a matching verbId, or undefined if (!word) return undefined; let focus = object; while (focus) { const focusVerbs = getAttribute(focus, "verbs"); for (const verbId of (focusVerbs || [])) { const fullVerb = lookUpObject(verbId); if (!fullVerb) continue; // test our word against if (word.toLowerCase() == fullVerb.title.toLowerCase()) { return verbId; } } focus = getAttribute(focus, "parent"); } return undefined; } function executeVerb(outputObject, verbId, subject, object, ...rest) { const fullVerb = lookUpObject(verbId); if (!fullVerb) { return sockets.get(outputObject)?.send(`verb not found`); } sockets.get(outputObject)?.send(`verb found as ${fullVerb.name}`); const func = fullVerb.fn; if (!func) { return sockets.get(outputObject)?.send(`verb was found but didn't have a fn attribute`); } return fullVerb.fn(outputObject, subject, object, ...rest); } const objectQuery = db.prepare('select * from pages where number=? order by time desc'); function lookUpObject(number) { return hardCodedObjects(number) || objectQuery.get(number); } const attributeQuery = db.prepare(`select * from attributes where number=?`); function lookUpObjectAttributes(number) { return JSON.parse(attributeQuery.get(number).contents); } function hardCodedObjects(id) { // return objectQuery.get(id); if (id == 29) return { title: "look", verb: true, description: "send description of direct object to subject's socket", fn: (logReceiver, subject, object, ...rest) => { sockets.get(logReceiver)?.send(`you looked around! subject ${subject} object: ${object} args: ${rest}`); console.log(`${subject} looked at ${object} with args ${rest}, and output was directed to ${logReceiver}`); } } if (id == 31) return { title: "warp", verb: true, description: "this built-in function changes the player's location, as well as telling their client to move", fn: (logReceiver, subject, object, ...rest) => { const objectNum = verifyObjectReference(object); const subjectNum = verifyObjectReference(subject); setAttribute(subjectNum, "location", objectNum); sockets.get(logReceiver)?.send(`location change to: ${object}`); } } } const pullAttribute = db.prepare(`select * from attributes where number=?`); function getAttribute(obj, attributeName) { const attributeStore = pullAttribute.get(verifyObjectReference(obj)); const contents = JSON.parse(attributeStore.contents); if (contents.hasOwnProperty(attributeName)) { return contents[attributeName]; } if (contents.hasOwnProperty("parent")) { return getAttribute(contents.parent, attributeName); } return undefined; } function hasOwnAttribute(obj, attributeName) { const attributeStore = pullAttribute.get(verifyObjectReference(obj)); const contents = JSON.parse(attributeStore.contents); return contents.hasOwnProperty(attributeName); } const insertAttribute = db.prepare(`update or replace attributes set contents=:contents where number=:number`); function setAttribute(obj, attributeName, value) { const attributeStore = pullAttribute.get(verifyObjectReference(obj)); const contents = JSON.parse(attributeStore.contents); contents[attributeName] = value; insertAttribute.run({...attributeStore, contents: JSON.stringify(contents)}); } function deleteAttribute(obj, attributeName) { const attributeStore = pullAttribute.get(verifyObjectReference(obj)); const contents = JSON.parse(attributeStore.contents); delete contents["attributeName"]; insertAttribute.run({...attributeStore, contents: JSON.stringify(contents)}); } function verifyObjectReference(obj) { try { if (typeof(obj) === "string") { return Number(obj.replace("#", "")); } else if (typeof(obj) === "number") { return obj; } else if (typeof(obj.number) == "number") { return obj.number; } } catch (error) { throw new Error("Tried to get an attribute from something which wasn't an object"); } } module.exports = { interpret, sockets, lookUpObject, getAttribute, setAttribute, deleteAttribute, hasOwnAttribute };