import { LogEntry } from '@/game/interface' export enum POV {First, Second, Third} export type SoloLine = (user: T) => LogEntry export type SoloLineArgs = (user: T, args: V) => LogEntry export type PairLine = (user: T, target: T) => LogEntry export type PairLineArgs = (user: T, target: T, args: V) => LogEntry export type GroupLine = (user: T, targets: Array) => LogEntry enum NounKind { Specific, Nonspecific, All } enum VowelSound { Default, Vowel, NonVowel } enum VerbKind { Root, Singular, Present, Past, PastParticiple } export interface Pluralizable { isPlural: boolean; } interface WordOptions { plural: boolean; capital: boolean; allCaps: boolean; proper: boolean; nounKind: NounKind; verbKind: VerbKind; vowel: VowelSound; count: boolean; possessive: boolean; objective: boolean; perspective: POV; } const emptyConfig: WordOptions = { allCaps: false, capital: false, count: false, nounKind: NounKind.Specific, verbKind: VerbKind.Root, plural: false, proper: false, vowel: VowelSound.Default, possessive: false, objective: false, perspective: POV.Third } export type TextLike = { toString: () => string } // updates as needed export class LiveText { constructor (private contents: T, private run: (thing: T) => TextLike) { } toString (): string { return this.run(this.contents).toString() } } export class DynText { private parts: Array constructor (...parts: TextLike[]) { this.parts = parts } toString (): string { return (this.parts.map(part => part.toString())).join('') } } export abstract class Word { constructor (public opt: WordOptions = emptyConfig) { } abstract configure (opts: WordOptions): Word; abstract toString (): string; // These functions are pure; they don't mutate the original object. // This is necessary to avoid causing chaos. get allCaps (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.allCaps = true return this.configure(opts) as this } get capital (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.capital = true return this.configure(opts) as this } get plural (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.plural = true return this.configure(opts) as this } get proper (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.proper = true return this.configure(opts) as this } get improper (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.proper = false return this.configure(opts) as this } get specific (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.nounKind = NounKind.Specific return this.configure(opts) as this } get nonspecific (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.nounKind = NounKind.Nonspecific return this.configure(opts) as this } get all (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.nounKind = NounKind.All return this.configure(opts) as this } get uncountable (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.count = false return this.configure(opts) as this } get root (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.verbKind = VerbKind.Root return this.configure(opts) as this } get singular (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.verbKind = VerbKind.Singular return this.configure(opts) as this } get present (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.verbKind = VerbKind.Present return this.configure(opts) as this } get past (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.verbKind = VerbKind.Past return this.configure(opts) as this } get pastParticiple (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.verbKind = VerbKind.PastParticiple return this.configure(opts) as this } get possessive (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.possessive = true return this.configure(opts) as this } get objective (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.objective = true return this.configure(opts) as this } get pov1 (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.perspective = POV.First return this.configure(opts) as this } get pov2 (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.perspective = POV.Second return this.configure(opts) as this } get pov3 (): this { const opts: WordOptions = Object.assign({}, this.opt) opts.perspective = POV.Third return this.configure(opts) as this } } export class OptionalWord extends Word { constructor (public word: Word, private chance = 0.5, private suffix = " ") { super(word.opt) } configure (opts: WordOptions): Word { return new OptionalWord(this.word.configure(opts), this.chance, this.suffix) } toString (): string { if (Math.random() < this.chance) { return this.word + this.suffix } else { return "" } } } export class RandomWord extends Word { private history: { last: number } constructor (public choices: Array, opt: WordOptions = emptyConfig, history: { last: number } = { last: -1 }) { super(opt) this.history = history } configure (opts: WordOptions): Word { return new RandomWord(this.choices, opts, this.history) } toString (): string { let choice do { choice = Math.floor(Math.random() * this.choices.length) } while (choice === this.history.last && this.choices.length > 1) this.history.last = choice return this.choices[choice].configure(this.opt).toString() } } export class Noun extends Word { constructor (protected singularNoun: string, protected pluralNoun: string|null = null, protected possessiveNoun: string|null = null, protected options: WordOptions = emptyConfig) { super(options) } configure (opts: WordOptions): Word { return new Noun(this.singularNoun, this.pluralNoun, this.possessiveNoun, opts) } toString (): string { let result: string // TODO: plural possessive nouns? if (this.options.possessive) { if (this.possessiveNoun === null) { result = this.singularNoun + "'s" } else { result = this.possessiveNoun } } else if (this.options.plural) { if (this.pluralNoun === null) { result = this.singularNoun } else { result = (this.pluralNoun as string) } } else { result = this.singularNoun } if (!this.options.proper) { if (this.options.nounKind === NounKind.Nonspecific && this.options.count) { if (this.options.plural) { result = 'some ' + result } else { if (this.options.vowel === VowelSound.Default) { if ('aeiouAEIOU'.indexOf(result.slice(0, 1)) >= 0) { result = 'an ' + result } else { result = 'a ' + result } } else if (this.options.vowel === VowelSound.Vowel) { result = 'an ' + result } else if (this.options.vowel === VowelSound.NonVowel) { result = 'a ' + result } } } else if (this.options.nounKind === NounKind.Specific) { result = 'the ' + result } } if (this.options.allCaps) { result = result.toUpperCase() } else if (this.options.capital) { result = result.slice(0, 1).toUpperCase() + result.slice(1) } return result } conjugate (verb: Word): Word { if (this.opt.plural) { return verb.root } else { return verb.singular } } } export class ImproperNoun extends Noun { constructor (singularNoun: string, pluralNoun: string = singularNoun) { 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 }) } } export class ProperNoun extends Noun { constructor (singularNoun: string) { 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 }) } } export class Adjective extends Word { constructor (private adjective: string, opt: WordOptions = emptyConfig) { super(opt) } configure (opts: WordOptions): Word { return new Adjective(this.adjective, opts) } // TODO caps et al. toString (): string { return this.adjective } } export class Adverb extends Word { constructor (private adverb: string, opt: WordOptions = emptyConfig) { super(opt) } configure (opt: WordOptions): Word { return new Adverb(this.adverb, opt) } toString (): string { return this.adverb } } export class Verb extends Word { 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) { super(opt) } configure (opts: WordOptions): Word { return new Verb( this._root, this._singular, this._present, this._past, this._pastParticiple, opts ) } toString (): string { let choice: string switch (this.opt.verbKind) { case VerbKind.Root: choice = this._root; break case VerbKind.Singular: choice = this._singular; break case VerbKind.Present: choice = this._present; break case VerbKind.Past: choice = this._past; break case VerbKind.PastParticiple: choice = this._pastParticiple; break } if (this.opt.allCaps) { choice = choice.toUpperCase() } else if (this.opt.capital) { choice = choice.slice(0, 1).toUpperCase() + choice.slice(1) } return choice } } export class Preposition extends Word { constructor (private word: string, public opt: WordOptions = emptyConfig) { super(opt) } configure (opts: WordOptions): Word { return new Preposition(this.word, opts) } toString (): string { if (this.opt.capital) { return this.word.slice(0, 1).toUpperCase() + this.word.slice(1) } else { return this.word } } } // this one is obnoxious export class ToBe extends Word { constructor (protected opts: WordOptions = emptyConfig) { super(opts) } configure (opts: WordOptions): Word { return new ToBe(opts) } toString (): string { let choice if (this.opts.plural) { choice = 'are' } switch (this.opts.perspective) { case POV.First: choice = 'am'; break case POV.Second: choice = 'are'; break case POV.Third: choice = 'is'; break } if (this.opt.allCaps) { choice = choice.toUpperCase() } else if (this.opt.capital) { choice = choice.slice(0, 1).toUpperCase() + choice.slice(1) } return choice } } interface PronounDict { subjective: string; objective: string; possessive: string; reflexive: string; } export class Pronoun implements Pluralizable { constructor (private pronouns: PronounDict, private capitalize: boolean = false, public isPlural: boolean = false) { } get capital (): Pronoun { return new Pronoun(this.pronouns, true) } get subjective (): string { return this.caps(this.pronouns.subjective) } get objective (): string { return this.caps(this.pronouns.objective) } get possessive (): string { return this.caps(this.pronouns.possessive) } get reflexive (): string { return this.caps(this.pronouns.reflexive) } conjugate (verb: Word): Word { if (this.isPlural) { return verb.root } else { return verb.singular } } private caps (input: string): string { if (this.capitalize) { return input.slice(0, 1).toUpperCase() + input.slice(1) } else { return input } } } export const MalePronouns = new Pronoun({ subjective: 'he', objective: 'him', possessive: 'his', reflexive: 'himself' }) export const FemalePronouns = new Pronoun({ subjective: 'she', objective: 'her', possessive: 'her', reflexive: 'herself' }) export const TheyPronouns = new Pronoun({ subjective: 'they', objective: 'them', possessive: 'their', reflexive: 'themself' }, false, true) export const TheyPluralPronouns = new Pronoun({ subjective: 'they', objective: 'them', possessive: 'their', reflexive: 'themselves' }, false, true) export const ObjectPronouns = new Pronoun({ subjective: 'it', objective: 'it', possessive: 'its', reflexive: 'itself' }) export const SecondPersonPronouns = new Pronoun({ subjective: 'you', objective: 'you', possessive: 'your', reflexive: 'yourself' }) export const FirstPersonPronouns = new Pronoun({ subjective: 'I', objective: 'me', possessive: 'my', reflexive: 'myself' }) export class PronounAsNoun extends Noun { constructor (private pronouns: Pronoun, opt: WordOptions = emptyConfig) { super(pronouns.subjective, pronouns.subjective, pronouns.possessive, opt) this.options.nounKind = NounKind.All this.options.plural = true } configure (opts: WordOptions): Word { return new PronounAsNoun(this.pronouns, opts) } toString (): string { if (this.options.objective) { return new Noun(this.pronouns.objective, this.pronouns.objective, this.pronouns.possessive, this.options).toString() } else { return super.toString() } } conjugate (verb: Word): Word { if (this.pronouns === FirstPersonPronouns) { return super.conjugate(verb.pov1) } else if (this.pronouns === SecondPersonPronouns) { return super.conjugate(verb.pov2) } else { return super.conjugate(verb.pov3) } } }