-
Notifications
You must be signed in to change notification settings - Fork 0
/
theory.js
6930 lines (6876 loc) · 269 KB
/
theory.js
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
import { BigNumber } from './api/BigNumber';
import { CompositeCost, ConstantCost, ExponentialCost, FirstFreeCost, FreeCost } from './api/Costs';
import { game } from './api/Game';
import { Localization } from './api/Localization';
import { MathExpression } from './api/MathExpression';
import { profilers } from './api/Profiler';
import { Theme } from './api/Settings';
import { Sound } from './api/Sound';
import { QuaternaryEntry, theory } from './api/Theory';
import { Utils, log } from './api/Utils';
import { Vector3 } from './api/Vector3';
import { ui } from './api/ui/UI';
import { Aspect } from './api/ui/properties/Aspect';
import { ClearButtonVisibility } from './api/ui/properties/ClearButtonVisibility';
import { Color } from './api/ui/properties/Color';
import { FontFamily } from './api/ui/properties/FontFamily';
import { ImageSource } from './api/ui/properties/ImageSource';
import { Keyboard } from './api/ui/properties/Keyboard';
import { LayoutOptions } from './api/ui/properties/LayoutOptions';
import { LineBreakMode } from './api/ui/properties/LineBreakMode';
import { ScrollOrientation } from './api/ui/properties/ScrollOrientation';
import { StackOrientation } from './api/ui/properties/StackOrientation';
import { TextAlignment } from './api/ui/properties/TextAlignment';
import { Thickness } from './api/ui/properties/Thickness';
import { TouchType } from './api/ui/properties/TouchType';
var id = 'lemmas_garden';
var getName = (language) => {
const names = {
en: `Lemma's Garden (perch)`,
ja: `レンマ苑`
};
return names[language] ?? names.en;
};
var getDescription = (language) => {
const descs = {
en: `Last night, Lemma swept away the fallen leaves on her old garden.
You are her first student in a long while.
Welcome to Lemma's Garden, an idle botanical theory built on the workings of ` +
`Lindenmayer systems. Reminisce the story of Lemma, a retired teacher, as ` +
`she rambles about things already long passed.`,
};
return descs[language] ?? descs.en;
};
var authors = 'propfeds (a_spiralist)\n\nThanks to:\nProf. Nakamura, ' +
'research supervisor\nThe six questionnaire takers\nSir Gilles\ngame-icons.net';
var version = 0.25;
// Numbers are often converted into 32-bit signed integers in JINT.
const INT_MAX = 0x7fffffff;
const INT_MIN = -0x80000000;
const TRIM_SP = /\s+/g;
const LS_RULE = /([^:]+)(:(.+))?=(.*)/;
// Context doesn't need to check for nested brackets!
const LS_CONTEXT = /((.)(\(([^\)]+)\))?<)?((.)(\(([^\)]+)\))?)(>(.)(\(([^\)]+)\))?)?/;
const BACKTRACK_LIST = new Set('+-&^\\/|[$T');
// Leaves and apices
const SYNTHABLE_SYMBOLS = new Set('AL');
const MAX_CHARS_PER_TICK = 300;
const NORMALISE_QUATERNIONS = false;
const MENU_LANG = Localization.language;
const LOC_STRINGS = {
en: {
versionName: `Version: 0.2.5, 'Grass Tidings'`,
wip: 'Work in Progress',
currencyTax: 'p (tax)',
pubTax: 'Publishing fee \\&\\ taxes\\colon',
btnView: 'View L-system',
btnViewAction: 'View Action L-system',
btnAlmanac: 'World of Plants',
btnAlmanacNoEntry: '(Unavailable)',
btnVar: 'Variables',
btnSave: 'Save',
btnReset: 'Clear Graphs',
btnRedraw: 'Redraw',
btnPrev: 'Prev.',
btnNext: 'Next',
btnContents: 'Table of\nContents',
btnPage: 'p. {0}',
actionConfirm: `You are about to perform a {0} on\\\\
{4}.\\\\(plot {1}, {2})\\\\{5}\\\\\n\n\\\\{6}`,
bulkActionConfirm: `You are about to perform a {0} on all plants in ` +
`plot {1}.\\\\\n\n\\\\{2}`,
labelSave: 'Last saved: {0}s',
labelSkip: 'Skip tutorial',
labelWater: 'Water',
labelWaterUrgent: 'Water!',
labelActions: ['Harvest', 'Prune'],
labelFilter: 'Filter: ',
labelParams: 'Parameters: ',
labelIndent: 'Indent: ',
labelExpand: 'Expand brackets: ',
labelAxiom: 'Axiom: ',
labelAngle: 'Turning angle (°): ',
labelRules: `Production rules: {0}\\\\Every stage, each symbol in
the plant's sequence chooses one rule to evolve. Rules are checked from top to
bottom.`,
labelIgnored: 'Turtle-ignored: ',
labelCtxIgnored: 'Context-ignored: ',
labelTropism: 'Tropism (gravity): ',
labelSeed: 'Random seed: ',
labelModels: `Model specifications: {0}\\\\Models define how each symbol
is drawn, with similar logic to production rules. If no model is specified, a
straight line will be drawn.`,
menuVariables: 'Defined Variables',
labelVars: 'Variables: {0}',
plotTitle: `\\text{{Plot }}{{{0}}}`,
plotTitleF: `\\mathcal{{P}} \\mkern -1mu l \\mkern -0.5mu o
\\mkern 1mu t \\enspace #{{\\mkern 2mu}}{{{0}}}`,
riverTitle: `\\text{{River }}`,
riverTitleF: `\\mathcal{{R}} \\mkern -0.5mu i \\mkern -0.5mu v
\\mkern 0.5mu e \\mkern 0.5mu r`,
forestTitle: `\\text{{Lemma }}{{{0}}}`,
forestTitleF: `\\mathcal{{L}} \\mkern -0.5mu e \\mkern 0.5mu mm
\\mkern 0.5mu a \\enspace #{{\\mkern 2mu}}{{{0}}}`,
plainTitle: `\\text{{Floodplain }}{{{0}}}`,
plainTitleF: `\\mathcal{{F}} \\mkern -1mu loodp \\mkern 0.5mu lain
\\enspace #{{\\mkern 2mu}}{{{0}}}`,
unlockPlot: `\\text{{plot }}{{{0}}}`,
unlockPlots: `\\text{{plots }}{{{0}}}~{{{1}}}`,
unlockPlant: `\\text{{a new plant}}`,
lockedPlot: `\\text{Untilled soil.}`,
permaExtraPot: `Borrow Lemma's flower pot`,
permaExtraPotInfo: `Holds one plant, seeds free of charge, pest-proof`,
permaNote: `Notebook \\&\\ 'Buy All' button`,
permaNoteInfo: 'Allows management of colony sizes',
menuNote: 'Notebook',
labelSpecies: 'Species',
labelMaxLevel: 'Maximum level',
labelNoteDesc: `The species' upgrade levels shall not go beyond these
limits.`,
menuAutoWater: 'Watering schedules',
labelMaxStage: 'Water until',
labelAutoWaterDesc: `Scheduling is unlocked for a species after
harvesting it for the first time.`,
menuExtraPot: 'Sheltered pot',
labelPlantPot: 'Shelter plant in pot: ',
labelTransferPot: 'Transfer to plot (stg. 20/max required): ',
extraPotEqPlaceholder: [
'',
`Writes a note on the bookshelf:
\\\\To my sister's best friend, for life
\\\\You can always find shelter here
\\\\don't you dare forget it.
\\\\Dorian`,
`Etched on the pot's side:
\\\\For Miss Ruddles' class,
\\\\With love of course!
\\\\C.`,
`The pot is adorned with pearly grooves,
\\\\reminiscent of a vast, misting lake.`
],
colony: `{0} of {1}, stage {2}`,
colonyWithMaxStg: `{0} of {1}, stage {2}/{3}`,
colonyProg: `{0} of {1}, stg. {2} ({3}\\%)`,
colonyNoPop: `{1}, stg. {2} ({3}%)`,
colonyNoPopEsc: `{1}, stg. {2} ({3}\\%)`,
invisibleColony: `\\text{Tilled soil.}`,
colonyStats: `\\text{{Energy\\colon\\enspace {0} +{1}/hr}}\\\\
\\text{{Growth\\colon\\enspace {2}/{3} +{4}/hr}}\\\\
\\text{{Base profit\\colon\\enspace {5}p}}\\\\
\\text{{({6}/{7}) {8}}}`,
hour: 'hr',
dateTime: 'Year {0} week {1}/{2}\\\\{3}:{4}',
dateTimeBottom: '{3}:{4}\\\\Year {0} week {1}/{2}',
hacks: 'Hax',
status: {
evolve: 'Growing...',
actions: [
'Harvesting...',
'Pruning...'
]
},
switchPlant: 'Switch plant (plot {0})',
switchPlantInfo: 'Cycles through the list of plants',
plotPlant: 'Plot {0}: {1}',
viewColony: 'Examine',
viewColonyInfo: 'Displays details about the colony',
switchColony: 'Switch colony ({0}/{1})',
switchColonyInfo: 'Cycles through the list of colonies',
labelSpeed: 'Game speed: 1/{0}',
labelGM3D: '3D illustration: ',
labelActionConfirm: 'Confirmation dialogue: ',
lineGraphModes: [
'2D graph: Off',
'2D graph: Photosynthesis',
'2D graph: Growth'
],
colonyModes: [
'Colony view: Off',
'Colony view: Verbose',
'Colony view: Simple',
'Colony view: List'
],
actionPanelModes: [
'Action panel: Bottom',
'Action panel: Top'
],
plotTitleModes: [
'Plot title: Serif',
'Plot title: Cursive'
],
quatModes: [
'Account: Off',
'Account: Expected revenue',
'Account: Colonies',
'Account: Performance (latest/avg)',
'Account: Performance (min/max)'
],
camModes: [
'Camera: Static',
'Camera: Follow (Linear)',
'Camera: Follow (Squared)'
],
plants: {
sprout: {
name: 'Pea sprout',
nameShort: 's',
info: `Tastes nice, innit? (\\(~\\)10 days)`,
LsDetails: `A(r, t): apex (stem bud) providing r energy/hr. Has
t stages left until it spurts.\\\\F(p): segment of length p.
Provides p pennies on harvest.\\\\L(r): leaf of size r, providing r energy/hr.`,
actions: [
`Harvest returns profit as the sum of all F lengths.`
],
narrations: [
{
index: [0, 1, 4, 5, 8, 12],
0: `(Why is this seed called an axiom? What is the
second parameter for?\\\\I must wait for her return.)`,
1: `I am back! Rule number... one:\\\\The seed begins to
crack, and there's a 'timer' on it until something happens.`,
4: `Rule number 1 fails, so rule number 2
triggers:\\\\A little stem rises, and there's a tiny pair of leaves on it.`,
5: `Rules 1, 3, and 4, all at the same time.`,
8: `The stem rises again, and with it, a new pair of
leaves.\\\\At the same time, the first segment and leaves reach their limits.`,
12: `You've made it. Welcome to class.\\\\Plant a few
more, then I'll get you something new.`
}
]
},
calendula: {
name: 'Calendula',
nameShort: 'C',
info: 'The classic flower to start a month. (\\(~\\)7 weeks)',
LsDetails: `A(r, t): apex (stem bud) providing r energy/hr. Has
t stages left until it splits.\\\\F(l, lim): internode of length l, growing up
to lim.\\\\I(t): flower stem. Grows a leaf every stage until t reaches 0,
when it turns into K.\\\\K(p): flower of size p. Provides p pennies on harvest.
\\\\L(r, lim): leaf providing r energy/hr, growing up to lim.`,
actions: [
`Harvest returns profit as the sum of all K sizes.`
],
narrations: [
{
index: [0, 5, 10, 15, 19, 21, 23, 26, 27, 28, 30, 31,
35, 39, 40],
0: 'A seed, curling over in its warm slumber.',
5: 'A little stem has just risen.',
10: `The second pair of leaves appears. See that for
this cultivar, each pair of leaves is rotated to 90° against the previous.
Others might generate leaves in a spiral around the stem.`,
15: 'The third pair of leaves appears.',
19: `The stem has split in two. It will start to flower
soon.`,
21: `On the flower stem, little leaves will start to
spawn in spiral around it. The spinning angle is approximately 137.508°, also
called by Mister Fibonacci as the golden angle.`,
23: 'Our first flower bud has risen.',
26: 'Wait for it...',
27: 'A second flower bud appears!',
28: 'The third and final flower appears.',
30: `My w... sister-in-law, she really liked munching on
these flowers raw. Try it.`,
31: `Try it!\\\\Naw, only teasing you ;). Keep your
saliva away from my little profit.`,
35: 'The first flower matures.',
39: 'The second flower matures.',
40: 'All flowers have reached maturity.',
}
]
},
basil: {
name: 'Basil',
nameShort: 'Ba',
info: `A fragrant herb requiring a bit of care. (6\\(
~\\)8 weeks)`,
LsDetails: `A(r, t): apex (stem bud).\\\\B: base, used for
communications.\\\\F(l, lim): internode.\\\\I(t): side stem. t stages left
until it advances.\\\\K(s, t): flower of size s. Grows another flower until t
reaches 0. Provides s pennies on harvest.\\\\L(p, lim, s): leaf. s denotes
whether a signal has been received. Provides p pennies on harvest.\\\\S(type):
signal (type 0 travels down, type 1 travels up).`,
actions: [
`Harvest returns profit as the sum of all L and K sizes
(first parameter).`,
`Prune cuts off all A symbols.`
],
narrations: [
// Track 0: un-pruned or pruned late
{
index: [0, 6, 10, 14, 18, 22, 26, 27, 31, 32, 35],
0: 'A seed taking its sweet slumber.',
6: `The first pair of leaves pops up on the stem. A side
stem too.`,
10: 'The second leaf pair appears, along wth a stem.',
14: `The third pair of leaves appears. This rhythm will
repeat for a while`,
18: `I'll show you what to do when it's about to bloom,
soon.`,
22: `It's about to bloom. You can stay up watching
flowers later, or snip the bud, if you don't want your leaves bitter.`,
26: `The plant blooms. The flower sends a signal down.
\\\\Rules 12 to 15 govern signal transport.`,
27: `Rules 12 and 14:\\\\The signal will travel until it
hits the base.`,
31: 'The signal touches base.',
32: 'Rule 15: Basil base relays the signal upward.',
35: `Rule 10: The first leaves receive the signal from
below. Let us unfold:\\\\
Bitter leaf, set us free,\\\\
as I whisper unto thee.\\\\
Flitting pollen, set me free,\\\\
as my mistress makes a tea,\\\\
out of me.`,
},
// Track 1: well pruned
{
name: 'well pruned',
index: [22],
22: `All leaves saved. Now watch the side stems grow.`,
},
// Track 2: pruned
{
name: 'pruned',
index: [0, 6, 10, 14, 18],
0: `A seed taking its sweet slumber. It shall never wake
up. Bye bye.`,
6: `Pruning at this point nets you fairly little.`,
10: `This point saves a few more leaves, at least.`,
14: `Pruning here can secure some more of the leaves.`,
18: `Pruning at this point can net you even more.`,
}
]
},
campion: {
name: 'Rose campion',
nameShort: 'R',
info: `A silvery shrub, passively providing income per ` +
`stage. (\\(~\\)32 weeks)`,
LsDetails: `A(r, t): apex (stem bud).\\\\F(l, t): internode of
length l. t stages until it stops growing.\\\\K(p, t): flower of size p. t
stages left until it disappears. Provides p pennies on harvest.\\\\L(s): leaf.
\\\\O(s): fruit of size s.`,
actions: [
`Harvest returns profit as the sum of all K sizes
(first parameter).`
],
narrations: [
{
index: [0, 6, 10, 12, 14, 18, 19, 22, 27],
0: 'A seed basking in its own dazing lullaby.',
6: 'A flower bud already?',
10: 'New stems rise forth from a bud.',
12: `A reminder, gardeners need to be early birds. Now,
why are you still up counting pennies?\\\\Look at the light. This lamp's wick
is, in fact, from our own campion leaves. I don't sell them.\\\\I don't want to
sell most of the flowers I plant. So... so if you ever needed to know if all I
ever cared about was crowns and riches...\\\\Are you... did you just pass out?`,
14: `New stems have risen. This pattern will repeat
periodically. It grows like a... fractal?\\\\What is a graftal- who wrote that
document? Oh, of course they'd be making that up.`,
18: `New stems have risen.\\\\Oh no. Perhaps luminaries
were right all along. Small campion, big campion. Lena surrenders!`,
19: `Too late to munch on thy flowers, for the first
fruit...\\\\cometh.`,
22: `Go to sleep. Was my campion sedative not good
enough?`,
27: `A fruit just fell off.\\\\Campion is a good self
seeder. Watch for the new one coming right near ya.`
}
]
},
broomrape: {
name: 'Broomrape',
nameShort: 'Br',
// No info because can't be bought
LsDetails: `B(r, t): base, providing r energy/hr.\\\\F(l, lim):
internode of length l. Provides l pennies on harvest.\\\\I(t): stem head
providing no energy. Spawns flowers for t turns.\\\\K(s): flower of size s.
Provides s pennies on harvest.\\\\O(s): fruit of size s.`,
actions: [
`Harvest returns profit as the sum of all K and F sizes
(first parameter).`
],
narrations: [
{
index: [0, 31, 32, 36, 40, 44, 47, 48, 50, 95],
0: `A curled up sleepy-head. It'll only grow if it can
establish a link with one of its favourite species.`,
31: `Rule number 4: The striga's head peeks above the
ground, seeking a vantage point.`,
32: `The base has grown a stem above it, so rule 1
triggers.\\\\At the same time, the head will grow a new flower every turn,
according to rule number 5.`,
36: `Yes, it is a weed, and I know it hurts your
daisies. But as long as it's not one of those bloody bunnies, I want you to
continue keeping it around. And keep watching it. It's not everyday that a
parasite graces your garden!`,
40: `What do I do with it after harvest?\\\\'Forgotten
by most, it carries great culinary value, and can be prepared in ways akin to
that of asparagus'?\\\\No, this pretend 'ancient cynomorion' I'd be well off
selling as diarrhoea medicine.`,
44: `The head stops growing. Flowers have also been
producing seeds for a while now.`,
47: `Rule number 2 produced a \\%\\ symbol on the base.
It signifies that the branch above it will fall away soon.`,
48: `The wind violently shook, and there went the seeds!
Can you spot where they landed?`,
50: `Most broomrape varieties would end here after
seeding. This variety, let's just say it's very much establishing a good link
with one of its favourite species!`,
95: `The wind violently shook, and there went the seeds!
Can you spot where they landed this time?`
}
]
},
arrow: {
name: '(Test) Arrow weed',
nameShort: 'Ar',
info: 'Not balanced for regular play.',
LsDetails: `The symbol A represents a rising shoot (apex), ` +
`while F represents the stem body.\\\\The Prune (scissors) action cuts every ` +
`F.\\\\The Harvest (bundle) action returns profit based on the sum of A, and ` +
`kills the colony.`,
narrations: [
{
index: [0, 1, 2, 4],
0: 'The first shoot rises.\\\\Already harvestable.',
1: 'The shoot splits in three.\\\\The stem lengthens.',
2: 'The shoots continue to divide.',
4: `What do you expect? It\'s a fractal. Arrow weed is
friend to all mathematicians.`
}
]
},
},
plantStats: `({0}) {1}\\\\—\\\\Photosynthetic rate: {2}/hr (noon)
\\\\Growth rate: {3}/hr\\\\Growth cost: {4} × {5} symbols\\\\—\\\\Sequence:`,
narrationTrack: '{0}, {1}',
noCommentary: 'No narrations.',
noLsDetails: 'No explanations.',
noActions: 'No actions available.',
permaShelf: 'Bookshelf',
permaShelfInfo: 'Access instructions and other tools',
menuToC: 'Table of Contents',
labelSource: 'Reference: ',
bookTitleFormat: '{0} ({1}/{2})',
almanacTitle: `Lemma's World of Plants`,
almanac: {
cover: {
title: 'Title Cover',
contents: `Lemma's World of Plants
An Introduction to Botany for Students
4th Edition (draft)
🌾🌻🌿
Lena Ruddles
Illustrations by Madeline H. Ruddles
Tau Publishing`
},
prep: {
title: 'Preparations!',
contents: `First, let's decide on your seeds.
I recommend any supplier that can provide consistent seeds in terms of ` +
`growth time, which would be perfect for setting up experiments. However, ` +
`such suppliers can be a bit expensive, so it's best to buy them in small ` +
`quantities in order to avoid turning a loss. ` +
`Don't worry about it, just take your time with any required calculations.
As students, you are also in charge of duties that, although quite mundane, ` +
`are more important than you think. Careful observation, a diligent plucking ` +
`of weeds, as well as a regular supply of water, are all needed to maintain ` +
`a healthy colony. (*)
Ready to sow some seeds?
(*) There are also important factors within the soil itself, such as its ` +
`elevation, the amount of sun it receives, its nutrients (prepare some good ` +
`compost for this), the moisture (make sure it has proper drainage and ` +
`doesn't clump up!).
Don't worry, I'm sure your teacher has a perfectly set up plot, though, so ` +
`go Sow Some Seeds!
Also, check your tool health: wash off the rust, clean them, sharpen very ` +
`well. Dip them in good soap and make a splash everywhere.
Don't forget to wash your best working dresses (also in good soap) and dry ` +
`them very well. You ought to look good while gardening. This is an ` +
`imperative.`
},
dayCycle: {
title: 'And the day cycles...',
contents: `Like us, plants in this world operate on a daily cycle, but their routine ` +
`is vastly different from ours. From the moment the sun comes up, they ` +
`harness the energy from its rays, converting it into sugar for its energy ` +
`storage. As the sun shines brightest at noon, the plant gets most of its ` +
`energy around this time.
By nightfall, where there is no sun to take advantage of, the plant focuses ` +
`on other routines. It converts its stored energy into the growth of its ` +
`cells. You can see that it would rise faster than it did during the day. ` +
`Some say that when it grows in the dark, it is really yearning for the sun ` +
`to come back.
Then the day cycles again...`
},
herbaceous: {
title: 'Chapter 1: Herbaceous plants',
contents: `In this book, plants will be divided into mainly two categories: herbaceous ` +
`and wooded plants.
Herbaceous plants are those that don't grow wood tissue on their stems, ` +
`such as ferns or grasses, and well, herbs! Compared to trees and wooded ` +
`shrubs, they have a relatively short life span, and are much more fragile.
Instead, they grow quickly, and rely on dispersing their seeds to survive. ` +
`Some of them can also grow their own underground food storage, such as ` +
`bulbs (onions), or tubers (potatoes).
Mind the medicinal definition of herbs, however! Not all herbaceous plants ` +
`are necessarily good for your health.`
},
calendula: {
title: 'Calendula',
contents: `Commonly called pot marigold (not to be confused with marigolds of the ` +
`genus Tagetes), calendulas are easy-going flowers known for numerous ` +
`medicinal and culinary uses. From inflammations to sunburns, scorpion ` +
`stings to hair care, you're going to see it everywhere!
The 'pot' in its name should also suggest it's uses as a cooking herb in ` +
`stews and soups too.
Life span: annual (~7 weeks)
Propagation: At life cycle's end, spread 1/3 population onto the same plot.
Here's a recipe to make some delicious calendula bread for your pleasures:`
},
basil: {
title: 'Basil',
contents: `Hailed as both queen and king of all herbs throughout the world, basil is ` +
`used as a spice in a vast number of recipes with its fragrance, accompanied ` +
`by a sweet and slightly intoxicating flavour. Even my dog loves it from ` +
`time to time.
Life span: annual (5~7 weeks)
If you plan to harvest leaves, snip off the stem before it flowers. ` +
`Otherwise, let the plant go into seed. The leaves will lose flavour, but ` +
`you will then be able to witness the fascinating communication signals ` +
`between the plant's organs. And the flowers do sell for good pennies.`
},
campion: {
title: 'Rose campion',
contents: `As a pest repellent, drought tolerant, and a great pollinator attractor, ` +
`campion is a vibrant shrub that will surely crown your garden with its ` +
`silvery sheen and bright pink blooms.
Rose campion can be used as a sedative, or for wound treatments, or wicks ` +
`for a lamp, which gave it the name of 'lamp flower'.
Life span: biennial (~32 weeks)
Propagation: Late in its life cycle, spread 1/2 population onto the same plot.
Passively provides income per stage equal to its current profit.
Occasionally, visitors and artists, generous donors, they would come and ` +
`toss a few pennies at your doorstep, as gratitude to keep the gardens ` +
`running. Well, mostly birds and bees paying for their hearty meals, but ` +
`there is the occasional human too.`
}
},
manualTitle: 'Lindenmayer Systems',
manual: {
note: {
title: `End Note`,
contents: `This manuscript was found lying beneath a burrow, inside the forest. No ` +
`written indication of whether it would ever be arranged for publish. It ` +
`does not look to be written in our time, either. There is nothing natural ` +
`about this, as if one had willingly put the manuscript down, waiting for ` +
`someone else to pick up.
I shall leave this thread to resolve in a future date. Regardless, it is ` +
`certainly intriguing that the structure of a plant can be laid down into ` +
`a single line of letters. And the rules seem to represent not the guidance ` +
`from mother nature to the plants on how they would grow, but a person's ` +
`abstractions of such. And as such, they would rather serve as the guidance ` +
`from teacher to student, on how a wrinkled seed can sprout and flourish.
On a side note, I am delighted of the fact he does not think the wrinkled ` +
`seed would look identical in form to a grown up tree. Once I finish ` +
`dissecting the manuscript, I shall contemplate handing it to Ellen, ` +
`although, frankly speaking, it is unlikely anyone would believe me.
- Lena`
},
cover: {
title: 'Title Cover',
contents: `Lindenmayer Systems Renderer
A User's Guide
2nd Edition
🐢💨❄️
propfeds
Not for sale`
},
intro: {
title: 'Lindenmayer systems: A primer',
contents: `Developed in 1968 by biologist Aristid Lindenmayer, an L-system is a formal ` +
`grammar that describes the growth of a sequence (string). It is often used ` +
`to model plants and fractal figures for its ability to construct complex ` +
`objects out of simple rules.
Every L-system starts with a sequence called the axiom. From the axiom, the ` +
`sequence grows according to a set of production rules. These rules describe ` +
`how each symbol (character) of the sequence shall be rewritten in the next ` +
`stage.
Each rule is represented in the form of:
{symbol} = {derivation(s)}
Considering a simple system with the axiom of 'b' and the rules:
b = a
a = ab,
the sequence will grow as follows:
Stage 0: b
Stage 1: a
Stage 2: ab
Stage 3: aba
Stage 4: abaab
Stage 5: abaababa`
},
context: {
title: 'Context-sensitivity',
contents: `Context-sensitive L-systems allow each symbol to interact with nearby ` +
`symbols, by letting an individual behave differently depending on its ` +
`ancestor (the symbol to its immediate left), and its child to the right ` +
`(children, if it opens up multiple branches). They are often used to model ` +
`forms of communication between a plant's organs.
A context-sensitive rule goes as follows:
{left} < {symbol} > {right} = {derivation}
The symbol will only evolve according to this rule if its ancestor bears the ` +
`same symbol as {left}, and one of its children bears the same symbol as ` +
`{right}.`
},
parametric: {
title: 'Parametric L-systems',
contents: `Beyond geometric applications, parametric L-systems allow individual ` +
`symbols to hold additional information such as its state of growth, elapsed ` +
`time, etc. They can be even peeked at in context-sensitive rules!
When there are multiple rules specified for a symbol, the chosen one will be ` +
`selected according to two criteria:
- The condition evaluates to true (anything but zero).
- The number of parameters on the symbol must match the rule. This also ` +
`includes left and right contexts.
The syntax for a parametric rule goes as follows:
{symbol}({param_0},...) : {condition*} = {derivation_0} : {probability**} ;...
Examples:
I(t) : t>0 = FI(t-1)
I(t) = K(0) (this rule will be chosen when t<=0)
Example with context:
A(x) < B(y) > C(z) : x+y+z>10 = E((x+y)/2)F(z*2)
* When omitted, the condition is assumed to always be true.
** Ranges from 0 to 1. When omitted, the chance is assumed to be 1 (100%).`
},
symbols: {
title: 'Appendix: Common organ symbols',
contents: `A: apex (stem shoot). Can photosynthesise.
B: base. Often used to receive and send signals.
I: alternate stem. May transform into a new branch, or a flower.
K: flower. Looks good.
L: leaf. Can photosynthesise.
O: fruit. Tastes good and may drop seed.
S: signal. Used to communicate between organs.`
},
turtleSymbols: {
title: 'Appendix: Geometric symbols',
contents: `F(l): moves forward and draw a line of length l. Defaults to length 1 when ` +
`omitted.
+(n), -(n): perform yaw rotation by n degrees. Defaults to the angle ` +
`specified by the L-system when omitted.
&(n), ^(n): perform pitch rotation by n degrees.
\\(n), /(n): perform roll rotation by n degrees.
|: reverses direction.
T(n): applies tropism (gravity) with a weight of n. Defaults to the tropism ` +
`specified by the L-system when omitted.
T(n, x, y, z): applies tropism towards a custom vector.
$: aligns the turtle's up vector closest to vertical.
[: pushes turtle position & rotation onto a stack.
]: pops the stack's topmost element onto the turtle.
%: cuts off the remainder of a branch.
{: initiates polygon drawing mode.
.: sets a polygon vertex.
}: ends the polygon drawing mode.`
},
},
chapters: {
intro: {
title: `Lemma's Garden`,
contents: `(The sky was dark.
You were out there picking up dirt.)
You there. Why are you picking up dirt?
Not one of my old students, are you?
(points lantern) You were not in my class,
so I can only spare you a corner... here.
Take this seed, till the soil,
then we'll start in the morning.
Tip: Tap on 'Upgrades' to acquire your first plot.`
},
sprout: {
title: `Welcome to...`,
contents: `Hum. Splendid work!
Can't even bear to look at this soil.
Luckily, this sprout won't die,
even if later you'd stub your toe over it.
But, neither would it thrive without you.
Lend it a few drops when it needs it,
then watch it grow.
Reach for my shelf if you get lost.
I'll be back in just a little,
so I can teach you about its growth.`
},
basil: {
title: `Restock at Corollary's`,
contents: `Sorry for letting you wait this long.
I have a... friend, who supplies me with seeds.
It's a bit exorbitant, but still reliable, I hope.
(scribbles)
...She didn't return until today. Apologies.
Wee bit sick of that marigold soup?
No, don't touch the other packet.
That is for my old... students!`
},
notebook: {
title: `Notebook`,
contents: `As you gather enough pennies to keep the batches
going, you decide to buy yourself a notebook.
This will help you keep track of your plantations.
Better than pretending to buy 10 seeds, at least.
Tip: Notebook is accessible at the bookshelf.`
},
flood: {
title: `Inclement`,
contents: `'Maximum statements?'
I hear you.
We had made too many complaints this season,
and now the weather answers with its toll.
The floodplains hasn't been doing very well anyway.
It's too likely to get logged this season...
Don't worry.
Try not to let your bushes spread too much,
this is unlike any regular flood.
Note: Your plots and settings have been wiped.
I'm sorry. I can't find a way around this.
Just deal with it as a 'gameplay mechanic'.
- propfeds`
},
nepo: {
title: `Dear Ellen of Tau Publishing,`,
contents: `You are going easy on my student.
What were you thinking? Super-exponential revenues?
Either you get accused of nepotism, or I of bribery.
And suddenly the next fortnight, she'd be cornering
the market.
I do not condone you letting her abuse the economy.
Not without giving something back for the community.
I need to do something.
- Lena`
}
},
achievements: {
debt: {
title: 'Studenthood',
desc: 'Reach negative pennies.'
},
immortal: {
title: 'Collective Experience',
desc: 'Mess around for 200 years.'
}
}
}
};
/**
* Returns a localised string.
* @param {string} name the internal name of the string.
* @returns {any} the string or folder.
*/
let getLoc = (name, lang = MENU_LANG) => {
if (lang in LOC_STRINGS && name in LOC_STRINGS[lang])
return LOC_STRINGS[lang][name];
if (name in LOC_STRINGS.en)
return LOC_STRINGS.en[name];
return `String missing: ${lang}.${name}`;
};
/* Image size reference
Size 20:
270x480
Size 24:
360x640
450x800
540x960
Size 36:
720x1280
Size 48:
1080x1920
*/
let getImageSize = (width) => {
if (width >= 1080)
return 48;
if (width >= 720)
return 36;
if (width >= 360)
return 24;
return 20;
};
let getBtnSize = (width) => {
if (width >= 1080)
return 96;
if (width >= 720)
return 72;
if (width >= 360)
return 48;
return 40;
};
let getMediumBtnSize = (width) => {
if (width >= 1080)
return 88;
if (width >= 720)
return 66;
if (width >= 360)
return 44;
return 36;
};
let getSmallBtnSize = (width) => {
if (width >= 1080)
return 80;
if (width >= 720)
return 60;
if (width >= 360)
return 40;
return 32;
};
let getProgBarSize = (width) => {
return getSmallBtnSize(width) / 2;
};
let getNavColumnDefs = (width) => {
// if(width >= 1080)
// return 80;
if (width >= 540) // 25%
return ['30*', '90*', '30*', '50*'];
if (width >= 450) // 30%
return ['28*', '84*', '28*', '60*'];
// 35%?
return ['25*', '75*', '25*', '75*'];
};
let getActBarColumnDefs = (width) => {
// if(width >= 1080)
// return 80;
if (width >= 540) // 25%
return ['75*', '25*'];
if (width >= 450) // 30%
return ['70*', '30*'];
// 35%?
return ['65*', '35*'];
};
/**
* Returns the index of the last element smaller than or equal to target.
* Array must be sorted ascending.
* @param {number[]} arr the array being searched.
* @param {number} target the value to search for.
* @returns {number}
*/
let binarySearchLast = (arr, target) => {
let l = 0;
let r = arr.length - 1;
while (l < r) {
let m = l + Math.ceil((r - l) / 2);
if (arr[m] <= target)
l = m;
else
r = m - 1;
}
return l;
};
/**
* Returns the index of the first element larger than target. Array must be
* sorted ascending.
* @param {number[]} arr the array being searched.
* @param {number} target the value to search for.
* @returns {number}
*/
let binarySearchNext = (arr, target) => {
let l = 0;
let r = arr.length - 1;
while (l < r) {
let m = l + Math.floor((r - l) / 2);
if (arr[m] <= target)
l = m + 1;
else
r = m;
}
return l;
};
/**
* Restricts a number into the specified range.
*/
let saturate = (x, min, max) => x > max ? max : x < min ? min : x;
/**
* Converts a number into a Unicode compliant subscripted string.
*/
let getSubscript = (x) => {
let xStr = x.toString();
let result = '';
for (let i = 0; i < xStr.length; ++i) {
result += String.fromCharCode(0x2080 + parseInt(xStr[i]));
}
return result;
};
/**
* Returns a string of a fixed decimal number, with a fairly uniform width.
* @param {number} x the number.
* @returns {string}
*/
let getCoordString = (x) => x.toFixed(x >= -0.01 ?
(x <= 9.999 ? 3 : (x <= 99.99 ? 2 : 1)) :
(x < -9.99 ? (x < -99.9 ? 0 : 1) : 2));
/**
* Returns a C-style formatted string from a BigNumber. Note that it can only
* handle up to the Number limit.
* @param {BigNumber} x the number.
* @returns {string}
*/
let getCString = (x) => parseFloat(x.toString(6)).toString();
/**
* Purge a string array of empty lines.
* @param {string[]} arr the array.
* @returns {string[]}
*/
let purgeEmpty = (arr) => {
let result = [];
let idx = 0;
for (let i = 0; i < arr.length; ++i) {
// I hope this deep-copies
if (arr[i]) {
result[idx] = arr[i];
++idx;
}
}
return result;
};
let isColonyVisible = (colony) => colony.sequence.length > 1 ||
trueSight.level > 0;
let getLeechRate = (colony) => colony.params[0][0];
const yearStartLookup = [0];
const dandelionSchedule = [];
const broomrapeSchedule = [];
const hopleekSchedule = [];
for (let i = 1; i <= 400; ++i) {
let leap = !(i % 4) && (!!(i % 100) || !(i % 400));
let offset = leap ? 366 : 365;
yearStartLookup[i] = yearStartLookup[i - 1] + offset;
if (leap)
hopleekSchedule.push(yearStartLookup[i - 1] + 60);
let b = i % 4;