Feast 2.0!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

578 lines
14 KiB

  1. import { LogEntry } from '@/game/interface'
  2. export enum POV {First, Second, Third}
  3. export type SoloLine<T> = (user: T) => LogEntry
  4. export type SoloLineArgs<T, V> = (user: T, args: V) => LogEntry
  5. export type PairLine<T> = (user: T, target: T) => LogEntry
  6. export type PairLineArgs<T, V> = (user: T, target: T, args: V) => LogEntry
  7. export type GroupLine<T> = (user: T, targets: Array<T>) => LogEntry
  8. enum NounKind {
  9. Specific,
  10. Nonspecific,
  11. All
  12. }
  13. enum VowelSound {
  14. Default,
  15. Vowel,
  16. NonVowel
  17. }
  18. enum VerbKind {
  19. Root,
  20. Singular,
  21. Present,
  22. Past,
  23. PastParticiple
  24. }
  25. export interface Pluralizable {
  26. isPlural: boolean;
  27. }
  28. interface WordOptions {
  29. plural: boolean;
  30. capital: boolean;
  31. allCaps: boolean;
  32. proper: boolean;
  33. nounKind: NounKind;
  34. verbKind: VerbKind;
  35. vowel: VowelSound;
  36. count: boolean;
  37. possessive: boolean;
  38. objective: boolean;
  39. perspective: POV;
  40. }
  41. const emptyConfig: WordOptions = {
  42. allCaps: false,
  43. capital: false,
  44. count: false,
  45. nounKind: NounKind.Specific,
  46. verbKind: VerbKind.Root,
  47. plural: false,
  48. proper: false,
  49. vowel: VowelSound.Default,
  50. possessive: false,
  51. objective: false,
  52. perspective: POV.Third
  53. }
  54. export type TextLike = { toString: () => string }
  55. // updates as needed
  56. export class LiveText<T> {
  57. constructor (private contents: T, private run: (thing: T) => TextLike) {
  58. }
  59. toString (): string {
  60. return this.run(this.contents).toString()
  61. }
  62. }
  63. export class DynText {
  64. private parts: Array<TextLike>
  65. constructor (...parts: TextLike[]) {
  66. this.parts = parts
  67. }
  68. toString (): string {
  69. return (this.parts.map(part => part.toString())).join('')
  70. }
  71. }
  72. export abstract class Word {
  73. constructor (public opt: WordOptions = emptyConfig) {
  74. }
  75. abstract configure (opts: WordOptions): Word;
  76. abstract toString (): string;
  77. // These functions are pure; they don't mutate the original object.
  78. // This is necessary to avoid causing chaos.
  79. get allCaps (): this {
  80. const opts: WordOptions = Object.assign({}, this.opt)
  81. opts.allCaps = true
  82. return this.configure(opts) as this
  83. }
  84. get capital (): this {
  85. const opts: WordOptions = Object.assign({}, this.opt)
  86. opts.capital = true
  87. return this.configure(opts) as this
  88. }
  89. get plural (): this {
  90. const opts: WordOptions = Object.assign({}, this.opt)
  91. opts.plural = true
  92. return this.configure(opts) as this
  93. }
  94. get proper (): this {
  95. const opts: WordOptions = Object.assign({}, this.opt)
  96. opts.proper = true
  97. return this.configure(opts) as this
  98. }
  99. get improper (): this {
  100. const opts: WordOptions = Object.assign({}, this.opt)
  101. opts.proper = false
  102. return this.configure(opts) as this
  103. }
  104. get specific (): this {
  105. const opts: WordOptions = Object.assign({}, this.opt)
  106. opts.nounKind = NounKind.Specific
  107. return this.configure(opts) as this
  108. }
  109. get nonspecific (): this {
  110. const opts: WordOptions = Object.assign({}, this.opt)
  111. opts.nounKind = NounKind.Nonspecific
  112. return this.configure(opts) as this
  113. }
  114. get all (): this {
  115. const opts: WordOptions = Object.assign({}, this.opt)
  116. opts.nounKind = NounKind.All
  117. return this.configure(opts) as this
  118. }
  119. get uncountable (): this {
  120. const opts: WordOptions = Object.assign({}, this.opt)
  121. opts.count = false
  122. return this.configure(opts) as this
  123. }
  124. get root (): this {
  125. const opts: WordOptions = Object.assign({}, this.opt)
  126. opts.verbKind = VerbKind.Root
  127. return this.configure(opts) as this
  128. }
  129. get singular (): this {
  130. const opts: WordOptions = Object.assign({}, this.opt)
  131. opts.verbKind = VerbKind.Singular
  132. return this.configure(opts) as this
  133. }
  134. get present (): this {
  135. const opts: WordOptions = Object.assign({}, this.opt)
  136. opts.verbKind = VerbKind.Present
  137. return this.configure(opts) as this
  138. }
  139. get past (): this {
  140. const opts: WordOptions = Object.assign({}, this.opt)
  141. opts.verbKind = VerbKind.Past
  142. return this.configure(opts) as this
  143. }
  144. get pastParticiple (): this {
  145. const opts: WordOptions = Object.assign({}, this.opt)
  146. opts.verbKind = VerbKind.PastParticiple
  147. return this.configure(opts) as this
  148. }
  149. get possessive (): this {
  150. const opts: WordOptions = Object.assign({}, this.opt)
  151. opts.possessive = true
  152. return this.configure(opts) as this
  153. }
  154. get objective (): this {
  155. const opts: WordOptions = Object.assign({}, this.opt)
  156. opts.objective = true
  157. return this.configure(opts) as this
  158. }
  159. get pov1 (): this {
  160. const opts: WordOptions = Object.assign({}, this.opt)
  161. opts.perspective = POV.First
  162. return this.configure(opts) as this
  163. }
  164. get pov2 (): this {
  165. const opts: WordOptions = Object.assign({}, this.opt)
  166. opts.perspective = POV.Second
  167. return this.configure(opts) as this
  168. }
  169. get pov3 (): this {
  170. const opts: WordOptions = Object.assign({}, this.opt)
  171. opts.perspective = POV.Third
  172. return this.configure(opts) as this
  173. }
  174. }
  175. export class OptionalWord extends Word {
  176. constructor (public word: Word, private chance = 0.5, private suffix = " ") {
  177. super(word.opt)
  178. }
  179. configure (opts: WordOptions): Word {
  180. return new OptionalWord(this.word.configure(opts), this.chance, this.suffix)
  181. }
  182. toString (): string {
  183. if (Math.random() < this.chance) {
  184. return this.word + this.suffix
  185. } else {
  186. return ""
  187. }
  188. }
  189. }
  190. export class RandomWord extends Word {
  191. private history: { last: number }
  192. constructor (public choices: Array<Word>, opt: WordOptions = emptyConfig, history: { last: number } = { last: -1 }) {
  193. super(opt)
  194. this.history = history
  195. }
  196. configure (opts: WordOptions): Word {
  197. return new RandomWord(this.choices, opts, this.history)
  198. }
  199. toString (): string {
  200. let choice
  201. do {
  202. choice = Math.floor(Math.random() * this.choices.length)
  203. } while (choice === this.history.last && this.choices.length > 1)
  204. this.history.last = choice
  205. return this.choices[choice].configure(this.opt).toString()
  206. }
  207. }
  208. export class Noun extends Word {
  209. constructor (protected singularNoun: string, protected pluralNoun: string|null = null, protected possessiveNoun: string|null = null, protected options: WordOptions = emptyConfig) {
  210. super(options)
  211. }
  212. configure (opts: WordOptions): Word {
  213. return new Noun(this.singularNoun, this.pluralNoun, this.possessiveNoun, opts)
  214. }
  215. toString (): string {
  216. let result: string
  217. // TODO: plural possessive nouns?
  218. if (this.options.possessive) {
  219. if (this.possessiveNoun === null) {
  220. result = this.singularNoun + "'s"
  221. } else {
  222. result = this.possessiveNoun
  223. }
  224. } else if (this.options.plural) {
  225. if (this.pluralNoun === null) {
  226. result = this.singularNoun
  227. } else {
  228. result = (this.pluralNoun as string)
  229. }
  230. } else {
  231. result = this.singularNoun
  232. }
  233. if (!this.options.proper) {
  234. if (this.options.nounKind === NounKind.Nonspecific && this.options.count) {
  235. if (this.options.plural) {
  236. result = 'some ' + result
  237. } else {
  238. if (this.options.vowel === VowelSound.Default) {
  239. if ('aeiouAEIOU'.indexOf(result.slice(0, 1)) >= 0) {
  240. result = 'an ' + result
  241. } else {
  242. result = 'a ' + result
  243. }
  244. } else if (this.options.vowel === VowelSound.Vowel) {
  245. result = 'an ' + result
  246. } else if (this.options.vowel === VowelSound.NonVowel) {
  247. result = 'a ' + result
  248. }
  249. }
  250. } else if (this.options.nounKind === NounKind.Specific) {
  251. result = 'the ' + result
  252. }
  253. }
  254. if (this.options.allCaps) {
  255. result = result.toUpperCase()
  256. } else if (this.options.capital) {
  257. result = result.slice(0, 1).toUpperCase() + result.slice(1)
  258. }
  259. return result
  260. }
  261. conjugate (verb: Word): Word {
  262. if (this.opt.plural) {
  263. return verb.root
  264. } else {
  265. return verb.singular
  266. }
  267. }
  268. }
  269. export class ImproperNoun extends Noun {
  270. constructor (singularNoun: string, pluralNoun: string = singularNoun) {
  271. super(singularNoun, pluralNoun, null, { plural: false, allCaps: false, capital: false, proper: false, nounKind: NounKind.Specific, verbKind: VerbKind.Root, vowel: VowelSound.Default, count: true, possessive: false, objective: false, perspective: POV.Third })
  272. }
  273. }
  274. export class ProperNoun extends Noun {
  275. constructor (singularNoun: string) {
  276. super(singularNoun, null, null, { plural: false, allCaps: false, capital: false, proper: true, nounKind: NounKind.Specific, verbKind: VerbKind.Root, vowel: VowelSound.Default, count: true, possessive: false, objective: false, perspective: POV.Third })
  277. }
  278. }
  279. export class Adjective extends Word {
  280. constructor (private adjective: string, opt: WordOptions = emptyConfig) {
  281. super(opt)
  282. }
  283. configure (opts: WordOptions): Word {
  284. return new Adjective(this.adjective, opts)
  285. }
  286. // TODO caps et al.
  287. toString (): string {
  288. return this.adjective
  289. }
  290. }
  291. export class Adverb extends Word {
  292. constructor (private adverb: string, opt: WordOptions = emptyConfig) {
  293. super(opt)
  294. }
  295. configure (opt: WordOptions): Word {
  296. return new Adverb(this.adverb, opt)
  297. }
  298. toString (): string {
  299. return this.adverb
  300. }
  301. }
  302. export class Verb extends Word {
  303. constructor (private _root: string, private _singular: string = _root + "s", private _present: string = _root + "ing", private _past: string = _root + "ed", private _pastParticiple: string = _past, public opt: WordOptions = emptyConfig) {
  304. super(opt)
  305. }
  306. configure (opts: WordOptions): Word {
  307. return new Verb(
  308. this._root,
  309. this._singular,
  310. this._present,
  311. this._past,
  312. this._pastParticiple,
  313. opts
  314. )
  315. }
  316. toString (): string {
  317. let choice: string
  318. switch (this.opt.verbKind) {
  319. case VerbKind.Root: choice = this._root; break
  320. case VerbKind.Singular: choice = this._singular; break
  321. case VerbKind.Present: choice = this._present; break
  322. case VerbKind.Past: choice = this._past; break
  323. case VerbKind.PastParticiple: choice = this._pastParticiple; break
  324. }
  325. if (this.opt.allCaps) {
  326. choice = choice.toUpperCase()
  327. } else if (this.opt.capital) {
  328. choice = choice.slice(0, 1).toUpperCase() + choice.slice(1)
  329. }
  330. return choice
  331. }
  332. }
  333. export class Preposition extends Word {
  334. constructor (private word: string, public opt: WordOptions = emptyConfig) {
  335. super(opt)
  336. }
  337. configure (opts: WordOptions): Word {
  338. return new Preposition(this.word, opts)
  339. }
  340. toString (): string {
  341. if (this.opt.capital) {
  342. return this.word.slice(0, 1).toUpperCase() + this.word.slice(1)
  343. } else {
  344. return this.word
  345. }
  346. }
  347. }
  348. // this one is obnoxious
  349. export class ToBe extends Word {
  350. constructor (protected opts: WordOptions = emptyConfig) {
  351. super(opts)
  352. }
  353. configure (opts: WordOptions): Word {
  354. return new ToBe(opts)
  355. }
  356. toString (): string {
  357. let choice
  358. if (this.opts.plural) {
  359. choice = 'are'
  360. }
  361. switch (this.opts.perspective) {
  362. case POV.First: choice = 'am'; break
  363. case POV.Second: choice = 'are'; break
  364. case POV.Third: choice = 'is'; break
  365. }
  366. if (this.opt.allCaps) {
  367. choice = choice.toUpperCase()
  368. } else if (this.opt.capital) {
  369. choice = choice.slice(0, 1).toUpperCase() + choice.slice(1)
  370. }
  371. return choice
  372. }
  373. }
  374. interface PronounDict {
  375. subjective: string;
  376. objective: string;
  377. possessive: string;
  378. reflexive: string;
  379. }
  380. export class Pronoun implements Pluralizable {
  381. constructor (private pronouns: PronounDict, private capitalize: boolean = false, public isPlural: boolean = false) {
  382. }
  383. get capital (): Pronoun {
  384. return new Pronoun(this.pronouns, true)
  385. }
  386. get subjective (): string {
  387. return this.caps(this.pronouns.subjective)
  388. }
  389. get objective (): string {
  390. return this.caps(this.pronouns.objective)
  391. }
  392. get possessive (): string {
  393. return this.caps(this.pronouns.possessive)
  394. }
  395. get reflexive (): string {
  396. return this.caps(this.pronouns.reflexive)
  397. }
  398. conjugate (verb: Word): Word {
  399. if (this.isPlural) {
  400. return verb.root
  401. } else {
  402. return verb.singular
  403. }
  404. }
  405. private caps (input: string): string {
  406. if (this.capitalize) {
  407. return input.slice(0, 1).toUpperCase() + input.slice(1)
  408. } else {
  409. return input
  410. }
  411. }
  412. }
  413. export const MalePronouns = new Pronoun({
  414. subjective: 'he',
  415. objective: 'him',
  416. possessive: 'his',
  417. reflexive: 'himself'
  418. })
  419. export const FemalePronouns = new Pronoun({
  420. subjective: 'she',
  421. objective: 'her',
  422. possessive: 'her',
  423. reflexive: 'herself'
  424. })
  425. export const TheyPronouns = new Pronoun({
  426. subjective: 'they',
  427. objective: 'them',
  428. possessive: 'their',
  429. reflexive: 'themself'
  430. }, false, true)
  431. export const TheyPluralPronouns = new Pronoun({
  432. subjective: 'they',
  433. objective: 'them',
  434. possessive: 'their',
  435. reflexive: 'themselves'
  436. }, false, true)
  437. export const ObjectPronouns = new Pronoun({
  438. subjective: 'it',
  439. objective: 'it',
  440. possessive: 'its',
  441. reflexive: 'itself'
  442. })
  443. export const SecondPersonPronouns = new Pronoun({
  444. subjective: 'you',
  445. objective: 'you',
  446. possessive: 'your',
  447. reflexive: 'yourself'
  448. })
  449. export const FirstPersonPronouns = new Pronoun({
  450. subjective: 'I',
  451. objective: 'me',
  452. possessive: 'my',
  453. reflexive: 'myself'
  454. })
  455. export class PronounAsNoun extends Noun {
  456. constructor (private pronouns: Pronoun, opt: WordOptions = emptyConfig) {
  457. super(pronouns.subjective, pronouns.subjective, pronouns.possessive, opt)
  458. this.options.nounKind = NounKind.All
  459. this.options.plural = true
  460. }
  461. configure (opts: WordOptions): Word {
  462. return new PronounAsNoun(this.pronouns, opts)
  463. }
  464. toString (): string {
  465. if (this.options.objective) {
  466. return new Noun(this.pronouns.objective, this.pronouns.objective, this.pronouns.possessive, this.options).toString()
  467. } else {
  468. return super.toString()
  469. }
  470. }
  471. conjugate (verb: Word): Word {
  472. if (this.pronouns === FirstPersonPronouns) {
  473. return super.conjugate(verb.pov1)
  474. } else if (this.pronouns === SecondPersonPronouns) {
  475. return super.conjugate(verb.pov2)
  476. } else {
  477. return super.conjugate(verb.pov3)
  478. }
  479. }
  480. }