-
Notifications
You must be signed in to change notification settings - Fork 1
/
journey.py
executable file
·1061 lines (790 loc) · 37.3 KB
/
journey.py
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
"""Script that generates a novel (!)"""
from pattern.en import numerals, pluralize, referenced, singularize
import gd
import json
import logging, logging.handlers
import os
import random
import re
import requests
import sys
CONCEPTNET_URL = "http://conceptnet5.media.mit.edu/data/5.2/c/en/"
class Journey:
"""Class that does all the heavy lifting"""
# Title of the book
TITLE = "FLORA AND FAUNA"
# Keeping track of chapter numbers
CHAPTER = 0
TEXT = ''
TEMP = ''
THEN = False
DEBUG = ''
# Maze variables
MAZE = []
MAZE_SIZE = 25
MAZE_EXIT = {}
MAZE_START = {}
FOUND_EXIT = False
DIR_INDEX = 0
DIR_STRINGS = {'n': ['north', 'up'], 's': ['south', 'down'], 'e': ['east', 'right'], 'w': ['west', 'left']}
DIR_OFFSETS = {'n': {'i': 0, 'j': -1}, 's': {'i': 0, 'j': 1}, 'e': {'i': 1, 'j': 0}, 'w': {'i': -1, 'j': 0}}
OPPOSITE = {'n': 's', 's': 'n', 'e': 'w', 'w': 'e'}
JSON = {}
COLORS = {}
# Variations of "walk"
WALK = ['walked', 'went', 'strode', 'ran', 'sprinted', 'hoofed it', 'beat cheeks', 'trekked', 'meandered', 'marched', 'shuffled', 'shambled', 'toddled', 'traipsed', 'plodded', 'strutted', 'trudged', 'paraded', 'hiked', 'ambled', 'sauntered', 'stepped']
# Options for flower tastes
TASTES = ['awful', 'delicious', 'terrible', 'like chicken', 'okay', 'bad', 'good']
# List of flowers held by the narrator
FLOWERS = []
# Lists of animals and their conversations
ANIMALS = []
CONVOS = {}
ANIMAL_CONCEPTS = {}
GHOST = {}
GHOSTS = ['ghost', 'shade', 'specter', 'apparition', 'haunt', 'phantasm', 'wraith']
GHOST_SEEN = False
GHOST_LAST = None
GHOST_LAST_DIR = None
# -------------------------------------------------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.load_json()
# -------------------------------------------------------------------------------------------------------------
def load_json(self):
"""Pulls various corpora and loads them into the JSON object"""
f = open('corpora/data/plants/flowers.json')
self.JSON['flowers'] = json.loads(f.read().lower())['flowers']
f.close()
f = open('corpora/data/animals/common.json')
self.JSON['animals'] = json.loads(f.read().lower())['animals']
f.close()
f = open('corpora/data/colors/crayola.json')
self.JSON['colors'] = json.loads(f.read().lower())['colors']
f.close()
f = open('corpora/data/foods/fruits.json')
self.JSON['fruits'] = json.loads(f.read().lower())['fruits']
f.close()
f = open('corpora/data/humans/firstNames.json')
self.JSON['names'] = json.loads(f.read())['firstNames']
f.close()
if os.path.exists('json/concepts.json'):
f = open('json/concepts.json')
self.ANIMAL_CONCEPTS = json.loads(f.read())
f.close()
f = open('corpora/data/humans/us_presidents.json')
self.JSON['presidents'] = json.loads(f.read())
f.close()
f = open('corpora/data/words/us_president_quotes.json')
self.JSON['quotes'] = json.loads(f.read())
f.close()
# -------------------------------------------------------------------------------------------------------------
def init_maze(self):
"""Initializes the maze object"""
self.MAZE = []
self.DEBUG = ''
self.DIR_INDEX = random.randrange(2)
for i in range(0, self.MAZE_SIZE):
self.MAZE.append([])
for j in range(0, self.MAZE_SIZE):
self.MAZE[i].append({'n': 0, 's': 0, 'e': 0, 'w': 0, 'v': 0})
self.IMAGE.rectangle((i * 15, j * 15), (i * 15 + 14, j * 15 + 14), self.COLORS['blue'])
self.IMAGE.rectangle((i * 15 + 1, j * 15 + 1), (i * 15 + 13, j * 15 + 13), self.COLORS['blue'])
# -------------------------------------------------------------------------------------------------------------
def reset_maze(self):
"""Resets the visited flag on each square to 0"""
for i in range(self.MAZE_SIZE):
for j in range(self.MAZE_SIZE):
self.MAZE[i][j]['v'] = 0
# -------------------------------------------------------------------------------------------------------------
def count_visited(self):
"""Counts the number of squares visited"""
total = 0
for i in range(self.MAZE_SIZE):
for j in range(self.MAZE_SIZE):
if self.MAZE[i][j]['v'] == 1:
total += 1
return total
# -------------------------------------------------------------------------------------------------------------
def next_maze(self, i, j, build = False):
"""Returns a list which is the "next" square in a maze traversal and the direction it came from"""
start = None
next = None
# Keeps track of valid directions to go from 'here' (unvisited squares)
dirs = []
# If we're building the maze, not traversing it, only check the visited flag in a given square
if build:
if j - 1 >= 0:
if not self.MAZE[i][j - 1]['v']:
dirs.append('n')
if j + 1 < self.MAZE_SIZE:
if not self.MAZE[i][j + 1]['v']:
dirs.append('s')
if i + 1 < self.MAZE_SIZE:
if not self.MAZE[i + 1][j]['v']:
dirs.append('e')
if i - 1 >= 0:
if not self.MAZE[i - 1][j]['v']:
dirs.append('w')
# If we're traversing the maze, not building it check the existence of a wall in the given direction
# as well as the visited flag
else:
if j - 1 >= 0:
if self.MAZE[i][j - 1]['s'] and not self.MAZE[i][j - 1]['v']:
dirs.append('n')
if self.MAZE_START == {'i': i, 'j': j - 1}:
start = 'n'
if j + 1 < self.MAZE_SIZE:
if self.MAZE[i][j + 1]['n'] and not self.MAZE[i][j + 1]['v']:
dirs.append('s')
if self.MAZE_START == {'i': i, 'j': j + 1}:
start = 's'
if i + 1 < self.MAZE_SIZE:
if self.MAZE[i + 1][j]['w'] and not self.MAZE[i + 1][j]['v']:
dirs.append('e')
if self.MAZE_START == {'i': i + 1, 'j': j}:
start = 'e'
if i - 1 >= 0:
if self.MAZE[i - 1][j]['e'] and not self.MAZE[i - 1][j]['v']:
dirs.append('w')
if self.MAZE_START == {'i': i - 1, 'j': j}:
start = 'w'
# If one of the possible directions is the start square for this floor and there are more
# available directions than just that square, remove it from the possible choices
if start and len(dirs) > 1:
dirs.remove(start)
if len(dirs):
# Pick a random direction and update the current square's visited flag and the wall flag in that direction
dir = random.choice(dirs)
self.MAZE[i][j][dir] = 1
# Find the next square in the random direction and update those flags, too
# and remove the walls on the map image.
# --------------------------------------------------------------
# !!! This whole part is terrible and needs to be refactored !!!
# --------------------------------------------------------------
if dir == 'n':
if build:
self.IMAGE.rectangle((i * 15 + 2, j * 15), (i * 15 + 12, j * 15 + 1), self.COLORS['grid'])
j -= 1
if build:
self.IMAGE.rectangle((i * 15 + 2, j * 15 + 13), (i * 15 + 12, j * 15 + 14), self.COLORS['grid'])
if dir == 's':
if build:
self.IMAGE.rectangle((i * 15 + 2, j * 15 + 13), (i * 15 + 12, j * 15 + 14), self.COLORS['grid'])
j += 1
if build:
self.IMAGE.rectangle((i * 15 + 2, j * 15), (i * 15 + 12, j * 15 + 1), self.COLORS['grid'])
if dir == 'e':
if build:
self.IMAGE.rectangle((i * 15 + 13, j * 15 + 2), (i * 15 + 14, j * 15 + 12), self.COLORS['grid'])
i += 1
if build:
self.IMAGE.rectangle((i * 15, j * 15 + 2), (i * 15 + 1, j * 15 + 12), self.COLORS['grid'])
if dir == 'w':
if build:
self.IMAGE.rectangle((i * 15, j * 15 + 2), (i * 15 + 1, j * 15 + 12), self.COLORS['grid'])
i -= 1
if build:
self.IMAGE.rectangle((i * 15 + 13, j * 15 + 2), (i * 15 + 14, j * 15 + 12), self.COLORS['grid'])
next = [i, j, self.OPPOSITE[dir]]
if not build:
if len(dirs):
self.DEBUG += "Available directions from (" + str(i) + "," + str(j) + "): " + str.join(" : ", dirs) + "\n"
else:
self.DEBUG += "No valid directions from (" + str(i) + "," + str(j) + ")!\n"
return next
# -------------------------------------------------------------------------------------------------------------
def build_maze(self):
"""Builds a new random maze"""
stack = []
self.IMAGE = gd.image((self.MAZE_SIZE * 15, self. MAZE_SIZE * 15))
self.COLORS['black'] = self.IMAGE.colorAllocate((0, 0, 0))
self.COLORS['white'] = self.IMAGE.colorAllocate((255, 255, 255))
self.COLORS['blue'] = self.IMAGE.colorAllocate((130, 130, 210))
self.COLORS['red'] = self.IMAGE.colorAllocate((210, 130, 130))
self.COLORS['orange'] = self.IMAGE.colorAllocate((255, 165, 0))
self.COLORS['green'] = self.IMAGE.colorAllocate((130, 210, 130))
self.COLORS['yellow'] = self.IMAGE.colorAllocate((255, 255, 200))
self.COLORS['purple'] = self.IMAGE.colorAllocate((210, 130, 210))
self.COLORS['grey'] = self.IMAGE.colorAllocate((180, 180, 180))
self.COLORS['floor'] = self.IMAGE.colorAllocate((240, 240, 240))
self.COLORS['grid'] = self.IMAGE.colorAllocate((220, 220, 220))
self.IMAGE.filledRectangle((0, 0), (self.MAZE_SIZE * 15, self. MAZE_SIZE * 15), self.COLORS['floor'])
self.init_maze()
# Pick an exit at random
i = random.randint(5, self.MAZE_SIZE - 5)
j = random.randint(5, self.MAZE_SIZE - 5)
self.IMAGE.filledRectangle((i * 15 + 4, j * 15 + 4), (i * 15 + 11, j * 15 + 11), self.COLORS['orange'])
stack.append({'i': i, 'j': j})
self.MAZE_EXIT = stack[0]
square = stack[0]
logging.info('Maze exit at (' + str(i) + ',' + str(j) + ')')
total = 0
# Run through the maze checking visiting every square
while (len(stack)):
i = square['i']
j = square['j']
# Get a random neighbor that hasn't been visited
next = self.next_maze(i, j, True)
# If one was found, add it to the stack and flip its visited flag
if next:
self.MAZE[i][j]['v'] = 1
self.MAZE[i][j][self.OPPOSITE[next[2]]] = 1
stack.append({'i': i, 'j': j})
i = next[0]
j = next[1]
self.MAZE[i][j]['v'] = 1
self.MAZE[i][j][next[2]] = 1
square = {'i': i, 'j': j}
stack.append({'i': i, 'j': j})
total += 1
# Or if there are no valid directions, step back through the path to get to a square with
# unvisited neighbors
else:
square = stack.pop()
# Reset the visited flags on each square
self.reset_maze()
# Set the ghost
i = random.randrange(self.MAZE_SIZE)
j = random.randrange(self.MAZE_SIZE)
self.GHOST = {'i': i, 'j': j}
logging.info('Built Maze... (' + str(total) + ')')
# -------------------------------------------------------------------------------------------------------------
def traverse_maze(self):
"""Traverses the maze from a random start point"""
stack = []
self.TEMP = ''
# Pick a start point at random
i = random.randrange(self.MAZE_SIZE)
j = random.randrange(self.MAZE_SIZE)
stack.append({'i': i, 'j': j})
square = stack[0]
logging.info('Starting maze at (' + str(i) + ',' + str(j) + ')')
self.IMAGE.filledRectangle((i * 15 + 4, j * 15 + 4), (i * 15 + 11, j * 15 + 11), self.COLORS['grey'])
self.MAZE_START = square
self.THEN = False
total = 0
last_dir = ''
last_i = -1
last_j = -1
# Run through the maze
while (len(stack)):
i = square['i']
j = square['j']
self.DEBUG += "Square = (" + str(i) + "," + str(j) + ")\n"
self.move_ghost(i, j)
# Get a random neighbor that hasn't been visited
next = self.next_maze(i, j)
# If one was found, add it to the stack and flip its visited flag
if next:
if square not in stack:
stack.append(square)
first_dead_end = True
last_i = i
last_j = j
self.DEBUG += "Moving " + self.OPPOSITE[next[2]] + "\n"
i = next[0]
j = next[1]
self.MAZE[i][j]['v'] = 1
square = {'i': i, 'j': j}
# If we found an exit!
if self.MAZE_EXIT['i'] == i and self.MAZE_EXIT['j'] == j:
self.do_exit(i, j, total, next, last_i, last_j)
stack = []
# Otherwise keep walking
else:
if last_dir == self.OPPOSITE[next[2]] and self.THEN:
self.TEMP += "Again I " + random.choice(self.WALK) + " " + self.DIR_STRINGS[self.OPPOSITE[next[2]]][self.DIR_INDEX] + ". "
elif self.THEN:
self.TEMP += "Then I " + random.choice(self.WALK) + " " + self.DIR_STRINGS[self.OPPOSITE[next[2]]][self.DIR_INDEX] + ". "
else:
self.TEMP += "I " + random.choice(self.WALK) + " " + self.DIR_STRINGS[self.OPPOSITE[next[2]]][self.DIR_INDEX] + ". "
self.THEN = True
# Is the ghost here?
if self.GHOST == square:
self.do_ghost()
# Or is there a flower here?
elif random.randrange(100) < 5:
self.do_flower(i, j)
# Or is there an animal here?
elif random.randrange(100) < 5:
self.do_animal(i, j)
# Or are two animals talking to each other?
elif random.randrange(100) < 1 and len(self.ANIMALS) > 1:
self.do_animal_conversation(i, j)
self.MAZE[i][j]['v'] = 1
self.MAZE[i][j][next[2]] = 1
stack.append({'i': i, 'j': j})
total += 1
last_dir = self.OPPOSITE[next[2]]
if next[2] in ['e', 'w']:
self.IMAGE.filledRectangle((last_i * 15 + 7, last_j * 15 + 6), (i * 15 + 7, j * 15 + 8), self.COLORS['green'])
else:
self.IMAGE.filledRectangle((last_i * 15 + 6, last_j * 15 + 7), (i * 15 + 8, j * 15 + 7), self.COLORS['green'])
# Or if there are no valid directions, step back through the path to get to a square with
# unvisited neighbors
else:
if first_dead_end:
self.TEMP += "Then I hit a dead-end. I was feeling lost so I retraced my steps.\n"
self.THEN = False
first_dead_end = False
self.DEBUG += "Couldn't find a neighbor, popping stack\n"
square = stack.pop()
# -------------------------------------------------------------------------------------------------------------
def move_ghost(self, i, j):
"""Moves the ghost roughly towards the narrator (can pass through walls)"""
dir = None
# If the ghost was already seen once on this floor, they won't move or be seen again on this same floor
# Denoted by the ghost being off the map
if self.GHOST != {'i': -1, 'j': -1}:
dx = self.GHOST['i'] - i
dy = self.GHOST['j'] - j
# If delta-x is 0 then move along the x-axis
if dx == 0:
if dx > 0:
dir = 'w'
else:
dir = 'e'
# Else if delta-y is 0 then move along the y-axis
elif dy == 0:
if dy > 0:
dir = 'n'
else:
dir = 's'
# Otherwise, pick an axis to move on
else:
if random.randrange(1, 100) > 50:
if dx > 0:
dir = 'w'
else:
dir = 'e'
else:
if dy > 0:
dir = 'n'
else:
dir = 's'
self.GHOST['i'] += self.DIR_OFFSETS[dir]['i']
self.GHOST['j'] += self.DIR_OFFSETS[dir]['j']
self.GHOST_LAST_DIR = dir
# -------------------------------------------------------------------------------------------------------------
def do_exit(self, i, j, total, next, last_i, last_j):
"""Process the end of a floor"""
self.FOUND_EXIT = True
# Place the last path on the image
if next[2] in ['e', 'w']:
self.IMAGE.filledRectangle((last_i * 15 + 7, last_j * 15 + 6), (i * 15 + 7, j * 15 + 8), self.COLORS['green'])
else:
self.IMAGE.filledRectangle((last_i * 15 + 6, last_j * 15 + 7), (i * 15 + 8, j * 15 + 7), self.COLORS['green'])
# Increment chapter, and write it
self.CHAPTER += 1
self.TEXT += "~~~ CHAPTER " + numerals(str(self.CHAPTER)).upper() + " ~~~\n"
self.TEXT += self.TEMP
self.TEXT += "Then I " + random.choice(self.WALK) + " down a flight of stairs. "
# Write the current map out to PNG
self.IMAGE.writePng("html/maps/" + str(self.CHAPTER) + ".png")
# Check to see if any animals stopped following the narrator, then print them
self.TEXT += self.unfollow()
self.get_animals_following()
self.get_flowers_held()
# Print the percent of this floor explored
self.TEXT += "It seemed like I had explored about "
self.TEXT += numerals(str(self.count_visited() * 1.0 / (self.MAZE_SIZE * self.MAZE_SIZE) * 100).split('.')[0])
self.TEXT += " percent of this floor.\n"
# Logging...
logging.info('--- CHAPTER ' + str(self.CHAPTER) + ' ---')
logging.info('Found the exit to the maze at (' + str(i) + ',' + str(j) + ')')
logging.info('Total steps: ' + str(total))
# -------------------------------------------------------------------------------------------------------------
def do_ghost(self):
"""Process encountering the ghost"""
# Pick an random US president to use as the ghost
potus = random.choice(self.JSON['presidents']['objects'])
# Where did the ghost come from?
fromi = self.GHOST['i'] + self.DIR_OFFSETS[self.GHOST_LAST_DIR]['i']
fromj = self.GHOST['j'] + self.DIR_OFFSETS[self.GHOST_LAST_DIR]['j']
# If we saw it beforeshow a slightly different message
if self.GHOST_SEEN:
self.TEMP += "The " + random.choice(self.GHOSTS) + " appeared again from the "
self.TEMP += self.DIR_STRINGS[self.OPPOSITE[self.GHOST_LAST_DIR]][0] + ". "
# Check to see if the ghost came from an invalid direction
if fromi < 0 or fromi >= self.MAZE_SIZE or fromj < 0 or fromj >= self.MAZE_SIZE:
self.TEMP += "It came right through the wall! "
elif self.MAZE[fromi][fromj][self.GHOST_LAST_DIR]:
self.TEMP += "It came right through the wall! "
# Check to see if it still looks like it did before
if self.GHOST_LAST == potus:
self.TEMP += "It still looked like " + potus['person']['firstname'] + " " + potus['person']['lastname'] + ". "
# Otherwise, it's a new president
else:
self.TEMP += "This time it looked like " + potus['person']['firstname'] + " " + potus['person']['lastname'] + ". "
# If this ghost has quotes, pick one and say it
for q in self.JSON['quotes']['data']:
if q['id'] == potus['id']:
self.TEMP += 'It said, "' + random.choice(q['quotes']) + '," to me. '
else:
self.TEMP += "A scary " + random.choice(self.GHOSTS) + " appeared from the "
self.TEMP += self.DIR_STRINGS[self.OPPOSITE[self.GHOST_LAST_DIR]][0] + "! "
if fromi < 0 or fromi >= self.MAZE_SIZE or fromj < 0 or fromj >= self.MAZE_SIZE:
self.TEMP += "It came right through the wall! "
elif self.MAZE[fromi][fromj][self.GHOST_LAST_DIR]:
self.TEMP += "It came right through the wall! "
self.TEMP += "It looked a little like " + potus['person']['firstname'] + " " + potus['person']['lastname'] + ". "
for q in self.JSON['quotes']['data']:
if q['id'] == potus['id']:
self.TEMP += 'It said, "' + random.choice(q['quotes']) + '," to me. '
# Update that we saw the ghost and remove it from this floor
self.GHOST_LAST = potus
self.GHOST_SEEN = True
self.GHOST = {'i': -1, 'j': -1}
# -------------------------------------------------------------------------------------------------------------
def do_flower(self, i, j):
"""Process finding a flower and possibly doing something with it"""
# Get a random color and flower name
color = random.choice(self.JSON['colors'])['color']
flower = singularize(random.choice(self.JSON['flowers']))
# Print them
self.TEMP += "There was a beautiful " + color + " " + flower + " there. "
self.TEMP += "It smelled like " + pluralize(random.choice(self.JSON['fruits'])) + "."
# Put a square on the map to mark the flower
self.IMAGE.filledRectangle((i * 15 + 4, j * 15 + 4), (i * 15 + 11, j * 15 + 10), self.COLORS['purple'])
# Is the narrator keeping this flower?
if random.randrange(100) < 10:
self.TEMP += " I picked it"
if self.FLOWERS:
self.TEMP += " and added it to the rest of my bouquet"
self.TEMP += "."
self.FLOWERS.append({'color': color, 'flower': flower})
# Does the narrator eat this flower instead?
elif random.randrange(100) < 5:
self.TEMP += " For some reason I ate it. It tasted " + random.choice(self.TASTES) + "."
self.TEMP += "\n"
self.THEN = False
# -------------------------------------------------------------------------------------------------------------
def do_animal(self, i, j):
"""Process finding an animal"""
# Get a random animal and give it a name
animal = random.choice(self.JSON['animals'])
name = random.choice(self.JSON['names'])
# Print that info
self.TEMP += "There was " + referenced(animal) + " there. "
self.TEMP += "I named it " + name + "."
# Put a square on the map to denote finding an animal here
self.IMAGE.filledRectangle((i * 15 + 4, j * 15 + 4), (i * 15 + 11, j * 15 + 10), self.COLORS['red'])
# Did the animal follow the narrator?
if random.randrange(100) < 10:
self.ANIMALS.append({'name': name, 'animal': animal})
self.get_animal_concepts(animal)
self.TEMP += " It started following me."
self.TEMP += "\n"
self.THEN = False
# -------------------------------------------------------------------------------------------------------------
def do_animal_conversation(self, i, j):
"""Make two animals talk to one another"""
# Pick two animals and make sure they're not the same one
to = random.choice(self.ANIMALS)
fro = random.choice(self.ANIMALS)
while fro == to:
fro = random.choice(self.ANIMALS)
# Check to make sure the these two animals didn't have a conversation already (or at least the "to"
# animal didn't already initiate a conversation with the "from" animal
already = False
if to['name']+to['animal'] in self.CONVOS.keys():
if fro['name']+fro['animal'] in self.CONVOS[to['name']+to['animal']]:
already = True
# If this is a new conversation, continue
if not already:
self.TEMP += "\n" + to['name'] + ' asked ' + fro['name'] + ', "What exactly are you?"\n'
self.TEMP += "\"Well, I'm " + referenced(fro['animal'])
# If the "fro" animal has some properties in ConceptNet, print one randomly
if "HasProperty" in self.ANIMAL_CONCEPTS[fro['animal']].keys():
self.TEMP += " and I'm " + self.clean_phrase(singularize(random.choice(self.ANIMAL_CONCEPTS[fro['animal']]['HasProperty'])))
self.TEMP += "."
# If the "fro" animal has a "HasA" relationship in ConceptNet, print one randomly
hasa = False
if "HasA" in self.ANIMAL_CONCEPTS[fro['animal']].keys():
has = referenced(singularize(random.choice(self.ANIMAL_CONCEPTS[fro['animal']]['HasA'])))
self.TEMP += " I have " + self.clean_phrase(has)
hasa = True
# If the "fro" animal is capable of something, talk about it
capable = False
if "CapableOf" in self.ANIMAL_CONCEPTS[fro['animal']].keys():
capable = True
ability = random.choice(self.ANIMAL_CONCEPTS[fro['animal']]['CapableOf'])
# Sometimes the "CapableOf" relationship in ConceptNet is negated, so make
# sure we have consistent logic
can = "can"
if ability.find("cannot ") == 0:
can = "cannot"
ability.replace("cannot ", "")
if hasa:
self.TEMP += " and"
# State the ability and ask the "to" animal if they can do the same thing
self.TEMP += " I " + can + " " + self.clean_phrase(ability) + ", can you?"
if hasa and not capable:
self.TEMP += "."
self.TEMP += "\"\n"
# If there was a stated ability for the "fro" animal
if capable:
# Check to see if the "to" animal also has the same ability, and if so say so
canto = False
if 'CapableOf' in self.ANIMAL_CONCEPTS[to['animal']].keys():
if ability in self.ANIMAL_CONCEPTS[to['animal']]['CapableOf']:
canto = True
self.TEMP += '"Yes I can!"'
# If not, say so
if not canto:
self.TEMP += '"No I can' + "'t"
# If they have other abilities, though, pick one and print it
if 'CapableOf' in self.ANIMAL_CONCEPTS[to['animal']].keys():
self.TEMP += ", but I do know how to " + self.clean_phrase(random.choice(self.ANIMAL_CONCEPTS[to['animal']]['CapableOf'])) + "!"
else:
self.TEMP += ","
self.TEMP += '" replied ' + to['name'] + ".\n"
# Add the conversation to the list
if to not in self.CONVOS.keys():
self.CONVOS[to['name']+to['animal']] = []
self.CONVOS[to['name']+to['animal']].append(fro['name']+fro['animal'])
self.THEN = False
# -------------------------------------------------------------------------------------------------------------
def unfollow(self):
"""Loop through the following animals list and randomly pick some to remove"""
# There's a percentage chance an animal may unfollow the narrator. Check for that,
# remove the animal from the list and add it to an unfollow list
unfollowed = []
for a in self.ANIMALS:
if random.randrange(100) < 3:
unfollowed.append(a)
self.ANIMALS.remove(a)
# Loop through the unfollow list building a textual representation of the animals that
# unfollowed. Return that string
temp = ""
for a in unfollowed:
if a == unfollowed[-1] and len(unfollowed) > 1:
temp += ", and "
elif a != unfollowed[0]:
temp += ", "
temp += a['name'] + " the " + a['animal']
if temp:
temp += " stopped following me for some reason.\n"
return temp
# -------------------------------------------------------------------------------------------------------------
def count_words(self):
"""Returns the current number of words in the text"""
return len(re.split(r'[^0-9A-Za-z]+', self.full_text()))
# -------------------------------------------------------------------------------------------------------------
def clean_phrase(self, str):
"""Cleans concept phrase for printing"""
return str.replace("_", " ")
# -------------------------------------------------------------------------------------------------------------
def get_animal_concepts(self, animal):
"""Queries ConceptNet for data on a particular animal"""
# ConceptNet API: https://github.com/commonsense/conceptnet5/wiki/API
if animal not in self.ANIMAL_CONCEPTS.keys():
r = requests.get(CONCEPTNET_URL + animal)
if r.json():
rels = {}
# For each edge found, save each "end" in a list of "rels"
for e in r.json()['edges']:
rel = e['rel'].split('/')[-1]
end = e['end'].split('/')[-1]
if len(end.split('_')) <= 2 and end != animal:
if rel not in rels.keys():
rels[rel] = []
rels[rel].append(end)
self.ANIMAL_CONCEPTS[animal] = rels
# Cache all current concepts found after each pull from the ConceptNet API
f = open('json/concepts.json', 'w')
f.write(json.dumps(self.ANIMAL_CONCEPTS) + "\n")
f.close()
# -------------------------------------------------------------------------------------------------------------
def get_animals_following(self):
"""Retrieve a textual list of animals currently following the narrator"""
if self.ANIMALS:
self.TEXT += "So far " + numerals(len(self.ANIMALS)) + " animal"
if len(self.ANIMALS) > 1:
self.TEXT += "s were following me"
else:
self.TEXT += " was following me"
if self.FLOWERS:
self.TEXT += ", and "
else:
self.TEXT += ". "
# -------------------------------------------------------------------------------------------------------------
def get_flowers_held(self):
"""Get a textual representation of flowers currently held by the narrator"""
if self.FLOWERS:
if not self.ANIMALS:
self.TEXT += "So far "
self.TEXT += "I held " + numerals(len(self.FLOWERS)) + " flower"
if len(self.FLOWERS) > 1:
self.TEXT += "s"
self.TEXT += ". "
# -------------------------------------------------------------------------------------------------------------
def get_prologue(self):
"""Returns the assembled prologue"""
temp = "~~~ PROLOGUE ~~~\nI am a botanist and a zoologist. "
temp += "I came to this complex of mazes to catalog flora and fauna. "
temp += "What follows is the journal of my travails.\n"
return temp
# -------------------------------------------------------------------------------------------------------------
def get_afterword(self):
"""Returns the assembled afterword"""
temp = "\n~~~ AFTERWORD ~~~\n"
temp += "I finally made my way through every floor and out into the sunlight. "
temp += "Below is a list of some of the things I cataloged inside this massive place.\n"
temp += self.get_animals()
temp += self.get_flowers()
temp += "\nTHE END\n"
return temp
# -------------------------------------------------------------------------------------------------------------
def get_animals(self):
"""Returns a textual representation of following animals"""
temp = ""
if self.ANIMALS:
temp += "I was followed by: "
for a in self.ANIMALS:
if a == self.ANIMALS[-1] and len(self.ANIMALS) > 1:
temp += ", and "
elif a != self.ANIMALS[0]:
temp += ", "
temp += a['name'] + " the " + a['animal']
return temp + ".\n"
# -------------------------------------------------------------------------------------------------------------
def get_flowers(self):
"""Returns a textual representation of carried flowers"""
temp = ""
if self.FLOWERS:
temp += "I left with a beautiful bouquet of flowers that contained: "
for f in self.FLOWERS:
if f == self.FLOWERS[-1] and len(self.FLOWERS) > 1:
temp += ", and "
elif f != self.FLOWERS[0]:
temp += ", "
temp += referenced(f['color'] + " " + f['flower'])
return temp + ".\n"
# -------------------------------------------------------------------------------------------------------------
def full_text(self):
"""Returns the full text of the novel as a string"""
return self.TITLE + "\n" + self.get_prologue() + self.TEXT + self.get_afterword()
# -------------------------------------------------------------------------------------------------------------
def write_html(self):
"""Builds HTML version of the book"""
html = "<!DOCTYPE html>\n"
html += "<html>\n"
html += "<head>\n"
html += "<title>Flora and Fauna</title>\n"
html += '<meta charset="UTF-8">\n'
html += '<link rel="stylesheet" href="css/main.css" type="text/css">\n'
html += "</head>\n"
html += "<body>\n"
html += '<div id="header">\n'
html += '<h1>' + self.TITLE + '</h1>\n'
html += '<div id="credits">\n'
html += '<span>Generated for <a href="https://github.com/dariusk/NaNoGenMo-2014">#NaNoGenMo 2014</a> by '
html += '<a href="https://twitter.com/amarriner">@amarriner</a></span>\n'
html += '<span>Source code on <a href="https://github.com/amarriner/Journey">GitHub</a></span>\n'
html += '</div>\n'
html += '</div>\n'
# Replace the tildas with <h2>
prologue = re.sub(r'~~~ PROLOGUE ~~~', '<h2>PROLOGUE</h2>', self.get_prologue()).split("\n")
for line in prologue:
if line == prologue[0]:
html += line + "\n"
else:
html += "<div>" + line + "</div>\n"
# Loop through all the text and add maps at chapter start
chapter = 1
for line in self.TEXT.split("\n"):
image = ""
# Replace tildas with <h3>, start chapter
if line[:3] == "~~~":
html += '<hr>\n'
image = '<div class="map"><img class="map_image" src="maps/' + str(chapter) + '.png" alt="Maze ' + str(chapter) + '"></div>\n'
chapter += 1
else:
line = "<div>" + line + "</div>\n"
line = re.sub(r'^~~~ ', '<h2>', line)
line = re.sub(r' ~~~$', '</h2>', line)
html += line
html += image
if not line:
logging.info('-----NONE------')
# Replace afterword tildas as before, and add it to HTML string
html += '<hr>\n'
afterword = re.sub(r'~~~ AFTERWORD ~~~', '<h2>AFTERWORD</h2>', self.get_afterword()).split("\n")
for line in afterword:
if line:
if line == afterword[1] or line == afterword[-2]:
line = re.sub(r'^THE END$', '<h2>THE END</h2>', line)
html += line + "\n"
else:
html += "<div>" + line + "</div>\n"
html += "</body>\n"
html += "</html>\n"
# Write HTML to file
f = open("html/index.html", "w")
f.write(html)
f.close()
# -------------------------------------------------------------------------------------------------------------
def write_text(self):
"""Writes the TEXT attribute out to a file"""
text = ""
# Loop through the lines and wrap those over 80 characters
for line in self.full_text().split("\n"):
if len(line) > 80:
count = 0
space = 0
start = 0
for i in range(len(line)):
c = line[i]
if c == ' ':
space = i
if count >= 80:
text += line[start:space].strip() + "\n"
start = space
i = space