Browse code

Remove arnesi; distinguish *string-lexicon* and *lexicon*

Jaidyn Levesque authored on 2020-01-15 05:46:57
Showing 2 changed files
... ...
@@ -3,5 +3,5 @@
3 3
            :license "GPLv3"
4 4
 	   :author "Jaidyn Ann <jadedctrl@teknik.io>"
5 5
 	   :description "A flexible IF engine."
6
-	   :depends-on (:cl-earley-parser :bknr.datastore :arnesi :cl-strings)
6
+	   :depends-on (:cl-earley-parser :bknr.datastore :anaphora :cl-strings)
7 7
 	   :components ((:file "x-if")))
... ...
@@ -7,39 +7,54 @@
7 7
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
8 8
 ;; GNU General Public License for more details.
9 9
 
10
-;; TODO: get x-if.lexicon working, so the lexicon can be modified on-the-fly
11
-;;       by x-if.environment, when new objects are created.
12
-;;       also add :synonyms and :adjectives to the GOD object class
10
+;; TODO: Get :x-if.interpret to make ACTION objects.
13 11
 
14 12
 ;; —————————————————————————————————————
15 13
 ;; PACKAGES
16 14
 
17 15
 (defpackage :x-if
18
-  (:use :cl :arnesi)
16
+  (:use :cl)
19 17
   (:nicknames :xif))
20 18
 
21 19
 (defpackage :x-if.misc
22
-  (:use :cl)
20
+  (:use :cl :anaphora)
23 21
   (:nicknames :xif.m)
24
-  (:export :line-cdr :line-car :line-length))
22
+  (:export :line-cdr :line-car
23
+           :line-length :line-position
24
+           :in-string-p
25
+           :remove-line
26
+           :position-equal))
25 27
 
26 28
 (defpackage :x-if.lexicon
27
-  (:use :cl :arnesi :earley-parser)
29
+  (:use :cl :earley-parser)
28 30
   (:nicknames :xif.l)
29 31
   (:export :action-indirect-required-p :action-direct-required-p
30
-           :action-p :action-function :add-action :add-verb
31
-           *lexicon* :add-adjective :add-noun :add-proper-noun :reload-lexicon))
32
+           :action-p :action-function :add-action :add-verb :remove-word
33
+           *string-lexicon* *lexicon*
34
+           :add-game-object-words :delete-game-object-words
35
+           :add-adjective :add-noun :add-proper-noun :add-verb
36
+           :reload-lexicon))
32 37
 
33 38
 (defpackage :x-if.environment
34
-  (:use :cl :arnesi :bknr.datastore)
39
+  (:use :cl :bknr.datastore)
35 40
   (:nicknames :xif.e)
36
-  (:export :name→object :all-objects :id→object :get-player
37
-           :adjectives :description :id :name :synonyms :adjectives
38
-           :location→entity :all-entities
39
-           :inventory :max-inventory :weight :hp :max-hp))
41
+  (:export :id→game-object :get-player
42
+           :all-game-objects
43
+           :noun→game-objects :proper-noun→game-objects :adjective→game-objects
44
+           :all-entities
45
+           :noun→entities :proper-noun→entities :adjective→entities
46
+           :all-mobs
47
+           :noun→mobs :proper-noun→mobs :adjective→mobs
48
+           :noun→locations :proper-noun→locations :adjective→locations
49
+           :all-locations
50
+           :id :description
51
+           :nouns :proper-nouns :adjectives
52
+           :max-children :weight :hp :max-hp
53
+           :children :parent
54
+           :link :unlink))
40 55
 
41 56
 (defpackage :x-if.parsing
42
-  (:use :cl :arnesi)
57
+  (:use :cl)
43 58
   (:nicknames :xif.p)
44 59
   (:export :parse
45 60
            :noun-phrases :det :prep :proper-noun :noun-name :det :prep
... ...
@@ -48,18 +63,18 @@
48 63
            :the-action :the-subject))
49 64
 
50 65
 (defpackage :x-if.interpret
51
-  (:use :cl :arnesi)
66
+  (:use :cl)
52 67
   (:nicknames :xif.i))
53 68
 
54 69
 (defpackage :x-if.client
55
-  (:use :cl :arnesi :xif.e :bknr.datastore)
70
+  (:use :cl :xif.e :bknr.datastore)
56 71
   (:nicknames :xif.c)
57 72
   (:export :start
58 73
            :text :input-sentence :display-options :input-options :play-music
59 74
            :show-image :status :prompt))
60 75
 
61 76
 (defpackage :x-if.client.terminal
62
-  (:use :cl :arnesi :xif.e)
77
+  (:use :cl :xif.e)
63 78
   (:nicknames :xif.c.t))
64 79
 
65 80
 
... ...
@@ -107,8 +122,7 @@
107 122
   (earley-parser:chart-listing->trees
108 123
     (earley-parser:earley-parse statement
109 124
       (earley-parser:load-bnf-grammar #p"example/grammar.txt")
110
-      (xif.l:reload-lexicon))))
111
-
125
+      xif.l:*lexicon*)))
112 126
 
113 127
 (defun split-statements (sentence)
114 128
   "Split up a string into different statements, based on punctuation."
... ...
@@ -206,12 +220,17 @@
206 220
 
207 221
 ;; —————————————————————————————————————
208 222
 
209
-(defvar *lexicon*
223
+(defvar *string-lexicon*)
224
+(setq *string-lexicon*
210 225
 "the :class <det>
211 226
 that :class <det>
212 227
 this :class <det>
213 228
 here :class <prep>
214 229
 there :class <prep>
230
+above :class <prep>
231
+below :class <prep>
232
+beside :class <prep>
233
+across :class <prep>
215 234
 be :class <aux>
216 235
 to :class <prep>
217 236
 with :class <prep>
... ...
@@ -219,46 +238,83 @@ a :class <det>
219 238
 an :class <det>
220 239
 ")
221 240
 
222
-(defvar *actions* (make-hash-table :test #'equal))
223
-
224
-(defmacro add-string-to-var (var string)
225
-  `(setq ,var (concatenate 'string ,var ,string)))
226
-
227
-(defun add-word (name class)
228
-  (add-string-to-var *lexicon* (format nil "~A :class \<~A\>~%" name class)))
229
-
230
-(defmethod add-noun ((name string))
231
-  (add-word name "noun"))
232
-
233
-(defmethod add-noun ((object xif.e::god))
234
-  (mapcar #'add-noun (xif.e:synonyms object))
235
-  (add-noun (xif.e:name object)))
236
-
237
-(defmethod add-adjective ((adj string))
238
-  (add-word adj "adjective"))
239
-
240
-(defmethod add-adjective ((object xif.e::god))
241
-  (mapcar #'add-adjective (xif.e:adjectives object)))
242
-
243
-(defmethod add-verb ((verb string))
244
-  (add-word verb "verb"))
245
-
246
-(defmethod add-action ((action xif.e::action))
247
-  (mapcar #'add-verb (xif.e::verbs action)))
248
-
241
+(defvar *lexicon* nil)
242
+
243
+
244
+;; STRING STRING → NIL
245
+(defun add-word (word class)
246
+  "Add a word of the given class to the string-lexicon and parsed lexicon."
247
+  (setq *string-lexicon*
248
+        (concatenate 'string *string-lexicon*
249
+                     (format nil "~A :class \<~A\>~%" word class)))
250
+  (reload-lexicon))
251
+
252
+
253
+;; STRING → NIL
254
+(defun remove-word (word)
255
+  "Remove a word from the string-lexicon and parsed lexicon."
256
+  (when (is-word-p word)
257
+    (setq *string-lexicon*
258
+          (xif.m:remove-line *string-lexicon* word
259
+                             :test #'cl-strings:starts-with))
260
+    (reload-lexicon)))
261
+
262
+;; STRING → BOOLEAN
263
+(defun is-word-p (word)
264
+  "Return whether or not a given word is in the lexicon."
265
+  (xif.m:line-position *string-lexicon* word :test #'cl-strings:starts-with))
266
+
267
+
268
+;; SYMBOL SYMBOL STRING → (DEFUN … )
269
+(defmacro defun-add-wordclass (function-name class-symbol class-string)
270
+  "Define the add-WORD function of the given word-class. #'add-noun, etc."
271
+  `(defun ,function-name (,class-symbol)
272
+     ,(format nil "Add a word of class ~A to the lexicon." class-string)
273
+     (add-word ,class-symbol ,class-string)))
274
+
275
+(defun-add-wordclass add-proper-noun proper-noun "proper-noun")
276
+(defun-add-wordclass add-noun noun "noun")
277
+(defun-add-wordclass add-adjective adjective "adjective")
278
+(defun-add-wordclass add-verb verb "verb")
279
+
280
+
281
+;; GAME-OBJECT → NIL
282
+(defmethod add-game-object-words ((object xif.e::game-object))
283
+  "Add a game-object's words (nouns, adjectives, proper-nouns) to the lexicon."
284
+  (mapcar #'add-adjective (xif.e:adjectives object))
285
+  (mapcar #'add-proper-noun (xif.e:proper-nouns object))
286
+  (mapcar #'add-noun (xif.e:nouns object)))
287
+
288
+;; GAME-OBJECT → NIL
289
+(defmethod remove-game-object-words ((object xif.e::game-object))
290
+  "Remove a game-object's words (nouns, adjectives, proper-nouns) to the lexicon."
291
+  (mapcar #'xif.l:remove-word (xif.e:nouns object))
292
+  (mapcar #'xif.l:remove-word (xif.e:proper-nouns object))
293
+  (mapcar #'xif.l:remove-word (xif.e:adjectives object)))
294
+
295
+;; STRING → LEXICON
249 296
 (defun load-string-lexicon (lex-string)
250
-  "Read all words from a dictionary file into a lexicon and a part of speech."
297
+  "Read all words from a dictionary file into a lexicon and a part of speech.
298
+  Abridged version of #'earley-parser:load-lexicon (which reads from a file)."
251 299
   (with-input-from-string (lex-str-stream lex-string)
252 300
   (let ((lexicon (make-hash-table :test earley-parser::*string-comparer*))
253 301
         (part-of-speech nil))
254 302
     (loop :while (listen lex-str-stream)
255 303
           :do (let ((w (earley-parser::read-lexicon-line lex-str-stream)))
256
-                (pushnew (earley-parser::terminal-class w) part-of-speech :test earley-parser::*string-comparer*)
304
+                (pushnew (earley-parser::terminal-class w)
305
+                         part-of-speech
306
+                         :test earley-parser::*string-comparer*)
257 307
                 (push w (gethash (earley-parser::terminal-word w) lexicon))))
258
-    (earley-parser::make-lexicon :dictionary lexicon :part-of-speech part-of-speech))))
308
+    (earley-parser::make-lexicon :dictionary lexicon
309
+                                 :part-of-speech part-of-speech))))
259 310
 
311
+;; NIL → NIL
260 312
 (defun reload-lexicon ()
261
-  (load-string-lexicon *lexicon*))
313
+  "Updates the lexicon by parsing the string-lexicon."
314
+  (setq *lexicon* (load-string-lexicon *string-lexicon*)))
315
+
316
+
317
+
262 318
 
263 319
 ;; —————————————————————————————————————
264 320
 ;; X-IF.INTERPRET
... ...
@@ -290,18 +346,23 @@ an :class <det>
290 346
 ;; It will also return an appropriate error symbol, as a second value—
291 347
 ;; I.E., 'INVALID-ACTION, etc.
292 348
 
293
-;; If a word *isn't* in lexicon, then #'x.if.parsing:parse will return an
294
-;; error-string instead of a parsed Earley tree— which means that #'interpret
295
-;; will recieve that error string from the engine. In this case, #'interpret
296
-;; will return the string as if it were its own error-string, but with an
297
-;; error-symbol of 'PARSE-DIE.
349
+;; If a word *isn't* in lexicon, or something else crops up, then
350
+;; #'x.if.parsing:parse will return an error symbol instead of a queued-action
351
+;; object— which means that #'interpret will recieve that error symbol from the
352
+;; engine. In this case, #'interpret will return the symbol as if it were its
353
+;; own error-symbol, but with an error-symbol of 'PARSE-DIE.
354
+;; If the error is in the interpretation method, it'll obviously return the
355
+;; second error-symbol as 'INTERPRET-DIE.
298 356
 
299 357
 ;; Anyway, ultimately the engine will actually execute the actionable list
300 358
 ;; generated by #'interpret.
301 359
 
302 360
 ;; —————————————————————————————————————
303 361
 
304
-;; TREE_OF_STATEMENT → LIST || (STRING SYMBOL)
362
+;; TODO: Obviously, if there are multiple matches it should error TF out
363
+;; and die, and... and... AHHHH good luck ;w;
364
+
365
+;; TREE_OF_STATEMENT → LIST || (SYMBOL SYMBOL)
305 366
 (defmethod interpret ((statement-tree list))
306 367
   "Actually interpret a parsed statement-tree; returns a list with the
307 368
   applicable function-name for the action, and the objects for the direct and
... ...
@@ -309,8 +370,8 @@ an :class <det>
309 370
   (let* ((subject  (or (the-subject statement-tree) (xif.e:get-player)))
310 371
          (action   (the-action statement-tree))
311 372
          (verb     (verb action))
312
-         (indirect (xif.e:name→object (indirect-object action)))
313
-         (direct   (xif.e:name→object (direct-object action))))
373
+         (indirect (xif.e:noun→game-objects (indirect-object action)))
374
+         (direct   (xif.e:noun→game-objects (direct-object action))))
314 375
     (cond ((not (xif.l:action-p verb))
315 376
            "That… that's just not a thing people do.")
316 377
           ((and (not indirect) (xif.l:action-indirect-required-p verb))
... ...
@@ -326,74 +387,78 @@ an :class <det>
326 387
 
327 388
 ;; —————————————————————————————————————
328 389
 ;; X-IF.ENVIRONMENT
390
+;; OBJECTS, GAME-STATE, ETC.
329 391
 
330 392
 (in-package :x-if.environment)
331 393
 
332 394
 ;; —————————————————————————————————————
333 395
 
396
+;; This package contains all aspect of the actual game 'environment'—
397
+;; all objects (rooms, entities, etc), means of accessing and modifying them,
398
+;; etc.
334 399
 
400
+;; There is a main overarching class (game-object), of which everything else is
401
+;; derived. From there, there are two divering subclasses— the 'location'
335 402
 
336
-(define-persistent-class god ()
403
+;; —————————————————————————————————————
404
+;; CLASSES
405
+
406
+;; The overarching class
407
+(define-persistent-class game-object ()
337 408
   ((id :read
338 409
       :initarg :id :reader id
339 410
       :index-type bknr.datastore::unique-index
340 411
       :index-initargs (:test #'equal)
341
-      :index-reader id→object
342
-      :index-values all-objects)
343
-   (nouns :read
344
-      :initarg :nouns :reader nouns
345
-      :index-type bknr.datastore::hash-index
346
-      :index-initargs (:test #'position)
347
-      :index-reader noun→object)
348
-   (proper-noun :read
349
-      :initarg :proper-name :reader proper-name
350
-      :index-type bknr.datastore::hash-index
351
-      :index-initargs (:test #'equal)
352
-      :index-reader proper-name→object
412
+      :index-reader id→game-object
413
+      :index-values all-game-objects)
414
+   (nouns :update
415
+      :initarg :nouns :reader nouns)
416
+   (proper-nouns :update
417
+      :initarg :proper-name :reader proper-nouns
353 418
       :initform nil)
354
-   (adjectives :read
419
+   (adjectives :update
355 420
       :initarg :adjectives :reader adjectives
356 421
       :initform nil)
357
-   (synonyms :read
358
-      :initarg :synonyms :reader synonyms
359
-      :initform nil)
360
-   (description :read
422
+   (description :update
361 423
       :initarg :desc :reader description
424
+      :initform nil)
425
+   (parent :update
426
+      :initarg :parent :reader parent
427
+      :initform nil)
428
+   (children :update
429
+      :initarg :children :reader children
430
+      :initform nil)
431
+   (max-children :update
432
+      :initarg :max-children :reader max-children
362 433
       :initform nil)))
363 434
 
364
-(define-persistent-class entity (god)
365
-  ((location :read
366
-      :initarg :location :reader location
367
-      :index-type bknr.datastore::hash-index
368
-      :index-initargs (:test #'equal)
369
-      :index-reader location→entity
370
-      :index-values all-entities)
371
-   (inventory :read
372
-      :initarg  :inventory :reader inventory
373
-      :initform nil)
374
-   (max-inventory :read
375
-      :initarg :max-inventory :reader max-inventory
376
-      :initform 0)
377
-   (weight :read
378
-      :initarg :weight :reader weight
379
-      :initform 0)
380
-   (hp :read
435
+;; For any object that can interacted with
436
+(define-persistent-class entity (game-object)
437
+  ((hp :update
381 438
       :initarg :hp :reader hp
382 439
       :initform nil)
383
-   (max-hp :read
440
+   (max-hp :update
384 441
        :initarg :max-hp :reader max-hp
385
-       :initform nil)))
442
+       :initform nil)
443
+   (weight :update
444
+       :initarg :weight :reader weight
445
+       :initform 0)))
386 446
 
387
-(define-persistent-class npc (entity) ((normie :read :initform T :index-values all-npcs)))
447
+;; These classes only exist for indexing and semantic purposes;
448
+;; they are identical to their super-classes
388 449
 
389
-(define-persistent-class player (npc)
390
-  ((mlg :read
391
-        :initform T :index-values get-player)))
450
+;; For NPCs, animals, etc.
451
+(define-persistent-class mob (entity) ())
392 452
 
393
-(define-persistent-class location (entity)
394
-  ((extreme-makeover-home-edition :read
395
-                                  :initform T :index-values all-locations)))
453
+;; For the PLAYER.
454
+(define-persistent-class player (mob) ())
396 455
 
456
+;; For LOCATIONS (rooms and such).
457
+(define-persistent-class location (game-object) ())
458
+
459
+
460
+;; For hypothetical ACTIONS;
461
+;; that is, commands the player can enter which will execute a given function.
397 462
 (define-persistent-class action ()
398 463
   ((function-name :read
399 464
       :initarg :function :reader function-name)
... ...
@@ -408,6 +473,9 @@ an :class <det>
408 473
    (indirect-object-p :read
409 474
        :initarg :indirect-object-p :initform nil :reader indirect-object-required-p)))
410 475
 
476
+
477
+;; For tuŝeblaj ACTIONS; generated by :x-if.interpret from interpreting a user
478
+;; statement. This is what will actually be executed by :x-if.interpret.
411 479
 (defclass queued-action ()
412 480
   ((function-name
413 481
        :initarg :function :accessor function-name)
... ...
@@ -418,22 +486,171 @@ an :class <det>
418 486
    (subject
419 487
        :initarg :subject :initform (get-player) :accessor subject)))
420 488
 
421
-(defun get-player ()
422
-  (id→object 100))
489
+
490
+;; —————————————————
491
+;; OBJECT ADD/DEL
492
+
493
+;; These are here to appropriately modify the LEXICON (xif.l:*lexicon*)
494
+;; according to objects in the game. Words used to describe objects
495
+;; and actions will be added when an object is initialized; deleted
496
+;; when destroyed.
497
+
498
+;; TODO
499
+;; Nuance is necessary; if a noun/adjective/etc is used for other objects,
500
+;; it shouldn't be added again nor deleted from the lexicon when adding a new
501
+;; object or deleting one, respectively.
502
+;; For now, it's assumed all words are new, and are all used by one object.
503
+
504
+(defmethod initialize-instance :after ((game-object game-object) &key)
505
+  (xif.l:add-game-object-words game-object))
506
+
507
+(defmethod destroy-object :after ((game-object game-object) &key)
508
+  (xif.l:delete-game-object-words game-object))
423 509
 
424 510
 
425
-(defmethod initialize-instance :after ((god god) &key)
426
-  (xif.l:add-noun god)
427
-  (xif.l:add-adjective god)
428
-  (xif.l:reload-lexicon))
429 511
 
430 512
 (defmethod initialize-instance :after ((action action) &key)
431
-  (xif.l:add-action action)
432
-  (xif.l:reload-lexicon))
513
+  (mapcar #'xif.l:add-verb (verbs action)))
514
+
515
+;; TODO: write #'xif.l:remove-action
516
+(defmethod destroy-object :after ((action action) &key))
517
+
518
+
519
+;; —————————————————
520
+;; PARENT/CHILD
521
+
522
+;; GAME-OBJECT GAME-OBJECT → NIL
523
+(deftransaction link (child parent)
524
+  "Link two objects together, as child and parent."
525
+  (setf (slot-value parent 'children)
526
+        (nconc (slot-value parent 'children) (list child)))
527
+  ;; --
528
+  (if (child-p child) (unlink child))
529
+  (setf (slot-value child 'parent) parent))
530
+
531
+;; GAME-OBJECT → NIL
532
+(deftransaction unlink (child)
533
+  "Seperate a child from it's parent (and vice-versa)."
534
+  (let ((parent (slot-value child 'parent)))
535
+    (setf (slot-value parent 'children)
536
+          (delete child (slot-value parent 'children)))
537
+    ;; --
538
+    (setf (slot-value child 'parent) nil)))
539
+
540
+
541
+;; GAME-OBJECT → BOOLEAN
542
+(defmethod parent-p ((object game-object))
543
+  "Return whether or not an object is a parent (has 1+ children)."
544
+  (slot-value object 'children))
545
+
546
+;; GAME-OBJECT → BOOLEAN
547
+(defmethod child-p ((object game-object))
548
+  "Return whether or not an object is a child (has a parent)."
549
+  (slot-value object 'parent))
550
+
551
+
552
+;; —————————————————
553
+;; INDEXING
554
+
555
+;; NIL → PLAYER
556
+(defun get-player ()
557
+  "Return the player object."
558
+  (car (store-objects-with-class 'player)))
559
+
560
+;; NIL → LIST_OF_LOCATIONS
561
+(defun all-locations ()
562
+  "Get all location objects."
563
+  (store-objects-with-class 'location))
564
+
565
+;; NIL → LIST_OF_MOBS
566
+(defun all-mobs ()
567
+  "Get all mob objects."
568
+  (store-objects-with-class 'mob))
569
+
570
+;; NIL → LIST_OF_ENTITIES
571
+(defun all-entities ()
572
+  "Get all entity objects."
573
+  (store-objects-with-class 'entity))
574
+
575
+;; NIL → LIST_OF_ENTITIES
576
+(defun all-actions ()
577
+  "Get all entity objects."
578
+  (store-objects-with-class 'action))
579
+
580
+
581
+;; LIST_OF_OBJECTS STRING → LIST_OF_OBJECTS
582
+(defun noun→objects (objects noun)
583
+  "Return objects within the current list of objects of the given noun."
584
+  (index-by-slot-list objects 'nouns noun))
585
+
586
+;; LIST_OF_OBJECTS STRING → LIST_OF_OBJECTS
587
+(defun adjective→objects (objects adjective)
588
+  "Return objects within the current list of objects of the given adjective."
589
+  (index-by-slot-list objects 'adjectives adjective))
590
+
591
+;; LIST_OF_OBJECTS STRING → LIST_OF_OBJECTS
592
+(defun proper-noun→objects (objects proper-noun)
593
+  "Return objects within the current list of objects of the given proper-noun"
594
+  (index-by-slot-list objects 'proper-nouns proper-noun))
595
+
596
+;; STRING → LIST_OF_ACTIONS
597
+(defun verb→actions (verb)
598
+  "Return all actions matching the given verb."
599
+  (index-by-slot-list (all-actions) 'verbs verb))
600
+
601
+
602
+;; SYMBOL SYMBOL FUNCTION SYMBOL FUNCTION → (DEFUN …)
603
+(defmacro defun-obj-word-index (name obj-type all-obj-type word-type word-fun)
604
+  "Makes an index function for the given object type and word-type.
605
+  I.E., will generate #'adjective→mobs for object-type 'mobs' and word-type of
606
+  'adjective'"
607
+  `(defun ,name (,word-type)
608
+     ,(format nil "Return the ~A corresponding with the given ~A."
609
+              obj-type word-type)
610
+     (funcall ,word-fun (funcall ,all-obj-type) ,word-type)))
611
+
612
+;; SYMBOL FUNCTION SYMBOL SYMBOL SYMBOL → ( (DEFUN …) (DEFUN …) (DEFUN …))
613
+(defmacro defuns-obj-word-indices (obj-type all-obj adj→obj noun→obj pnoun→obj)
614
+  "Makes the index functions for a given object type for every word type.
615
+  I.E., when given 'mobs', it'll make #'noun→mobs, #'adjective→mobs, etc."
616
+  `(progn
617
+    (defun-obj-word-index ,adj→obj ,obj-type ,all-obj adjective
618
+      #'adjective→objects)
619
+    (defun-obj-word-index ,noun→obj ,obj-type ,all-obj noun #'noun→objects)
620
+    (defun-obj-word-index ,pnoun→obj ,obj-type ,all-obj proper-noun
621
+      #'proper-noun→objects)))
622
+
623
+
624
+(defuns-obj-word-indices game-object #'all-game-objects
625
+  adjective→game-objects noun→game-objects proper-noun→game-objects)
626
+(defuns-obj-word-indices entity #'all-entities
627
+  adjective→entities noun→entities proper-noun→entities)
628
+(defuns-obj-word-indices mob #'all-mobs
629
+  adjective→mobs noun→mobs proper-noun→mobs)
630
+(defuns-obj-word-indices locations #'all-locations
631
+  adjective→locations noun→locations proper-noun→locations)
632
+
633
+
634
+;; LIST_OF_OBJECTS SYMBOL VARYING → LIST_OF_OBJECTS
635
+(defun index-by-slot-list (objects slot-name target)
636
+  "Return objects from the given list that contain a target item in the list
637
+  of the given slot."
638
+  (index-by-slot objects slot-name target :test #'xif.m:position-equal))
639
+
640
+;; LIST_OF_OBJECTS SYMBOL VARYING :FUNCTION → LIST_OF_OBJECTS
641
+(defun index-by-slot (objects slot-name target &key (test #'equal))
642
+  "Return objects from the given list that pass the given test between
643
+  slot-value and target."
644
+  (loop :for object :in objects
645
+        :if (funcall test target (slot-value object slot-name))
646
+        :collect object))
647
+
648
+
433 649
 
434 650
 
435 651
 ;; —————————————————————————————————————
436 652
 ;; X-IF.CLIENT
653
+;; ACTUALLY PLAY THE GAME
437 654
 
438 655
 (in-package :x-if.client)
439 656
 
... ...
@@ -444,7 +661,7 @@ an :class <det>
444 661
 ;; —————————————————————————————————————
445 662
 
446 663
 (defun status ()
447
-  (format nil "~A~%" (slot-value (get-player) 'xif.e::location)))
664
+  (format nil "~A~%" (slot-value (get-player) 'xif.e::parent)))
448 665
 
449 666
 (defun prompt () ">> ")
450 667
 
... ...
@@ -453,7 +670,7 @@ an :class <det>
453 670
     (display-status)
454 671
     (setq m (input-sentence))
455 672
     (text "You said: ~A~%" m)
456
-    (text "~A~%" (xif.e:all-objects))
673
+    (text "~A~%" (xif.e:all-game-objects))
457 674
     (text "I LOVE YOU~%")
458 675
     (sleep 2)
459 676
     (game-loop)))
... ...
@@ -461,7 +678,7 @@ an :class <det>
461 678
 (defun start (game)
462 679
   (make-instance 'mp-store :directory #p"~/.local/share/x-if/"
463 680
                  :subsystems (list (make-instance 'store-object-subsystem)))
464
-  (if (not (all-objects))
681
+  (if (not (all-game-objects))
465 682
     (populate-world))
466 683
   (game-loop))
467 684
 
... ...
@@ -469,22 +686,34 @@ an :class <det>
469 686
   (text (xif.e:description object)))
470 687
 
471 688
 (defun populate-world ()
472
-  (make-instance 'xif.e::location :name "Lobby" :id 0
689
+  (make-instance 'xif.e::location :id 0  :proper-nouns '("Lobby")
690
+                 :nouns '("room")
691
+                 :adjectives '("ugly")
473 692
                  :description "It's rather ugly, really.")
474
-  (make-instance 'xif.e::npc :name "Barry"      :id 101
475
-                 :description "He looks suspicious, no?"
476
-                 :location (id→object 0))
477
-  (make-instance 'xif.e::player :name "Maria"   :id 100
478
-                 :description "A rather hideous lass."
479
-                 :location (id→object 0))
693
+
694
+  (make-instance 'xif.e::mob :id 101  :proper-nouns '("Barry")
695
+                 :nouns '("human" "person" "man" "gentleman" "sir" "dude")
696
+                 :adjectives '("suspicious")
697
+                 :description "He looks suspicious, no?")
698
+
699
+  (make-instance 'xif.e::player :id 100
700
+                 :proper-nouns '("Maria" "I" "myself" "me")
701
+                 :nouns '("human" "person" "woman" "lady" "lass" "dudette")
702
+                 :adjectives '("hideous")
703
+                 :description "A rather hideous lass.")
704
+
480 705
   (make-instance 'xif.e::action :function-name 'xif.c::examine :direct-object-p T
481
-                 :verbs '("examine" "look" "view")))
706
+                 :verbs '("examine" "look" "view"))
707
+  
708
+  (link (get-player) (id→game-object 0))
709
+  (link (id→game-object 100) (id→game-object 0)))
482 710
 
483 711
 
484 712
 
485 713
 
486 714
 ;; —————————————————————————————————————
487 715
 ;; X-IF.CLIENT.TERMINAL
716
+;; A SIMPLE CLIENT
488 717
 
489 718
 (in-package :x-if.client.terminal)
490 719
 
... ...
@@ -501,20 +730,90 @@ an :class <det>
501 730
 (defun xif.c::input-sentence ()
502 731
   (read-line))
503 732
 
733
+
734
+
735
+
736
+;; —————————————————————————————————————
737
+;; X-IF.MISC
738
+;; MISC HELPER FUNCTIONS
739
+
504 740
 (in-package :x-if.misc)
505 741
 
742
+;; —————————————————————————————————————
743
+
744
+;; This package just contains random, useful functions that're used throughout
745
+;; x-if. Mainly they're for string manipulations, etc.
746
+
747
+;; STRING → LIST_OF_STRINGS
506 748
 (defun string-lines (string)
749
+  "Turn a multi-line string into a list of lines."
507 750
   (cl-strings:split string #\newline))
508 751
 
509
-(defun line-cdr (string)
510
-  (lines-string (cdr (string-lines string))))
752
+;; LIST_OF_STRINGS → STRING
753
+(defun lines-string (lines)
754
+  "Turn a list of strings into a multi-lined string, with each string being
755
+  a seperate line."
756
+  (cl-strings:join lines :separator "
757
+"))
758
+
759
+
760
+;; STRING → STRING
761
+(defmethod line-cdr ((string string))
762
+  "Get the 'cdr' of a multi-lined string (pop off the first line)."
763
+  (line-cdr (string-lines string)))
764
+
765
+;; LIST_OF_STRINGS → STRING
766
+(defmethod line-cdr ((lines list))
767
+  "Get the 'cdr' of a list of lines, then turn back into a string."
768
+  (lines-string (cdr lines)))
769
+
770
+;; STRING → STRING
771
+(defmethod line-car ((string string))
772
+  "Get a multi-lined string's 'car' (the first line)."
773
+  (line-car (string-lines string)))
511 774
 
512
-(defun line-car (string)
513
-  (lines-string (car (string-lines string))))
775
+;; LIST_OF_STRINGS → STRING
776
+(defmethod line-car ((lines list))
777
+  "Get the 'car' of a list of lines."
778
+  (car lines))
514 779
 
780
+
781
+;; STRING → NUMBER
515 782
 (defun line-length (string)
783
+  "Return the amount of lines in a given string."
516 784
   (length (string-lines string)))
517 785
 
518
-(defun lines-string (lines)
519
-  (cl-strings:join lines :separator "
520
-"))
786
+;; STRING STRING FUNCTION → NUMBER
787
+(defmethod line-position ((string string) target &key (test #'in-string-p))
788
+  "Return the number of the line in which a given target-string can be found,
789
+  within a multi-lined string."
790
+  (line-position (string-lines string) target :test test))
791
+
792
+;; LIST_OF_STRINGS STRING FUNCTION → NUMBER
793
+(defmethod line-position ((lines list) target &key (test #'in-string-p))
794
+  "Return which number string the given target-string can be found in."
795
+  (position T (mapcar (lambda (line) (funcall test line target)) lines)))
796
+
797
+;; STRING STRING → BOOLEAN
798
+(defun in-string-p (string target)
799
+  "Return whether or not a target-string is within another string."
800
+  (< 1 (length (cl-strings:split string target))))
801
+
802
+;; STRING STRING FUNCTION → STRING
803
+(defmethod remove-line ((string string) target &key (test #'in-string-p))
804
+  "Remove a line matching the given test, given a target string."
805
+  (remove-line (string-lines string) target :test test))
806
+
807
+;; LIST_OF_STRINGS STRING FUNCTION → STRING
808
+(defmethod remove-line ((lines list) target &key (test #'in-string-p))
809
+  "Remove a line matching the test, return a multi-lined string based on
810
+  given list, but sans that removed line, ofc."
811
+  (aif (ignore-errors (line-position lines target :test test))
812
+       (lines-string
813
+        (remove (nth it lines) lines :test #'equal :count 1))
814
+       (lines-string list)))
815
+
816
+;; ITEM LIST → NUMBER
817
+(defun position-equal (item list)
818
+  "Literally just #'cl:position but with the test equal."
819
+  (position item list :test #'equal))