summaryrefslogtreecommitdiffstats
path: root/rules.mjs
diff options
context:
space:
mode:
authorBrian Cully <bjc@kublai.com>2021-01-03 18:34:19 -0500
committerBrian Cully <bjc@kublai.com>2021-02-06 10:39:32 -0500
commite92ad9f4b19a0670a80cdd293970c3a08c27a8b4 (patch)
treee8659dcdbf5f7ba3c55a118909d82dd8f0d0bcbd /rules.mjs
downloadmolsim-e92ad9f4b19a0670a80cdd293970c3a08c27a8b4.tar.gz
molsim-e92ad9f4b19a0670a80cdd293970c3a08c27a8b4.zip
Initial commit.
Diffstat (limited to 'rules.mjs')
-rw-r--r--rules.mjs361
1 files changed, 361 insertions, 0 deletions
diff --git a/rules.mjs b/rules.mjs
new file mode 100644
index 0000000..cc1363b
--- /dev/null
+++ b/rules.mjs
@@ -0,0 +1,361 @@
+import { randomItem, ordinalSuffix } from './utils.mjs'
+import Genome from './genome.mjs'
+import Nucleotide from './nucleotide.mjs'
+import Die from './die.mjs'
+import GenomeList from './genome-list.mjs'
+import NucleotideSelector from './nucleotide-selector.mjs'
+import Error from './error.mjs'
+
+class CloneNucleotide {
+ id = 'clone-nucleotide'
+
+ constructor(rules) {
+ this.rules = rules
+
+ this._boundClickHandler = this.clickHandler.bind(this)
+ }
+
+ enter() {
+ this.rules.cloneButton.addEventListener('click',
+ this._boundClickHandler)
+ this.rules.cloneButton.disabled = false
+ }
+
+ exit() {
+ this.rules.cloneButton.removeEventListener('click',
+ this._boundClickHandler)
+ this.rules.cloneButton.disabled = true
+ }
+
+ clickHandler(evt) {
+ const genome = this.rules.currentGenome.clone()
+ this.rules.genomeList.push(genome)
+ this.rules.next(new RollForNucleotide(this.rules))
+ }
+}
+
+class RollForNucleotide {
+ id = 'roll-for-nucleotide'
+
+ constructor(rules) {
+ this.rules = rules
+ }
+
+ enter() {
+ this.rules.die.value = '--'
+ this.rules.die.onChanged = this.handleDieRoll.bind(this)
+ this.rules.die.enable()
+ }
+
+ exit() {
+ this.rules.die.disable()
+ this.rules.die.onChanged = undefined
+ }
+
+ handleDieRoll() {
+ if (this.rules.die.value > Genome.size) {
+ this.rules.iterations--
+ if (this.rules.isLastIteration) {
+ this.rules.next(new DoNothing(this.rules))
+ } else {
+ this.rules.next(new CloneNucleotide(this.rules))
+ }
+ } else {
+ this.rules.next(new NucleotideSelect(this.rules))
+ }
+ }
+}
+
+class NucleotideSelect {
+ id = 'nucleotide-select'
+
+ constructor(rules) {
+ this.rules = rules
+ }
+
+ enter() {
+ this.want = this.rules.die.value
+ this.rules.instructions.querySelector('#select-number').innerHTML =
+ `${this.want}<sup>${ordinalSuffix(this.want)}</sup>`
+
+ this.rules.currentGenome.onSelectionChanged =
+ this.handleSelectionChanged.bind(this)
+ this.rules.currentGenome.unlock()
+ }
+
+ exit() {
+ this.rules.currentGenome.lock()
+ this.rules.currentGenome.onSelectionChanged = undefined;
+ }
+
+ handleSelectionChanged(nucleotide, i) {
+ i++;
+ if (i != this.rules.die.value) {
+ this.rules.error.innerHTML =
+ `You selected the ${i}<sup>${ordinalSuffix(i)}</sup> nucleotide. Please select the ${this.want}<sup>${ordinalSuffix(this.want)}</sup> one.`
+ this.rules.next(new ShowError(this.rules, this))
+ return
+ }
+ this.rules.next(new RollForMutation(this.rules))
+ }
+}
+
+class RollForMutation {
+ id = 'roll-for-mutation'
+
+ constructor(rules) {
+ this.rules = rules
+ }
+
+ enter() {
+ this.rules.die.value = '--'
+ this.rules.die.onChanged = this.handleDieRoll.bind(this)
+ this.rules.die.enable()
+ }
+
+ exit() {
+ this.rules.die.disable()
+ this.rules.die.onChanged = undefined
+ }
+
+ handleDieRoll() {
+ this.rules.next(new PerformMutation(this.rules))
+ }
+}
+
+class PerformMutation {
+ id = 'perform-mutation'
+
+ constructor(rules) {
+ this.rules = rules
+ }
+
+ enter() {
+ const selector = this.rules.nucleotideSelector
+ selector.onItemSelected = this.handleItemSelected.bind(this)
+ selector.attach(this.selectedNucleotide)
+ }
+
+ exit() {
+ this.rules.nucleotideSelector.detach()
+ }
+
+ validMutation(from, to) {
+ return to == this.expectedMutation[from]
+ }
+
+ get expectedMutation() {
+ if (this.rules.die.value <= 14) {
+ return Nucleotide.translation
+ } else if (this.rules.die.value <= 17) {
+ return Nucleotide.complementingTransversion
+ } else {
+ return Nucleotide.defaultTransversion
+ }
+ }
+
+ get selectedNucleotide() {
+ return this.rules.currentGenome.selectedNucleotide
+ }
+
+ get errorTranslationHTML() {
+ return `Select the base that corresponds to a <em>translation</em> of ${this.selectedNucleotide.value}.`
+ }
+ get errorComplementingTransversionHTML() {
+ return `Select the base that corresponds to a <em>complementing transversion</em> of ${this.selectedNucleotide.value}.`
+ }
+ get errorDefaultTransversionHTML() {
+ return `Select the base that corresponds to the <em>other transversion</em> of ${this.selectedNucleotide.value}.`
+ }
+
+ get errorHTML() {
+ if (this.expectedMutation == Nucleotide.translation) {
+ return this.errorTranslationHTML
+ } else if (this.expectedMutation == Nucleotide.complementingTransversion) {
+ return this.errorComplementingTransversionHTML
+ } else {
+ return this.errorDefaultTransversionHTML
+ }
+ }
+
+ handleItemSelected(base) {
+ if (!this.validMutation(this.selectedNucleotide.value, base)) {
+ this.rules.error.innerHTML = this.errorHTML
+ this.rules.next(new ShowError(this.rules, this))
+ return
+ }
+
+ this.selectedNucleotide.value = base
+ this.rules.iterations--
+ if (this.rules.isLastIteration) {
+ this.rules.next(new DoNothing(this.rules))
+ } else {
+ this.rules.next(new CloneNucleotide(this.rules))
+ }
+ }
+}
+
+class DoNothing {
+ id = 'print-results'
+
+ constructor(rules) {
+ this.rules = rules
+
+ this._boundClickHandler = this.clickHandler.bind(this)
+ }
+
+ enter() {
+ this.rules.printButton.addEventListener('click', this._boundClickHandler)
+ this.rules.printButton.disabled = false
+ }
+
+ exit() {
+ this.rules.printButton.disabled = true
+ this.rules.printButton.removeEventListener('click', this._boundClickHandler)
+ }
+
+ clickHandler() {
+ window.print()
+ }
+}
+
+class ShowError {
+ constructor(rules, nextState) {
+ this.rules = rules
+ this.nextState = nextState
+
+ this._boundClickHandler = this.clickHandler.bind(this)
+ }
+
+ enter() {
+ this.rules.error.onClick = this._boundClickHandler
+ this.rules.error.show()
+ }
+
+ exit() {
+ this.rules.error.hide()
+ this.rules.error.onClick = undefined
+ }
+
+ clickHandler() {
+ this.rules.next(this.nextState)
+ }
+}
+
+class Rules {
+ constructor(die, instructions, genomeList, nucleotideSelector, cloneButton, remainingIterations, printButton, errors) {
+ this.die = new Die(die)
+ this.instructions = instructions
+ this.genomeList = new GenomeList(genomeList)
+ this.nucleotideSelector = new NucleotideSelector(nucleotideSelector)
+ this.cloneButton = cloneButton
+ this.remainingIterations = remainingIterations
+ this.printButton = printButton
+ this.error = new Error(errors)
+
+ this.iterations = Rules.maxIterations
+ this.cloneButton.disabled = true
+ this.genomeList.push(new Genome(Rules.initialGenomeBases))
+
+ if (false) {
+ this._debugStartAtRollForMutation()
+ } else if (false) {
+ this._debugStartAtPerformMutation(4)
+ } else if (false) {
+ this._debugStartWithError()
+ } else {
+ this.currentState = new CloneNucleotide(this)
+ }
+ this.enterState()
+ }
+
+ get iterations() {
+ return Number(this.remainingIterations.innerText)
+ }
+
+ set iterations(val) {
+ this.remainingIterations.innerText = val
+ }
+
+ get isLastIteration() {
+ return this.iterations == 0
+ }
+
+ _debugStartAtRollForMutation() {
+ this.currentState = new RollForMutation(this)
+ const nucleotide = this.currentGenome.nucleotides[2]
+ this.currentGenome.selectedNucleotide = nucleotide
+ }
+
+ _debugStartAtPerformMutation(n) {
+ [...Array(n)].forEach(i => {
+ const n = randomItem(this.currentGenome.nucleotides)
+ n.value = randomItem(Nucleotide.bases)
+ this.currentGenome.selectedNucleotide = n
+ const g = this.currentGenome.clone()
+ window.g = g
+ console.log(g)
+ this.genomeList.push(g)
+ })
+
+ this.currentState = new PerformMutation(this)
+ this.die.value = 15
+ const nucleotide = this.currentGenome.nucleotides[15]
+ this.currentGenome.selectedNucleotide = nucleotide
+ }
+
+ _debugStartWithError() {
+ this.currentState = new ShowError(this, new CloneNucleotide(this))
+ this.error.innerHTML = 'test an error'
+ }
+
+ get currentGenome() {
+ return this.genomeList.last
+ }
+
+ showCurrent() {
+ if (this.currentState.id === undefined) {
+ return
+ }
+ const elt =
+ this.instructions.querySelector(`#${this.currentState.id}`)
+ elt.classList.add('current')
+ elt.scrollIntoView(true)
+ }
+
+ hideCurrent() {
+ if (this.currentState.id === undefined) {
+ return
+ }
+ const elt =
+ this.instructions.querySelector(`#${this.currentState.id}`)
+ elt.classList.remove('current')
+ }
+
+ enterState() {
+ this.currentState.enter()
+ this.showCurrent()
+ }
+
+ exitState() {
+ this.hideCurrent()
+ this.currentState.exit()
+ }
+
+ next(nextState) {
+ this.exitState()
+ this.currentState = nextState
+ this.enterState()
+ }
+}
+Rules.maxIterations = 10
+Rules.initialGenomeBases = [
+ 'G', 'C', 'A',
+ 'C', 'T', 'C',
+ 'G', 'G', 'A',
+ 'T', 'C', 'G',
+ 'A', 'A', 'T',
+ 'T', 'C', 'T'
+]
+
+export default Rules