├── .gitignore
├── data
├── GUI.png
├── act1.png
├── act2.png
├── act3.png
├── player.png
├── robots.png
├── theend.png
├── background.png
├── braingames.png
├── sfx
│ ├── beep.wav
│ ├── doorOpen.wav
│ ├── itemPickup.wav
│ └── openContainer.wav
├── vehicles.png
├── player-suit.png
├── cutscene-diary1.png
├── cutscene-fungi1.png
├── cutscene-fungi2.png
├── cutscene-intro1.png
├── cutscene-intro2.png
├── cutscene-intro3.png
├── cutscene-msx1.png
├── shrdlu-title-bg.png
├── cutscene-corpse1.png
├── cutscene-crater1.png
├── cutscene-datapad1.png
├── cutscene-datapad2.png
├── cutscene-datapad3.png
├── cutscene-datapad5.png
├── cutscene-poster1.png
├── cutscene-poster2.png
├── cutscene-travel1.png
├── shrdlu-title-logo.png
├── cutscene-ending-A-2.png
├── cutscene-ending-A-3.png
├── cutscene-ending-A-5.png
├── cutscene-sax-diary1.png
├── cutscene-sax-diary3.png
├── player-suit-helmet.png
├── shrdlu-title-credits.png
├── cutscene-datapad4-male.png
├── cutscene-death-oxygen.png
├── cutscene-crashed-shuttle.png
├── cutscene-datapad4-female.png
├── cutscene-ending-A-1-male.png
├── cutscene-ending-A-4-male.png
├── cutscene-euriclea-diary2.png
├── cutscene-sax-diary2-male.png
├── cutscene-ending-A-1-female.png
├── cutscene-ending-A-4-female.png
├── cutscene-sax-diary2-female.png
├── cutscene-euriclea-diary1-male.png
├── cutscene-euriclea-diary1-female.png
├── etaoin-kb.xml
├── qwerty-kb.xml
├── baseclasses.xml
├── shrdlu-kb.xml
├── additional-kb-memoryrepair.xml
├── additional-kb-trantor.xml
├── additional-kb-ending-female.xml
├── additional-kb-ending-male.xml
├── additional-kb-datapad.xml
└── map5-east-cave.tmx
├── fonts
└── MSX68.ttf
├── misc
├── SHRDLU.pdf
├── SHRDLU-ss1.png
├── SHRDLU-ss2.png
└── sample.env
├── server
├── run.js
├── package.json
├── utilities.js
├── server.js
├── app.js
└── routes.js
├── src
├── a4engine3
│ ├── objects
│ │ ├── A4Food.ts
│ │ ├── A4Obstacle.ts
│ │ ├── A4Spade.ts
│ │ ├── A4Key.ts
│ │ ├── A4Item.ts
│ │ ├── A4PushableWall.ts
│ │ ├── A4PlayerCharacter.ts
│ │ ├── A4AICharacter.ts
│ │ ├── A4MapBridge.ts
│ │ ├── A4Lever.ts
│ │ ├── A4Trigger.ts
│ │ ├── A4PressurePlate.ts
│ │ └── A4WalkingObject.ts
│ ├── A4GraphicFile.ts
│ ├── A4MapTile.ts
│ └── A4Agenda.ts
├── blocksworld
│ ├── BWInferenceEffectFactory.ts
│ ├── BWStackAction.ts
│ ├── BWPutUnderAction.ts
│ ├── main.ts
│ ├── BWLocateAction.ts
│ └── ActionShrdluTalk.ts
├── shrdlu
│ ├── ShrdluA4ObjectFactory.ts
│ ├── ai
│ │ ├── inferences
│ │ │ └── A4InferenceEffectFactory.ts
│ │ ├── actions
│ │ │ ├── RobotReboot.ts
│ │ │ ├── RobotStop.ts
│ │ │ ├── EtaoinRead.ts
│ │ │ ├── A4Locate.ts
│ │ │ ├── EtaoinConnectTo.ts
│ │ │ ├── EtaoinSwitchOn.ts
│ │ │ ├── EtaoinSwitchOff.ts
│ │ │ ├── EtaoinHelp.ts
│ │ │ ├── A4IntentionActionFactory.ts
│ │ │ ├── EtaoinReboot.ts
│ │ │ └── RobotHelp.ts
│ │ ├── QwertyAI.ts
│ │ └── ShrdluAI.ts
│ ├── loggingToServer.ts
│ └── ShrdluAirlockDoor.ts
├── auxiliar
│ ├── GLTile.ts
│ ├── SFXManager.ts
│ ├── BFrame.ts
│ ├── auxiliar-gfx.ts
│ ├── BButton.ts
│ ├── auxiliar.ts
│ └── BInterfaceElement.ts
├── ai
│ ├── actions
│ │ ├── IntentionActionFactory.ts
│ │ ├── AnswerWhatIs.ts
│ │ ├── Call.ts
│ │ ├── AnswerHearSee.ts
│ │ ├── AnswerDefine.ts
│ │ ├── AnswerWhoIs.ts
│ │ ├── AnswerDistance.ts
│ │ └── AnswerHow.ts
│ ├── inferences
│ │ ├── InferenceEffectFactory.ts
│ │ ├── InferenceStopAction.ts
│ │ ├── InferenceAnswerHow.ts
│ │ ├── InferenceAnswerWhatIs.ts
│ │ ├── InferenceMemorize.ts
│ │ ├── InferenceAnswerHowMany.ts
│ │ └── InferenceAnswerWhy.ts
│ ├── TimeInference.ts
│ ├── Ontology.ts
│ └── Sort.ts
└── tests
│ └── definition-test.ts
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS & Cache Files
2 | .DS_Store
3 | Thumbs.db
4 | .Trashes
5 |
6 |
--------------------------------------------------------------------------------
/data/GUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/GUI.png
--------------------------------------------------------------------------------
/data/act1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/act1.png
--------------------------------------------------------------------------------
/data/act2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/act2.png
--------------------------------------------------------------------------------
/data/act3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/act3.png
--------------------------------------------------------------------------------
/data/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/player.png
--------------------------------------------------------------------------------
/data/robots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/robots.png
--------------------------------------------------------------------------------
/data/theend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/theend.png
--------------------------------------------------------------------------------
/fonts/MSX68.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/fonts/MSX68.ttf
--------------------------------------------------------------------------------
/misc/SHRDLU.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/misc/SHRDLU.pdf
--------------------------------------------------------------------------------
/data/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/background.png
--------------------------------------------------------------------------------
/data/braingames.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/braingames.png
--------------------------------------------------------------------------------
/data/sfx/beep.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/sfx/beep.wav
--------------------------------------------------------------------------------
/data/vehicles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/vehicles.png
--------------------------------------------------------------------------------
/misc/SHRDLU-ss1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/misc/SHRDLU-ss1.png
--------------------------------------------------------------------------------
/misc/SHRDLU-ss2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/misc/SHRDLU-ss2.png
--------------------------------------------------------------------------------
/data/player-suit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/player-suit.png
--------------------------------------------------------------------------------
/data/sfx/doorOpen.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/sfx/doorOpen.wav
--------------------------------------------------------------------------------
/data/cutscene-diary1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-diary1.png
--------------------------------------------------------------------------------
/data/cutscene-fungi1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-fungi1.png
--------------------------------------------------------------------------------
/data/cutscene-fungi2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-fungi2.png
--------------------------------------------------------------------------------
/data/cutscene-intro1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-intro1.png
--------------------------------------------------------------------------------
/data/cutscene-intro2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-intro2.png
--------------------------------------------------------------------------------
/data/cutscene-intro3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-intro3.png
--------------------------------------------------------------------------------
/data/cutscene-msx1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-msx1.png
--------------------------------------------------------------------------------
/data/sfx/itemPickup.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/sfx/itemPickup.wav
--------------------------------------------------------------------------------
/data/shrdlu-title-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/shrdlu-title-bg.png
--------------------------------------------------------------------------------
/data/cutscene-corpse1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-corpse1.png
--------------------------------------------------------------------------------
/data/cutscene-crater1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-crater1.png
--------------------------------------------------------------------------------
/data/cutscene-datapad1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-datapad1.png
--------------------------------------------------------------------------------
/data/cutscene-datapad2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-datapad2.png
--------------------------------------------------------------------------------
/data/cutscene-datapad3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-datapad3.png
--------------------------------------------------------------------------------
/data/cutscene-datapad5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-datapad5.png
--------------------------------------------------------------------------------
/data/cutscene-poster1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-poster1.png
--------------------------------------------------------------------------------
/data/cutscene-poster2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-poster2.png
--------------------------------------------------------------------------------
/data/cutscene-travel1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-travel1.png
--------------------------------------------------------------------------------
/data/sfx/openContainer.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/sfx/openContainer.wav
--------------------------------------------------------------------------------
/data/shrdlu-title-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/shrdlu-title-logo.png
--------------------------------------------------------------------------------
/data/cutscene-ending-A-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-ending-A-2.png
--------------------------------------------------------------------------------
/data/cutscene-ending-A-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-ending-A-3.png
--------------------------------------------------------------------------------
/data/cutscene-ending-A-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-ending-A-5.png
--------------------------------------------------------------------------------
/data/cutscene-sax-diary1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-sax-diary1.png
--------------------------------------------------------------------------------
/data/cutscene-sax-diary3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-sax-diary3.png
--------------------------------------------------------------------------------
/data/player-suit-helmet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/player-suit-helmet.png
--------------------------------------------------------------------------------
/data/shrdlu-title-credits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/shrdlu-title-credits.png
--------------------------------------------------------------------------------
/data/cutscene-datapad4-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-datapad4-male.png
--------------------------------------------------------------------------------
/data/cutscene-death-oxygen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-death-oxygen.png
--------------------------------------------------------------------------------
/data/cutscene-crashed-shuttle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-crashed-shuttle.png
--------------------------------------------------------------------------------
/data/cutscene-datapad4-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-datapad4-female.png
--------------------------------------------------------------------------------
/data/cutscene-ending-A-1-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-ending-A-1-male.png
--------------------------------------------------------------------------------
/data/cutscene-ending-A-4-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-ending-A-4-male.png
--------------------------------------------------------------------------------
/data/cutscene-euriclea-diary2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-euriclea-diary2.png
--------------------------------------------------------------------------------
/data/cutscene-sax-diary2-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-sax-diary2-male.png
--------------------------------------------------------------------------------
/data/cutscene-ending-A-1-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-ending-A-1-female.png
--------------------------------------------------------------------------------
/data/cutscene-ending-A-4-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-ending-A-4-female.png
--------------------------------------------------------------------------------
/data/cutscene-sax-diary2-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-sax-diary2-female.png
--------------------------------------------------------------------------------
/data/cutscene-euriclea-diary1-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-euriclea-diary1-male.png
--------------------------------------------------------------------------------
/data/cutscene-euriclea-diary1-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiontanon/SHRDLU/HEAD/data/cutscene-euriclea-diary1-female.png
--------------------------------------------------------------------------------
/server/run.js:
--------------------------------------------------------------------------------
1 | // External script for launching our server and app
2 |
3 | const server = require('./server.js')(function(server) {});
4 |
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4Food.ts:
--------------------------------------------------------------------------------
1 | class A4Food extends A4Item {
2 | constructor(name:string, sort:Sort, a:A4Animation, gold:number)
3 | {
4 | super(name, sort);
5 | this.animations[A4_ANIMATION_IDLE] = a;
6 | this.gold = gold;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4Obstacle.ts:
--------------------------------------------------------------------------------
1 | class A4Obstacle extends A4Object {
2 |
3 | constructor(name:string, sort:Sort)
4 | {
5 | super(name, sort);
6 | }
7 |
8 |
9 | isWalkable():boolean {return false;}
10 | };
11 |
--------------------------------------------------------------------------------
/src/blocksworld/BWInferenceEffectFactory.ts:
--------------------------------------------------------------------------------
1 | class BWInferenceEffectFactory extends InferenceEffectFactory {
2 | loadFromXML(xml:Element, ai:RuleBasedAI, o:Ontology, variables:TermAttribute[], variableNames:string[]) : InferenceEffect
3 | {
4 | if (xml.getAttribute("type") == "BWAnswerWhere_InferenceEffect") return BWAnswerWhere_InferenceEffect.loadFromXML(xml, ai, o, variables, variableNames);
5 |
6 | return super.loadFromXML(xml, ai, o, variables, variableNames);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/data/etaoin-kb.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/data/qwerty-kb.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/shrdlu/ShrdluA4ObjectFactory.ts:
--------------------------------------------------------------------------------
1 | class ShrdluA4ObjectFactory extends A4ObjectFactory {
2 |
3 | constructor()
4 | {
5 | super();
6 | this.baseClasses.push("ShrdluAirlockDoor");
7 | }
8 |
9 |
10 | createObjectFromBaseClass(baseClassName:string, s:Sort, o_name:string, isPlayer:boolean, dead:boolean) : A4Object
11 | {
12 | if (baseClassName == "ShrdluAirlockDoor") {
13 | return new ShrdluAirlockDoor(o_name, s);
14 | }
15 |
16 | return super.createObjectFromBaseClass(baseClassName, s, o_name, isPlayer, dead);
17 | }
18 | };
19 |
20 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/inferences/A4InferenceEffectFactory.ts:
--------------------------------------------------------------------------------
1 | class A4InferenceEffectFactory extends InferenceEffectFactory {
2 | loadFromXML(xml:Element, ai:RuleBasedAI, o:Ontology, variables:TermAttribute[], variableNames:string[]) : InferenceEffect
3 | {
4 | if (xml.getAttribute("type") == "AnswerHowGoto_InferenceEffect") return AnswerHowGoto_InferenceEffect.loadFromXML(xml, ai, o, variables, variableNames);
5 | if (xml.getAttribute("type") == "AnswerWhere_InferenceEffect") return AnswerWhere_InferenceEffect.loadFromXML(xml, ai, o, variables, variableNames);
6 |
7 | return super.loadFromXML(xml, ai, o, variables, variableNames);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/data/baseclasses.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/data/shrdlu-kb.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4Spade.ts:
--------------------------------------------------------------------------------
1 | class A4Spade extends A4Item {
2 | constructor(sort:Sort, a:A4Animation, gold:number)
3 | {
4 | super("Spade", sort);
5 | this.animations[A4_ANIMATION_IDLE] = a;
6 | this.gold = gold;
7 | this.usable = true;
8 | }
9 |
10 |
11 | event(a_event:number, otherCharacter:A4Character, map:A4Map, game:A4Game): boolean
12 | {
13 | let retval:boolean = super.event(a_event, otherCharacter, map, game);
14 |
15 | if (a_event == A4_EVENT_USE) {
16 | let o:A4Object = map.getBurrowedObject(otherCharacter.x, otherCharacter.y,
17 | otherCharacter.getPixelWidth(), otherCharacter.getPixelHeight());
18 | if (o==null) {
19 | game.addMessage("Nothing to dig here...");
20 | } else {
21 | o.burrowed = false;
22 | return true;
23 | }
24 | }
25 |
26 | return retval;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4Key.ts:
--------------------------------------------------------------------------------
1 | class A4Key extends A4Item {
2 |
3 | constructor(name:string, sort:Sort, id:string, a1:A4Animation)
4 | {
5 | super(name, sort);
6 | this.keyID = id;
7 | this.animations[A4_ANIMATION_IDLE] = a1;
8 | }
9 |
10 |
11 |
12 | loadObjectAttribute(attribute_xml:Element) : boolean
13 | {
14 | if (super.loadObjectAttribute(attribute_xml)) return true;
15 | let a_name:string = attribute_xml.getAttribute("name");
16 |
17 | if (a_name == "keyID") {
18 | this.keyID = attribute_xml.getAttribute("value");
19 | return true;
20 | }
21 |
22 | return false;
23 | }
24 |
25 |
26 | isKey() : boolean
27 | {
28 | return true;
29 | }
30 |
31 |
32 | savePropertiesToXML(game:A4Game) : string
33 | {
34 | let xmlString:string = super.savePropertiesToXML(game);
35 |
36 | xmlString += this.saveObjectAttributeToXML("keyID",this.keyID) + "\n";
37 |
38 | return xmlString;
39 | }
40 |
41 |
42 | keyID:string;
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/data/additional-kb-memoryrepair.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SHRDLU",
3 | "version": "3.5.0",
4 | "description": "An adventure game based around natural language parsing (inspired by Winograd's SHRDLU)",
5 | "author": "Santiago Ontañón",
6 | "license": "Apache-2.0",
7 | "main": "./app.js",
8 | "scripts": {
9 | "start": "node ./run.js",
10 | "dev": "set DEBUG=shrdlu:* && node ./run.js",
11 | "debug": "node --inspect ./run.js"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/santiontanon/SHRDLU.git"
16 | },
17 | "bugs": {
18 | "url": "https://github.com/santiontanon/SHRDLU/issues"
19 | },
20 | "homepage": "https://github.com/santiontanon/SHRDLU#readme",
21 | "dependencies": {
22 | "body-parser": "^1.19.0",
23 | "cookie-parser": "^1.4.4",
24 | "cors": "^2.8.5",
25 | "dotenv": "^8.2.0",
26 | "express": "^4.17.1",
27 | "express-jwt": "^5.3.1",
28 | "http-status": "^1.4.2",
29 | "jsonwebtoken": "^8.5.1",
30 | "morgan": "^1.9.1",
31 | "uuid": "^3.3.3"
32 | },
33 | "devDependencies": {
34 | "typescript": "^3.7.4"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/data/additional-kb-trantor.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/misc/sample.env:
--------------------------------------------------------------------------------
1 | # sample.env - for use with dotenv (https://www.npmjs.com/package/dotenv)
2 | #
3 | # 1) Create a copy of this file in this directory - it will be .gitignored
4 | # 2) Replace the values below to the values you need for a given run configuration (mongo auth info, etc.)
5 | # 3) Repeat as needed for however many run configs you need (dev, local db, production db, production)
6 | # 4) Copy/paste the file to the main directory and rename it to ".env" to have the app run in that config context
7 | #
8 | # Because this file is .gitignored, this allows us to keep sensitive information out of github
9 | # You can also create several different versions and swap them in and out to test multiple environments while developing
10 | # Add more variables for anything you wish to keep sensitive or that are expected to change from machine to machine
11 | # or when running in different contexts (development, testing, production).
12 | # If you do add another env variable, please update this sample.env and force git add so others know.
13 | #
14 |
15 | NODE_ENV=dev
16 | PORT=7002
17 | URL=https://127.0.0.1:7002/
18 | DB_USER=
19 | DB_PASS=
20 | DB_URL=localhost/shrdlu
21 | JWT_SECRET=somethingrandom23897134
22 | JWT_EXPIRATION_SECONDS=30d
23 |
--------------------------------------------------------------------------------
/server/utilities.js:
--------------------------------------------------------------------------------
1 | // utilities and helpful functions for the server
2 |
3 | const jwt = require('jsonwebtoken');
4 |
5 | // returns the token from the request header (authorization: 'Bearer ...')
6 | function getToken(req) {
7 | if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
8 | return req.headers.authorization.split(' ')[1];
9 | }
10 | return null;
11 | }
12 |
13 | // create authentication token json to return to the user
14 | function createAuthToken(sessionID) {
15 | let data = {
16 | sessionID
17 | };
18 | let token = jwt.sign(data, process.env.JWT_SECRET, {expiresIn: process.env.JWT_EXPIRATION_SECONDS});
19 | return token;
20 | }
21 |
22 | // returns the current time (+ msOffset) as ISO string
23 | function currentEpochTime(msOffset) {
24 | let nowDate = new Date();
25 | if (msOffset) {
26 | nowDate.setMilliseconds(nowDate.getMilliseconds() + msOffset);
27 | }
28 | return nowDate.getTime();
29 | }
30 |
31 | // creates an error object
32 | function error(status, message, title) {
33 | let ret = new Error(message);
34 | ret.status = status;
35 | ret.title = title || "Error";
36 | return ret;
37 | }
38 |
39 | module.exports = {
40 | getToken,
41 | createAuthToken,
42 | currentEpochTime,
43 | error
44 | };
45 |
--------------------------------------------------------------------------------
/src/blocksworld/BWStackAction.ts:
--------------------------------------------------------------------------------
1 | var ARM_MOVE_SPEED:number = 0.5;
2 |
3 | class BWStack_IntentionAction extends IntentionAction {
4 |
5 | constructor()
6 | {
7 | super();
8 | this.needsContinuousExecution = true;
9 | }
10 |
11 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
12 | {
13 | if (intention.functor.is_a(ai.o.getSort("action.stack"))) return true;
14 | return false;
15 | }
16 |
17 |
18 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
19 | {
20 | this.ir = ir;
21 | let ai:BlocksWorldRuleBasedAI = ai_raw;
22 | let requester:TermAttribute = ir.requester;
23 |
24 | // This is just a dummy action, it should never be executed (it only exists so that SHRDLU knows that it can
25 | // plan for this action)
26 |
27 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
28 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
29 | this.ir.succeeded = false;
30 | return true;
31 | }
32 |
33 |
34 | saveToXML(ai:RuleBasedAI) : string
35 | {
36 | let str = "";
38 | }
39 |
40 |
41 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
42 | {
43 | let a:BWStack_IntentionAction = new BWStack_IntentionAction();
44 | return a;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/blocksworld/BWPutUnderAction.ts:
--------------------------------------------------------------------------------
1 | var ARM_MOVE_SPEED:number = 0.5;
2 |
3 | class BWPutUnder_IntentionAction extends IntentionAction {
4 |
5 | constructor()
6 | {
7 | super();
8 | this.needsContinuousExecution = true;
9 | }
10 |
11 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
12 | {
13 | if (intention.functor.is_a(ai.o.getSort("action.put-under"))) return true;
14 | return false;
15 | }
16 |
17 |
18 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
19 | {
20 | this.ir = ir;
21 | let ai:BlocksWorldRuleBasedAI = ai_raw;
22 | let requester:TermAttribute = ir.requester;
23 |
24 | // This is just a dummy action, it should never be executed (it only exists so that SHRDLU knows that it can
25 | // plan for this action)
26 |
27 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
28 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
29 | this.ir.succeeded = false;
30 | return true;
31 | }
32 |
33 |
34 | saveToXML(ai:RuleBasedAI) : string
35 | {
36 | let str = "";
38 | }
39 |
40 |
41 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
42 | {
43 | let a:BWPutUnder_IntentionAction = new BWPutUnder_IntentionAction();
44 | return a;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | // Node.js server entry point
2 |
3 | const http = require('http');
4 | const debug = require('debug')('shrdlu:server');
5 | const app = require('./app');
6 |
7 | let server;
8 |
9 | // event listener for http server error event
10 | function onError(error) {
11 | console.log(error);
12 | if (error.syscall !== 'listen') {
13 | throw error;
14 | }
15 | const bind = typeof port === 'string'
16 | ? 'Pipe ' + process.env.PORT
17 | : 'Port ' + process.env.PORT;
18 | // handle specific listen errors with friendly messages
19 | switch (error.code) {
20 | case 'EACCES':
21 | console.error(bind + ' requires elevated privileges');
22 | process.exit(1);
23 | break;
24 | case 'EADDRINUSE':
25 | console.error(bind + ' is already in use');
26 | process.exit(1);
27 | break;
28 | default:
29 | throw error;
30 | }
31 | }
32 |
33 | // event listener for http server listening event
34 | function onListening() {
35 | const addr = server.address();
36 | const bind = typeof addr === 'string'
37 | ? 'pipe ' + addr
38 | : 'port ' + addr.port;
39 | debug('Listening on ' + bind);
40 | }
41 |
42 | // create http server
43 | function createServer(callback) {
44 | server = http.createServer(app);//https.createServer(cert, app);
45 | server.on('error', onError);
46 | server.on('listening', onListening);
47 |
48 | server.listen(process.env.PORT, callback(server));
49 | }
50 |
51 | module.exports = createServer;
52 |
--------------------------------------------------------------------------------
/src/auxiliar/GLTile.ts:
--------------------------------------------------------------------------------
1 | class GLTile {
2 | constructor(src:HTMLImageElement, x1:number, y1:number, width:number, height:number)
3 | {
4 | this.src = src;
5 | this.x1 = x1;
6 | this.y1 = y1;
7 | this.width = width;
8 | this.height = height;
9 | }
10 |
11 | draw(x:number, y:number)
12 | {
13 | ctx.drawImage(this.src, this.x1, this.y1, this.width, this.height,
14 | x, y, this.width, this.height);
15 | }
16 |
17 |
18 | drawWithZoom(x:number, y:number, zoom:number)
19 | {
20 | ctx.drawImage(this.src, this.x1, this.y1, this.width, this.height,
21 | x, y, this.width*zoom, this.height*zoom);
22 | }
23 |
24 |
25 | drawWithAlpha(x:number, y:number, alpha:number)
26 | {
27 | let tmp:number = ctx.globalAlpha;
28 | ctx.globalAlpha = alpha;
29 | ctx.drawImage(this.src, this.x1, this.y1, this.width, this.height,
30 | x, y, this.width, this.height);
31 | ctx.globalAlpha = tmp;
32 | }
33 |
34 |
35 | drawCentered(x:number, y:number)
36 | {
37 | ctx.drawImage(this.src, this.x1, this.y1, this.width, this.height,
38 | x-this.width/2, y-this.height/2, this.width, this.height);
39 | }
40 |
41 |
42 | src:HTMLImageElement;
43 | x1:number;
44 | y1:number;
45 | width:number;
46 | height:number;
47 | }
48 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/RobotReboot.ts:
--------------------------------------------------------------------------------
1 | class RobotReboot_IntentionAction extends IntentionAction {
2 |
3 |
4 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
5 | {
6 | if (intention.functor.is_a(ai.o.getSort("verb.reboot")) &&
7 | intention.attributes.length == 1) return true;
8 | return false;
9 | }
10 |
11 |
12 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
13 | {
14 | this.ir = ir;
15 | let ai:RobotAI = ai_raw;
16 | let requester:TermAttribute = ir.requester;
17 |
18 | // Reset the AI variables:
19 | for(let c of ai.contexts) c.reset();
20 | ai.intentions = [];
21 | ai.queuedIntentions = [];
22 | ai.intentionsCausedByRequest = [];
23 | ai.currentInferenceProcess = null;
24 | ai.queuedInferenceProcesses = [];
25 | ai.respondToPerformatives = true;
26 | ai.terminateConversationAfterThisPerformative = false;
27 | ai.clearCurrentAction();
28 |
29 | app.achievement_nlp_all_robot_actions[14] = true;
30 | app.trigger_achievement_complete_alert();
31 |
32 | if (requester != null) {
33 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))", ai.o);
34 | ai.queueIntention(term, requester, null);
35 | }
36 |
37 | return true;
38 | }
39 |
40 |
41 | saveToXML(ai:RuleBasedAI) : string
42 | {
43 | return "";
44 | }
45 |
46 |
47 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
48 | {
49 | let a:RobotReboot_IntentionAction = new RobotReboot_IntentionAction();
50 | return a;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/ai/actions/IntentionActionFactory.ts:
--------------------------------------------------------------------------------
1 | class IntentionActionFactory {
2 | loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
3 | {
4 | if (xml.getAttribute("type") == "AnswerDefine_IntentionAction") return AnswerDefine_IntentionAction.loadFromXML(xml, ai);
5 | if (xml.getAttribute("type") == "AnswerHearSee_IntentionAction") return AnswerHearSee_IntentionAction.loadFromXML(xml, ai);
6 | if (xml.getAttribute("type") == "AnswerHow_IntentionAction") return AnswerHow_IntentionAction.loadFromXML(xml, ai);
7 | if (xml.getAttribute("type") == "AnswerHowMany_IntentionAction") return AnswerHowMany_IntentionAction.loadFromXML(xml, ai);
8 | if (xml.getAttribute("type") == "AnswerPredicate_IntentionAction") return AnswerPredicate_IntentionAction.loadFromXML(xml, ai);
9 | if (xml.getAttribute("type") == "AnswerQuery_IntentionAction") return AnswerQuery_IntentionAction.loadFromXML(xml, ai);
10 | if (xml.getAttribute("type") == "AnswerWhatIs_IntentionAction") return AnswerWhatIs_IntentionAction.loadFromXML(xml, ai);
11 | if (xml.getAttribute("type") == "AnswerWhen_IntentionAction") return AnswerWhen_IntentionAction.loadFromXML(xml, ai);
12 | if (xml.getAttribute("type") == "AnswerWhoIs_IntentionAction") return AnswerWhoIs_IntentionAction.loadFromXML(xml, ai);
13 | if (xml.getAttribute("type") == "AnswerWhy_IntentionAction") return AnswerWhy_IntentionAction.loadFromXML(xml, ai);
14 | if (xml.getAttribute("type") == "Call_IntentionAction") return Call_IntentionAction.loadFromXML(xml, ai);
15 | if (xml.getAttribute("type") == "Memorize_IntentionAction") return Memorize_IntentionAction.loadFromXML(xml, ai);
16 | return null;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4Item.ts:
--------------------------------------------------------------------------------
1 | class A4Item extends A4Object {
2 |
3 | constructor(name:string, sort:Sort)
4 | {
5 | super(name, sort);
6 | this.takeable = true;
7 | this.burrowed = false;
8 | }
9 |
10 |
11 | loadObjectAttribute(attribute_xml:Element) : boolean
12 | {
13 | if (super.loadObjectAttribute(attribute_xml)) return true;
14 |
15 | let a_name:string = attribute_xml.getAttribute("name");
16 |
17 | if (a_name == "useUponTake") {
18 | this.useUponTake = false;
19 | if (attribute_xml.getAttribute("value") == "true") this.useUponTake = true;
20 | return true;
21 | } else if (a_name == "droppable") {
22 | this.droppable = false;
23 | if (attribute_xml.getAttribute("value") == "true") this.droppable = true;
24 | return true;
25 | } else if (a_name == "weight") {
26 | this.weight = Number(attribute_xml.getAttribute("value"));
27 | return true;
28 | }
29 | return false;
30 | }
31 |
32 |
33 | savePropertiesToXML(game:A4Game) : string
34 | {
35 | let xmlString:string = super.savePropertiesToXML(game);
36 |
37 | xmlString += this.saveObjectAttributeToXML("useUponTake",this.useUponTake) + "\n";
38 | xmlString += this.saveObjectAttributeToXML("droppable",this.droppable) + "\n";
39 | xmlString += this.saveObjectAttributeToXML("weight",this.weight) + "\n";
40 |
41 | return xmlString;
42 | }
43 |
44 |
45 | useUponTake:boolean = false;
46 | droppable:boolean = true;
47 | weight:number = 1;
48 | };
49 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | // main backend/server application entry point
2 |
3 | const bodyParser = require('body-parser');
4 | const cookieParser = require('cookie-parser');
5 | const express = require('express');
6 | const morgan = require('morgan');
7 | const cors = require('cors');
8 | const httpStatus = require('http-status');
9 | const path = require('path');
10 |
11 | require('dotenv').config();
12 |
13 | // app setup
14 | const app = express();
15 | app.set('port', process.env.PORT);
16 | app.use(morgan('dev'));
17 | app.use(bodyParser.json());
18 | app.use(bodyParser.urlencoded({extended: false}));
19 | app.use(cookieParser());
20 | app.use(cors());
21 |
22 | // by default, we serve URL requests from the TS build output directory
23 | app.use(express.static(path.join(__dirname, '..', 'built')));
24 |
25 | // routes
26 | require('./routes')(app);
27 |
28 | // catch any remaining path (not found) and forward to error handler
29 | app.use((req, res, next) => {
30 | const err = new Error('Not Found');
31 | err.status = httpStatus.NOT_FOUND;
32 | next(err);
33 | });
34 |
35 | // return the full error object in development
36 | const getError = function(err) {
37 | if (process.env.NODE_ENV === 'dev') {
38 | return { error: err, message: err.message };
39 | } else {
40 | return null;
41 | }
42 | };
43 |
44 | // error on /session (no JWT) should return json error
45 | app.use('/session', (err, req, res, next) => {
46 | res.status(err.status || httpStatus.NOT_FOUND);
47 | const errorJson = getError(err) || { error: err.message };
48 | res.json(errorJson);
49 | });
50 |
51 | // all other web errors return our app
52 | app.use((err, req, res, next) => {
53 | res.sendFile(path.join(__dirname, '..', 'built', 'shrdlu.html'));
54 | });
55 |
56 | module.exports = app;
57 |
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4PushableWall.ts:
--------------------------------------------------------------------------------
1 | class A4PushableWall extends A4Object {
2 |
3 | constructor(name:string, sort:Sort, a:A4Animation)
4 | {
5 | super(name, sort);
6 | this.animations[A4_ANIMATION_IDLE] = a;
7 | }
8 |
9 |
10 | isWalkable() : boolean
11 | {
12 | return false;
13 | }
14 |
15 |
16 | isPushable() : boolean
17 | {
18 | return true;
19 | }
20 |
21 |
22 | event(a_event:number, character:A4Character, map:A4Map, game:A4Game): boolean
23 | {
24 | let retval:boolean = super.event(a_event, character, map, game);
25 |
26 | if (a_event == A4_EVENT_PUSH &&
27 | character.canMoveIgnoringObject(character.direction, true, this) &&
28 | this.canMoveIgnoringObject(character.direction, true, character)) {
29 | let d:number = character.direction;
30 | this.x += direction_x_inc[d]*map.tileWidth;
31 | this.y += direction_y_inc[d]*map.tileHeight;
32 | if (character != null) map.reevaluateVisibilityRequest();
33 | return true;
34 | }
35 | return retval;
36 | }
37 |
38 |
39 | loadObjectAttribute(attribute_xml:Element) : boolean
40 | {
41 | if (super.loadObjectAttribute(attribute_xml)) return true;
42 | let a_name:string = attribute_xml.getAttribute("name");
43 |
44 | if (a_name == "weight") {
45 | this.weight = Number(attribute_xml.getAttribute("value"));
46 | return true;
47 | }
48 |
49 | return false;
50 | }
51 |
52 |
53 | savePropertiesToXML(game:A4Game) : string
54 | {
55 | let xmlString:string = super.savePropertiesToXML(game);
56 |
57 | xmlString += this.saveObjectAttributeToXML("weight",this.weight) + "\n";
58 |
59 | return xmlString;
60 | }
61 |
62 |
63 | weight:number = 1;
64 | }
65 |
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4PlayerCharacter.ts:
--------------------------------------------------------------------------------
1 | class A4PlayerCharacter extends A4Character {
2 | constructor(name:string, sort:Sort)
3 | {
4 | super(name, sort);
5 | }
6 |
7 |
8 | loadObjectAttribute(attribute_xml:Element) : boolean
9 | {
10 | if (super.loadObjectAttribute(attribute_xml)) return true;
11 |
12 | // This function is just so that we can reuse object class definitions between Players and AI characters.
13 | let a_name:string = attribute_xml.getAttribute("name");
14 | if (a_name == "sightRadius") {
15 | return true;
16 | } else if (a_name == "respawn") {
17 | return true;
18 | } else if (a_name == "AI.period") {
19 | return true;
20 | } else if (a_name == "AI.cycle") {
21 | return true;
22 | } else if (a_name == "respawnRecordID") {
23 | return true;
24 | }
25 |
26 | return false;
27 | }
28 |
29 |
30 | isPlayer() : boolean
31 | {
32 | return true;
33 | }
34 |
35 |
36 | nextItem()
37 | {
38 | for(let i:number = 0;i
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/data/additional-kb-ending-male.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/auxiliar/SFXManager.ts:
--------------------------------------------------------------------------------
1 | class SFXManagerNode {
2 |
3 | constructor(b:AudioBuffer, n:string)
4 | {
5 | this.buffer = b;
6 | this.name =n;
7 | }
8 |
9 | buffer:AudioBuffer;
10 | name:string;
11 | }
12 |
13 |
14 | class SFXManager {
15 | constructor()
16 | {
17 | console.log("SFXManager created.");
18 | }
19 |
20 |
21 | play(sfxName:string)
22 | {
23 | let sfx:SFXManagerNode = this.hash[sfxName];
24 |
25 | if (sfx == null) {
26 | // load it:
27 | let SFXM:SFXManager = this;
28 | let request:XMLHttpRequest = new XMLHttpRequest();
29 | request.open('GET', sfxName, true);
30 | request.responseType = 'arraybuffer';
31 | // Decode asynchronously
32 | request.onload = function() {
33 | audioCtx.decodeAudioData(request.response, function(buffer) {
34 | sfx = new SFXManagerNode(buffer, sfxName);
35 | SFXM.hash[sfxName] = sfx;
36 | SFXM.playInternal(sfx)
37 | });
38 | }
39 | request.send();
40 | } else {
41 | this.playInternal(sfx);
42 | }
43 | }
44 |
45 |
46 | playInternal(sfx:SFXManagerNode)
47 | {
48 | // do not play the same SFX more than one during the same game cycle (to avoid volume issues)
49 | if (this.already_played.indexOf(sfx)==-1) {
50 | this.already_played.push(sfx);
51 | let source:AudioBufferSourceNode = audioCtx.createBufferSource();
52 | source.buffer = sfx.buffer;
53 | source.connect(audioCtx.destination);
54 | source.start();
55 | }
56 | }
57 |
58 |
59 | // clears the list of already played SFX
60 | next_cycle()
61 | {
62 | this.already_played = [];
63 | }
64 |
65 |
66 | hash: { [id: string] : SFXManagerNode; } = {};
67 | already_played:SFXManagerNode[] = [];
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/src/auxiliar/BFrame.ts:
--------------------------------------------------------------------------------
1 | class BFrame extends BInterfaceElement {
2 | constructor(x:number, y:number, width:number, height:number)
3 | {
4 | super(x, y, width, height);
5 | }
6 |
7 |
8 | drawAlpha(alpha:number)
9 | {
10 | ctx.save();
11 | // background:
12 | let color:string = generateRGBColor(40, 40, 40);
13 | ctx.globalAlpha = 0.8*alpha;
14 | ctx.fillStyle = color;
15 | ctx.fillRect(this.x+2, this.y, this.width-4, this.height);
16 |
17 | // top bar:
18 | ctx.globalAlpha = alpha;
19 | ctx.fillStyle = "white";
20 | ctx.fillRect(this.x, this.y, this.width, 6);
21 |
22 | // bottom bar:
23 | ctx.fillRect(this.x, this.y + this.height-6, this.width, 6);
24 | ctx.restore();
25 | }
26 | }
27 |
28 |
29 | class BTextFrame extends BFrame {
30 | constructor(initial_text:string[], centered:boolean, font:string, fontHeight:number, x:number, y:number, width:number, height:number)
31 | {
32 | super(x, y, width, height);
33 |
34 | this.centered = centered;
35 | this.font = font;
36 | this.fontHeight = fontHeight;
37 | this.text = initial_text;
38 | }
39 |
40 |
41 | drawAlpha(alpha:number)
42 | {
43 | super.drawAlpha(alpha);
44 |
45 | let x:number = this.x + 10;
46 | let y:number = this.y + 10 + this.fontHeight;
47 | if (this.centered) {
48 | x = this.x + this.width/2;
49 | ctx.textAlign = "center";
50 | } else {
51 | ctx.textAlign = "left";
52 | }
53 | ctx.fillStyle = "white";
54 | ctx.font = this.font;
55 | ctx.textBaseline = "bottom";
56 | for(let line of this.text) {
57 | ctx.fillText(line, x, y);
58 | y += this.fontHeight + 4;
59 | }
60 | }
61 |
62 |
63 | centered:boolean = false;
64 | font:string = null;
65 | fontHeight:number = 8;
66 | text:string[] = null;
67 | }
68 |
--------------------------------------------------------------------------------
/src/shrdlu/loggingToServer.ts:
--------------------------------------------------------------------------------
1 | // get a new Session ID in a token and assign it to this game
2 | function assignNewSessionID(game:ShrdluA4Game) {
3 | requestTokenFromServer((err:string, token:string) => {
4 | if (err) {
5 | console.log(err);
6 | } else if (token != null && game != null) {
7 | game.serverToken = token;
8 | }
9 | });
10 | }
11 |
12 |
13 | // requests from the server an authenticated token with a new UUID for this gameplay session
14 | function requestTokenFromServer(callback:(err:string, token:string) => void) {
15 | let xhr = ((window).XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
16 | xhr.onload = function () {
17 | if (this.status === 200) {
18 | let json = JSON.parse(this.response);
19 | if (json.hasOwnProperty('token')) {
20 | return callback(null, json.token);
21 | }
22 | }
23 | return callback('Could not retrieve token', null);
24 | };
25 | xhr.open("GET", 'session');
26 | xhr.send();
27 | }
28 |
29 |
30 | function writeLogToServer(game:ShrdluA4Game) {
31 | let token:string = game.serverToken;
32 | if (!token) return;
33 | let xhr = ((window).XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
34 | xhr.onload = function () {
35 | if (this.status !== 201) console.log('Could not record debug log to server');
36 | };
37 | xhr.open("POST", 'session');
38 | xhr.setRequestHeader('Authorization', 'Bearer ' + token);
39 | xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
40 | xhr.send('data=' + generateDebugLog(game, true));
41 | }
42 |
43 |
44 | // extract the Session ID (UUID) from the server-provided authentication token
45 | function getIDFromSessionToken(token:string) {
46 | try {
47 | let decoded = token.split('.')[1].replace('-', '+').replace('_', '/');
48 | let json = JSON.parse((window).atob(decoded));
49 | return json.sessionID;
50 | } catch (e) {
51 | return '';
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/ai/inferences/InferenceStopAction.ts:
--------------------------------------------------------------------------------
1 | class StopAction_InferenceEffect extends InferenceEffect {
2 |
3 | constructor(action:Term)
4 | {
5 | super()
6 | this.action = action;
7 | }
8 |
9 |
10 | execute(inf:InferenceRecord, ai:RuleBasedAI)
11 | {
12 | if (inf.inferences.length == 1 &&
13 | inf.inferences[0].endResults.length > 0) {
14 | let speaker:string = inf.triggeredBySpeaker;
15 | let context:NLContext = ai.contextForSpeaker(speaker);
16 | let nlcp:NLContextPerformative = context.getNLContextPerformative(inf.triggeredBy);
17 | this.action = this.action.applyBindings(inf.inferences[0].endResults[0].bindings);
18 | if (ai.stopAction(this.action, speaker)) {
19 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok('"+context.speaker+"'[#id]))", ai.o);
20 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
21 | } else {
22 | let tmp:string = "action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest('"+speaker+"'[#id]))";
23 | let term:Term = Term.fromString(tmp, ai.o);
24 | ai.intentions.push(new IntentionRecord(term, new ConstantTermAttribute(speaker, ai.cache_sort_id), nlcp, null, ai.timeStamp));
25 | }
26 | } else {
27 | let tmp:string = "action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest('"+inf.triggeredBySpeaker+"'[#id]))";
28 | let term:Term = Term.fromString(tmp, ai.o);
29 | let cause:Term = Term.fromString("#not("+this.action+")", ai.o);
30 | ai.intentions.push(new IntentionRecord(term, null, null, new CauseRecord(cause, null, ai.timeStamp), ai.timeStamp));
31 | }
32 | }
33 |
34 |
35 | saveToXMLInternal(ai:RuleBasedAI, variables:TermAttribute[], variableNames:string[]) : string
36 | {
37 | return "";
38 | }
39 |
40 |
41 | static loadFromXML(xml:Element, ai:RuleBasedAI, o:Ontology, variables:TermAttribute[], variableNames:string[]) : InferenceEffect
42 | {
43 | let t:Term = Term.fromStringInternal(xml.getAttribute("action"), o, variableNames, variables).term;
44 | return new StopAction_InferenceEffect(t);
45 | }
46 |
47 |
48 | action:Term = null;
49 | }
50 |
--------------------------------------------------------------------------------
/data/additional-kb-datapad.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4AICharacter.ts:
--------------------------------------------------------------------------------
1 | class A4AICharacter extends A4Character {
2 |
3 | constructor(name:string, sort:Sort)
4 | {
5 | super(name, sort);
6 | this.AI = new A4PathFinding(this);
7 | }
8 |
9 |
10 | loadObjectAttribute(attribute_xml:Element) : boolean
11 | {
12 | if (super.loadObjectAttribute(attribute_xml)) return true;
13 | let a_name:string = attribute_xml.getAttribute("name");
14 |
15 | if (a_name == "AI.sightRadius" || a_name == "sightRadius") {
16 | this.AI.sightRadius = Number(attribute_xml.getAttribute("value"));
17 | return true;
18 | } else if (a_name == "AI.period") {
19 | this.AI.period = Number(attribute_xml.getAttribute("value"));
20 | return true;
21 | } else if (a_name == "AI.cycle") {
22 | this.AI.cycle = Number(attribute_xml.getAttribute("value"));
23 | return true;
24 | }
25 |
26 | return false;
27 | }
28 |
29 |
30 | savePropertiesToXML(game:A4Game) : string
31 | {
32 | let xmlString:string = super.savePropertiesToXML(game);
33 |
34 | xmlString += this.saveObjectAttributeToXML("AI.sightRadius",this.AI.sightRadius) + "\n";
35 | xmlString += this.saveObjectAttributeToXML("AI.period",this.AI.period) + "\n";
36 | xmlString += this.saveObjectAttributeToXML("AI.cycle",this.AI.cycle) + "\n";
37 |
38 | let tagOpen:boolean = false;
39 |
40 | for(let map_name of this.AI.maps_familiar_with) {
41 | if (!tagOpen) {
42 | xmlString += "\n";
43 | tagOpen = true;
44 | }
45 | xmlString += "\n";
46 | }
47 | if (tagOpen) xmlString += "\n";
48 |
49 | return xmlString;
50 | }
51 |
52 |
53 | update(game:A4Game) : boolean
54 | {
55 | if (!super.update(game)) return false;
56 | if (this.map != null) this.AI.update(game);
57 |
58 | return true;
59 | }
60 |
61 |
62 | isAICharacter():boolean
63 | {
64 | return true;
65 | }
66 |
67 |
68 | objectRemoved(o:A4Object)
69 | {
70 | super.objectRemoved(o);
71 | this.AI.objectRemoved(o);
72 | }
73 |
74 |
75 | AI:A4PathFinding = null;
76 | }
77 |
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4MapBridge.ts:
--------------------------------------------------------------------------------
1 | class A4MapBridge extends A4Object {
2 | constructor(bridge_xml:Element, map:A4Map)
3 | {
4 | super(null, null);
5 | this.name = bridge_xml.getAttribute("name");
6 | this.x = Number(bridge_xml.getAttribute("x"));
7 | this.y = Number(bridge_xml.getAttribute("y"));
8 | this.width = Number(bridge_xml.getAttribute("width"));
9 | this.height = Number(bridge_xml.getAttribute("height"));
10 | this.appearDirection = A4_DIRECTION_NONE;
11 | this.appearWalking = false;
12 | this.linkedTo = null;
13 | this.map = map;
14 | }
15 |
16 |
17 | link(b:A4MapBridge) {
18 | this.linkedTo = b;
19 | b.linkedTo = this;
20 | }
21 |
22 |
23 | findAvailableTargetLocation(o:A4Object, tile_dx:number, tile_dy:number) : [number, number]
24 | {
25 | let best_x:number, best_y:number;
26 | let best_d:number = null;
27 | for(let i:number = 0;i<=this.height-o.getPixelHeight();i+=tile_dy) {
28 | for(let j:number = 0;j<=this.width-o.getPixelWidth();j+=tile_dx) {
29 | // if (this.map.walkable(this.x+j, this.y+i, o.getPixelWidth(), o.getPixelHeight(), o)) {
30 | if (this.y+i >= 0) {
31 | if (this.map.walkable(this.x+j, this.y+i, o.getPixelWidth(), o.getPixelHeight(), o)) {
32 | let d:number = Math.abs((j+o.getPixelWidth()/2) - this.width/2) +
33 | Math.abs((i+o.getPixelHeight()/2) - this.height/2);
34 | if (best_d == null || dwindow).AudioContext || (window).webkitAudioContext)();
6 |
7 | var PIXEL_SIZE: number = 2;
8 | var WINDOW_WIDTH: number = 512*PIXEL_SIZE;
9 | var WINDOW_HEIGHT: number = 384*PIXEL_SIZE;
10 |
11 | var d:Date = new Date();
12 | var k:KeyboardState;
13 | var game_time:number;
14 | var init_time:number;
15 |
16 | var fonts_loaded:boolean = false;
17 |
18 | var global_mouse_x:number = 0;
19 | var global_mouse_y:number = 0;
20 |
21 | var fontFamily8px:string = "8px MSX68";
22 | var fontFamily16px:string = "16px MSX68";
23 |
24 |
25 | window.onload = () => {
26 | // get the canvas and set double buffering:
27 | canvas = document.getElementById('cnvs');
28 | ctx = canvas.getContext("2d");
29 | // replaces deprecated: ctx.mozImageSmoothingEnabled and ctx.webkitImageSmoothingEnabled
30 | ctx.imageSmoothingEnabled = false;
31 |
32 | app = new BlocksWorldApp(WINDOW_WIDTH, WINDOW_HEIGHT);
33 | k = new KeyboardState(-1);
34 |
35 | document.addEventListener('keydown', keyboardInputDown);
36 | document.addEventListener('keyup', keyboardInputUp);
37 |
38 | game_time = init_time = d.getTime();
39 |
40 | requestAnimationFrame(gameLoop);
41 | }
42 |
43 |
44 | function checkIfFontsAreLoaded():boolean {
45 | // This is a trick, since in JavaScript, there is no way to know if a font is loaded
46 | // I know how wide these images should be once the fonts are loaded, so, I wait for that!
47 | var tmp2:HTMLImageElement = getTextTile("TEST16", fontFamily16px, 16, "white");
48 | var tmp3:HTMLImageElement = getTextTile("TEST8", fontFamily8px, 8, "white");
49 | if (tmp2.width == 72 && tmp3.width == 30) {
50 | return true;
51 | }
52 | return false;
53 | }
54 |
55 |
56 | function keyboardInputDown(event: KeyboardEvent) {
57 | k.keyDown(event);
58 | }
59 |
60 |
61 | function keyboardInputUp(event: KeyboardEvent) {
62 | k.keyUp(event);
63 | }
64 |
65 |
66 | function gameLoop(timestamp: number) {
67 |
68 | if (!fonts_loaded) {
69 | fonts_loaded = checkIfFontsAreLoaded();
70 | } else {
71 | app.cycle(global_mouse_x, global_mouse_y, k);
72 |
73 | k.cycle();
74 | k.clearEvents();
75 | app.draw(WINDOW_WIDTH, WINDOW_HEIGHT);
76 | }
77 |
78 | requestAnimationFrame(gameLoop);
79 | }
80 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/RobotStop.ts:
--------------------------------------------------------------------------------
1 | class RobotStop_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.stop"))) return true;
6 | return false;
7 | }
8 |
9 |
10 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
11 | {
12 | this.ir = ir;
13 | let ai:RobotAI = ai_raw;
14 | let intention:Term = ir.action;
15 | let requester:TermAttribute = ir.requester;
16 |
17 | if (intention.attributes.length==1) {
18 | if (requester != null) {
19 | let tmp:string = "action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))";
20 | let term:Term = Term.fromString(tmp, ai.o);
21 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
22 | }
23 |
24 | app.achievement_nlp_all_robot_actions[3] = true;
25 | app.trigger_achievement_complete_alert();
26 |
27 | ai.clearCurrentAction();
28 | ai.addLongTermTerm(Term.fromString("verb.do('"+ai.selfID+"'[#id], 'nothing'[nothing])", ai.o), PERCEPTION_PROVENANCE);
29 | } else if (intention.attributes.length == 2 &&
30 | (intention.attributes[1] instanceof VariableTermAttribute) &&
31 | (intention.attributes[1].sort.is_a(ai.o.getSort("space.here")) ||
32 | intention.attributes[1].sort.is_a(ai.o.getSort("space.there")))) {
33 | if (requester != null) {
34 | let tmp:string = "action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))";
35 | let term:Term = Term.fromString(tmp, ai.o);
36 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
37 | }
38 |
39 | app.achievement_nlp_all_robot_actions[3] = true;
40 | app.trigger_achievement_complete_alert();
41 |
42 | ai.clearCurrentAction();
43 | ai.addLongTermTerm(Term.fromString("verb.do('"+ai.selfID+"'[#id], 'nothing'[nothing])", ai.o), PERCEPTION_PROVENANCE);
44 | } else {
45 | if (requester != null) {
46 | let tmp:string = "action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))";
47 | let term:Term = Term.fromString(tmp, ai.o);
48 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
49 | }
50 | }
51 |
52 | return true;
53 | }
54 |
55 |
56 | saveToXML(ai:RuleBasedAI) : string
57 | {
58 | return "";
59 | }
60 |
61 |
62 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
63 | {
64 | return new RobotStop_IntentionAction();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4Lever.ts:
--------------------------------------------------------------------------------
1 | class A4Lever extends A4Object {
2 |
3 | constructor(sort:Sort, ID:string, state:boolean, a_closed:A4Animation, a_open:A4Animation)
4 | {
5 | super("lever", sort);
6 | this.leverID = ID;
7 | this.leverState = state;
8 | this.animations[A4_ANIMATION_CLOSED] = a_closed;
9 | this.animations[A4_ANIMATION_OPEN] = a_open;
10 | if (this.leverState) this.currentAnimation = A4_ANIMATION_CLOSED;
11 | else this.currentAnimation = A4_ANIMATION_OPEN;
12 | this.usable = true;
13 | }
14 |
15 |
16 | loadObjectAttribute(attribute_xml:Element) : boolean
17 | {
18 | if (super.loadObjectAttribute(attribute_xml)) return true;
19 | let a_name:string = attribute_xml.getAttribute("name");
20 |
21 | if (a_name == "leverID") {
22 | this.leverID = attribute_xml.getAttribute("value");
23 | return true;
24 | } else if (a_name == "leverState") {
25 | this.leverState = false;
26 | if (attribute_xml.getAttribute("value") == "true") this.leverState = true;
27 | if (this.leverState) this.currentAnimation = A4_ANIMATION_CLOSED;
28 | else this.currentAnimation = A4_ANIMATION_OPEN;
29 | return true;
30 | }
31 | return false;
32 | }
33 |
34 |
35 | savePropertiesToXML(game:A4Game) : string
36 | {
37 | let xmlString:string = super.savePropertiesToXML(game);
38 |
39 | xmlString += this.saveObjectAttributeToXML("leverID",this.leverID) + "\n";
40 | xmlString += this.saveObjectAttributeToXML("leverState",this.leverState) + "\n";
41 |
42 | return xmlString;
43 | }
44 |
45 |
46 | event(event_type:number, character:A4Character, map:A4Map, game:A4Game): boolean
47 | {
48 | let retval:boolean = super.event(event_type, character, map, game);
49 | if (event_type == A4_EVENT_USE) {
50 | let s:A4Script = new A4Script(A4_SCRIPT_OPENDOORS, this.leverID, null, 0, false, false);
51 | s.execute(this, map, game, character);
52 |
53 | this.leverState = (this.leverState ? false:true);
54 | if (this.leverState) {
55 | this.event(A4_EVENT_ACTIVATE, character, this.map, game);
56 | } else {
57 | this.event(A4_EVENT_DEACTIVATE, character, this.map, game);
58 | }
59 | if (this.leverState) this.currentAnimation = A4_ANIMATION_CLOSED;
60 | else this.currentAnimation = A4_ANIMATION_OPEN;
61 | return true;
62 | }
63 | return retval;
64 | }
65 |
66 |
67 |
68 | leverID:string;
69 | leverState:boolean;
70 | }
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/EtaoinRead.ts:
--------------------------------------------------------------------------------
1 | class EtaoinRead_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if ((intention.functor.is_a(ai.o.getSort("verb.analyze")) ||
6 | intention.functor.is_a(ai.o.getSort("verb.examine")) ||
7 | intention.functor.is_a(ai.o.getSort("verb.read"))) &&
8 | intention.attributes.length >= 2) {
9 | let targetID:string = ((intention.attributes[1])).value;
10 | if (targetID == "tardis-memory-core") {
11 | return true;
12 | } else {
13 | return false;
14 | }
15 | }
16 | return false;
17 | }
18 |
19 |
20 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
21 | {
22 | this.ir = ir;
23 | let ai:EtaoinAI = ai_raw;
24 | let intention:Term = ir.action;
25 | let requester:TermAttribute = ir.requester;
26 | let targetID:string = ((intention.attributes[1])).value;
27 |
28 | console.log(ai.selfID + " read: " + intention);
29 |
30 | let item_tmp:A4Object = ai.game.findObjectByIDJustObject(targetID);
31 | if (item_tmp != null && (item_tmp instanceof A4Item)) {
32 | if (targetID == "tardis-memory-core") {
33 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform("+requester+", action.put-in("+requester+", '"+targetID+"'[#id], [console])))", ai.o);
34 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
35 | ir.succeeded = true;
36 | } else {
37 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
38 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
39 | ir.succeeded = false;
40 | }
41 | } else if (item_tmp == null) {
42 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
43 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
44 | ir.succeeded = false;
45 | } else {
46 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
47 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
48 | ir.succeeded = false;
49 | }
50 |
51 | return true;
52 | }
53 |
54 |
55 | saveToXML(ai:RuleBasedAI) : string
56 | {
57 | return "";
58 | }
59 |
60 |
61 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
62 | {
63 | return new EtaoinRead_IntentionAction();
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/src/ai/TimeInference.ts:
--------------------------------------------------------------------------------
1 | class TimeInference {
2 |
3 | // This function replaces all Sentences that have a past sentence by their immediately past value:
4 | static applyTimePast(sc:SentenceContainer) : SentenceContainer
5 | {
6 | let newsc:SentenceContainer = new SentenceContainer();
7 | console.log("TimeInference.applyTimePast!");
8 | for(let s of sc.previousSentencesWithNoCurrentSentence) {
9 | // console.log("from previousSentencesWithNoCurrentSentence: " + s.sentence);
10 | // we have to use "addStateSentenceIfNew" here, since some of these might overwrite each other...
11 | newsc.addStateSentenceIfNew(s.sentence, s.provenance, s.activation, s.time);
12 | }
13 |
14 | for(let s of sc.plainSentenceList) {
15 | if (s.previousInTime == null) {
16 | newsc.addSentence(s.sentence, s.provenance, s.activation, s.time);
17 | } else {
18 | // console.log("from plainSentenceList.previousInTime: " + s.previousInTime.sentence);
19 | newsc.addSentence(s.previousInTime.sentence, s.previousInTime.provenance, s.previousInTime.activation, s.previousInTime.time);
20 | }
21 | }
22 |
23 | return newsc;
24 | }
25 |
26 |
27 | static timeMatch(current:number, start:number, end:number, timeTerm:Term) : boolean
28 | {
29 | /*
30 | // TODO:
31 |
32 |
33 |
34 |
35 |
36 | */
37 | if (timeTerm.functor.name == "time.past") {
38 | if (start < current) return true;
39 | return false;
40 | } else if (timeTerm.functor.name == "time.present" ||
41 | timeTerm.functor.name == "time.now") {
42 | if (start <= current &&
43 | (end == null || end > current)) return true;
44 | } else if (timeTerm.functor.name == "time.future" ||
45 | timeTerm.functor.name == "time.later") {
46 | if (end == null || end > current) return true;
47 | } else if (timeTerm.functor.name == "time.today") {
48 | let dayStart:number = Math.floor(current / (60*60*24)) * 60*60*24;
49 | let dayEnd:number = dayStart + 60*60*24;
50 | if (start < dayEnd &&
51 | (end == null || end >= dayStart)) return true;
52 | } else if (timeTerm.functor.name == "time.yesterday") {
53 | let dayStart:number = (Math.floor(current / (60*60*24)) - 1) * 60*60*24;
54 | let dayEnd:number = dayStart + 60*60*24;
55 | if (start < dayEnd &&
56 | (end == null || end >= dayStart)) return true;
57 | }
58 | return false;
59 | }
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/A4Locate.ts:
--------------------------------------------------------------------------------
1 | class A4Locate_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.locate"))) return true;
6 | return false;
7 | }
8 |
9 |
10 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
11 | {
12 | this.ir = ir;
13 | let ai:A4RuleBasedAI = ai_raw;
14 | let requester:TermAttribute = ir.requester;
15 | let alternative_actions:Term[] = ir.alternative_actions;
16 | if (alternative_actions == null) alternative_actions = [ir.action];
17 | let denyrequestCause:Term = null;
18 |
19 | for(let intention of alternative_actions) {
20 | let targetID:string = ((intention.attributes[1])).value;
21 |
22 | // Check if it's an object:
23 | let targetObjectL:A4Object[] = ai.game.findObjectByID(targetID);
24 | if (targetObjectL == null) {
25 | denyrequestCause = Term.fromString("#not(verb.see('"+ai.selfID+"'[#id], '"+targetID+"'[#id]))", ai.o);
26 | continue;
27 | }
28 | this.targetObject = targetID;
29 |
30 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))", ai.o);
31 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
32 |
33 | // If the object was not mentioned explicitly in the performative, add it to the natural language context:
34 | if (ir.requestingPerformative != null) ir.requestingPerformative.addMentionToPerformative(targetID, ai.o);
35 | ir.succeeded = true;
36 | return true;
37 | }
38 |
39 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
40 | if (denyrequestCause == null) {
41 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
42 | } else {
43 | ai.intentions.push(new IntentionRecord(term, null, null, new CauseRecord(denyrequestCause, null, ai.timeStamp), ai.timeStamp));
44 | }
45 | ir.succeeded = false;
46 | return true;
47 | }
48 |
49 |
50 | saveToXML(ai:RuleBasedAI) : string
51 | {
52 | let str = "";
55 | } else {
56 | return str + " targetObject=\""+this.targetObject+"\"/>";
57 | }
58 | }
59 |
60 |
61 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
62 | {
63 | let a:A4Locate_IntentionAction = new A4Locate_IntentionAction();
64 | if (xml.getAttribute("targetObject") != null) {
65 | a.targetObject = xml.getAttribute("targetObject");
66 | }
67 | return a;
68 | }
69 |
70 | targetObject:string = null;
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/blocksworld/BWLocateAction.ts:
--------------------------------------------------------------------------------
1 | class BWLocate_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.locate"))) return true;
6 | return false;
7 | }
8 |
9 |
10 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
11 | {
12 | let ai:BlocksWorldRuleBasedAI = ai_raw;
13 | let world:ShrdluBlocksWorld = ai.world;
14 | let requester:TermAttribute = ir.requester;
15 | let alternative_actions:Term[] = ir.alternative_actions;
16 | if (alternative_actions == null) alternative_actions = [ir.action];
17 | let denyrequestCause:Term = null;
18 |
19 | for(let intention of alternative_actions) {
20 | let targetID:string = ((intention.attributes[1])).value;
21 |
22 | // Check if it's an object:
23 | this.targetObject = world.getObject(targetID);
24 | if (this.targetObject == null) {
25 | denyrequestCause = Term.fromString("#not(verb.see('"+ai.selfID+"'[#id], '"+targetID+"'[#id]))", ai.o);
26 | }
27 |
28 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))", ai.o);
29 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
30 |
31 | // If the object was not mentioned explicitly in the performative, add it to the natural language context:
32 | if (ir.requestingPerformative != null) ir.requestingPerformative.addMentionToPerformative(targetID, ai.o);
33 | return true;
34 | }
35 |
36 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
37 | if (denyrequestCause == null) {
38 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
39 | } else {
40 | ai.intentions.push(new IntentionRecord(term, null, null, new CauseRecord(denyrequestCause, null, ai.timeStamp), ai.timeStamp));
41 | }
42 | return true;
43 | }
44 |
45 |
46 | saveToXML(ai:RuleBasedAI) : string
47 | {
48 | let str = "";
51 | } else {
52 | return str + " targetObject=\""+this.targetObject.ID+"\"/>";
53 | }
54 | }
55 |
56 |
57 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
58 | {
59 | let a:BWLocate_IntentionAction = new BWLocate_IntentionAction();
60 | if (xml.getAttribute("targetObject") != null) {
61 | let world:ShrdluBlocksWorld = (ai).world;
62 | a.targetObject = world.getObject(xml.getAttribute("targetObject"));
63 | }
64 | return a;
65 | }
66 |
67 | targetObject:ShrdluBlock = null;
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/auxiliar/auxiliar-gfx.ts:
--------------------------------------------------------------------------------
1 | function generateRGBColor(r:number, g:number, b:number)
2 | {
3 | return "rgb(" + r + "," + g + "," + b + ")"
4 | }
5 |
6 |
7 | function fillTextTopLeft(text:string, x:number, y:number, font:string, color:string)
8 | {
9 | ctx.fillStyle = color;
10 | ctx.font = font;
11 | ctx.textBaseline = "top";
12 | ctx.textAlign = "left";
13 | ctx.fillText(text, x, y);
14 | }
15 |
16 |
17 | function fillTextTopCenter(text:string, x:number, y:number, font:string, color:string)
18 | {
19 | ctx.fillStyle = color;
20 | ctx.font = font;
21 | ctx.textBaseline = "top";
22 | ctx.textAlign = "center";
23 | ctx.fillText(text, x, y);
24 | }
25 |
26 |
27 | function fillTextTopRight(text:string, x:number, y:number, font:string, color:string)
28 | {
29 | ctx.fillStyle = color;
30 | ctx.font = font;
31 | ctx.textBaseline = "top";
32 | ctx.textAlign = "right";
33 | ctx.fillText(text, x, y);
34 | }
35 |
36 |
37 |
38 | // Code adapted from here: http://fabiensanglard.net/fizzlefade/index.php
39 | class FizzleFade {
40 |
41 | constructor(w:number, h:number)
42 | {
43 | this.width = w;
44 | this.height = h;
45 | }
46 |
47 |
48 | done() : boolean
49 | {
50 | return this.rndval == 1;
51 | }
52 |
53 |
54 | nextPixelToFizzle() : [number,number]
55 | {
56 | do {
57 | let y:number = this.rndval & 0x000FF;
58 | let x:number = (this.rndval & 0x1FF00) >> 8;
59 | let lsb:number = this.rndval & 1;
60 | this.rndval >>= 1;
61 | if (lsb != 0) {
62 | this.rndval ^= 0x00012000;
63 | }
64 | if (x < this.width && y<= this.height) return [x,y];
65 | } while(!this.done());
66 | return null;
67 | }
68 |
69 |
70 | rndval:number = 1;
71 | width:number = 256;
72 | height:number = 192;
73 | }
74 |
75 |
76 | // Cache to prevent generating them again and again!
77 | // note: we do not store colors not font, since for this particular game, they are always the same
78 | var textTilesWithOutline: { [text: string] : HTMLImageElement; } = {};
79 |
80 | function fillTextTopLeftWithOutline(text:string, x:number, y:number, font:string, color:string, outlineColor:string)
81 | {
82 | let img:HTMLImageElement;
83 |
84 | if (textTilesWithOutline[text] != null) {
85 | img = textTilesWithOutline[text];
86 | } else {
87 | img = getTextTileWithOutline(text, font, 8, color, outlineColor);
88 | textTilesWithOutline[text] = img;
89 | }
90 | // draw it:
91 | ctx.drawImage(img, 0, 0, img.width, img.height,
92 | x, y, img.width, img.height);
93 | }
94 |
--------------------------------------------------------------------------------
/src/ai/inferences/InferenceAnswerHow.ts:
--------------------------------------------------------------------------------
1 | class AnswerHow_InferenceEffect extends InferenceEffect {
2 | constructor(effectParameter:Term)
3 | {
4 | super()
5 | this.effectParameter = effectParameter;
6 | }
7 |
8 |
9 | execute(inf:InferenceRecord, ai:RuleBasedAI)
10 | {
11 | console.log("executeInferenceEffect: INFERENCE_RECORD_EFFECT_ANSWER_HOW");
12 | console.log("inf.inferences.length: " + inf.inferences.length);
13 | console.log("inf.inferences[0].endResults: " + inf.inferences[0].endResults);
14 |
15 |
16 | if (!(this.effectParameter.attributes[1] instanceof ConstantTermAttribute)) {
17 | console.error("AnswerHow_InferenceEffect.execute: Trying to talk to a character for which we don't know the ID!");
18 | return;
19 | }
20 | let speakerCharacterID:string = ((this.effectParameter.attributes[1])).value;
21 |
22 | console.log("query result, answer how (source): " + inf.inferences[0].endResults);
23 | if (inf.inferences[0].endResults.length == 0) {
24 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id],'unknown'[symbol]))", ai.o);
25 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
26 | } else {
27 | // get the location ID
28 | let how:Term = null;
29 | if (inf.inferences[0].endResults.length != 0) {
30 | for(let b of inf.inferences[0].endResults[0].bindings.l) {
31 | if (b[0].name == "HOW") {
32 | let v:TermAttribute = b[1];
33 | if (v instanceof TermTermAttribute) {
34 | how = (v).term;
35 | break;
36 | }
37 | }
38 | }
39 | }
40 | if (how == null) {
41 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id],'unknown'[symbol]))", ai.o);
42 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
43 | return;
44 | }
45 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id]))", ai.o);
46 | (term.attributes[1]).term.attributes.push(new TermTermAttribute(how));
47 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
48 | }
49 | }
50 |
51 |
52 | saveToXMLInternal(ai:RuleBasedAI, variables:TermAttribute[], variableNames:string[]) : string
53 | {
54 | return "";
55 | }
56 |
57 |
58 | static loadFromXML(xml:Element, ai:RuleBasedAI, o:Ontology, variables:TermAttribute[], variableNames:string[]) : InferenceEffect
59 | {
60 | let t:Term = Term.fromStringInternal(xml.getAttribute("effectParameter"), o, variableNames, variables).term;
61 | return new AnswerHow_InferenceEffect(t);
62 | }
63 |
64 |
65 | effectParameter:Term = null;
66 | }
--------------------------------------------------------------------------------
/src/ai/Ontology.ts:
--------------------------------------------------------------------------------
1 | var SORT_HASH_SIZE:number = 2048;
2 |
3 | class Ontology {
4 | loadSortsFromXML(xml:Element)
5 | {
6 | let sort_xml_l:Element[] = getElementChildrenByTag(xml,"sort");
7 | for(let sort_xml of sort_xml_l) {
8 | let name:string = sort_xml.getAttribute("name");
9 | let super_raw:string = sort_xml.getAttribute("super");
10 | let super_l:Sort[] = [];
11 | if (super_raw != null) {
12 | for(let super_s of super_raw.split(",")) {
13 | super_l.push(this.getSort(super_s));
14 | }
15 | }
16 | //console.log("loaded sort " + name + " supers: " + super_l);
17 | this.newSort(name, super_l);
18 | }
19 | }
20 |
21 |
22 | newSort(name:string, parents:Sort[]):Sort
23 | {
24 | let s:Sort = new Sort(name, parents);
25 | let bin:number = stringHashFunction(name)%SORT_HASH_SIZE;
26 | if (this.sorts[bin] == null) this.sorts[bin] = [];
27 | this.sorts[bin].push(s);
28 | return s;
29 | }
30 |
31 |
32 | newSortStrings(name:string, parentsStr:string[]):Sort
33 | {
34 | let parents:Sort[] = [];
35 | for(let str of parentsStr) {
36 | parents.push(this.getSort(str));
37 | }
38 | let s:Sort = new Sort(name, parents);
39 | let bin:number = stringHashFunction(name)%SORT_HASH_SIZE;
40 | if (this.sorts[bin] == null) this.sorts[bin] = [];
41 | this.sorts[bin].push(s);
42 | return s;
43 | }
44 |
45 |
46 | getSort(name:string)
47 | {
48 | let bin:number = stringHashFunction(name)%SORT_HASH_SIZE;
49 | if (this.sorts[bin] == null) {
50 | console.error("Sort " + name + " does not exist!");
51 | return null;
52 | }
53 | for(let s of this.sorts[bin]) {
54 | if (s.name == name) return s;
55 | }
56 | console.error("Sort " + name + " does not exist!");
57 | return null;
58 | }
59 |
60 |
61 | // same as getSort, but does not print errors if sort do not exist
62 | getSortSilent(name:string)
63 | {
64 | let bin:number = stringHashFunction(name)%SORT_HASH_SIZE;
65 | if (this.sorts[bin] == null) {
66 | return null;
67 | }
68 | for(let s of this.sorts[bin]) {
69 | if (s.name == name) return s;
70 | }
71 | return null;
72 | }
73 |
74 |
75 | getAllSorts():Sort[]
76 | {
77 | let allSorts:Sort[] = [];
78 | for(let i:number = 0;i= this.n_tiles) {
34 | console.log("Requesting tile outside of range: " + n);
35 | return null;
36 | }
37 |
38 | if (this.tiles[n] != null) return this.tiles[n];
39 | let x:number = (n%this.tilesPerRow)*this.tileWidth;
40 | let y:number = Math.floor(n/this.tilesPerRow)*this.tileHeight;
41 | // output_debug_message("%i -> %i,%i - %i,%i\n",n,x,y,m_tile_dx,m_tile_dy);
42 | this.tiles[n] = this.GLTM.getPiece(this.fullName , x, y, this.tileWidth, this.tileHeight);
43 | return this.tiles[n];
44 | }
45 |
46 |
47 | getTileDark(n:number) : GLTile
48 | {
49 | if (n < 0 || n >= this.n_tiles) {
50 | console.log("Requesting tile outside of range: " + n);
51 | return null;
52 | }
53 |
54 | if (this.tilesDark[n] != null) return this.tilesDark[n];
55 | let x:number = (n%this.tilesPerRow)*this.tileWidth;
56 | let y:number = Math.floor(n/this.tilesPerRow)*this.tileHeight;
57 | // output_debug_message("%i -> %i,%i - %i,%i\n",n,x,y,m_tile_dx,m_tile_dy);
58 | this.tilesDark[n] = this.GLTM.getPieceDark(this.fullName , x, y, this.tileWidth, this.tileHeight);
59 | return this.tilesDark[n];
60 | }
61 |
62 |
63 | name:string;
64 | fullName:string;
65 | path:string;
66 | img:HTMLImageElement = null;
67 | n_tiles:number;
68 | tilesPerRow:number;
69 | tileWidth:number;
70 | tileHeight:number;
71 | tiles:GLTile[] = null;
72 | tilesDark:GLTile[] = null;
73 | // tileTypes:number[];
74 | // tileSeeThrough:number[];
75 | // tileCanDig:number[];
76 | GLTM:GLTManager;
77 | }
--------------------------------------------------------------------------------
/src/tests/definition-test.ts:
--------------------------------------------------------------------------------
1 | var g_o:Ontology = new Ontology();
2 | Sort.clear();
3 | var xmlhttp:XMLHttpRequest = new XMLHttpRequest();
4 | xmlhttp.overrideMimeType("text/xml");
5 | xmlhttp.open("GET", "data/shrdluontology.xml", false);
6 | xmlhttp.send();
7 | g_o.loadSortsFromXML(xmlhttp.responseXML.documentElement);
8 |
9 | var g_posParser:POSParser = new POSParser(g_o);
10 | var g_nlg:NLGenerator = new NLGenerator(g_o, g_posParser);
11 | var g_ai:RuleBasedAI = new RuleBasedAI(g_o, null, 10, 0, DEFAULT_QUESTION_PATIENCE_TIMER);
12 | g_ai.selfID = "etaoin";
13 | var g_context:NLContext = g_ai.contextForSpeaker('1');
14 |
15 | var idSort:Sort = g_o.getSort("#id");
16 | var ceg1:NLContextEntity = new NLContextEntity(new ConstantTermAttribute('1', g_o.getSort("#id")),
17 | null, 0,
18 | [Term.fromString("human('1'[#id])",g_o),
19 | Term.fromString("name('1'[#id], 'david'[symbol])",g_o)]);
20 | var ceg_l:NLContextEntity[] = [ceg1];
21 | for(let ceg of ceg_l) {
22 | g_context.shortTermMemory.push(ceg);
23 | for(let t of ceg.terms) {
24 | g_ai.addShortTermTerm(t, "perception");
25 | }
26 | }
27 |
28 |
29 | let sortsToDefine:Sort[] = [];
30 | for(let pos_key in g_posParser.POS) {
31 | for(let pos of g_posParser.POS[pos_key]) {
32 | if (pos.term.functor.is_a(g_o.getSort("noun"))) {
33 | let s:Sort = g_o.getSort(""+((pos.term.attributes[0])).value);
34 | if (POSParser.sortIsConsideredForTypes(s, g_o) &&
35 | POSParser.sortsToConsiderForTypes.indexOf(s.name)==-1 &&
36 | sortsToDefine.indexOf(s) == -1) {
37 | sortsToDefine.push(s);
38 | //console.log(pos.token + " -> " + s);
39 | }
40 | }
41 | }
42 | }
43 |
44 | for(let sortToDefine of sortsToDefine) {
45 | // sortToDefine.parents is the set of sorts we want to use for the definition:
46 | var definitionAsTerm:TermAttribute = null;
47 | for(let i:number = 0;i 0) {
73 | let s:Sort = open[0];
74 | open.splice(0,1);
75 | if (closed.indexOf(s)==-1) closed.push(s);
76 | open = open.concat(s.parents);
77 | }
78 | return closed;
79 | }
80 |
81 |
82 | toString()
83 | {
84 | return this.name;
85 | }
86 |
87 |
88 | static precomputeIsA()
89 | {
90 | Sort.s_precomputedIsA = new Array(Sort.s_next_ID*Sort.s_next_ID);
91 | for(let i:number = 0;i=2) {
27 | let thingToRepair:TermAttribute = actionRequest.attributes[1];
28 | if (thingToRepair instanceof ConstantTermAttribute) {
29 | let thingToRepair_id:string = (thingToRepair).value;
30 | if (thingToRepair_id == "spacesuit") {
31 | let thingToRepairObject:A4Object = this.game.findObjectByIDJustObject(thingToRepair_id);
32 | if (thingToRepairObject.sort.name == "brokenspacesuit") {
33 | // broken space suit:
34 | app.achievement_nlp_all_robot_actions[11] = true;
35 | app.trigger_achievement_complete_alert();
36 |
37 | return ACTION_REQUEST_CAN_BE_SATISFIED;
38 | }
39 | } else if (thingToRepair_id == "shuttle-datapad") {
40 | app.achievement_nlp_all_robot_actions[11] = true;
41 | app.trigger_achievement_complete_alert();
42 |
43 | return ACTION_REQUEST_CAN_BE_SATISFIED;
44 | }
45 | } else {
46 | return ACTION_REQUEST_CANNOT_BE_SATISFIED;
47 | }
48 | }
49 |
50 | return super.canSatisfyActionRequest(ir);
51 | }
52 |
53 |
54 | executeIntention(ir:IntentionRecord) : boolean
55 | {
56 | let intention:Term = ir.action;
57 | let repairSort:Sort = this.o.getSort("verb.repair");
58 | if (intention.functor.is_a(repairSort)) {
59 | // just ignore, the story script will take charge of making qwerty do the repair...
60 | this.clearCurrentAction();
61 |
62 | return true;
63 | }
64 |
65 | return super.executeIntention(ir);
66 | }
67 |
68 |
69 | /*
70 | - If it returns "null", it means the robot can go
71 | - If it returns a Term, it means the robot cannot go, for the reason specified in the Term (e.g., not allowed)
72 | */
73 | canGoTo(map:A4Map, locationID:string, requester:TermAttribute) : Term
74 | {
75 | if (map != this.robot.map) {
76 | let cause:Term = Term.fromString("#not(verb.can(ME:'"+this.selfID+"'[#id], verb.go(ME, [space.outside])))", this.o);
77 | return cause;
78 | }
79 | return super.canGoTo(map, locationID, requester);
80 | }
81 |
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/ai/actions/AnswerWhatIs.ts:
--------------------------------------------------------------------------------
1 | class AnswerWhatIs_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("action.answer.whatis.name")) ||
6 | intention.functor.is_a(ai.o.getSort("action.answer.whatis.noname"))) return true;
7 | return false;
8 | }
9 |
10 |
11 | execute(ir:IntentionRecord, ai:RuleBasedAI) : boolean
12 | {
13 | this.ir = ir;
14 | let intention:Term = ir.action;
15 |
16 | if (intention.functor == ai.o.getSort("action.answer.whatis.name")) {
17 | console.log(ai.selfID + " answer whatis.name: " + intention.attributes[2]);
18 | if (intention.attributes[1] instanceof ConstantTermAttribute &&
19 | intention.attributes[2] instanceof ConstantTermAttribute) {
20 | let listenerID:string = (intention.attributes[1]).value;
21 | // Don't do any inference for now (we'll see if I need it later on),
22 | // directly call the same function that will be called after the inference in whatis.noname:
23 | AnswerWhatIs_InferenceEffect.executeInferenceEffect_AnswerWhatis(null, (intention.attributes[2]).value, listenerID, ai);
24 | // TODO: this should have some temporary value (in all actions that require inference or continuous execution)
25 | // that is then replaced with true/false after inference/continuous is done
26 | ir.succeeded = true;
27 | } else {
28 | console.error("executeIntention answer whatis.name: attribute[1] or attribute[2] was not a ConstantTermAttribute: " + intention);
29 | ir.succeeded = false;
30 | }
31 | return true;
32 |
33 |
34 | } else if (intention.functor == ai.o.getSort("action.answer.whatis.noname")) {
35 | console.log(ai.selfID + " answer whatis.noname: " + intention.attributes[2]);
36 | if (intention.attributes[1] instanceof ConstantTermAttribute &&
37 | intention.attributes[2] instanceof ConstantTermAttribute) {
38 | // target 1: name of the entity:
39 | let target1:Sentence[] = [new Sentence([new Term(ai.o.getSort("name"),
40 | [intention.attributes[2],
41 | new VariableTermAttribute(ai.o.getSort("symbol"), "NAME")])],[false])];
42 | ai.queuedInferenceProcesses.push(new InferenceRecord(ai, [], [target1], 1, 0, false, null, new AnswerWhatIs_InferenceEffect(intention)));
43 | // TODO: this should have some temporary value (in all actions that require inference or continuous execution)
44 | // that is then replaced with true/false after inference/continuous is done
45 | ir.succeeded = true;
46 | } else {
47 | console.error("executeIntention answer whatis.noname: attribute[1] or attribute[2] was not a ConstantTermAttribute: " + intention);
48 | ir.succeeded = false;
49 | }
50 | return true;
51 | }
52 |
53 | return false;
54 | }
55 |
56 |
57 | saveToXML(ai:RuleBasedAI) : string
58 | {
59 | return "";
60 | }
61 |
62 |
63 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
64 | {
65 | return new AnswerWhatIs_IntentionAction();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/server/routes.js:
--------------------------------------------------------------------------------
1 | // API endpoints for the server
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const uuidv4 = require('uuid/v4');
6 | const expressJwt = require('express-jwt');
7 | const httpStatus = require('http-status');
8 | const util = require('./utilities');
9 |
10 | const routes = function(app) {
11 |
12 | /// Unsecured Endpoints (no JWT required)
13 | /// ********************************************
14 |
15 | // main SPA
16 | app.get('/', (req, res) => {
17 | res.sendFile(path.join(__dirname, '..', 'built', 'shrdlu.html'));
18 | });
19 |
20 | // test API
21 | app.get('/test', (req, res) => {
22 | res.status(httpStatus.OK).json({message: "API test endpoint"});
23 | });
24 |
25 | // create a new playtest session and issue JWT
26 | app.get('/session', (req, res, next) => {
27 | // when a user starts a new game, the client applies for a session ID from the server.
28 | // this allows us to manage this user through a token that we authenticate,
29 | // which makes security easier later when we are receiving and recording log data for this session
30 |
31 | // issue a random, unique ID for this session
32 | const sessionID = uuidv4();
33 | const ret = {
34 | token: util.createAuthToken(sessionID)
35 | };
36 | res.status(httpStatus.OK).json(ret);
37 | });
38 |
39 | /// Secured Endpoints (requires JWT)
40 | /// ********************************************
41 |
42 | // this will cover all further defined endpoints and require they have a valid JWT or will error 401
43 | // once validated, token will be saved to req.token in the endpoints below
44 | app.use(
45 | expressJwt({
46 | secret: process.env.JWT_SECRET,
47 | getToken: util.getToken,
48 | requestProperty: 'token'
49 | })
50 | );
51 |
52 | // post log data for a playtest session based on issued sessionID
53 | app.post('/session', (req, res, next) => {
54 | if (!req.body.data) {
55 | return next(util.error(400, "No Data specified", "No data specified"));
56 | }
57 | // !!!: note that session ID is used here to create a filename, and usually would require heavy sanitization
58 | // However, we know from the JWT signature authentication that the sessionID we are reading from the token is
59 | // something that was issued by us via '/session' endpoint, and is therefore safe.
60 | let sessionID = req.token.sessionID;
61 |
62 | // find existing file or create a new one
63 | let logdir = path.join(__dirname, 'logs');
64 | // not efficient, but better than failing to capture a log
65 | if (!fs.existsSync(logdir)) {
66 | fs.mkdirSync(logdir);
67 | }
68 | let sessionFile = path.join(logdir, sessionID + '.' + util.currentEpochTime() + '.log');
69 | // write the file. fs.writeFile() handles input sanitization
70 | fs.writeFile(sessionFile, req.body.data, { flag: 'wx' }, (err) => {
71 | if (err) {
72 | // 'wx' flag causes failure if the file exists - but would be very strange for this file to exist already!
73 | return next(util.error(500, "Data not recorded", "An internal error has occurred and data was not saved"));
74 | }
75 | // else return success
76 | return res.status(201).end();
77 | });
78 | });
79 |
80 | };
81 |
82 | module.exports = routes;
83 |
--------------------------------------------------------------------------------
/src/blocksworld/ActionShrdluTalk.ts:
--------------------------------------------------------------------------------
1 | class ShrdluTalk_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("action.talk"))) return true;
6 | return false;
7 | }
8 |
9 |
10 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
11 | {
12 | let ai:BlocksWorldRuleBasedAI = ai_raw;
13 | let intention:Term = ir.action;
14 | let needToSpecifyListener:boolean = false;
15 | let performative:Term = null;
16 |
17 | if (intention.attributes[1] instanceof TermTermAttribute) {
18 | performative = ((intention.attributes[1])).term;
19 | if ((performative.attributes[0] instanceof ConstantTermAttribute) &&
20 | ai.canHear((performative.attributes[0]).value)) {
21 | // if we are already talking, just wait:
22 | let targetID:string = (performative.attributes[0]).value;
23 | let context:NLContext = ai.updateContext(targetID);
24 | let txt:string = null;
25 |
26 | if (!context.inConversation &&
27 | performative.functor.name != "perf.callattention" &&
28 | performative.functor.name != "perf.greet") {
29 | // we need to greet first:
30 | performative = Term.fromString("perf.callattention('"+targetID+"'[#id])",ai.o);
31 | ai.queueIntentionRecord(ir);
32 | } else {
33 | for(let c of ai.contexts) {
34 | if (c!=context && c.inConversation) {
35 | needToSpecifyListener = true;
36 | c.inConversation = false; // terminate the other conversations
37 | }
38 | }
39 | }
40 |
41 | console.log(ai.selfID + " trying to say: " + performative);
42 | if (needToSpecifyListener) {
43 | txt = ai.naturalLanguageGenerator.termToEnglish(performative, ai.selfID, performative.attributes[0], context);
44 | } else {
45 | txt = ai.naturalLanguageGenerator.termToEnglish(performative, ai.selfID, null, context);
46 | }
47 | txt = ai.naturalLanguageGenerator.capitalize(txt);
48 |
49 | if (txt != null) {
50 | ai.app.addMessageWithColorTime(ai.selfID + ": " + txt, MSX_COLOR_WHITE, ai.timeStamp);
51 |
52 | // update natural language context:
53 | if (performative != null) context.newPerformative(ai.selfID, txt, performative, null, null, ir.cause, ai.o, ai.timeStamp);
54 | for(let c2 of ai.contexts) {
55 | if (c2 != context) c2.inConversation = false;
56 | }
57 | }
58 | }
59 | } else if (intention.attributes[1] instanceof ConstantTermAttribute) {
60 | // this is just a shortcut for the 3 laws of robotics easter egg:
61 | let txt:string = (intention.attributes[1]).value;
62 | ai.app.addMessageWithColorTime("SHRDLU: " + txt, MSX_COLOR_WHITE, ai.timeStamp);
63 | } else {
64 | console.error("ShrdluTalk_IntentionAction: malformed intention: " + intention.toString());
65 | }
66 |
67 | return true;
68 | }
69 |
70 |
71 | saveToXML(ai:RuleBasedAI) : string
72 | {
73 | return "";
74 | }
75 |
76 |
77 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
78 | {
79 | return new ShrdluTalk_IntentionAction();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4Trigger.ts:
--------------------------------------------------------------------------------
1 | class A4Trigger extends A4Object {
2 |
3 | constructor(sort:Sort, w:number, h:number)
4 | {
5 | super("trigger", sort);
6 | this.width = w;
7 | this.height = h;
8 | this.currentAnimation = A4_ANIMATION_OPEN;
9 | }
10 |
11 |
12 | loadObjectAttribute(xml:Element) : boolean
13 | {
14 | if (super.loadObjectAttribute(xml)) return true;
15 |
16 | let name:string = xml.getAttribute("name");
17 | if (name == "triggerState") {
18 | this.triggerState = false;
19 | if (xml.getAttribute("value") == "true") this.triggerState = true;
20 | return true;
21 | }
22 |
23 | return false;
24 | }
25 |
26 |
27 | loadObjectAdditionalContent(xml:Element, game:A4Game, of:A4ObjectFactory, objectsToRevisit_xml:Element[], objsctsToRevisit_object:A4Object[])
28 | {
29 | super.loadObjectAdditionalContent(xml, game, of, objectsToRevisit_xml, objsctsToRevisit_object);
30 |
31 | this.width = Number(xml.getAttribute("width"));
32 | this.height = Number(xml.getAttribute("height"));
33 | }
34 |
35 |
36 | savePropertiesToXML(game:A4Game) : string
37 | {
38 | let xmlString:string = super.savePropertiesToXML(game);
39 |
40 | xmlString += this.saveObjectAttributeToXML("triggerState",this.triggerState) + "\n";
41 |
42 | return xmlString;
43 | }
44 |
45 |
46 | getPixelWidth() : number
47 | {
48 | return this.width;
49 | }
50 |
51 |
52 | getPixelHeight() : number
53 | {
54 | return this.height;
55 | }
56 |
57 |
58 | isTrigger() : boolean
59 | {
60 | return true;
61 | }
62 |
63 |
64 | update(game:A4Game) : boolean
65 | {
66 | super.update(game);
67 |
68 | let l:A4Object[] = this.map.getAllObjectCollisions(this);
69 | let triggered_by:A4Object = null;
70 | let playerOver:boolean = false;
71 |
72 | for(let o of l) {
73 | if (o.isPlayer()) {
74 | playerOver = true;
75 | triggered_by = o;
76 | }
77 | }
78 |
79 | if (this.triggerState) {
80 | if (playerOver) {
81 | // nothing to do, keep pressed
82 | } else {
83 | // release
84 | this.triggerState = false;
85 | this.event(A4_EVENT_DEACTIVATE, null, this.map, game);
86 | this.event(A4_EVENT_USE, null, this.map, game);
87 | }
88 | } else {
89 | if (triggered_by!=null && playerOver) {
90 | // press!
91 | this.triggerState = true;
92 | this.event(A4_EVENT_ACTIVATE, triggered_by, this.map, game);
93 | this.event(A4_EVENT_USE, triggered_by, this.map, game);
94 | } else {
95 | // nothing to do, keep released
96 | }
97 | }
98 |
99 | return true;
100 | }
101 |
102 |
103 | width:number;
104 | height:number;
105 | triggerState:boolean = false;
106 | };
107 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/EtaoinConnectTo.ts:
--------------------------------------------------------------------------------
1 | class EtaoinConnectTo_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.connect-to")) &&
6 | intention.attributes.length == 3 &&
7 | (intention.attributes[1] instanceof ConstantTermAttribute) &&
8 | (intention.attributes[2] instanceof ConstantTermAttribute)) return true;
9 | return false;
10 | }
11 |
12 |
13 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
14 | {
15 | this.ir = ir;
16 | let ai:EtaoinAI = ai_raw;
17 | let game:ShrdluA4Game = ai.game;
18 | let intention:Term = ir.action;
19 | let requester:TermAttribute = ir.requester;
20 |
21 | // execute the memorize action:
22 | console.log(ai.selfID + " connect to: " + intention);
23 |
24 | let target:string = (intention.attributes[2]).value;
25 | if (target == "etaoin") {
26 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform("+requester+", #and(#and(X:verb.can("+requester+", #and(Y:action.talk("+requester+"), relation.target(Y, '"+target+"'[#id]))), relation.tool(X, 'communicator'[#id]), time.now(X)))))", ai.o);
27 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
28 | } else {
29 | let targetObject:A4Object = game.findObjectByIDJustObject(target);
30 | if (targetObject != null &&
31 | ai.withinEtaoinViewRange(targetObject)) {
32 | // Etaoin can see the target:
33 | if (target == "qwerty" ||
34 | target == "shrdlu") {
35 |
36 | game.communicatorConnectedTo = target;
37 | game.communicatorConnectionTime = ai.timeStamp;
38 |
39 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform("+requester+", #and(#and(X:verb.can("+requester+", #and(Y:action.talk("+requester+"), relation.target(Y, '"+target+"'[#id]))), relation.tool(X, 'communicator'[#id]), time.now(X)))))", ai.o);
40 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
41 |
42 | app.achievement_nlp_all_etaoin_actions[0] = true;
43 | app.trigger_achievement_complete_alert();
44 | } else {
45 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
46 | let cause:Term = Term.fromString("#not(verb.can('"+target+"'[#id], action.talk('"+target+"'[#id])))", ai.o);
47 | ai.intentions.push(new IntentionRecord(term, null, null, new CauseRecord(cause, null, ai.timeStamp), ai.timeStamp));
48 | }
49 | } else {
50 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
51 | let cause:Term = Term.fromString("#not(verb.see('"+ai.selfID+"'[#id],'"+target+"'[#id]))", ai.o);
52 | ai.intentions.push(new IntentionRecord(term, null, null, new CauseRecord(cause, null, ai.timeStamp), ai.timeStamp));
53 | }
54 | }
55 |
56 | return true;
57 | }
58 |
59 |
60 | saveToXML(ai:RuleBasedAI) : string
61 | {
62 | return "";
63 | }
64 |
65 |
66 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
67 | {
68 | return new EtaoinConnectTo_IntentionAction();
69 | }
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/src/ai/actions/Call.ts:
--------------------------------------------------------------------------------
1 | class Call_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.call")) &&
6 | intention.attributes.length == 3) return true;
7 | if (intention.functor.name == "#not" &&
8 | (intention.attributes[0] instanceof TermTermAttribute) &&
9 | (intention.attributes[0]).term.functor.is_a(ai.o.getSort("verb.call")) &&
10 | (intention.attributes[0]).term.attributes.length == 3) return true;
11 | return false;
12 | }
13 |
14 |
15 | execute(ir:IntentionRecord, ai:RuleBasedAI) : boolean
16 | {
17 | this.ir = ir;
18 | let intention:Term = ir.action;
19 | let requester:TermAttribute = ir.requester;
20 |
21 | // execute the memorize action:
22 | console.log(ai.selfID + " call: " + intention);
23 |
24 | if (intention.functor.name == "#not") {
25 | intention = (intention.attributes[0]).term;
26 |
27 | // ask about the name of the character:
28 | let question:Term = Term.fromString("perf.q.query("+requester+", X, name("+intention.attributes[1]+",X))", ai.o);
29 | let action:Term = new Term(ai.o.getSort("action.talk"),
30 | [intention.attributes[0], // this is "self"
31 | new TermTermAttribute(question)]);
32 | ai.intentions.push(new IntentionRecord(action, null, null, null, ai.timeStamp));
33 | ir.succeeded = true;
34 | } else if (intention.attributes[2] instanceof ConstantTermAttribute) {
35 | // see if we were waiting for an answer to this question:
36 | if (requester instanceof ConstantTermAttribute) {
37 | let context:NLContext = ai.contextForSpeakerWithoutCreatingANewOne((requester).value);
38 | let pattern1:Term = Term.fromString("perf.q.query("+requester+", X, name("+intention.attributes[1]+",X))", ai.o);
39 | let pattern2:Term = Term.fromString("perf.q.predicate("+requester+", verb.know($PLAYER,#and(#query(Y), name($PLAYER,Y))))", ai.o);
40 | for(let i:number = 0;i";
70 | }
71 |
72 |
73 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
74 | {
75 | return new Call_IntentionAction();
76 | }
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/EtaoinSwitchOn.ts:
--------------------------------------------------------------------------------
1 | class EtaoinSwitchOn_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.switch-on"))) return true;
6 | return false;
7 | }
8 |
9 |
10 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
11 | {
12 | this.ir = ir;
13 | let ai:EtaoinAI = ai_raw;
14 | let requester:TermAttribute = ir.requester;
15 | let alternative_actions:Term[] = ir.alternative_actions;
16 | if (alternative_actions == null) alternative_actions = [ir.action];
17 | let denyrequestCause:Term = null;
18 | let anyTurnedOn:boolean = false;
19 | let numberConstraint:number = ir.resolveNumberConstraint(ir.numberConstraint, alternative_actions.length);
20 |
21 | for(let intention of alternative_actions) {
22 | let targetID:string = ((intention.attributes[1])).value;
23 | let light:A4Object = ai.game.findObjectByIDJustObject(targetID);
24 | if (light.sort.is_a(ai.o.getSort("light"))) {
25 | let room:AILocation = ai.game.getAILocation(light);
26 | if (ai.game.turnLightOn(room.id)) {
27 | anyTurnedOn = true;
28 |
29 | // If the object was not mentioned explicitly in the performative, add it to the natural language context:
30 | if (ir.requestingPerformative != null) ir.requestingPerformative.addMentionToPerformative(light.ID, ai.o);
31 |
32 | // add a causation record:
33 | let causetext:string = "relation.cause(powered.state('"+targetID+"'[#id], 'powered.on'[powered.on]), verb.switch-on('"+ai.selfID+"'[#id], '"+targetID+"'[#id]))";
34 | let causeTerm:Term = Term.fromString(causetext, ai.o);
35 | ai.addLongTermTerm(causeTerm, PERCEPTION_PROVENANCE);
36 |
37 | app.achievement_nlp_all_etaoin_actions[3] = true;
38 | app.trigger_achievement_complete_alert();
39 | numberConstraint --;
40 | if (numberConstraint <= 0) break;
41 | } else {
42 | denyrequestCause = Term.fromString("powered.state('"+targetID+"'[#id], 'powered.on'[powered.on])", ai.o);
43 | continue;
44 | }
45 | } else {
46 | denyrequestCause = Term.fromString("#not(light('"+targetID+"'[#id]))", ai.o);
47 | continue;
48 | }
49 | }
50 |
51 | if (requester != null) {
52 | if (anyTurnedOn) {
53 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))", ai.o);
54 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
55 | ai.intentionsCausedByRequest.push(ir);
56 | } else {
57 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
58 | if (denyrequestCause == null) {
59 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
60 | } else {
61 | ai.intentions.push(new IntentionRecord(term, null, null, new CauseRecord(denyrequestCause, null, ai.timeStamp), ai.timeStamp));
62 | }
63 | ir.succeeded = false;
64 | return true;
65 | }
66 | }
67 | ir.succeeded = true;
68 | return true;
69 | }
70 |
71 |
72 | saveToXML(ai:RuleBasedAI) : string
73 | {
74 | return "";
75 | }
76 |
77 |
78 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
79 | {
80 | return new EtaoinSwitchOn_IntentionAction();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/EtaoinSwitchOff.ts:
--------------------------------------------------------------------------------
1 | class EtaoinSwitchOff_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.switch-off"))) return true;
6 | return false;
7 | }
8 |
9 |
10 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
11 | {
12 | this.ir = ir;
13 | let ai:EtaoinAI = ai_raw;
14 | let requester:TermAttribute = ir.requester;
15 | let alternative_actions:Term[] = ir.alternative_actions;
16 | if (alternative_actions == null) alternative_actions = [ir.action];
17 | let denyrequestCause:Term = null;
18 | let anyTurnedOff:boolean = false;
19 | let numberConstraint:number = ir.resolveNumberConstraint(ir.numberConstraint, alternative_actions.length);
20 |
21 | for(let intention of alternative_actions) {
22 | let targetID:string = ((intention.attributes[1])).value;
23 | let light:A4Object = ai.game.findObjectByIDJustObject(targetID);
24 | if (light.sort.is_a(ai.o.getSort("light"))) {
25 | let room:AILocation = ai.game.getAILocation(light);
26 | if (ai.game.turnLightOff(room.id)) {
27 | anyTurnedOff = true;
28 |
29 | // If the object was not mentioned explicitly in the performative, add it to the natural language context:
30 | if (ir.requestingPerformative != null) ir.requestingPerformative.addMentionToPerformative(light.ID, ai.o);
31 |
32 | // add a causation record:
33 | let causetext:string = "relation.cause(powered.state('"+targetID+"'[#id], 'powered.off'[powered.off]), verb.switch-off('"+ai.selfID+"'[#id], '"+targetID+"'[#id]))";
34 | let causeTerm:Term = Term.fromString(causetext, ai.o);
35 | ai.addLongTermTerm(causeTerm, PERCEPTION_PROVENANCE);
36 |
37 | app.achievement_nlp_all_etaoin_actions[4] = true;
38 | app.trigger_achievement_complete_alert();
39 | numberConstraint --;
40 | if (numberConstraint <= 0) break;
41 | } else {
42 | denyrequestCause = Term.fromString("powered.state('"+targetID+"'[#id], 'powered.off'[powered.off])", ai.o);
43 | continue;
44 | }
45 | } else {
46 | denyrequestCause = Term.fromString("#not(light('"+targetID+"'[#id]))", ai.o);
47 | continue;
48 | }
49 | }
50 |
51 | if (requester != null) {
52 | if (anyTurnedOff) {
53 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))", ai.o);
54 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
55 | ai.intentionsCausedByRequest.push(ir);
56 | } else {
57 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
58 | if (denyrequestCause == null) {
59 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
60 | } else {
61 | ai.intentions.push(new IntentionRecord(term, null, null, new CauseRecord(denyrequestCause, null, ai.timeStamp), ai.timeStamp));
62 | }
63 | ir.succeeded = false;
64 | return true;
65 | }
66 | }
67 | ir.succeeded = true;
68 | return true;
69 | }
70 |
71 |
72 | saveToXML(ai:RuleBasedAI) : string
73 | {
74 | return "";
75 | }
76 |
77 |
78 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
79 | {
80 | return new EtaoinSwitchOff_IntentionAction();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/a4engine3/A4MapTile.ts:
--------------------------------------------------------------------------------
1 | class A4MapTile {
2 | static loadFromXML(object_xml:Element, game:A4Game) : A4MapTile
3 | {
4 | let t:A4MapTile = new A4MapTile();
5 |
6 | t.ID = Number(object_xml.getAttribute("ID"));
7 | let tilesStr:string[] = object_xml.getAttribute("tiles").split(",");
8 | t.tileIds = [];
9 | for(let tileStr of tilesStr) {
10 | t.tileIds.push(Number(tileStr));
11 | }
12 |
13 | if (object_xml.getAttribute("walkable") == "false") {
14 | t.walkable = false;
15 | }
16 |
17 | if (object_xml.getAttribute("seeThrough") == "false") {
18 | t.seeThrough = false;
19 | }
20 |
21 | return t;
22 | }
23 |
24 |
25 | saveToXML() : string
26 | {
27 | let xmlString:string = "\n";
42 | }
43 |
44 |
45 | cacheDrawTiles(graphicFiles:A4GraphicFile[], gfs_startTile:number[])
46 | {
47 | this.glTiles = new Array(this.tileIds.length);
48 | this.glTilesDark = new Array(this.tileIds.length);
49 | for(let i:number = 0; i=0) {
51 | for(let j:number = 0;j=gfs_startTile[j+1]) continue;
53 | this.glTiles[i] = graphicFiles[j].getTile(this.tileIds[i]-gfs_startTile[j]);
54 | this.glTilesDark[i] = graphicFiles[j].getTileDark(this.tileIds[i]-gfs_startTile[j]);
55 | // if images are not yet loaded, wait!
56 | if (this.glTiles[i] == null || this.glTilesDark[i] == null) {
57 | this.glTiles = null;
58 | this.glTilesDark = null;
59 | return;
60 | }
61 | break;
62 | }
63 | } else {
64 | this.glTiles[i] = null;
65 | this.glTilesDark[i] = null;
66 | }
67 | }
68 | }
69 |
70 |
71 | draw(x:number, y:number, stride:number)
72 | {
73 | for(let glTile of this.glTiles) {
74 | if (glTile != null) glTile.draw(x, y);
75 | y -= stride;
76 | }
77 | }
78 |
79 |
80 | drawDark(x: number, y:number, stride:number)
81 | {
82 | for(let glTile of this.glTilesDark) {
83 | if (glTile != null) glTile.draw(x, y);
84 | y -= stride;
85 | }
86 | }
87 |
88 |
89 |
90 | // the number used to identify this tile in Tiled:
91 | ID:number = -1;
92 | // Each of the entries in these lists will be drawn one after another vertically, when this tile has to be drawn:
93 | tileIds:number[] = null;
94 | walkable:boolean = true;
95 | seeThrough:boolean = true;
96 |
97 | glTiles:GLTile[] = null;
98 | glTilesDark:GLTile[] = null;
99 | }
100 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/EtaoinHelp.ts:
--------------------------------------------------------------------------------
1 | class EtaoinHelp_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.help")) &&
6 | intention.attributes.length >= 2) return true;
7 | return false;
8 | }
9 |
10 |
11 | execute(ir:IntentionRecord, ai:RuleBasedAI) : boolean
12 | {
13 | this.ir = ir;
14 | let intention:Term = ir.action;
15 | let requester:TermAttribute = ir.requester;
16 |
17 | console.log(ai.selfID + " help: " + intention);
18 |
19 | if (intention.attributes.length == 2) {
20 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.q.how("+requester+", verb.help('"+ai.selfID+"'[#id],"+intention.attributes[1]+")))", ai.o);
21 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
22 | ir.succeeded = true;
23 | } else if (intention.attributes.length == 3 && (intention.attributes[2] instanceof TermTermAttribute)) {
24 | let nestedIntention:Term = (intention.attributes[2]).term;
25 | if (nestedIntention.attributes.length > 0 &&
26 | (nestedIntention.attributes[0] instanceof ConstantTermAttribute)) {
27 | let handler:IntentionAction = null;
28 | let newNestedIntention:Term = nestedIntention.clone([]);
29 | newNestedIntention.attributes[0] = new ConstantTermAttribute(ai.selfID, ai.cache_sort_id);
30 |
31 | if (newNestedIntention.functor.is_a(ai.o.getSort("verb.go"))) {
32 | // Special case for verb "go":
33 | newNestedIntention = new Term(ai.o.getSort("action.answer.how"),
34 | [intention.attributes[0],
35 | intention.attributes[1],
36 | new TermTermAttribute(nestedIntention)]);
37 | }
38 | console.log("EtaoinHelp_IntentionAction, newNestedIntention:" + newNestedIntention);
39 | for(let ih of ai.intentionHandlers) {
40 | if (ih.canHandle(newNestedIntention, ai)) {
41 | handler = ih;
42 | break;
43 | }
44 | }
45 | if (handler != null) {
46 | let newIr:IntentionRecord = new IntentionRecord(newNestedIntention, ir.requester, ir.requestingPerformative, ir.cause, ir.timeStamp);
47 | ir.succeeded = true;
48 | return handler.execute(newIr, ai);
49 | } else {
50 | if (requester != null) {
51 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
52 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
53 | }
54 | ir.succeeded = false;
55 | }
56 | } else {
57 | if (requester != null) {
58 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
59 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
60 | }
61 | ir.succeeded = false;
62 | }
63 |
64 | } else {
65 | if (requester != null) {
66 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
67 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
68 | }
69 | ir.succeeded = false;
70 | }
71 |
72 | return true;
73 | }
74 |
75 |
76 | saveToXML(ai:RuleBasedAI) : string
77 | {
78 | return "";
79 | }
80 |
81 |
82 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
83 | {
84 | return new EtaoinHelp_IntentionAction();
85 | }
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/A4IntentionActionFactory.ts:
--------------------------------------------------------------------------------
1 | class A4IntentionActionFactory extends IntentionActionFactory {
2 | loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
3 | {
4 | if (xml.getAttribute("type") == "A4AnswerHow_IntentionAction") return A4AnswerHow_IntentionAction.loadFromXML(xml, ai);
5 | if (xml.getAttribute("type") == "A4AnswerWhere_IntentionAction") return AnswerWhere_IntentionAction.loadFromXML(xml, ai);
6 | if (xml.getAttribute("type") == "A4Locate_IntentionAction") return A4Locate_IntentionAction.loadFromXML(xml, ai);
7 | if (xml.getAttribute("type") == "EtaoinHelp_IntentionAction") return EtaoinHelp_IntentionAction.loadFromXML(xml, ai);
8 | if (xml.getAttribute("type") == "EtaoinClose_IntentionAction") return EtaoinClose_IntentionAction.loadFromXML(xml, ai);
9 | if (xml.getAttribute("type") == "EtaoinOpen_IntentionAction") return EtaoinOpen_IntentionAction.loadFromXML(xml, ai);
10 | if (xml.getAttribute("type") == "EtaoinSwitchOff_IntentionAction") return EtaoinSwitchOff_IntentionAction.loadFromXML(xml, ai);
11 | if (xml.getAttribute("type") == "EtaoinSwitchOn_IntentionAction") return EtaoinSwitchOn_IntentionAction.loadFromXML(xml, ai);
12 | if (xml.getAttribute("type") == "EtaoinTalk_IntentionAction") return EtaoinTalk_IntentionAction.loadFromXML(xml, ai);
13 | if (xml.getAttribute("type") == "EtaoinConnectTo_IntentionAction") return EtaoinConnectTo_IntentionAction.loadFromXML(xml, ai);
14 | if (xml.getAttribute("type") == "Etaoin3DPrint_IntentionAction") return Etaoin3DPrint_IntentionAction.loadFromXML(xml, ai);
15 | if (xml.getAttribute("type") == "EtaoinRead_IntentionAction") return EtaoinRead_IntentionAction.loadFromXML(xml, ai);
16 | if (xml.getAttribute("type") == "RobotFollow_IntentionAction") return RobotFollow_IntentionAction.loadFromXML(xml, ai);
17 | if (xml.getAttribute("type") == "RobotGive_IntentionAction") return RobotGive_IntentionAction.loadFromXML(xml, ai);
18 | if (xml.getAttribute("type") == "RobotGo_IntentionAction") return RobotGo_IntentionAction.loadFromXML(xml, ai);
19 | if (xml.getAttribute("type") == "RobotOpenClose_IntentionAction") return RobotOpenClose_IntentionAction.loadFromXML(xml, ai);
20 | if (xml.getAttribute("type") == "RobotStop_IntentionAction") return RobotStop_IntentionAction.loadFromXML(xml, ai);
21 | if (xml.getAttribute("type") == "RobotTake_IntentionAction") return RobotTake_IntentionAction.loadFromXML(xml, ai);
22 | if (xml.getAttribute("type") == "RobotTakeTo_IntentionAction") return RobotTakeTo_IntentionAction.loadFromXML(xml, ai);
23 | if (xml.getAttribute("type") == "RobotPutIn_IntentionAction") return RobotPutIn_IntentionAction.loadFromXML(xml, ai);
24 | if (xml.getAttribute("type") == "RobotTalk_IntentionAction") return RobotTalk_IntentionAction.loadFromXML(xml, ai);
25 | if (xml.getAttribute("type") == "RobotHelp_IntentionAction") return RobotHelp_IntentionAction.loadFromXML(xml, ai);
26 | if (xml.getAttribute("type") == "RobotPushPull_IntentionAction") return RobotPushPull_IntentionAction.loadFromXML(xml, ai);
27 | if (xml.getAttribute("type") == "RobotEnter_IntentionAction") return RobotEnter_IntentionAction.loadFromXML(xml, ai);
28 | if (xml.getAttribute("type") == "RobotExit_IntentionAction") return RobotExit_IntentionAction.loadFromXML(xml, ai);
29 | if (xml.getAttribute("type") == "RobotReboot_IntentionAction") return RobotReboot_IntentionAction.loadFromXML(xml, ai);
30 | if (xml.getAttribute("type") == "RobotTurn_IntentionAction") return RobotTurn_IntentionAction.loadFromXML(xml, ai);
31 | if (xml.getAttribute("type") == "RobotStay_IntentionAction") return RobotStay_IntentionAction.loadFromXML(xml, ai);
32 |
33 | return super.loadFromXML(xml, ai);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4PressurePlate.ts:
--------------------------------------------------------------------------------
1 |
2 | var TRIGGER_PRESSURE_ITEM:number = 1;
3 | var TRIGGER_PRESSURE_HEAVY_ITEM:number = 2;
4 | var TRIGGER_PRESSURE_PLAYER:number = 3;
5 |
6 |
7 | class A4PressurePlate extends A4Object {
8 |
9 | constructor(sort:Sort, pressed:A4Animation, released:A4Animation, pr:number)
10 | {
11 | super("pressure-plate", sort);
12 | this.animations[A4_ANIMATION_CLOSED] = pressed;
13 | this.animations[A4_ANIMATION_OPEN] = released;
14 | this.pressureRequired = pr;
15 | if (this.pressurePlateState) this.currentAnimation = A4_ANIMATION_CLOSED;
16 | else this.currentAnimation = A4_ANIMATION_OPEN;
17 |
18 | }
19 |
20 |
21 | loadObjectAttribute(attribute_xml:Element) : boolean
22 | {
23 | if (super.loadObjectAttribute(attribute_xml)) return true;
24 | let a_name:string = attribute_xml.getAttribute("name");
25 |
26 | if (a_name == "pressurePlateState") {
27 | this.pressurePlateState = false;
28 | if (attribute_xml.getAttribute("value") == "true") this.pressurePlateState = true;
29 | return true;
30 | } else if (a_name == "pressureRequired") {
31 | this.pressureRequired = Number(attribute_xml.getAttribute("value"));
32 | return true;
33 | }
34 |
35 | return false;
36 | }
37 |
38 |
39 | savePropertiesToXML(game:A4Game) : string
40 | {
41 | let xmlString:string = super.savePropertiesToXML(game);
42 |
43 | xmlString += this.saveObjectAttributeToXML("pressureRequired",this.pressureRequired) + "\n";
44 | xmlString += this.saveObjectAttributeToXML("pressurePlateState",this.pressurePlateState) + "\n";
45 |
46 | return xmlString;
47 | }
48 |
49 |
50 | update(game:A4Game) : boolean
51 | {
52 | super.update(game);
53 |
54 | let l:A4Object[] = this.map.getAllObjectCollisions(this);
55 | let heaviest:A4Object = null;
56 | let pressure:number = 0;
57 |
58 | for(let o of l) {
59 | if (pressure=this.pressureRequired) {
75 | // nothing to do, keep pressed
76 | } else {
77 | // release
78 | this.pressurePlateState = false;
79 | this.event(A4_EVENT_DEACTIVATE, null, this.map, game);
80 | this.event(A4_EVENT_USE, null, this.map, game);
81 | this.currentAnimation = A4_ANIMATION_OPEN;
82 | }
83 | } else {
84 | if (heaviest!=null && pressure>=this.pressureRequired) {
85 | // press!
86 | this.pressurePlateState = true;
87 | if (heaviest.isPlayer()) {
88 | this.event(A4_EVENT_ACTIVATE, heaviest, this.map, game);
89 | this.event(A4_EVENT_USE, heaviest, this.map, game);
90 | } else {
91 | this.event(A4_EVENT_ACTIVATE, null, this.map, game);
92 | this.event(A4_EVENT_USE, null, this.map, game);
93 | }
94 | this.currentAnimation = A4_ANIMATION_CLOSED;
95 | } else {
96 | // nothing to do, keep released
97 | }
98 | }
99 |
100 | return true;
101 | }
102 |
103 | pressurePlateState:boolean = false;
104 | pressureRequired:number; // 0 : any item, 1: only heavy objects, characters/walls, 2: only players
105 | }
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/EtaoinReboot.ts:
--------------------------------------------------------------------------------
1 | class EtaoinReboot_IntentionAction extends IntentionAction {
2 |
3 |
4 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
5 | {
6 | if (intention.functor.is_a(ai.o.getSort("verb.reboot")) &&
7 | (intention.attributes.length == 1 ||
8 | intention.attributes.length == 2)) return true;
9 | return false;
10 | }
11 |
12 |
13 | execute(ir:IntentionRecord, ai_raw:RuleBasedAI) : boolean
14 | {
15 | this.ir = ir;
16 | let ai:EtaoinAI = ai_raw;
17 | let intention:Term = ir.action;
18 | let requester:TermAttribute = ir.requester;
19 |
20 | if (intention.attributes.length == 1) {
21 | // Reset the AI variables:
22 | for(let c of ai.contexts) c.reset();
23 | ai.intentions = [];
24 | ai.queuedIntentions = [];
25 | ai.intentionsCausedByRequest = [];
26 | ai.currentInferenceProcess = null;
27 | ai.queuedInferenceProcesses = [];
28 | ai.respondToPerformatives = true;
29 | ai.terminateConversationAfterThisPerformative = false;
30 | ai.currentEpisodeTerms = [];
31 | ai.oxygen_message_timer = 0;
32 |
33 | app.achievement_nlp_all_etaoin_actions[6] = true;
34 | app.trigger_achievement_complete_alert();
35 |
36 | if (requester != null) {
37 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))", ai.o);
38 | ai.queueIntention(term, requester, null);
39 | }
40 |
41 | return true;
42 | } else if (intention.attributes.length == 2 &&
43 | intention.attributes[1] instanceof ConstantTermAttribute) {
44 | let target:string = (intention.attributes[1]).value;
45 | if (target == "shrdlu") {
46 | if (requester != null) {
47 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))", ai.o);
48 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
49 | }
50 | let term2:Term = new Term(ai.o.getSort("verb.reboot"),[intention.attributes[0]]);
51 | ai.game.shrdluAI.queueIntention(term2, null, null);
52 | ir.succeeded = true;
53 | return true;
54 | } else if (target == "qwerty") {
55 | if (requester != null) {
56 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))", ai.o);
57 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
58 | }
59 | let term2:Term = new Term(ai.o.getSort("verb.reboot"),[intention.attributes[0]]);
60 | ai.game.qwertyAI.queueIntention(term2, null, null);
61 | ir.succeeded = true;
62 | return true;
63 | } else if (target == "etaoin") {
64 | if (requester != null) {
65 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok("+requester+"))", ai.o);
66 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
67 | }
68 | let term2:Term = new Term(ai.o.getSort("verb.reboot"),[intention.attributes[0]]);
69 | ai.queueIntention(term2, null, null);
70 | ir.succeeded = true;
71 | return true;
72 | }
73 | }
74 |
75 | if (requester != null) {
76 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
77 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
78 | }
79 | ir.succeeded = false;
80 | return true;
81 | }
82 |
83 |
84 | saveToXML(ai:RuleBasedAI) : string
85 | {
86 | return "";
87 | }
88 |
89 |
90 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
91 | {
92 | let a:EtaoinReboot_IntentionAction = new EtaoinReboot_IntentionAction();
93 | return a;
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/src/ai/inferences/InferenceAnswerWhatIs.ts:
--------------------------------------------------------------------------------
1 | class AnswerWhatIs_InferenceEffect extends InferenceEffect {
2 | constructor(effectParameter:Term)
3 | {
4 | super()
5 | this.effectParameter = effectParameter;
6 | }
7 |
8 |
9 | execute(inf:InferenceRecord, ai:RuleBasedAI)
10 | {
11 | let listenerID:string = ((this.effectParameter.attributes[1])).value;
12 | let targetID:string = ((this.effectParameter.attributes[2])).value;
13 | let targetName:string = null;
14 | if (inf.inferences[0].endResults.length != 0) {
15 | for(let b of inf.inferences[0].endResults[0].bindings.l) {
16 | if (b[0].name == "NAME") {
17 | let v:TermAttribute = b[1];
18 | if (v instanceof ConstantTermAttribute) {
19 | targetName = (v).value;
20 | break;
21 | }
22 | }
23 | }
24 | }
25 | AnswerWhatIs_InferenceEffect.executeInferenceEffect_AnswerWhatis(targetName, targetID, listenerID, ai);
26 | }
27 |
28 | static executeInferenceEffect_AnswerWhatis(name:string, whatID:string, listenerID:string, ai:RuleBasedAI)
29 | {
30 | // console.log("executeInferenceEffect_AnswerWhatis: " + name + ", " + whatID);
31 |
32 | // get the types:
33 | let mostSpecificTypes:Term[] = ai.mostSpecificMatchesFromShortOrLongTermMemoryThatCanBeRendered(Term.fromString("object('"+whatID+"'[#id])", ai.o));
34 | if (mostSpecificTypes == null || mostSpecificTypes.length == 0) mostSpecificTypes = ai.mostSpecificMatchesFromShortOrLongTermMemoryThatCanBeRendered(Term.fromString("space.location('"+whatID+"'[#id])", ai.o));
35 | if (mostSpecificTypes == null || mostSpecificTypes.length == 0) mostSpecificTypes = ai.mostSpecificMatchesFromShortOrLongTermMemoryThatCanBeRendered(Term.fromString("abstract-entity('"+whatID+"'[#id])", ai.o));
36 | if (mostSpecificTypes == null || mostSpecificTypes.length == 0) mostSpecificTypes = ai.mostSpecificMatchesFromShortOrLongTermMemoryThatCanBeRendered(Term.fromString("role('"+whatID+"'[#id])", ai.o));
37 | console.log("executeInferenceEffect_AnswerWhatis: mostSpecificTypes: " + mostSpecificTypes);
38 |
39 | // generate the talk intentions:
40 | if (name == null && mostSpecificTypes.length == 0) {
41 | ai.intentions.push(new IntentionRecord(Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+listenerID+"'[#id],'unknown'[symbol]))", ai.o),
42 | new ConstantTermAttribute(listenerID, ai.o.getSort("#id")), null, null, ai.timeStamp));
43 | } else {
44 | if (name != null) {
45 | ai.intentions.push(new IntentionRecord(Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+listenerID+"'[#id],name('"+whatID+"'[#id],'"+name+"'[symbol])))", ai.o),
46 | new ConstantTermAttribute(listenerID, ai.o.getSort("#id")), null, null, ai.timeStamp));
47 | }
48 | if (mostSpecificTypes.length != 0) {
49 | ai.intentions.push(new IntentionRecord(Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+listenerID+"'[#id], "+mostSpecificTypes[0]+"))", ai.o),
50 | new ConstantTermAttribute(listenerID, ai.o.getSort("#id")), null, null, ai.timeStamp));
51 | }
52 | }
53 | }
54 |
55 |
56 | saveToXMLInternal(ai:RuleBasedAI, variables:TermAttribute[], variableNames:string[]) : string
57 | {
58 | return "";
59 | }
60 |
61 |
62 | static loadFromXML(xml:Element, ai:RuleBasedAI, o:Ontology, variables:TermAttribute[], variableNames:string[]) : InferenceEffect
63 | {
64 | let t:Term = Term.fromStringInternal(xml.getAttribute("effectParameter"), o, variableNames, variables).term;
65 | return new AnswerWhatIs_InferenceEffect(t);
66 | }
67 |
68 |
69 | effectParameter:Term = null;
70 | }
--------------------------------------------------------------------------------
/src/ai/inferences/InferenceMemorize.ts:
--------------------------------------------------------------------------------
1 | class Memorize_InferenceEffect extends InferenceEffect {
2 |
3 | constructor(effectParameter:Term, negated:boolean)
4 | {
5 | super()
6 | this.effectParameter = effectParameter;
7 | this.negated = negated;
8 | }
9 |
10 |
11 | execute(inf:InferenceRecord, ai:RuleBasedAI)
12 | {
13 | // memorize the target:
14 | console.log("Memorize_InferenceEffect");
15 | if (!(this.effectParameter.attributes[1] instanceof ConstantTermAttribute)) {
16 | console.error("Memorize_InferenceEffect.executeInferenceEffect: Trying to talk to a character for which we don't know the ID!");
17 | return;
18 | }
19 | let targetCharacterID:string = ((this.effectParameter.attributes[1])).value;
20 | let memorize:boolean = false;
21 |
22 | if (this.negated) {
23 | // this was the complex case:
24 | if (inf.inferences[0].endResults.length == 0) {
25 | // there was no contradiction...
26 | // We are not sure..., let's not memorize, just in case...
27 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.unsure('"+targetCharacterID+"'[#id]))", ai.o);
28 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
29 | } else {
30 | // we already knew, just say ok:
31 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok('"+targetCharacterID+"'[#id]))", ai.o);
32 | let causeRecord:CauseRecord = this.generateCauseRecord(inf.inferences[0].originalTarget, inf.inferences[0].endResults[0], ai);
33 | ai.intentions.push(new IntentionRecord(term, null, null, causeRecord, ai.timeStamp));
34 | console.log("Memorize_InferenceEffect, we already knew: " + inf.inferences[0].endResults);
35 | }
36 | } else {
37 | if (inf.inferences[0].endResults.length == 0) {
38 | // there was no contradiction... we can add the sentence safely
39 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.ok('"+targetCharacterID+"'[#id]))", ai.o);
40 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
41 | memorize = true;
42 | } else {
43 | // contradiction:
44 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.contradict('"+targetCharacterID+"'[#id]))", ai.o);
45 | let causeRecord:CauseRecord = this.generateCauseRecord(inf.inferences[0].originalTarget, inf.inferences[0].endResults[0], ai);
46 | ai.intentions.push(new IntentionRecord(term, null, null, causeRecord, ai.timeStamp));
47 | }
48 | }
49 |
50 | if (memorize) {
51 | let s_l:Sentence[] = Term.termToSentences(((this.effectParameter.attributes[2])).term, ai.o);
52 | for(let s of s_l) {
53 | if (s.terms.length == 1 && s.sign[0] == true) {
54 | console.log("Memorize_InferenceEffect, term: " + s);
55 | ai.addLongTermTerm(s.terms[0], MEMORIZE_PROVENANCE);
56 | } else {
57 | console.log("Memorize_InferenceEffect, sentence: " + s);
58 | ai.addLongTermRuleNow(s, MEMORIZE_PROVENANCE);
59 | }
60 | }
61 | }
62 | }
63 |
64 |
65 | saveToXMLInternal(ai:RuleBasedAI, variables:TermAttribute[], variableNames:string[]) : string
66 | {
67 | return "";
70 | }
71 |
72 |
73 | static loadFromXML(xml:Element, ai:RuleBasedAI, o:Ontology, variables:TermAttribute[], variableNames:string[]) : InferenceEffect
74 | {
75 | let t:Term = Term.fromStringInternal(xml.getAttribute("effectParameter"), o, variableNames, variables).term;
76 | return new Memorize_InferenceEffect(t, xml.getAttribute("negated")=="true");
77 | }
78 |
79 |
80 | effectParameter:Term = null;
81 | negated:boolean = false;
82 | }
83 |
--------------------------------------------------------------------------------
/src/ai/actions/AnswerHearSee.ts:
--------------------------------------------------------------------------------
1 | class AnswerHearSee_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.hear")) ||
6 | intention.functor.is_a(ai.o.getSort("verb.see"))) return true;
7 | return false;
8 | }
9 |
10 |
11 | execute(ir:IntentionRecord, ai:RuleBasedAI) : boolean
12 | {
13 | this.ir = ir;
14 | let requester:TermAttribute = ir.requester;
15 | let best:string = null;
16 | let l:Term[] = [ir.action];
17 | if (requester == null) return true;
18 |
19 | if (ir.alternative_actions != null && ir.alternative_actions.length > 0) l = ir.alternative_actions;
20 |
21 | for(let intention of l) {
22 | if (intention.attributes.length==2 &&
23 | (intention.attributes[0] instanceof ConstantTermAttribute) &&
24 | ((intention.attributes[0])).value == ai.selfID) {
25 |
26 | if (intention.attributes[1] instanceof ConstantTermAttribute) {
27 | // Case where the target is a constant:
28 | if (intention.functor.is_a(ai.o.getSort("verb.see"))) {
29 | if (ai.canSee(((intention.attributes[1])).value)) {
30 | // If the object was not mentioned explicitly in the performative, add it to the natural language context:
31 | if (ir.requestingPerformative != null) {
32 | ir.requestingPerformative.addMentionToPerformative(((intention.attributes[1])).value, ai.o);
33 | }
34 |
35 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer("+requester+",'yes'[symbol]))", ai.o);
36 | ai.intentions.push(new IntentionRecord(term, requester, null, null, ai.timeStamp));
37 | ir.succeeded = true;
38 | return true;
39 | } else {
40 | if (best == null) best = "no";
41 | }
42 | } else if (intention.functor.is_a(ai.o.getSort("verb.hear"))) {
43 | if (ai.canHear(((intention.attributes[1])).value)) {
44 | // If the object was not mentioned explicitly in the performative, add it to the natural language context:
45 | if (ir.requestingPerformative != null) {
46 | ir.requestingPerformative.addMentionToPerformative(((intention.attributes[1])).value, ai.o);
47 | }
48 |
49 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer("+requester+",'yes'[symbol]))", ai.o);
50 | ai.intentions.push(new IntentionRecord(term, requester, null, null, ai.timeStamp));
51 | ir.succeeded = true;
52 | return true;
53 | } else {
54 | if (best == null) best = "no";
55 | }
56 | } else {
57 | // we should never get here...
58 | if (best == null || best=="no") best = "unknown";
59 | }
60 | } else {
61 | if (best == null || best=="no") best = "unknown";
62 | }
63 |
64 | } else {
65 | if (best == null || best=="no") best = "unknown";
66 | }
67 | }
68 |
69 | if (best == "no") {
70 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer("+requester+",'no'[symbol]))", ai.o);
71 | ai.intentions.push(new IntentionRecord(term, requester, null, null, ai.timeStamp));
72 | } else {
73 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer("+requester+",'unknown'[symbol]))", ai.o);
74 | ai.intentions.push(new IntentionRecord(term, requester, null, null, ai.timeStamp));
75 | }
76 | ir.succeeded = true;
77 | return true;
78 | }
79 |
80 |
81 | saveToXML(ai:RuleBasedAI) : string
82 | {
83 | return "";
84 | }
85 |
86 |
87 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
88 | {
89 | return new AnswerHearSee_IntentionAction();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/shrdlu/ShrdluAirlockDoor.ts:
--------------------------------------------------------------------------------
1 | class ShrdluAirlockDoor extends A4Obstacle {
2 |
3 | constructor(name:string, sort:Sort)
4 | {
5 | super(name, sort);
6 | this.interacteable = true;
7 |
8 | }
9 |
10 |
11 | loadObjectAttribute(attribute_xml:Element) : boolean
12 | {
13 | if (super.loadObjectAttribute(attribute_xml)) return true;
14 | let a_name:string = attribute_xml.getAttribute("name");
15 |
16 | if (a_name == "otherDoorID") {
17 | this.otherDoorID = attribute_xml.getAttribute("value");
18 | if (this.otherDoorID == "null") this.otherDoorID = null;
19 | return true;
20 | } else if (a_name == "requireSuit") {
21 | this.requireSuit = false;
22 | if (attribute_xml.getAttribute("value") == "true") this.requireSuit = true;
23 | return true;
24 | } else if (a_name == "targetMap") {
25 | this.targetMap = attribute_xml.getAttribute("value");
26 | return true;
27 | } else if (a_name == "targetX") {
28 | this.targetX = Number(attribute_xml.getAttribute("value"));
29 | return true;
30 | } else if (a_name == "targetY") {
31 | this.targetY = Number(attribute_xml.getAttribute("value"));
32 | return true;
33 | }
34 | return false;
35 | }
36 |
37 |
38 | savePropertiesToXML(game:A4Game) : string
39 | {
40 | let xmlString:string = super.savePropertiesToXML(game);
41 |
42 | if (this.otherDoorID != null) xmlString += this.saveObjectAttributeToXML("otherDoorID",this.otherDoorID) + "\n";
43 | xmlString += this.saveObjectAttributeToXML("requireSuit",this.requireSuit) + "\n";
44 | xmlString += this.saveObjectAttributeToXML("targetMap",this.targetMap) + "\n";
45 | xmlString += this.saveObjectAttributeToXML("targetX",this.targetX) + "\n";
46 | xmlString += this.saveObjectAttributeToXML("targetY",this.targetY) + "\n";
47 |
48 | return xmlString;
49 | }
50 |
51 |
52 | isEquipable() : boolean
53 | {
54 | return true;
55 | }
56 |
57 |
58 | event(event_type:number, character:A4Character, map:A4Map, game:A4Game): boolean
59 | {
60 | let retval:boolean = super.event(event_type, character, map, game);
61 | if (event_type == A4_EVENT_INTERACT) {
62 | if (this.otherDoorID != null) {
63 | let otherdoor:A4Door = game.findObjectByIDJustObject(this.otherDoorID);
64 |
65 | if (!otherdoor.closed) {
66 | let script:A4Script = new A4Script(A4_SCRIPT_TALK, null, "I need to close the other airlock door first", 0, true, true);
67 | character.pushScripttoExecute(script, map, game, null);
68 | return false;
69 | }
70 | }
71 |
72 | if (this.requireSuit) {
73 | let suit:string = game.getStoryStateVariable("spacesuit");
74 | if (suit != "helmet") {
75 | let script:A4Script = new A4Script(A4_SCRIPT_TALK, null, "I cannot go through the airlock without a spacesuit!", 0, true, true);
76 | character.pushScripttoExecute(script, map, game, null);
77 | return false;
78 | }
79 | }
80 |
81 | // go to the target destination:
82 | let targetMap:A4Map = game.getMap(this.targetMap);
83 | if (targetMap != null) {
84 | game.requestWarp(character, targetMap, this.targetX, this.targetY);
85 | }
86 | return true;
87 | }
88 |
89 | return retval;
90 | }
91 |
92 |
93 | otherDoorID:string = null;
94 | requireSuit:boolean = false;
95 | targetMap:string = null;
96 | targetX:number = -1;
97 | targetY:number = -1;
98 | };
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Play on your browser (v3.9)
2 |
3 | https://braingames.santiontanon.dev/games/shrdlu/v39/shrdlu.html
4 |
5 | # SHRDLU
6 |
7 |
8 |
9 |
10 |
11 | SHRDLU is an adventure game based around natural language parsing (inspired by Winograd's SHRDLU). It is an experiment, so, I am not expecting the resulting game to be an amazing game, but just an exploration of the posibilities of this type of AI system in a game.
12 |
13 | You can play the game here (still work in progress, but it can already be played beginning to end!): https://braingames.santiontanon.dev/games/shrdlu/v39/shrdlu.html
14 |
15 | Alternatively, you can view demo videos here:
16 | - https://youtu.be/8FNBTs2yv4s (version 1.1)
17 | - https://www.youtube.com/watch?v=dPKfS9DsLmM (version 2.5)
18 |
19 | ## Other NLP-based games:
20 |
21 | SHRDLU is by no means the first game to use NLP techniques. Besides the classic parser-based interactive fiction games like Zork, or The Hobbit, or those that have keyword based conversations (like many (MANY) RPG games), several games attempt to use natural language parsing to allow players to interact in open-ended natural language. For example (this is not an exhaustive list):
22 | - Façade
23 | - MKUltra
24 | - The LabLabLab games (like SimHamlet)
25 | - Event[0]
26 | - Bot Colony
27 |
28 |
29 | ## Notes
30 |
31 | - If you are interested on how the AI in the game works, there is a PDF explaining it inside of the misc folder: https://github.com/santiontanon/SHRDLU/blob/master/misc/SHRDLU.pdf
32 |
33 | - The game has been coded using TypeScript; basically a typed version of JavaScript, which is much more natural to code in if you are used to object oriented languages. TypeScript compiles to JavaScript, and thus the game can run in any web browser that supports JavaScript. Although in principle any modern browser should do, I usually test using Safari and Chrome. The game is built in plain TypeScript, and no additional libraries have been used.
34 |
35 | - Concerning the visual style, I chose a 256x192 screen resolution with a 16 color palette. These were the specs of the MSX computer, which was the 8bit computer with which I learned how to code when I was a kid.
36 |
37 | - The font used for the game is the original font used in the BIOS of 1983 MSX computers. I used this procedure to generate it: http://www.ateijelo.com/blog/2016/09/13/making-an-msx-font
38 |
39 | - Concerning the use of time in the game: I know dates and times in a 24 hour system with Earh years doesn't make any sense in an alien planet with an undisclosed day/night or star revolution cycle. However, I decided to go with Earth time and dates, to keep the AI simple, and not having to create domain knowledge rules to handle time scale conversions, etc. So, this was a small concesion to make time-handling in the AI-side easier.
40 |
41 | - I would like to thank all the people that helped me betatest the game so far (in chronological order): Jichen Zhu, Josep Valls-Vargas, Jordi Sureda, Sam Snodgrass, Javier Torres, Adam Summerville, Ahmed Khalifa, Sri Krishna, Bayan Mashat, Pavan Kantharaju, Chris Martens, Yaqirah Rice, Zuozhi Yang, Shannen Angell, Robert Gray and every one who played my demo at AIIDE 2018 and 2019! Thank you one more time! :D
42 |
43 |
--------------------------------------------------------------------------------
/src/auxiliar/BButton.ts:
--------------------------------------------------------------------------------
1 | var BBUTTON_STATE_NORMAL:number = 0;
2 | var BBUTTON_STATE_MOUSEOVER:number = 1;
3 | var BBUTTON_STATE_PRESSED:number = 2;
4 |
5 |
6 | class BButton extends BInterfaceElement {
7 | constructor(text:string, font:string, x:number, y:number, width:number, height:number, ID:number, callback:((any, number) => void))
8 | {
9 | super(x, y, width, height);
10 | this.text = text;
11 | this.font = font;
12 | this.ID = ID;
13 | this.callback = callback;
14 |
15 | this.active=true;
16 | }
17 |
18 | setText(text:string)
19 | {
20 | this.text = text;
21 | }
22 |
23 |
24 | update(mouse_x:number, mouse_y:number, k:KeyboardState, arg:any) : boolean
25 | {
26 | this.cycle++;
27 | this.status = BBUTTON_STATE_NORMAL;
28 | if (!this.enabled) return false;
29 | if (this.highlighted(mouse_x, mouse_y)) {
30 | this.status = BBUTTON_STATE_MOUSEOVER;
31 | if (k.key_press(KEY_CODE_RETURN) ||
32 | k.key_press(KEY_CODE_SPACE)) {
33 | if (this.callback != null) this.callback(arg, this.ID);
34 | }
35 | }
36 | return false;
37 | }
38 |
39 |
40 | mouseClick(mouse_x: number, mouse_y: number, button: number, arg:any)
41 | {
42 | if (this.callback != null) this.callback(arg, this.ID);
43 | }
44 |
45 |
46 | drawAlpha(alpha:number)
47 | {
48 | ctx.save();
49 | if (!this.enabled) alpha /= 3;
50 | ctx.globalAlpha = alpha;
51 | switch(this.status) {
52 | case BBUTTON_STATE_MOUSEOVER:
53 | ctx.fillStyle = generateRGBColor(80, 80, 160);
54 | break;
55 | case BBUTTON_STATE_PRESSED:
56 | ctx.fillStyle = generateRGBColor(160, 160, 224);
57 | break;
58 | default:
59 | ctx.fillStyle = generateRGBColor(40, 40, 80);
60 | }
61 | ctx.fillRect(this.x, this.y, this.width, this.height);
62 |
63 | ctx.fillStyle = "white";
64 | ctx.font = this.font;
65 | ctx.textBaseline = "middle";
66 | ctx.textAlign = "center";
67 | ctx.fillText(this.text, this.x+this.width/2, this.y+this.height/2);
68 | ctx.restore();
69 | }
70 |
71 |
72 | text:string = null;
73 | font:string = null;
74 | status:number = BBUTTON_STATE_NORMAL;
75 | cycle:number = 0;
76 | callback:((any, number) => void);
77 |
78 | }
79 |
80 | class BButtonTransparent extends BButton {
81 | constructor(text:string, font:string, x:number, y:number, width:number, height:number, ID:number, color:string, callback:((any, number) => void))
82 | {
83 | super(text, font, x, y, width, height, ID, callback);
84 | this.color = color;
85 | }
86 |
87 | drawAlpha(alpha:number)
88 | {
89 | ctx.save();
90 | if (!this.enabled) alpha /= 3;
91 | let f:number = (0.5+0.3*Math.sin((this.cycle)*0.3));
92 | ctx.fillStyle = generateRGBColor(192, 192, 192);
93 | ctx.globalAlpha = alpha;
94 | switch(this.status) {
95 | case BBUTTON_STATE_MOUSEOVER:
96 | ctx.fillStyle = generateRGBColor(f, f, f);
97 | ctx.globalAlpha = alpha*f;
98 | break;
99 | case BBUTTON_STATE_PRESSED:
100 | ctx.fillStyle = this.color;
101 | break;
102 | default:
103 | }
104 |
105 | ctx.font = this.font;
106 | ctx.textBaseline = "middle";
107 | ctx.textAlign = "center";
108 | ctx.fillText(this.text, this.x+this.width/2, this.y+this.height/2);
109 | ctx.restore();
110 | }
111 |
112 | color:string;
113 | }
114 |
115 |
--------------------------------------------------------------------------------
/src/auxiliar/auxiliar.ts:
--------------------------------------------------------------------------------
1 | function getElementChildrenByTag(xml:Element, tag:string) : Element[]
2 | {
3 | let l:Element[] = [];
4 | for(let i:number = 0;i=maxWidth) {
71 | if (last_space==0) {
72 | // a single word doesn't fit, just split it!
73 | lines.push(buffer);
74 | if (buffer.length>longestLine) longestLine = buffer.length;
75 | buffer = "";
76 | } else {
77 | let backspaces:number = i - last_space;
78 | buffer = buffer.substring(0,buffer.length-backspaces);
79 | i-=backspaces;
80 | lines.push(buffer);
81 | if (buffer.length>longestLine) longestLine = buffer.length;
82 | buffer = "";
83 | last_space = 0;
84 | }
85 | }
86 | }
87 |
88 | if (buffer != "") {
89 | lines.push(buffer);
90 | if (buffer.length>longestLine) longestLine = buffer.length;
91 | }
92 |
93 | return lines;
94 | }
95 |
96 |
97 | function allArrayElementsTrue(booleanArray:boolean[]) : boolean
98 | {
99 | for(let i:number = 0; i 0; i--) {
119 | let j:number = Math.floor(Math.random() * (i+1));
120 | let tmp:any = list[i];
121 | list[i] = list[j];
122 | list[j] = tmp;
123 | }
124 | }
125 |
126 |
127 | function startsWith(str:string, prefix:string): boolean
128 | {
129 | return str.substring(0, prefix.length) == prefix;
130 | }
131 |
--------------------------------------------------------------------------------
/data/map5-east-cave.tmx:
--------------------------------------------------------------------------------
1 |
2 |
49 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/ShrdluAI.ts:
--------------------------------------------------------------------------------
1 | class ShrdluAI extends RobotAI {
2 | constructor(o:Ontology, nlp:NLParser, shrdlu:A4AICharacter, game:ShrdluA4Game,
3 | rulesFileNames:string[])
4 | {
5 | super(o, nlp, shrdlu, game, rulesFileNames);
6 | console.log("ShrdluAI.constructor end...");
7 | this.robot.ID = "shrdlu";
8 | this.selfID = "shrdlu";
9 | this.visionActive = false;
10 |
11 | this.addLongTermTerm(Term.fromString("name('"+this.robot.ID+"'[#id],'shrdlu'[symbol])", this.o), BACKGROUND_PROVENANCE);
12 |
13 | this.objectsNotAllowedToGive.push("master-key2");
14 |
15 | console.log("ShrdluAI.constructor end...");
16 | }
17 |
18 |
19 | canSatisfyActionRequest(ir:IntentionRecord) : number
20 | {
21 | let actionRequest:Term = ir.action;
22 | let repairSort:Sort = this.o.getSort("verb.repair");
23 | if (actionRequest.functor.is_a(repairSort) && actionRequest.attributes.length>=2) {
24 | let thingToRepair:TermAttribute = actionRequest.attributes[1];
25 | if (thingToRepair instanceof ConstantTermAttribute) {
26 | let thingToRepair_id:string = (thingToRepair).value;
27 | if (thingToRepair_id == "garage-shuttle") {
28 | let thingToRepairObject:A4Object = this.game.findObjectByIDJustObject(thingToRepair_id);
29 | if (thingToRepairObject.sort.name == "brokenshuttle") {
30 | // broken shuttle:
31 | app.achievement_nlp_all_robot_actions[11] = true;
32 | app.trigger_achievement_complete_alert();
33 | return ACTION_REQUEST_CAN_BE_SATISFIED;
34 | }
35 | } else if (thingToRepair_id == "tardis-wall-computer") {
36 |
37 | app.achievement_nlp_all_robot_actions[11] = true;
38 | app.trigger_achievement_complete_alert();
39 | return ACTION_REQUEST_CAN_BE_SATISFIED;
40 | } else if (thingToRepair_id == "tardis-broken-cable") {
41 |
42 | app.achievement_nlp_all_robot_actions[11] = true;
43 | app.trigger_achievement_complete_alert();
44 | return ACTION_REQUEST_CAN_BE_SATISFIED;
45 | }
46 | } else {
47 | return ACTION_REQUEST_CANNOT_BE_SATISFIED;
48 | }
49 | }
50 |
51 | return super.canSatisfyActionRequest(ir);
52 | }
53 |
54 |
55 | executeIntention(ir:IntentionRecord) : boolean
56 | {
57 | let intention:Term = ir.action;
58 | let repairSort:Sort = this.o.getSort("verb.repair");
59 | if (intention.functor.is_a(repairSort)) {
60 | // just ignore, the story script will take charge of making shrdlu do the repair...
61 | this.clearCurrentAction();
62 | return true;
63 | }
64 |
65 | return super.executeIntention(ir);
66 | }
67 |
68 |
69 | /*
70 | - If it returns "null", it means the robot can go
71 | - If it returns a Term, it means the robot cannot go, for the reason specified in the Term (e.g., not allowed)
72 | */
73 | canGoTo(map:A4Map, locationID:string, requester:TermAttribute) : Term
74 | {
75 | if ((this.robot.map.name == "Aurora Station" || this.robot.map.name == "Aurora Station Outdoors") &&
76 | map.name != "Aurora Station" &&
77 | map.name != "Aurora Station Outdoors") {
78 | if (this.game.getStoryStateVariable("permission-to-take-shrdlu") == "false") {
79 | if ((requester instanceof ConstantTermAttribute) &&
80 | (requester).value == "etaoin") {
81 | this.game.setStoryStateVariable("permission-to-take-shrdlu", "true");
82 | return null;
83 | } else {
84 | let cause:Term = Term.fromString("verb.need('"+this.selfID+"'[#id], #and(X:[permission-to], relation.origin(X, 'etaoin'[#id])))", this.o);
85 | return cause;
86 | }
87 | }
88 | }
89 | return super.canGoTo(map, locationID, requester);
90 | }
91 |
92 |
93 | considerGoals() : boolean
94 | {
95 | // Shrdlu only has goals that are within the station:
96 | if (this.robot.map.name != "Aurora Station" && this.robot.map.name != "Aurora Station Outdoors") return false;
97 | return super.considerGoals();
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/ai/actions/AnswerDefine.ts:
--------------------------------------------------------------------------------
1 | class AnswerDefine_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.define"))) return true;
6 | return false;
7 | }
8 |
9 |
10 | execute(ir:IntentionRecord, ai:RuleBasedAI) : boolean
11 | {
12 | this.ir = ir;
13 | let intention:Term = ir.action;
14 | let requester:TermAttribute = ir.requester;
15 |
16 | if (intention.attributes.length == 2 &&
17 | (intention.attributes[0] instanceof ConstantTermAttribute) &&
18 | ((intention.attributes[1] instanceof VariableTermAttribute) ||
19 | (intention.attributes[1] instanceof ConstantTermAttribute)) &&
20 | ((intention.attributes[0])).value == ai.selfID) {
21 | let sortToDefine:Sort = intention.attributes[1].sort;
22 | let definitionAsTerm:TermAttribute = null;
23 |
24 | if (sortToDefine.name == "three-laws-of-robotics") {
25 | // easeter egg!
26 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], '1. A robot may not injure a human being or, through inaction, allow a human being to come to harm.'[symbol])", ai.o);
27 | ai.intentions.push(new IntentionRecord(term, requester, null, null, ai.timeStamp));
28 | term = Term.fromString("action.talk('"+ai.selfID+"'[#id], '2. A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.'[symbol])", ai.o);
29 | ai.intentions.push(new IntentionRecord(term, requester, null, null, ai.timeStamp));
30 | term = Term.fromString("action.talk('"+ai.selfID+"'[#id], '3. A robot must protect its own existence as long as such protection does not conflict with the First or Second Laws.'[symbol])", ai.o);
31 | ai.intentions.push(new IntentionRecord(term, requester, null, null, ai.timeStamp));
32 | ir.succeeded = true;
33 | return true;
34 | }
35 |
36 | for(let i:number = 0;i";
80 | }
81 |
82 |
83 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
84 | {
85 | return new AnswerDefine_IntentionAction();
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/ai/inferences/InferenceAnswerHowMany.ts:
--------------------------------------------------------------------------------
1 | class AnswerHowMany_InferenceEffect extends InferenceEffect {
2 | constructor(effectParameter:Term)
3 | {
4 | super()
5 | this.effectParameter = effectParameter;
6 | }
7 |
8 |
9 | execute(inf:InferenceRecord, ai:RuleBasedAI)
10 | {
11 | console.log("executeInferenceEffect: INFERENCE_RECORD_EFFECT_ANSWER_HOWMANY");
12 | console.log("inf.inferences.length: " + inf.inferences.length);
13 | console.log("inf.inferences[0].endResults.length: " + inf.inferences[0].endResults.length);
14 |
15 | if (!(this.effectParameter.attributes[1] instanceof ConstantTermAttribute)) {
16 | console.error("AnswerHowMany_InferenceEffect.execute: Trying to talk to a character for which we don't know the ID!");
17 | return;
18 | }
19 | let speakerCharacterID:string = ((this.effectParameter.attributes[1])).value;
20 | let queryPerformative:Term = ((this.effectParameter.attributes[2])).term;
21 | let queryVariable:VariableTermAttribute = (queryPerformative.attributes[1]);
22 | let queryTerm:Term = null;
23 | if (queryPerformative.attributes[2] instanceof TermTermAttribute) {
24 | queryTerm = ((queryPerformative.attributes[2])).term;
25 | }
26 | let negativeAnswer:string = "'no-matches-found'[symbol]";
27 | if (queryTerm != null &&
28 | (queryTerm.functor.is_a(ai.cache_sort_property_with_value) ||
29 | queryTerm.functor.is_a(ai.cache_sort_relation_with_value))) negativeAnswer = "'unknown'[symbol]";
30 | if (inf.inferences[0].endResults.length != 0) {
31 | let results:TermAttribute[] = [];
32 | for(let result of inf.inferences[0].endResults) {
33 | for(let [variable, value] of result.bindings.l) {
34 | if (variable == queryVariable) {
35 | let found:boolean = false;
36 | for(let value2 of results) {
37 | if (Term.equalsAttribute(value2, value, new Bindings())) {
38 | found = true;
39 | break;
40 | }
41 | }
42 | if (!found) {
43 | // we have a result!
44 | results.push(value);
45 | }
46 | }
47 | }
48 | }
49 | console.log("results: " + results);
50 | if (results.length > 0) {
51 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id],'"+results.length+"'[number]))", ai.o);
52 | // store the state in case there are more answers to be given later using perf.more answers
53 | let context:NLContext = ai.contextForSpeaker(speakerCharacterID);
54 | if (context != null) {
55 | ai.intentions.push(new IntentionRecord(term, null, context.getNLContextPerformative(queryPerformative), null, ai.timeStamp));
56 | } else {
57 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
58 | }
59 | } else {
60 | console.error("Inference produced a result, but none of the resulting variables is the query variable!");
61 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id],"+negativeAnswer+"))", ai.o);
62 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
63 | }
64 | } else {
65 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id],"+negativeAnswer+"))", ai.o);
66 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
67 | }
68 | }
69 |
70 |
71 | saveToXMLInternal(ai:RuleBasedAI, variables:TermAttribute[], variableNames:string[]) : string
72 | {
73 | return "";
74 | }
75 |
76 |
77 | static loadFromXML(xml:Element, ai:RuleBasedAI, o:Ontology, variables:TermAttribute[], variableNames:string[]) : InferenceEffect
78 | {
79 | let t:Term = Term.fromStringInternal(xml.getAttribute("effectParameter"), o, variableNames, variables).term;
80 | return new AnswerHowMany_InferenceEffect(t);
81 | }
82 |
83 |
84 | effectParameter:Term = null;
85 | }
--------------------------------------------------------------------------------
/src/ai/actions/AnswerWhoIs.ts:
--------------------------------------------------------------------------------
1 | class AnswerWhoIs_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("action.answer.whois.name")) ||
6 | intention.functor.is_a(ai.o.getSort("action.answer.whois.noname"))) return true;
7 | return false;
8 | }
9 |
10 |
11 | execute(ir:IntentionRecord, ai:RuleBasedAI) : boolean
12 | {
13 | this.ir = ir;
14 | let intention:Term = ir.action;
15 |
16 | if (intention.functor.is_a(ai.o.getSort("action.answer.whois.name"))) {
17 | console.log(ai.selfID + " answer whois.name: " + intention.attributes[2]);
18 | if (intention.attributes[1] instanceof ConstantTermAttribute &&
19 | intention.attributes[2] instanceof ConstantTermAttribute) {
20 | let listenerID:string = (intention.attributes[1]).value;
21 | // Don't do any inference for now (we'll see if I need it later on),
22 | // directly call the same function that will be called after the inference in whois.noname:
23 | AnswerWho_InferenceEffect.AnswerWhois(null, (intention.attributes[2]).value, listenerID, true, ai);
24 | } else if (intention.attributes.length == 4 &&
25 | intention.attributes[1] instanceof ConstantTermAttribute &&
26 | intention.attributes[2] instanceof VariableTermAttribute &&
27 | intention.attributes[3] instanceof TermTermAttribute) {
28 | let query:Term = ((intention.attributes[3])).term;
29 | let query_l:Term[] = [query];
30 | if (query.functor.name == "#and") {
31 | query_l = NLParser.termsInList(query, "#and");
32 | }
33 | let query_l_signs:boolean[] = [];
34 | for(let i:number = 0;i(query_l[i].attributes[0])).term;
37 | query_l_signs.push(true);
38 | } else {
39 | query_l_signs.push(false);
40 | }
41 | }
42 | let target1:Sentence[] = [new Sentence(query_l,query_l_signs)];
43 | ai.queuedInferenceProcesses.push(new InferenceRecord(ai, [], [target1], 1, 0, false, null, new AnswerWho_InferenceEffect(intention)));
44 | } else {
45 | console.error("executeIntention answer whois.name: case not handled: " + intention);
46 | ir.succeeded = false;
47 | }
48 | return true;
49 |
50 |
51 | } else if (intention.functor.is_a(ai.o.getSort("action.answer.whois.noname"))) {
52 | if (intention.attributes.length >= 3) {
53 | console.log(ai.selfID + " answer whois.noname: " + intention.attributes[2]);
54 | if (intention.attributes[1] instanceof ConstantTermAttribute &&
55 | intention.attributes[2] instanceof ConstantTermAttribute) {
56 | // target 1: name of the entity:
57 | let target1:Sentence[] = [new Sentence([new Term(ai.o.getSort("name"),
58 | [intention.attributes[2],
59 | new VariableTermAttribute(ai.o.getSort("symbol"), "NAME")])],[false])];
60 | ai.queuedInferenceProcesses.push(new InferenceRecord(ai, [], [target1], 1, 0, false, null, new AnswerWho_InferenceEffect(intention)));
61 | // TODO: this should have some temporary value (in all actions that require inference or continuous execution)
62 | // that is then replaced with true/false after inference/continuous is done
63 | ir.succeeded = true;
64 | } else {
65 | console.error("executeIntention answer whois.noname: attribute[1] or attribute[2] was not a ConstantTermAttribute: " + intention);
66 | ir.succeeded = false;
67 | }
68 | } else {
69 | console.error("executeIntention answer whois.noname: less attributes than expected!");
70 | ir.succeeded = false;
71 | }
72 |
73 | return true;
74 | }
75 |
76 | ir.succeeded = false;
77 | return false;
78 | }
79 |
80 |
81 | saveToXML(ai:RuleBasedAI) : string
82 | {
83 | return "";
84 | }
85 |
86 |
87 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
88 | {
89 | return new AnswerWhoIs_IntentionAction();
90 | }
91 | }
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/shrdlu/ai/actions/RobotHelp.ts:
--------------------------------------------------------------------------------
1 | class RobotHelp_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("verb.help")) &&
6 | intention.attributes.length >= 2) return true;
7 | return false;
8 | }
9 |
10 |
11 | execute(ir:IntentionRecord, ai:RuleBasedAI) : boolean
12 | {
13 | this.ir = ir;
14 | let intention:Term = ir.action;
15 | let requester:TermAttribute = ir.requester;
16 |
17 | // execute the memorize action:
18 | console.log(ai.selfID + " help: " + intention);
19 |
20 | if (intention.attributes.length == 2) {
21 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.q.how("+requester+", verb.help('"+ai.selfID+"'[#id],"+intention.attributes[1]+")))", ai.o);
22 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
23 | ir.succeeded = true;
24 | } else if (intention.attributes.length == 3 && (intention.attributes[2] instanceof TermTermAttribute)) {
25 |
26 | if ((ai).robot.isInVehicle()) {
27 | if (requester != null) {
28 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
29 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
30 | }
31 | ir.succeeded = false;
32 | return true;
33 | }
34 |
35 | let nestedIntention:Term = (intention.attributes[2]).term;
36 | if (nestedIntention.attributes.length > 0 &&
37 | (nestedIntention.attributes[0] instanceof ConstantTermAttribute)) {
38 | let handler:IntentionAction = null;
39 | let newNestedIntention:Term = nestedIntention.clone([]);
40 | newNestedIntention.attributes[0] = new ConstantTermAttribute(ai.selfID, ai.cache_sort_id);
41 |
42 | if (newNestedIntention.functor.is_a(ai.o.getSort("verb.go"))) {
43 | // Special case for verb "go":
44 | newNestedIntention = new Term(ai.o.getSort("verb.take-to"),
45 | [intention.attributes[0], // the AI ID
46 | nestedIntention.attributes[0], // the requester ID
47 | nestedIntention.attributes[1]]); // the target destination
48 | }
49 | console.log("RobotHelp_IntentionAction, newNestedIntention:" + newNestedIntention);
50 | for(let ih of ai.intentionHandlers) {
51 | if (ih.canHandle(newNestedIntention, ai)) {
52 | handler = ih;
53 | break;
54 | }
55 | }
56 | if (handler != null) {
57 | let newIr:IntentionRecord = new IntentionRecord(newNestedIntention, ir.requester, ir.requestingPerformative, ir.cause, ir.timeStamp);
58 | ir.succeeded = true;
59 | return handler.execute(newIr, ai);
60 | } else {
61 | if (requester != null) {
62 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
63 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
64 | }
65 | ir.succeeded = false;
66 | return true;
67 | }
68 | } else {
69 | if (requester != null) {
70 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
71 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
72 | }
73 | return true;
74 | ir.succeeded = false;
75 | }
76 |
77 | } else {
78 | if (requester != null) {
79 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.ack.denyrequest("+requester+"))", ai.o);
80 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
81 | }
82 | ir.succeeded = false;
83 | return true;
84 | }
85 | ir.succeeded = true;
86 | return true;
87 | }
88 |
89 |
90 | saveToXML(ai:RuleBasedAI) : string
91 | {
92 | return "";
93 | }
94 |
95 |
96 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
97 | {
98 | return new RobotHelp_IntentionAction();
99 | }
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/src/ai/actions/AnswerDistance.ts:
--------------------------------------------------------------------------------
1 | class AnswerDistance_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("action.answer.distance"))) return true;
6 | return false;
7 | }
8 |
9 |
10 | execute(ir:IntentionRecord, ai:RuleBasedAI) : boolean
11 | {
12 | this.ir = ir;
13 | let intention:Term = ir.action;
14 | let requester:TermAttribute = ir.requester;
15 |
16 | let o1ID:string = null;
17 | let o2ID:string = null;
18 | let units:Sort = ai.o.getSort("meter");
19 |
20 | if (intention.attributes.length>=4) {
21 | if (intention.attributes[2] instanceof ConstantTermAttribute) {
22 | o1ID = (intention.attributes[2]).value;
23 | }
24 | if (intention.attributes[3] instanceof ConstantTermAttribute) {
25 | o2ID = (intention.attributes[3]).value;
26 | }
27 | }
28 | if (intention.attributes.length>=5) units = intention.attributes[4].sort;
29 |
30 | if (o1ID == null || o2ID == null) {
31 | if (requester != null) {
32 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer("+intention.attributes[1]+",'unknown'[symbol]))", ai.o);
33 | ai.intentions.push(new IntentionRecord(term, intention.attributes[1], null, null, ai.timeStamp));
34 | }
35 | ir.succeeded = false;
36 | return true;
37 | }
38 |
39 | let d:number = ai.distanceBetweenIds(o1ID, o2ID);
40 | if (d != null) {
41 | let d2:number = null;
42 | d2 = this.convertToUnits(d, units);
43 | if (d2 != null) {
44 | d = d2;
45 | } else {
46 | units = ai.o.getSort("meter");
47 | }
48 | if (requester != null) {
49 | // we know the answer already without inference!
50 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer("+intention.attributes[1]+",'"+d+"'["+units.name+"]))", ai.o);
51 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
52 | }
53 | ir.succeeded = true;
54 | return true;
55 | }
56 |
57 | // launch an inference process:
58 | {
59 | let newPerformative:Term = Term.fromString("action.answer.distance('"+ai.selfID+"'[#id], "+requester+", perf.q.query('"+ai.selfID+"'[#id], DISTANCE, distance('"+o1ID+"'[#id],'"+o2ID+"'[#id], DISTANCE)))", ai.o);
60 | let negated_s_l:Sentence[] = Term.termToSentences(new Term(ai.o.getSort("#not"), [((newPerformative.attributes[2])).term.attributes[2]]), ai.o);
61 | ai.queuedInferenceProcesses.push(new InferenceRecord(ai, [], [negated_s_l], 1, 0, false, null, new AnswerQuery_InferenceEffect(newPerformative, ir.requestingPerformative)));
62 | // TODO: this should have some temporary value (in all actions that require inference or continuous execution)
63 | // that is then replaced with true/false after inference/continuous is done
64 | ir.succeeded = true;
65 | }
66 | // if (requester != null) {
67 | // let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer("+intention.attributes[1]+",'unknown'[symbol]))", ai.o);
68 | // ai.intentions.push(new IntentionRecord(term, intention.attributes[1], null, null, ai.timeStamp));
69 | // }
70 | return true;
71 | }
72 |
73 |
74 | convertToUnits(meters:number, unit:Sort) : number
75 | {
76 | /*
77 |
78 |
79 |
80 |
81 | */
82 | if (unit.name == "milimiter") return meters*1000;
83 | if (unit.name == "meter") return meters;
84 | if (unit.name == "kilometer") return meters/1000;
85 | if (unit.name == "light-year") return meters/9.461E15;
86 | return null;
87 | }
88 |
89 |
90 | saveToXML(ai:RuleBasedAI) : string
91 | {
92 | return "";
93 | }
94 |
95 |
96 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
97 | {
98 | return new AnswerDistance_IntentionAction();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/ai/inferences/InferenceAnswerWhy.ts:
--------------------------------------------------------------------------------
1 | class AnswerWhy_InferenceEffect extends InferenceEffect {
2 | constructor(effectParameter:Term)
3 | {
4 | super()
5 | this.effectParameter = effectParameter;
6 | }
7 |
8 |
9 | execute(inf:InferenceRecord, ai:RuleBasedAI)
10 | {
11 | console.log("executeInferenceEffect: INFERENCE_RECORD_EFFECT_ANSWER_WHY");
12 | console.log("inf.inferences.length: " + inf.inferences.length);
13 | console.log("inf.inferences[0].endResults: " + inf.inferences[0].endResults);
14 |
15 | if (!(this.effectParameter.attributes[1] instanceof ConstantTermAttribute)) {
16 | console.error("AnswerWhy_InferenceEffect.execute: Trying to talk to a character for which we don't know the ID!");
17 | return;
18 | }
19 | let speakerCharacterID:string = ((this.effectParameter.attributes[1])).value;
20 | let toExplain:TermAttribute = this.effectParameter.attributes[2];
21 | let negativeAnswer:string = "'unknown'[symbol]";
22 | if (inf.inferences[0].endResults.length != 0) {
23 | let results:TermAttribute[] = [];
24 | for(let result of inf.inferences[0].endResults) {
25 | for(let [variable, value] of result.bindings.l) {
26 | if (variable.name == "CAUSE" &&
27 | results.indexOf(value) == -1) {
28 | // we have a result!
29 | results.push(value);
30 | }
31 | }
32 | }
33 | // console.log("result: " + result);
34 | if (results.length > 0) {
35 | let answer:Term = new Term(ai.o.getSort("relation.cause"),[toExplain, results[0]]);
36 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id],"+answer+"))", ai.o);
37 | // console.log("term: " + term);
38 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
39 | } else {
40 | console.error("Inference produced a result, but none of the resulting variables is the query variable!");
41 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id],"+negativeAnswer+"))", ai.o);
42 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
43 | }
44 | } else if (inf.inferences[1].endResults.length != 0) {
45 | let causeRecord:CauseRecord = this.generateCauseRecord(inf.inferences[1].originalTarget, inf.inferences[1].endResults[0], ai);
46 | if (causeRecord == null) {
47 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id],"+negativeAnswer+"))", ai.o);
48 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
49 | } else {
50 | let causeTerm:Term = causeRecord.term;
51 | let causeTerms:Term[] = [];
52 | if (causeTerm.functor.name == "#and") {
53 | let tal:TermAttribute[] = Term.elementsInList(causeTerm,"#and");
54 | for(let ta of tal) {
55 | if (ta instanceof TermTermAttribute) {
56 | causeTerms.push((ta).term);
57 | }
58 | }
59 | } else {
60 | causeTerms = [causeTerm];
61 | }
62 | for(let causeTerm2 of causeTerms) {
63 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id], relation.cause([any],"+causeTerm2+")))", ai.o);
64 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
65 | }
66 | }
67 | } else {
68 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer('"+speakerCharacterID+"'[#id],"+negativeAnswer+"))", ai.o);
69 | ai.intentions.push(new IntentionRecord(term, null, null, null, ai.timeStamp));
70 | }
71 | }
72 |
73 |
74 | saveToXMLInternal(ai:RuleBasedAI, variables:TermAttribute[], variableNames:string[]) : string
75 | {
76 | return "";
77 | }
78 |
79 |
80 | static loadFromXML(xml:Element, ai:RuleBasedAI, o:Ontology, variables:TermAttribute[], variableNames:string[]) : InferenceEffect
81 | {
82 | let t:Term = Term.fromStringInternal(xml.getAttribute("effectParameter"), o, variableNames, variables).term;
83 | return new AnswerWhy_InferenceEffect(t);
84 | }
85 |
86 |
87 | effectParameter:Term = null;
88 | }
--------------------------------------------------------------------------------
/src/a4engine3/objects/A4WalkingObject.ts:
--------------------------------------------------------------------------------
1 | class A4WalkingObject extends A4Object {
2 | constructor(name:string, sort:Sort)
3 | {
4 | super(name, sort);
5 | this.currentAnimation = A4_ANIMATION_IDLE_RIGHT;
6 | this.direction = A4_DIRECTION_RIGHT;
7 | for(let i:number = 0;i= (this.map.width*this.map.tileWidth-this.getPixelWidth())) {
21 | let bridge:A4MapBridge = this.map.getBridge(this.map.width*this.map.tileWidth-1, this.y+this.getPixelHeight()/2);
22 | return bridge;
23 | } else if (direction == A4_DIRECTION_UP && this.y <= 0) {
24 | let bridge:A4MapBridge = this.map.getBridge(this.x+this.getPixelWidth()/2, 1);
25 | return bridge;
26 | } else if (direction == A4_DIRECTION_DOWN && this.y >= (this.map.height*this.map.tileHeight-this.getPixelHeight())) {
27 | let bridge:A4MapBridge = this.map.getBridge(this.x+this.getPixelWidth()/2, this.map.height*this.map.tileHeight-1);
28 | return bridge;
29 | }
30 | return null;
31 | }
32 |
33 |
34 | loadObjectAttribute(attribute_xml:Element) : boolean
35 | {
36 | if (super.loadObjectAttribute(attribute_xml)) return true;
37 | let a_name:string = attribute_xml.getAttribute("name");
38 |
39 | if (a_name == "walk_speed") {
40 | this.walkSpeed = Number(attribute_xml.getAttribute("value"));
41 | return true;
42 | } else if (a_name == "previous_direction") {
43 | this.previousDirection = Number(attribute_xml.getAttribute("value"));
44 | return true;
45 | } else if (a_name == "state") {
46 | this.state = Number(attribute_xml.getAttribute("value"));
47 | return true;
48 | } else if (a_name == "previous_state") {
49 | this.previousState = Number(attribute_xml.getAttribute("value"));
50 | return true;
51 | } else if (a_name == "state_cycle") {
52 | this.stateCycle = Number(attribute_xml.getAttribute("value"));
53 | return true;
54 | }
55 | return false;
56 | }
57 |
58 |
59 | savePropertiesToXML(game:A4Game) : string
60 | {
61 | let xmlString:string = super.savePropertiesToXML(game);
62 |
63 | xmlString += this.saveObjectAttributeToXML("previous_direction",this.previousDirection) + "\n";
64 | xmlString += this.saveObjectAttributeToXML("state",this.state) + "\n";
65 | xmlString += this.saveObjectAttributeToXML("previous_state",this.previousState) + "\n";
66 | xmlString += this.saveObjectAttributeToXML("state_cycle",this.stateCycle) + "\n";
67 | xmlString += this.saveObjectAttributeToXML("walk_speed",this.walkSpeed) + "\n";
68 |
69 | return xmlString;
70 | }
71 |
72 |
73 | // I need a function for this, since items can change it!
74 | getWalkSpeed() : number
75 | {
76 | return this.walkSpeed;
77 | }
78 |
79 |
80 | // attributes:
81 | walkSpeed:number = 16;
82 |
83 | previousDirection:number = A4_DIRECTION_NONE;
84 | state:number = A4CHARACTER_STATE_IDLE;
85 | previousState:number = A4CHARACTER_STATE_NONE;
86 | stateCycle:number = 0;
87 |
88 | // walking temporary counters (to make sure characters walk at the desired speed):
89 | walkingCounter:number = 0;
90 |
91 | // some variables to make moving the character around intuitive:
92 | continuous_direction_command_timers:number[] = new Array(A4_NDIRECTIONS);
93 | continuous_direction_command_max_movement:number[] = new Array(A4_NDIRECTIONS); // a command might specify a direction and a maximum amount of pixels to move in that direction
94 | direction_command_received_this_cycle:boolean[] = new Array(A4_NDIRECTIONS);
95 | }
96 |
--------------------------------------------------------------------------------
/src/a4engine3/A4Agenda.ts:
--------------------------------------------------------------------------------
1 | class AgendaEntry {
2 | time: number;
3 | scripts: A4Script[] = [];
4 |
5 | static fromXML(xml: Element): AgendaEntry
6 | {
7 | let ae:AgendaEntry = new AgendaEntry();
8 |
9 | ae.time = Number(xml.getAttribute("time"));
10 | for(let i:number = 0;i=this.duration && !this.loop) {
129 | if (!this.loop) return true;
130 | }
131 | return false;
132 | }
133 |
134 | name:string;
135 | duration:number;
136 | loop:boolean;
137 | absoluteTime:boolean;
138 | absoluteStartCycle:number;
139 | cycle:number;
140 | entries:AgendaEntry[] = [];
141 | }
142 |
143 |
--------------------------------------------------------------------------------
/src/ai/actions/AnswerHow.ts:
--------------------------------------------------------------------------------
1 | class AnswerHow_IntentionAction extends IntentionAction {
2 |
3 | canHandle(intention:Term, ai:RuleBasedAI) : boolean
4 | {
5 | if (intention.functor.is_a(ai.o.getSort("action.answer.how"))) return true;
6 | return false;
7 | }
8 |
9 |
10 | execute(ir:IntentionRecord, ai:RuleBasedAI) : boolean
11 | {
12 | this.ir = ir;
13 | let intention:Term = ir.action;
14 | let requester:TermAttribute = ir.requester;
15 |
16 | if (intention.attributes.length == 2) {
17 | if (intention.attributes[1] instanceof ConstantTermAttribute) {
18 | let targetID:string = (intention.attributes[1]).value;
19 | console.log(ai.selfID + " answer followup how to " + targetID);
20 | // this is a follow up question! see if we can reconstruct the question...
21 | let context:NLContext = ai.contextForSpeakerWithoutCreatingANewOne(targetID);
22 | if (context != null) {
23 | // get the last sentence we said:
24 | let lastPerf:NLContextPerformative = context.lastPerformativeBy(ai.selfID);
25 |
26 | let newIntention:Term = null;
27 | if (lastPerf != null) newIntention = this.convertPerformativeToHowQuestionAnswerIntention(lastPerf, ai, context);
28 | if (newIntention != null) {
29 | intention = newIntention;
30 | } else {
31 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer("+requester+",'unknown'[symbol]))", ai.o);
32 | ai.intentions.push(new IntentionRecord(term, intention.attributes[1], null, null, ai.timeStamp));
33 | ir.succeeded = false;
34 | return true;
35 | }
36 | } else {
37 | // this should never happen
38 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.parseerror('"+context.speaker+"'[#id], #not(verb.understand('"+ai.selfID+"'[#id],#and(the(NOUN:'perf.question'[perf.question],S:[singular]),noun(NOUN,S))))))", ai.o);
39 | ai.intentions.push(new IntentionRecord(term, intention.attributes[1], null, null, ai.timeStamp));
40 | ir.succeeded = false;
41 | return true;
42 | }
43 | }
44 | }
45 |
46 | if (intention.attributes.length>=3 &&
47 | intention.attributes[2] instanceof TermTermAttribute) {
48 | let action:Term = (intention.attributes[2]).term;
49 |
50 | console.log(ai.selfID + " answer how: " + intention.attributes[2]);
51 | // we add the sentence with negative sign, to see if it introduces a contradiction
52 | let target1:Sentence[] = [new Sentence([new Term(ai.o.getSort("relation.howto"),
53 | [new TermTermAttribute(action),
54 | new VariableTermAttribute(ai.o.getSort("any"), "HOW")])],[false])];
55 | ai.queuedInferenceProcesses.push(new InferenceRecord(ai, [], [target1], 1, 0, false, null, new AnswerHow_InferenceEffect(intention)));
56 | // TODO: this should have some temporary value (in all actions that require inference or continuous execution)
57 | // that is then replaced with true/false after inference/continuous is done
58 | ir.succeeded = true;
59 | /*
60 | if (requester != null) {
61 | let term:Term = Term.fromString("action.talk('"+ai.selfID+"'[#id], perf.inform.answer("+requester+",'unknown'[symbol]))", ai.o);
62 | ai.intentions.push(new IntentionRecord(term, requester, null, null, ai.timeStamp));
63 | }
64 | */
65 | }
66 | ir.succeeded = true;
67 | return true;
68 | }
69 |
70 |
71 | convertPerformativeToHowQuestionAnswerIntention(nlcp:NLContextPerformative, ai:RuleBasedAI, context:NLContext) : Term
72 | {
73 | if ((nlcp.performative.functor.is_a(ai.o.getSort("perf.request.action")) ||
74 | nlcp.performative.functor.is_a(ai.o.getSort("perf.q.action"))) &&
75 | (nlcp.performative.attributes[1] instanceof TermTermAttribute)) {
76 | console.log("convertPerformativeToHowQuestionAnswerIntention: perf.request.action/perf.q.action");
77 |
78 | let predicate:Term = (nlcp.performative.attributes[1]).term;
79 | let newIntention:Term = new Term(ai.o.getSort("action.answer.how"),
80 | [new ConstantTermAttribute(nlcp.speaker, ai.o.getSort("#id")),
81 | nlcp.performative.attributes[0],
82 | new TermTermAttribute(predicate)]);
83 | console.log("convertPerformativeToHowQuestionAnswerIntention, newIntention: " + newIntention);
84 | return newIntention;
85 | }
86 |
87 | return null;
88 | }
89 |
90 |
91 | saveToXML(ai:RuleBasedAI) : string
92 | {
93 | return "";
94 | }
95 |
96 |
97 | static loadFromXML(xml:Element, ai:RuleBasedAI) : IntentionAction
98 | {
99 | return new AnswerHow_IntentionAction();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/auxiliar/BInterfaceElement.ts:
--------------------------------------------------------------------------------
1 | class BInterfaceElement {
2 | constructor(x:number, y:number, width:number, height:number)
3 | {
4 | this.x = x;
5 | this.y = y;
6 | this.width = width;
7 | this.height = height;
8 | }
9 |
10 |
11 | mouseOver(mousex:number, mousey:number) : boolean
12 | {
13 | return mousex >= this.x && mousex < this.x+this.width &&
14 | mousey >= this.y && mousey < this.y+this.height;
15 | }
16 |
17 |
18 | highlighted(mousex:number, mousey:number) : boolean
19 | {
20 | if (BInterface.highlightedByKeyboard == -1) {
21 | return this.mouseOver(mousex, mousey);
22 | } else {
23 | return BInterface.elements[BInterface.highlightedByKeyboard] == this;
24 | }
25 | }
26 |
27 |
28 | update(mouse_x:number, mouse_y:number, k:KeyboardState, arg:any) : boolean
29 | {
30 | return false;
31 | }
32 |
33 |
34 | mouseClick(mouse_x: number, mouse_y: number, button: number, arg:any)
35 | {
36 | }
37 |
38 |
39 | draw()
40 | {
41 | this.drawAlpha(1.0);
42 | }
43 |
44 |
45 | drawAlpha(alpha:number)
46 | {
47 | }
48 |
49 |
50 | getChildren() : BInterfaceElement[]
51 | {
52 | return this.children;
53 | }
54 |
55 |
56 | getEnabled() : boolean
57 | {
58 | return this.enabled;
59 | }
60 |
61 |
62 | setEnabled(enabled:boolean) {
63 | this.enabled = enabled;
64 | }
65 |
66 |
67 | getID() : number
68 | {
69 | return this.ID;
70 | }
71 |
72 |
73 | ID:number;
74 | modal:boolean = false; // If any element is modal, only him has the control until it is destroyed (the rest of the interface is faded)
75 | enabled:boolean = true; // whether the element can b interacted with or not
76 | active:boolean = true; // This indicates whether the component is active or passive (passive elements are only decorative)
77 | // e.g.: BText and BFrame are passive
78 | to_be_deleted:boolean = false;
79 |
80 | x:number;
81 | y:number;
82 | width:number;
83 | height:number;
84 | children:BInterfaceElement[] = [];
85 | }
86 |
87 |
88 |
89 | class BText extends BInterfaceElement {
90 | constructor(text:string, font:string, textHeight:number, x:number, y:number, centered:boolean, ID:number)
91 | {
92 | super(x, y, 0, 0);
93 | ctx.font = font;
94 | this.width = ctx.measureText(text).width;
95 | this.height = textHeight;
96 | this.centered = centered;
97 | this.text = text;
98 | this.font = font;
99 | this.enabled = true;
100 | this.active = false;
101 | }
102 |
103 |
104 | drawAlpha(alpha:number)
105 | {
106 | let color:string = "white";
107 | if (!this.enabled) {
108 | color = generateRGBColor(80,80,80);
109 | }
110 | if (this.centered) {
111 | fillTextTopCenter(this.text, this.x, this.y, this.font, color);
112 | } else {
113 | fillTextTopLeft(this.text, this.x, this.y, this.font, color);
114 | }
115 | }
116 |
117 | centered:boolean;
118 | text:string;
119 | font:string;
120 | }
121 |
122 |
123 | class BQuad extends BInterfaceElement {
124 | constructor(x:number, y:number, dx:number, dy:number, color:string)
125 | {
126 | super(x, y, dx, dy);
127 | this.color = color;
128 | this.enabled = true;
129 | this.active = false;
130 | }
131 |
132 | drawAlpha(alpha:number)
133 | {
134 | if (this.enabled) {
135 | ctx.fillStyle = this.color;
136 | ctx.fillRect(this.x, this.y, this.width, this.height);
137 | } else {
138 | ctx.save();
139 | ctx.globalAlpha *= 0.5;
140 | ctx.fillStyle = this.color;
141 | ctx.fillRect(this.x, this.y, this.width, this.height);
142 | ctx.restore();
143 | }
144 | }
145 |
146 | color:string
147 | }
148 |
149 |
150 | /*
151 | class BInterfaceAnimation extends BInterfaceElement {
152 | constructor(a:Animation, x:number, y:number)
153 | {
154 | super(x, y, a.getPixelWidth(), a.getPixelHeight());
155 | this.animation = a;
156 | }
157 |
158 | drawAlpha(alpha:number)
159 | {
160 | if (this.enabled) {
161 | this.animation.drawWithAlpha(this.x,this.y, alpha);
162 | } else {
163 | this.animation.drawWithAlpha(this.x,this.y, alpha*0.5);
164 | }
165 | }
166 |
167 | animation:Animation
168 | }
169 | */
--------------------------------------------------------------------------------