{"version":3,"file":"app-2b6bda04.f584a1446b2fc034d810.bundle.js","mappings":";AACA","sources":["webpack://lernplattform-admin-frontend/../common/polyfills/AudioContext.js","webpack://lernplattform-admin-frontend/../common/screenfull/screenfull.js","webpack://lernplattform-admin-frontend/../common/markdown/ShowdownAnnotationExtensions.ts","webpack://lernplattform-admin-frontend/../common/markdown/ShowdownJumpSlideLinkExtensions.ts","webpack://lernplattform-admin-frontend/../common/markdown/ShowdownLinkExtensions.ts","webpack://lernplattform-admin-frontend/../common/markdown/ShowdownMpExtensions.ts","webpack://lernplattform-admin-frontend/../common/markdown/ShowdownParagraphExtensions.ts","webpack://lernplattform-admin-frontend/../common/markdown/ShowdownTableExtensions.ts","webpack://lernplattform-admin-frontend/../common/markdown/ShowdownUtils.ts","webpack://lernplattform-admin-frontend/../common/risi-game/RisiController.ts","webpack://lernplattform-admin-frontend/../common/risi-game/RisiLevels.ts","webpack://lernplattform-admin-frontend/../common/risi-game/RisiMoves.ts","webpack://lernplattform-admin-frontend/../common/risi-game/RisiTypes.ts","webpack://lernplattform-admin-frontend/../common/risi-game/RisiUtils.ts","webpack://lernplattform-admin-frontend/../common/risi-game/actors/RisiCar.ts","webpack://lernplattform-admin-frontend/../common/risi-game/actors/RisiPlayer.ts","webpack://lernplattform-admin-frontend/../common/risi-game/images/index.ts"],"sourcesContent":["// original version from https://github.com/jonathantneal/AudioContext/blob/master/AudioContext.js\n(function() {\n  'use strict';\n\n  var\n    AudioContext = window.AudioContext = window.AudioContext || window.webkitAudioContext,\n    AudioContextPrototype = AudioContext.prototype;\n\n  Object.defineProperties(AudioContextPrototype, {\n    createGain: {\n      value: AudioContextPrototype.createGain || AudioContextPrototype.createGainNode,\n    },\n    createDelay: {\n      value: AudioContextPrototype.createDelay || AudioContextPrototype.createDelayNode,\n    },\n    createScriptProcessor: {\n      value: AudioContextPrototype.createScriptProcessor || AudioContextPrototype.createJavaScriptNode,\n    },\n  });\n\n  if(AudioContextPrototype.decodeAudioData.length === 3) {\n    Object.defineProperties(AudioContextPrototype, {\n      __original_decodeAudioData: {\n        value: AudioContextPrototype.__original_decodeAudioData,\n      },\n      decodeAudioData: {\n        value: (arrayBuffer, successCallback, errorCallback) => new Promise((resolve, reject) => this.__original_decodeAudioData(arrayBuffer, audioBuffer => {\n          resolve(audioBuffer);\n          successCallback(audioBuffer);\n        }, error => {\n          reject(error);\n          errorCallback(error);\n        })),\n      },\n    });\n  }\n\n\n  var\n    audioContext = new AudioContext(),\n    oscillatorPrototype = audioContext.createOscillator().constructor.prototype,\n    bufferSourcePrototype = audioContext.createBufferSource().constructor.prototype,\n    gainGainConstructorPrototype = audioContext.createGain().gain.constructor.prototype;\n\n  Object.defineProperties(oscillatorPrototype, {\n    setPeriodicWave: {\n      value: oscillatorPrototype.setPeriodicWave || oscillatorPrototype.setWaveTable,\n    },\n    start: {\n      value: oscillatorPrototype.start || oscillatorPrototype.noteOn,\n    },\n    stop: {\n      value: oscillatorPrototype.stop || oscillatorPrototype.noteOff,\n    },\n  });\n\n  Object.defineProperties(bufferSourcePrototype, {\n    start: {\n      value: bufferSourcePrototype.start || function start() {\n        return arguments.length > 1 ? bufferSourcePrototype.noteGrainOn.apply(this, arguments) : bufferSourcePrototype.noteOn.apply(this, arguments);\n      },\n    },\n    stop: {\n      value: bufferSourcePrototype.stop || bufferSourcePrototype.noteOff,\n    },\n  });\n\n  Object.defineProperties(gainGainConstructorPrototype, {\n    setTargetAtTime: {\n      value: gainGainConstructorPrototype.setTargetAtTime || gainGainConstructorPrototype.setTargetValueAtTime,\n    },\n  });\n})();","/*!\n* screenfull\n* v5.1.0 - 2020-12-24\n* (c) Sindre Sorhus; MIT License\n*/\n(function () {\n\t'use strict';\n\n\tvar document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};\n\tvar isCommonjs = typeof module !== 'undefined' && module.exports;\n\n\tvar fn = (function () {\n\t\tvar val;\n\n\t\tvar fnMap = [\n\t\t\t[\n\t\t\t\t'requestFullscreen',\n\t\t\t\t'exitFullscreen',\n\t\t\t\t'fullscreenElement',\n\t\t\t\t'fullscreenEnabled',\n\t\t\t\t'fullscreenchange',\n\t\t\t\t'fullscreenerror'\n\t\t\t],\n\t\t\t// New WebKit\n\t\t\t[\n\t\t\t\t'webkitRequestFullscreen',\n\t\t\t\t'webkitExitFullscreen',\n\t\t\t\t'webkitFullscreenElement',\n\t\t\t\t'webkitFullscreenEnabled',\n\t\t\t\t'webkitfullscreenchange',\n\t\t\t\t'webkitfullscreenerror'\n\n\t\t\t],\n\t\t\t// Old WebKit\n\t\t\t[\n\t\t\t\t'webkitRequestFullScreen',\n\t\t\t\t'webkitCancelFullScreen',\n\t\t\t\t'webkitCurrentFullScreenElement',\n\t\t\t\t'webkitCancelFullScreen',\n\t\t\t\t'webkitfullscreenchange',\n\t\t\t\t'webkitfullscreenerror'\n\n\t\t\t],\n\t\t\t[\n\t\t\t\t'mozRequestFullScreen',\n\t\t\t\t'mozCancelFullScreen',\n\t\t\t\t'mozFullScreenElement',\n\t\t\t\t'mozFullScreenEnabled',\n\t\t\t\t'mozfullscreenchange',\n\t\t\t\t'mozfullscreenerror'\n\t\t\t],\n\t\t\t[\n\t\t\t\t'msRequestFullscreen',\n\t\t\t\t'msExitFullscreen',\n\t\t\t\t'msFullscreenElement',\n\t\t\t\t'msFullscreenEnabled',\n\t\t\t\t'MSFullscreenChange',\n\t\t\t\t'MSFullscreenError'\n\t\t\t]\n\t\t];\n\n\t\tvar i = 0;\n\t\tvar l = fnMap.length;\n\t\tvar ret = {};\n\n\t\tfor (; i < l; i++) {\n\t\t\tval = fnMap[i];\n\t\t\tif (val && val[1] in document) {\n\t\t\t\tfor (i = 0; i < val.length; i++) {\n\t\t\t\t\tret[fnMap[0][i]] = val[i];\n\t\t\t\t}\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t})();\n\n\tvar eventNameMap = {\n\t\tchange: fn.fullscreenchange,\n\t\terror: fn.fullscreenerror\n\t};\n\n\tvar fullscreenMap = new WeakMap();\n\n\tvar screenfull = {\n\t\trequest: function (element, options) {\n\t\t\treturn new Promise(function (resolve, reject) {\n\t\t\t\tvar onFullScreenEntered = function () {\n\t\t\t\t\tthis.off('change', onFullScreenEntered);\n\t\t\t\t\tfullscreenMap.set(element, true);\n\t\t\t\t\tresolve();\n\t\t\t\t}.bind(this);\n\n\t\t\t\tthis.on('change', onFullScreenEntered);\n\n\t\t\t\telement = element || document.documentElement;\n\n\t\t\t\tvar returnPromise = element[fn.requestFullscreen](options);\n\n\t\t\t\tif (returnPromise instanceof Promise) {\n\t\t\t\t\treturnPromise.then(onFullScreenEntered).catch(reject);\n\t\t\t\t}\n\t\t\t}.bind(this));\n\t\t},\n\t\texit: function (element) {\n\t\t\treturn new Promise(function (resolve, reject) {\n\t\t\t\tif (!this.isFullscreen) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar onFullScreenExit = function () {\n\t\t\t\t\tthis.off('change', onFullScreenExit);\n\t\t\t\t\tfullscreenMap.set(element, false);\n\t\t\t\t\tresolve();\n\t\t\t\t}.bind(this);\n\n\t\t\t\tthis.on('change', onFullScreenExit);\n\n\t\t\t\tvar returnPromise = document[fn.exitFullscreen]();\n\n\t\t\t\tif (returnPromise instanceof Promise) {\n\t\t\t\t\treturnPromise.then(onFullScreenExit).catch(reject);\n\t\t\t\t}\n\t\t\t}.bind(this));\n\t\t},\n\t\ttoggle: function (element, options) {\n\t\t\treturn this.isFullscreen ? this.exit(element) : this.request(element, options);\n\t\t},\n\t\tisFullscreen: function (element) {\n\t\t\treturn !!fullscreenMap.get(element) && Boolean(document[fn.fullscreenElement]);\n\t\t},\n\t\tonchange: function (callback) {\n\t\t\tthis.on('change', callback);\n\t\t},\n\t\tonerror: function (callback) {\n\t\t\tthis.on('error', callback);\n\t\t},\n\t\ton: function (event, callback) {\n\t\t\tvar eventName = eventNameMap[event];\n\t\t\tif (eventName) {\n\t\t\t\tdocument.addEventListener(eventName, callback, false);\n\t\t\t}\n\t\t},\n\t\toff: function (event, callback) {\n\t\t\tvar eventName = eventNameMap[event];\n\t\t\tif (eventName) {\n\t\t\t\tdocument.removeEventListener(eventName, callback, false);\n\t\t\t}\n\t\t},\n\t\traw: fn\n\t};\n\n\tif (!fn) {\n\t\tif (isCommonjs) {\n\t\t\tmodule.exports = {isEnabled: false};\n\t\t} else {\n\t\t\twindow.screenfull = {isEnabled: false};\n\t\t}\n\n\t\treturn;\n\t}\n\n\tObject.defineProperties(screenfull, {\n\t\telement: {\n\t\t\tenumerable: true,\n\t\t\tget: function () {\n\t\t\t\treturn document[fn.fullscreenElement];\n\t\t\t}\n\t\t},\n\t\tisEnabled: {\n\t\t\tenumerable: true,\n\t\t\tget: function () {\n\t\t\t\t// Coerce to boolean in case of old WebKit\n\t\t\t\treturn Boolean(document[fn.fullscreenEnabled]);\n\t\t\t}\n\t\t}\n\t});\n\n\tif (isCommonjs) {\n\t\tmodule.exports = screenfull;\n\t} else {\n\t\twindow.screenfull = screenfull;\n\t}\n})();\n","import { annotate, annotationGroup } from 'rough-notation';\r\nimport { RoughAnnotation, RoughAnnotationGroup, RoughAnnotationType } from 'rough-notation/lib/model';\r\nimport { ShowdownExtension } from 'showdown';\r\nimport { createBbStyleTagRegExp } from './ShowdownUtils';\r\n\r\ninterface IAnnotationTagConfig {\r\n  tag: string;\r\n  color: string;\r\n  type: RoughAnnotationType;\r\n}\r\n\r\nexport type AnnotationControl = RoughAnnotationGroup & { remove: () => void };\r\n\r\nconst annotationTagConfigs: IAnnotationTagConfig[] = [\r\n  {\r\n    tag: 'strike',\r\n    color: 'var(--annotate-strike-through)',\r\n    type: 'strike-through',\r\n  },\r\n  {\r\n    tag: 'highlight',\r\n    color: 'var(--annotate-highlight)',\r\n    type: 'highlight',\r\n  },\r\n  {\r\n    tag: 'box',\r\n    color: 'var(--annotate-box)',\r\n    type: 'box',\r\n  },\r\n  {\r\n    tag: 'circle',\r\n    color: 'var(--annotate-circle)',\r\n    type: 'circle',\r\n  },\r\n];\r\n\r\nexport function createAnnotationExtensions(): Array<ShowdownExtension> {\r\n  return annotationTagConfigs.map<ShowdownExtension>(config => {\r\n    return {\r\n      type: 'lang',\r\n      regex: createBbStyleTagRegExp(config.tag),\r\n      replace: (_: string, innerText: string) => {\r\n        return `<span class=\"annotate annotate-${config.type}\">${innerText}</span>`;\r\n      },\r\n    };\r\n  });\r\n}\r\n\r\nexport function tryAnnotateElement(element: HTMLElement, animate: boolean): RoughAnnotation | null {\r\n  for(const config of annotationTagConfigs) {\r\n    if(element.classList.contains(`annotate-${config.type}`)) {\r\n      return annotate(element, {\r\n        type: config.type,\r\n        color: config.color,\r\n        multiline: true,\r\n        animate,\r\n      });\r\n    }\r\n  }\r\n\r\n  return null;\r\n}\r\n\r\nexport function tryAnnotateMarkdown(markdownContainer: HTMLElement, animate: boolean): AnnotationControl {\r\n  const annotationElements = Array.from(markdownContainer.querySelectorAll('.annotate'));\r\n\r\n  const annotations: RoughAnnotation[] = [];\r\n  for(const annotationElement of annotationElements) {\r\n    if(!(annotationElement instanceof HTMLElement)) {\r\n      continue;\r\n    }\r\n\r\n    const annotation = tryAnnotateElement(annotationElement, animate);\r\n    if(annotation) {\r\n      annotations.push(annotation);\r\n    }\r\n  }\r\n\r\n  const group = annotationGroup(annotations);\r\n  group.show();\r\n  return {\r\n    ...group,\r\n    remove: () => {\r\n      for(const annotation of annotations) {\r\n        annotation.remove();\r\n      }\r\n    },\r\n  };\r\n}\r\n","import { ShowdownExtension } from 'showdown';\r\nimport { createBbStyleTagRegExp } from './ShowdownUtils';\r\n\r\nconst allowedStyles = ['correct', 'wrong'];\r\n\r\nconst configs = [\r\n  {\r\n    tag: 'jumpslide',\r\n  },\r\n];\r\n\r\nexport type MarkdownJumpSlideLinkClickEvent = CustomEvent<{ jumpSlideOrdinal: number }>;\r\n\r\n/**\r\n * Creates native js code to be injected into plain html returned by the markdown parser.\r\n * This will then fire the @see MarkdownJumpSlideLinkClickEvent\r\n * No aurelia, no babel, no nothing (except for global polyfills)\r\n *\r\n * @param jumpSlideOrdinal - the ordinal of the block.jumpSlide<ordinal>Id property\r\n */\r\nfunction createJumpSlideEventFunctionCode(jumpSlideOrdinal: number): string {\r\n  return /* javascript */`\r\n  var newEvent = new CustomEvent('markdown-jump-slide-link-click', {\r\n    bubbles: true,\r\n    cancelable: true,\r\n    detail: {\r\n      jumpSlideOrdinal: ${jumpSlideOrdinal}\r\n    }\r\n  });\r\n  event.target.dispatchEvent(newEvent);\r\n  return false;\r\n`;\r\n}\r\n\r\nexport function createJumpSlideLinkExtensions(): Array<ShowdownExtension> {\r\n  return configs.map(config => {\r\n    return {\r\n      type: 'lang',\r\n      regex: createBbStyleTagRegExp(config.tag),\r\n      replace: (_: string, innerText: string) => {\r\n        const [text, jumpSlideOrdinalString, style] = innerText.split('|');\r\n        const jumpSlideOrdinal = parseInt(jumpSlideOrdinalString, 10);\r\n        if(jumpSlideOrdinal > 0 && jumpSlideOrdinal <= 4) {\r\n          if(allowedStyles.includes(style)) {\r\n            return `<a href=\"#\" class=\"showdown-button showdown-button--${style}\" onClick=\"${createJumpSlideEventFunctionCode(jumpSlideOrdinal)}\">${text}</a>`;\r\n          }\r\n          return `<a href=\"#\" class=\"showdown-link\" onClick=\"${createJumpSlideEventFunctionCode(jumpSlideOrdinal)}\">${text}</a>`;\r\n        }\r\n        return innerText;\r\n      },\r\n    };\r\n  });\r\n}\r\n","import { ShowdownExtension } from 'showdown';\r\nimport { createBbStyleTagRegExp } from './ShowdownUtils';\r\n\r\n/* eslint-disable max-len */\r\nconst inlineLinkSvg = `\r\n<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\">\r\n    <g fill=\"none\" fill-rule=\"evenodd\">\r\n        <path fill=\"none\" d=\"M0 0H360V2527H0z\" transform=\"translate(-130 -383)\"/>\r\n        <path fill=\"currentColor\" fill-rule=\"nonzero\" d=\"M136 387c.552 0 1 .448 1 1 0 .513-.386.936-.883.993L136 389h-1c-.513 0-.936.386-.993.883L134 390v4c0 .513.386.936.883.993L135 395h4c.513 0 .936-.386.993-.883L140 394v-1c0-.552.448-1 1-1 .513 0 .936.386.993.883L142 393v1c0 1.598-1.249 2.904-2.824 2.995L139 397h-4c-1.598 0-2.904-1.249-2.995-2.824L132 394v-4c0-1.598 1.249-2.904 2.824-2.995L135 387h1zm7-2h.02c.023 0 .046.002.07.004L143 385c.05 0 .1.004.149.011l.052.01c.022.004.043.009.065.015l.046.014.06.021.051.023.061.03.037.022c.025.016.05.032.074.05l.022.017c.063.05.12.107.17.17l-.08-.09c.036.035.068.073.097.112.018.024.034.049.05.074l.021.037c.011.02.022.04.031.06l.023.053.021.06.014.045.016.065.009.053.004.031.003.03.003.055.001.032v4c0 .552-.448 1-1 1-.513 0-.936-.386-.993-.883L142 390v-1.586l-4.293 4.293c-.39.39-1.024.39-1.414 0-.36-.36-.388-.928-.083-1.32l.083-.094 4.291-4.293H139c-.513 0-.936-.386-.993-.883L138 386c0-.513.386-.936.883-.993L139 385h4z\" transform=\"translate(-130 -383)\"/>\r\n    </g>\r\n</svg>\r\n`;\r\n/* eslint-enable max-len */\r\n\r\nconst linkConfigs = [\r\n  {\r\n    tag: 'impressum',\r\n    parse: defaultLinkParser,\r\n  },\r\n] as const;\r\n\r\ntype SupportedLinks = (typeof linkConfigs)[number]['tag'];\r\n\r\nexport interface IConverterConfig {\r\n  urls?: { [L in SupportedLinks]?: string | null };\r\n}\r\n\r\nexport type MarkdownUrls = IConverterConfig['urls'];\r\n\r\nexport function createLinkExtensions(converterConfig: IConverterConfig): Array<ShowdownExtension> {\r\n  return linkConfigs.map<ShowdownExtension>(config => {\r\n    return {\r\n      type: 'lang',\r\n      regex: createBbStyleTagRegExp(config.tag),\r\n      replace: (_: string, innerText: string) => {\r\n        const { url, title } = config.parse(innerText, converterConfig.urls?.[config.tag] ?? undefined);\r\n        if(url) {\r\n          return `<a class=\"showdown-link\" href=\"${url}\" target=\"_blank\" rel=\"noopener noreferrer\">${inlineLinkSvg}${title}</a>`;\r\n        }\r\n\r\n        return `<a class=\"showdown-link showdown-link__broken\">${inlineLinkSvg}${title}</a>`;\r\n      },\r\n    };\r\n  });\r\n}\r\n\r\ntype LinkParserParams = [innerText: string, url?: string];\r\ntype LinkParserResult = { title: string; url?: string };\r\n\r\nexport function defaultLinkParser(...[innerText, url]: LinkParserParams): LinkParserResult {\r\n  return { url, title: innerText };\r\n}\r\n","import { ShowdownExtension } from 'showdown';\r\nimport { createBbStyleTagRegExp } from './ShowdownUtils';\r\n\r\nconst mpConfigs = [\r\n  {\r\n    tag: 'luecke',\r\n    gapHtmlTag: 'mp-fill-in-the-blank-input',\r\n  },\r\n  {\r\n    tag: 'luecke_sort',\r\n    gapHtmlTag: 'mp-fill-in-the-blank-input',\r\n    dataPrefix: '%SORTED%',\r\n  },\r\n  {\r\n    tag: 'buchstaben',\r\n    gapHtmlTag: 'mp-letter-input',\r\n  },\r\n];\r\n\r\nexport function createMpExtensions(): Array<ShowdownExtension> {\r\n  return mpConfigs.map(config => {\r\n    return {\r\n      type: 'lang',\r\n      regex: createBbStyleTagRegExp(config.tag),\r\n      replace: (_: string, innerText: string) => {\r\n        return `<${config.gapHtmlTag} data=\"${config.dataPrefix ? `${config.dataPrefix};` : ''}${innerText}\">${innerText}</${config.gapHtmlTag}>`;\r\n      },\r\n    };\r\n  });\r\n}\r\n","import { ShowdownExtension } from 'showdown';\r\nimport { createBbStyleTagRegExp } from './ShowdownUtils';\r\n\r\nconst configs = [\r\n  {\r\n    tag: 'center',\r\n    style: 'text-align: center; display: block;',\r\n  },\r\n  {\r\n    tag: 'right',\r\n    style: 'text-align: right; display: block;',\r\n  },\r\n  {\r\n    tag: 'left',\r\n    style: 'text-align: left; display: block;',\r\n  },\r\n  {\r\n    tag: 'justify',\r\n    style: 'text-align: justify; display: block;',\r\n  },\r\n];\r\n\r\nexport function createParagraphExtensions(): Array<ShowdownExtension> {\r\n  return configs.map(config => {\r\n    return {\r\n      type: 'output',\r\n      regex: createBbStyleTagRegExp(config.tag, 'gs'),\r\n      replace: (_: string, innerText: string) => {\r\n        return `<span style=\"${config.style}\">${innerText}</span>`;\r\n      },\r\n    };\r\n  });\r\n}\r\n","import { ShowdownExtension } from 'showdown';\r\nimport { createBbStyleTagRegExp } from './ShowdownUtils';\r\n\r\nconst configs = [\r\n  {\r\n    tag: 'tborder',\r\n    class: 'md-table-border',\r\n  },\r\n  {\r\n    tag: 'tcolor',\r\n    class: 'md-table-color',\r\n  },\r\n];\r\n\r\nexport function createTableExtensions(): Array<ShowdownExtension> {\r\n  const extensions = [];\r\n  extensions.push(...configs.map(config => {\r\n    return {\r\n      type: 'output',\r\n      // Sadly we cannot use the same regex as in the paragraph extension because tables seem to be a special thing in showdown\r\n      // We have to let showdown render the tags an replace them after the fact. This means table style tags must be in their own line\r\n      regex: new RegExp(`<p>\\\\[${config.tag}\\\\]<\\\\/p>(.*)<p>\\\\[\\\\/${config.tag}\\\\]<\\\\/p>`, 'gs'),\r\n      replace: (_: string, innerText: string) => {\r\n        return `<span class=\"${config.class}\">${innerText}</span>`;\r\n      },\r\n    };\r\n  }));\r\n  extensions.push({\r\n    type: 'lang',\r\n    regex: createBbStyleTagRegExp('tsmall'),\r\n    replace: (_: string, innerText: string) => {\r\n      return `<div class=\"md-table-small\">${innerText}</div>`;\r\n    },\r\n  });\r\n  return extensions;\r\n}\r\n","import { Converter, ShowdownExtension } from 'showdown';\nimport { AnnotationControl, createAnnotationExtensions, tryAnnotateMarkdown } from './ShowdownAnnotationExtensions';\nimport { createJumpSlideLinkExtensions } from './ShowdownJumpSlideLinkExtensions';\nimport { createLinkExtensions, IConverterConfig, MarkdownUrls } from './ShowdownLinkExtensions';\nimport { createMpExtensions } from './ShowdownMpExtensions';\nimport { createParagraphExtensions } from './ShowdownParagraphExtensions';\nimport { createTableExtensions } from './ShowdownTableExtensions';\n\nexport interface IShowdownConverter {\n  makeHtml: (markdown: string, urlConfig?: MarkdownUrls) => string;\n}\n\nexport function createBbStyleTagRegExp(tag: string, flags = 'g'): RegExp {\n  return new RegExp(`\\\\[${tag}](((?!\\\\[${tag}]).)*)\\\\[/${tag}]`, flags);\n}\n\nexport { MarkdownUrls };\n\nexport class ShowdownUtils {\n\n  public static createShowdownConverter(includeMethodPool = false): IShowdownConverter {\n    const converterConfig: IConverterConfig = {};\n    const extensions: Array<ShowdownExtension> = [];\n\n    extensions.push(...createLinkExtensions(converterConfig));\n    extensions.push(...createJumpSlideLinkExtensions());\n    extensions.push(...createAnnotationExtensions());\n    extensions.push(...createParagraphExtensions());\n    extensions.push(...createTableExtensions());\n\n    if(includeMethodPool) {\n      extensions.push(...createMpExtensions());\n    }\n\n    const converter = new Converter({\n      extensions,\n      simpleLineBreaks: true,\n      tables: true,\n      underline: true,\n    });\n\n    return {\n      makeHtml(markdown, urlConfig): string {\n        converterConfig.urls = { ...urlConfig };\n        return converter.makeHtml(markdown);\n      },\n    };\n  }\n\n  /**\n   * make sure to define the css variables defined in the annotationTagConfigs or nothing will be displayed\n   *\n   * @param markdownContainer\n   */\n  public static annotateMarkdown(markdownContainer: HTMLElement, animate: boolean): AnnotationControl {\n    return tryAnnotateMarkdown(markdownContainer, animate);\n  }\n\n}\n","import { createCar } from './actors/RisiCar';\nimport { createPlayer } from './actors/RisiPlayer';\nimport { PlayerMove, playerMoves } from './RisiMoves';\nimport {\n  Direction,\n  FailReason,\n  IAbsoluteIntersectionConfig,\n  ICoords,\n  IIntersectionArmConfig,\n  IRisiActor,\n  IRisiActorMove,\n  IRisiActorUndoMove,\n  IRisiConfig,\n  IRisiPlayerMove,\n  ISpace,\n  Marking,\n  RelativeDirection,\n  RisiStatus,\n  Sign,\n} from './RisiTypes';\nimport { RisiUtils } from './RisiUtils';\n\nexport class RisiController {\n  private status = RisiStatus.Plan;\n  private actors: IRisiActor[] = [];\n  private spaceMap: ISpace[][] = [[]];\n  private failReason: FailReason | null = null;\n  private config: IRisiConfig | null = null;\n  private undoStack: IRisiActorUndoMove[][] = [];\n\n  // Example moves\n  private possibleMoves: PlayerMove[] = [...playerMoves];\n\n  public initialize(config: IRisiConfig): void {\n    if(this.status === RisiStatus.Execute) return;\n\n    this.config = config;\n    this.spaceMap = this.buildSpaceMap(config);\n    this.actors = this.setupActors(config);\n    this.status = RisiStatus.Plan;\n    this.failReason = null;\n    this.undoStack = [];\n  }\n\n  public getSpaceMap(): ISpace[][] {\n    return this.spaceMap;\n  }\n\n  public getActors(): IRisiActor[] {\n    return this.actors;\n  }\n\n  // TODO: use these to set up the controls\n  public getPossibleMoves(): PlayerMove[] {\n    return this.possibleMoves;\n  }\n\n  public getFailReason(): FailReason | null {\n    return this.failReason;\n  }\n\n  public getUndoState(): { size: number } {\n    return { size: this.undoStack.length };\n  }\n\n  public async execute(playerMove: IRisiPlayerMove, moveVisualizer?: (move: IRisiActorMove[]) => Promise<void>): Promise<RisiStatus> {\n    if(this.status !== RisiStatus.Plan) throw new Error(`need to be in '${RisiStatus.Plan}' state`);\n\n    this.setState(RisiStatus.Execute);\n    const actorMoves = this.actors.map((_, idx) => this.computeActorMove(idx, playerMove));\n\n    const player = this.getPlayer();\n    if(player) {\n      const failReason = this.isMoveForbidden(player, playerMove.type, this.config?.playerTarget ?? null);\n      // if the player could move but doesn't, it counts as a failure\n      if(failReason && playerMove.type !== 'wait') this.failReason = failReason;\n      else if(playerMove.type === 'wait' && !failReason) this.failReason = FailReason.Slowpoke;\n    }\n\n    await moveVisualizer?.(actorMoves);\n    this.pushToUndoState(actorMoves);\n    actorMoves.forEach((move, idx) => this.executeActorMove(idx, move));\n\n    const failReason = this.checkFailCondition(this.failReason);\n    if(failReason) {\n      this.failReason = failReason;\n      this.setState(RisiStatus.Fail);\n    } else if(this.checkWinCondition()) {\n      this.setState(RisiStatus.Success);\n    } else {\n      this.setState(RisiStatus.Plan);\n    }\n\n    return this.status;\n  }\n\n  public async undo(moveVisualizer?: (move: IRisiActorUndoMove[]) => Promise<void>): Promise<void> {\n    if(![RisiStatus.Plan, RisiStatus.Fail].includes(this.status)) throw new Error(`need to be in '${RisiStatus.Plan}' state`);\n\n    const actorMoves = this.getUndoMoves();\n    if(!actorMoves) return;\n\n    this.setState(RisiStatus.Execute);\n\n    await moveVisualizer?.(actorMoves);\n\n    actorMoves.forEach((move, idx) => this.undoActorMove(idx, move));\n\n    this.failReason = null;\n    this.setState(RisiStatus.Plan);\n  }\n\n  private getUndoMoves(): IRisiActorUndoMove[] | null {\n    return this.undoStack.pop() ?? null;\n  }\n\n  private pushToUndoState(actorMoves: IRisiActorMove[]): void {\n    this.undoStack.push(actorMoves.map(m => ({\n      move: m,\n      oldState: {\n        ...m.actor,\n        coords: { ...m.actor.coords },\n      },\n    })));\n  }\n\n  private buildSpaceMap(config: IRisiConfig): ISpace[][] {\n    // set up all spaces\n    const spaceMap: ISpace[][] = [[]];\n    for(let x = 0; x < config.columns; x++) {\n      spaceMap[x] = [];\n      for(let y = 0; y < config.rows; y++) {\n        spaceMap[x][y] = {\n          coords: { x, y },\n        };\n      }\n    }\n\n    const intersectionConfig = {\n      south: {},\n      ...config.arms,\n    };\n\n    // Add the intersection arms\n    setIntersectionArm(config.origin.x - 1, config.origin.y, Direction.Down, intersectionConfig.south ?? {}, config.playerTarget === RelativeDirection.Back);\n    if(intersectionConfig.west) {\n      setIntersectionArm(config.origin.x - 1, config.origin.y - 1, Direction.Left, intersectionConfig.west, config.playerTarget === RelativeDirection.Left);\n    }\n    if(intersectionConfig.east) {\n      setIntersectionArm(config.origin.x, config.origin.y, Direction.Right, intersectionConfig.east, config.playerTarget === RelativeDirection.Right);\n    }\n    if(intersectionConfig.north) {\n      setIntersectionArm(config.origin.x, config.origin.y - 1, Direction.Up, intersectionConfig.north, config.playerTarget === RelativeDirection.Straight);\n    }\n\n    // The actual intersection tiles.\n    setIntersectionSpace(config.origin.x - 1, config.origin.y - 1, Direction.Down, intersectionConfig);\n    setIntersectionSpace(config.origin.x, config.origin.y - 1, Direction.Left, intersectionConfig);\n    setIntersectionSpace(config.origin.x - 1, config.origin.y, Direction.Right, intersectionConfig);\n    setIntersectionSpace(config.origin.x, config.origin.y, Direction.Up, intersectionConfig);\n\n    return spaceMap;\n\n    function setIntersectionArm(startX: number, startY: number, direction: Direction, armConfig: IIntersectionArmConfig, containsGoal: boolean): void {\n      const vector = RisiUtils.getDirectionalVector(direction);\n      const oppositeDirection = RisiUtils.turnAround(direction);\n\n      let current = { x: startX, y: startY };\n      let leftOfCurrent = RisiUtils.getCoordsAhead(current, RisiUtils.turnLeft(direction));\n      for(let i = 0; ; i++) {\n        current = RisiUtils.addVectors(current, vector);\n        leftOfCurrent = RisiUtils.addVectors(leftOfCurrent, vector);\n        if(current.x < 0 || current.y < 0 || current.x >= config.columns || current.y >= config.rows) {\n          break;\n        }\n\n        if(containsGoal && i === config.goalDistance) {\n          spaceMap[current.x][current.y].goal = true;\n        }\n\n        spaceMap[current.x][current.y].street = {\n          directions: [direction],\n          afterIntersection: i === 0 ? direction : undefined,\n        };\n\n        spaceMap[leftOfCurrent.x][leftOfCurrent.y].street = {\n          directions: [oppositeDirection],\n          stopLine: i === 0 ? armConfig.stopLine : undefined,\n          marking: Marking.Dashed,\n          beforeIntersection: i === 0 ? oppositeDirection : undefined,\n          sign: armConfig.sign && i === 0 ? {\n            ...armConfig.sign,\n          } : undefined,\n        };\n      }\n    }\n\n    function setIntersectionSpace(x: number, y: number, direction: Direction, intersection: IAbsoluteIntersectionConfig): void {\n      const arms = RisiUtils.relativeIntersection(intersection, direction);\n      let marking: Marking | undefined;\n      if(arms.back && arms.right && !arms.back?.stopLine && !arms.right?.stopLine) marking = Marking.DashedCurve;\n      if(arms.back && arms.straight && !arms.back?.stopLine && !arms.straight?.stopLine) marking = Marking.Dashed;\n\n      const directions: Direction[] = [];\n      if(arms.back || arms.straight) directions.push(direction);\n      if(arms.right) directions.push(RisiUtils.turnRight(direction));\n\n      spaceMap[x][y].street = {\n        directions,\n        marking,\n        intersection: direction,\n      };\n    }\n  }\n\n  private getSpace(coords: ICoords): ISpace | undefined {\n    return RisiUtils.getSpace(this.spaceMap, coords);\n  }\n\n  private setupActors(config: IRisiConfig): IRisiActor[] {\n    const actors: IRisiActor[] = [];\n    actors.push(createPlayer({ x: config.origin.x, y: config.origin.y + config.playerStartPosition }, Direction.Up));\n    let nextActorId = 1;\n\n    addActors(config.origin.x - 2, config.origin.y, Direction.Right, config.arms.west ?? {});\n    addActors(config.origin.x, config.origin.y + 1, Direction.Up, config.arms.south ?? {});\n    addActors(config.origin.x - 1, config.origin.y - 2, Direction.Down, config.arms.north ?? {});\n    addActors(config.origin.x + 1, config.origin.y - 1, Direction.Left, config.arms.east ?? {});\n\n    return actors;\n\n    function addActors(x: number, y: number, direction: Direction, armConfig: IIntersectionArmConfig): void {\n      const oppositeDirection = RisiUtils.turnAround(direction);\n      const intersectionStart: ICoords = { x, y };\n      armConfig.cars?.forEach(a => actors.push(createCar(\n        `car${nextActorId++}`,\n        RisiUtils.getCoordsAhead(intersectionStart, oppositeDirection, a.distance ?? 0),\n        direction,\n        a.target ?? RelativeDirection.Straight,\n      )));\n    }\n  }\n\n  private setState(state: RisiStatus): void {\n    this.status = state;\n  }\n\n  private computeActorMove(actorIndex: number, playerMove: IRisiPlayerMove): IRisiActorMove {\n    // TODO: more complex computing of moves ;)\n    const actor = this.actors[actorIndex];\n    let type: IRisiActorMove['type'] = 'wait';\n\n    if(actor.type === 'player') {\n      type = playerMove.type;\n    } else {\n      const space = this.getSpace(actor.coords);\n      if(space?.street?.sign?.type === 'stop' && !actor.stopping) {\n        type = 'wait';\n      } else if(actor.signalingRight && space?.street?.beforeIntersection === actor.direction) {\n        type = 'turn-right';\n      } else if(actor.signalingLeft && space?.street?.intersection === actor.direction) {\n        type = 'turn-left';\n      } else if(actor.signalingLeft && !actor.bearingLeft && space?.street?.intersection == null) {\n        type = 'bear-left';\n      } else if(!actor.signalingLeft && actor.bearingLeft) {\n        type = 'bear-right';\n      } else {\n        type = 'forward';\n      }\n\n      if(this.isMoveForbidden(actor, type, null)) type = 'wait';\n    }\n\n    return { type, actor };\n  }\n\n  private isMoveForbidden(actor: IRisiActor, plannedMove: IRisiActorMove['type'], goalDirection: RelativeDirection | null): FailReason | false {\n    const space = this.getSpace(actor.coords);\n    const right = RisiUtils.turnRight(actor.direction);\n    const left = RisiUtils.turnLeft(actor.direction);\n    const oppositeDirection = RisiUtils.turnAround(actor.direction);\n\n    if(space?.street?.beforeIntersection === actor.direction) {\n      const { sign } = space.street;\n      if(sign?.type === Sign.Stop && !actor.stopping) return FailReason.IgnoredStopSign;\n\n      // Check the four intersection area spaces\n      // someone coming from the left\n      if(this.getActorsAt(RisiUtils.getCoordsAhead(actor.coords, actor.direction, 1, -1)).some(a => a.direction === right)) {\n        return FailReason.IgnoredCrossingTraffic;\n      }\n      // someone coming from the right can be ignored if I'm going right myself\n      if(plannedMove !== 'turn-right' && this.getActorsAt(RisiUtils.getCoordsAhead(actor.coords, actor.direction, 2)).some(a => a.direction === left)) {\n        return FailReason.IgnoredCrossingTraffic;\n      }\n      // someone oncoming who wants to turn left\n      if(this.getActorsAt(RisiUtils.getCoordsAhead(actor.coords, actor.direction, 2, -1)).some(a => a.signalingLeft && a.direction === oppositeDirection)) {\n        return FailReason.InterruptedOncomingLeftTurn;\n      }\n\n      // check which directions could potentially require yielding to\n      const yieldsToCheck: RelativeDirection[] = [];\n      switch(sign?.type) {\n        case Sign.RightOfWay:\n          // If I have ROW and the ROW curves to the right, I still need to consider right\n          if(sign.direction === RelativeDirection.Right) yieldsToCheck.push(RelativeDirection.Right);\n          break;\n        case Sign.Stop:\n        case Sign.Yield:\n          // Stop signs and yield signs should yield to everybody unless the yield-course goes to the left\n          if(sign.direction !== RelativeDirection.Left) yieldsToCheck.push(RelativeDirection.Left);\n          yieldsToCheck.push(RelativeDirection.Straight, RelativeDirection.Right);\n          break;\n        default:\n          // basic \"right-hand\" rule\n          yieldsToCheck.push(RelativeDirection.Right);\n      }\n\n      for(const direction of yieldsToCheck) {\n        let startCoords: ICoords;\n        let armDirection: Direction;\n        // determine the \"start position\" for checking down the street\n        switch(direction) {\n          case RelativeDirection.Right:\n            // I can't intersect vehicles from the right, if I'm turning right myself\n            if(plannedMove === 'turn-right') continue;\n            startCoords = RisiUtils.getCoordsAhead(actor.coords, actor.direction, 2, 1);\n            armDirection = right;\n            break;\n          case RelativeDirection.Left:\n            startCoords = RisiUtils.getCoordsAhead(actor.coords, actor.direction, 1, -2);\n            armDirection = left;\n            break;\n          case RelativeDirection.Straight:\n            startCoords = RisiUtils.getCoordsAhead(actor.coords, actor.direction, 3, -1);\n            armDirection = actor.direction;\n            break;\n          default:\n            continue;\n        }\n\n        // somebody who has a \"stop\" sign always has to yield to me\n        if(this.getSpace(startCoords)?.street?.sign?.type === Sign.Stop) break;\n\n        // check the first three spaces, because that's how many turns a vehicle takes to traverse the intersection\n        for(let i = 0; i < 3; i++) {\n          const coordsToCheck = RisiUtils.getCoordsAhead(startCoords, armDirection, i);\n          const otherVehicles = this.getActorsAt(coordsToCheck, actor.id);\n          if(otherVehicles.length) {\n            return FailReason.IgnoredRightOfWay;\n          }\n        }\n      }\n    }\n\n    if(goalDirection != null) {\n      if(goalDirection !== RelativeDirection.Right) {\n        switch(plannedMove) {\n          case 'signal-right': return FailReason.WrongSignal;\n          case 'bear-right': if(goalDirection === RelativeDirection.Left) return FailReason.WrongAlignment; break;\n          case 'turn-right': return FailReason.WrongRoute;\n          default:\n        }\n      }\n\n      if(goalDirection !== RelativeDirection.Left) {\n        switch(plannedMove) {\n          case 'signal-left': return FailReason.WrongSignal;\n          case 'bear-left': return FailReason.WrongAlignment;\n          case 'turn-left': return FailReason.WrongRoute;\n          default:\n        }\n      }\n    }\n\n    // Check sequential move requirements (looking, signaling, bearing)\n    if(plannedMove === 'signal-left' && !actor.lookedLeft) return FailReason.DidNotLookLeft;\n    if(plannedMove === 'signal-right' && !actor.lookedRight && !this.config?.specialRules?.ignoreMissingLookRight) return FailReason.DidNotLook;\n    if(\n      plannedMove !== 'wait' && plannedMove !== 'turn-right'\n      && !actor.lookedBeforeIntersection && space?.street?.beforeIntersection === actor.direction\n    ) return FailReason.DidNotLook;\n    if((plannedMove === 'turn-left' || plannedMove === 'bear-left') && !actor.signalingLeft) return FailReason.DidNotSignal;\n    if((plannedMove === 'turn-left') && !actor.bearingLeft) return FailReason.NotCorrectlyAligned;\n    if(plannedMove === 'turn-right' && !actor.signalingRight && (\n      // signaling to the right is only required if the other paths are possible to take\n      // TODO: this is a special rule for level 1, and needs to be generalized for other directions as well\n      this.getSpace(RisiUtils.getCoordsAhead(actor.coords, actor.direction, 3))?.street\n      || this.getSpace(RisiUtils.getCoordsAhead(actor.coords, actor.direction, 2, -2))?.street\n    )) return FailReason.DidNotSignal;\n\n    const coordsAhead = RisiUtils.getCoordsAhead(actor.coords, actor.direction);\n    // can't move if a car is right in front of you\n    if(this.getActorsAt(coordsAhead, actor.id).length) {\n      return FailReason.ViolatedSafetyDistance;\n    }\n\n    // If I want to turn left, I have to also check the target space\n    if(plannedMove === 'turn-left') {\n      if(this.getActorsAt(RisiUtils.getCoordsAhead(actor.coords, actor.direction, 1, -1), actor.id).length) {\n        return FailReason.ViolatedSafetyDistance;\n      }\n    }\n\n    return false;\n  }\n\n  private getPlayer(): IRisiActor | null {\n    return this.actors.find(a => a.type === 'player') ?? null;\n  }\n\n  private checkWinCondition(): boolean {\n    const player = this.getPlayer();\n    if(!player) return false;\n\n    return this.getSpace(player.coords)?.goal ?? false;\n  }\n\n  private checkFailCondition(existingFailCondition: FailReason | null): FailReason | false {\n    const player = this.getPlayer();\n    if(!player || !this.config) return FailReason.OffRoad;\n\n    const space = this.getSpace(player.coords);\n    const spaceAhead = this.getSpace(RisiUtils.getCoordsAhead(player.coords, player.direction));\n\n    // graver than pre-move fail conditions\n    if(this.getActorsAt(player.coords, player.id).length) return FailReason.Collision;\n    if(!space) return FailReason.WrongRoute;\n    if(!space?.street) return FailReason.OffRoad;\n    if(!spaceAhead?.street && !this.checkWinCondition()) return FailReason.FacingOffRoad;\n    if(!space.street.directions?.includes(player.direction)) {\n      if(space.street.directions?.includes(RisiUtils.turnAround(player.direction))) return FailReason.GoingAgainstTraffic;\n      return FailReason.ViolatedTrafficFlow;\n    }\n\n    if(this.config.playerTarget === RelativeDirection.Right && space?.street?.beforeIntersection === Direction.Up\n      && !player.signalingRight && !this.config.specialRules?.ignoreMissingLookRight\n    ) {\n      return FailReason.DidNotSignal;\n    }\n\n    if(this.config.playerTarget === RelativeDirection.Right && space?.street?.intersection === Direction.Up && player.direction !== Direction.Right) {\n      return FailReason.WrongRoute;\n    }\n\n    // the existing fail condition goes here\n    if(existingFailCondition) return existingFailCondition;\n\n    if(space.street.intersection === Direction.Left && this.config.playerTarget !== RelativeDirection.Straight) {\n      return FailReason.WrongRoute;\n    }\n\n    return false;\n  }\n\n  private getActorsAt(coords: ICoords, requestingActorId?: string): IRisiActor[] {\n    return this.actors.filter(a => a.id !== requestingActorId && RisiUtils.areCoordsEqual(a.coords, coords));\n  }\n\n  private executeActorMove(actorIndex: number, actorMove: IRisiActorMove): void {\n    const actor = this.actors[actorIndex];\n\n    if(actor.lookingLeft) actor.lookingLeft = false;\n    if(actor.lookingRight) actor.lookingRight = false;\n    if(this.getSpace(actor.coords)?.street?.afterIntersection === actor.direction) {\n      actor.lookedBeforeIntersection = false;\n      actor.lookedLeft = false;\n      actor.lookedRight = false;\n    }\n\n    if(actor.type === 'player') {\n      if(actor.handExtendedLeft) actor.handExtendedLeft = false;\n      if(actor.handExtendedRight) actor.handExtendedRight = false;\n    }\n\n    actor.stopping = actorMove.type === 'wait';\n\n    // TODO: perhaps extract/modularize?\n    switch(actorMove.type) {\n      case 'forward': {\n        actor.coords = RisiUtils.getCoordsAhead(actor.coords, actor.direction);\n        if(this.getSpace(actor.coords)?.street?.afterIntersection === actor.direction) {\n          actor.signalingLeft = false;\n          actor.signalingRight = false;\n        }\n        break;\n      }\n\n      case 'turn-left': {\n        actor.coords = RisiUtils.getCoordsAhead(actor.coords, actor.direction, 1, -1);\n        actor.direction = RisiUtils.turnLeft(actor.direction);\n        actor.bearingLeft = false;\n        break;\n      }\n\n      case 'turn-right': {\n        actor.coords = RisiUtils.getCoordsAhead(actor.coords, actor.direction);\n        actor.direction = RisiUtils.turnRight(actor.direction);\n        break;\n      }\n\n      case 'bear-left': {\n        actor.coords = RisiUtils.getCoordsAhead(actor.coords, actor.direction);\n        actor.bearingLeft = true;\n        break;\n      }\n\n      case 'bear-right': {\n        actor.coords = RisiUtils.getCoordsAhead(actor.coords, actor.direction);\n        actor.bearingLeft = false;\n        break;\n      }\n\n      case 'look-left': {\n        actor.coords = RisiUtils.getCoordsAhead(actor.coords, actor.direction);\n        actor.lookingLeft = true;\n        actor.lookedLeft = true;\n        break;\n      }\n\n      case 'look': {\n        actor.coords = RisiUtils.getCoordsAhead(actor.coords, actor.direction);\n        actor.lookingRight = true;\n        actor.lookedRight = true;\n        if(this.getSpace(actor.coords)?.street?.beforeIntersection === actor.direction) {\n          actor.lookedBeforeIntersection = true;\n        }\n        break;\n      }\n\n      case 'signal-left': {\n        actor.coords = RisiUtils.getCoordsAhead(actor.coords, actor.direction);\n        actor.signalingLeft = true;\n        if(actor.type === 'player') actor.handExtendedLeft = true;\n        break;\n      }\n\n      case 'signal-right': {\n        actor.coords = RisiUtils.getCoordsAhead(actor.coords, actor.direction);\n        actor.signalingRight = true;\n        if(actor.type === 'player') actor.handExtendedRight = true;\n        break;\n      }\n\n      case 'wait': {\n        if(this.getSpace(actor.coords)?.street?.beforeIntersection === actor.direction) {\n          actor.lookedBeforeIntersection = true;\n        }\n        break;\n      }\n\n      default:\n        console.error('unknown move', actorMove.type, 'for actor', actor);\n    }\n  }\n\n  private undoActorMove(actorIndex: number, undoMove: IRisiActorUndoMove): void {\n    const actor = this.actors[actorIndex];\n    Object.assign(actor, {\n      bearingLeft: undefined,\n      handExtendedLeft: undefined,\n      handExtendedRight: undefined,\n      lookedBeforeIntersection: undefined,\n      lookedLeft: undefined,\n      lookedRight: undefined,\n      lookingLeft: undefined,\n      lookingRight: undefined,\n      signalingLeft: undefined,\n      signalingRight: undefined,\n      stopping: undefined,\n      ...undoMove.oldState,\n    });\n  }\n}\n","import {\n  IRisiConfig,\n  RelativeDirection,\n  RisiLevelStyle,\n  Sign,\n  StopLine,\n} from './RisiTypes';\n\nexport const baseConfig: Pick<IRisiConfig, 'columns' | 'rows' | 'origin' | 'goalDistance' | 'playerStartPosition'> = {\n  columns: 6,\n  rows: 12,\n  playerStartPosition: 4,\n  origin: {\n    x: 3,\n    y: 6,\n  },\n  goalDistance: 1,\n};\n\nexport const levels: Record<string, IRisiConfig> = {\n  demo: {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'forward'],\n    playerTarget: RelativeDirection.Left,\n    arms: {\n      south: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Right,\n        },\n      },\n      north: {\n        stopLine: StopLine.Solid,\n        cars: [\n          {\n            type: 'car',\n            distance: 2,\n            target: RelativeDirection.Left,\n          },\n        ],\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Right,\n        },\n      },\n      west: {\n        stopLine: StopLine.Solid,\n        cars: [\n          {\n            type: 'car',\n            distance: 1,\n            target: RelativeDirection.Right,\n          },\n        ],\n        sign: {\n          type: Sign.Yield,\n          direction: RelativeDirection.Left,\n        },\n      },\n      east: {\n        stopLine: StopLine.Solid,\n        cars: [\n          {\n            type: 'car',\n            distance: 3,\n            target: RelativeDirection.Straight,\n          },\n        ],\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Left,\n        },\n      },\n    },\n  },\n  '1-1': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'forward', 'forward', 'turn-right', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Outskirts,\n    playerTarget: RelativeDirection.Right,\n    arms: {\n      south: {},\n      east: {},\n    },\n    specialRules: {\n      ignoreMissingLookRight: true,\n    },\n  },\n  '1-2': {\n    ...baseConfig,\n    suggestedSolution: ['look', 'signal-right', 'forward', 'turn-right', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Outskirts,\n    playerTarget: RelativeDirection.Right,\n    arms: {\n      south: {},\n      east: {\n        stopLine: StopLine.Hidden,\n      },\n      north: {},\n    },\n  },\n  '2-1': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'forward', 'forward', 'wait', 'forward', 'forward', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Outskirts,\n    playerTarget: RelativeDirection.Straight,\n    arms: {\n      south: {},\n      east: {\n        stopLine: StopLine.Hidden,\n        cars: [{\n          type: 'car',\n          target: RelativeDirection.Right,\n          distance: 3,\n        }],\n      },\n      north: {},\n    },\n  },\n  '2-2': {\n    ...baseConfig,\n    suggestedSolution: ['look', 'signal-right', 'forward', 'wait', 'wait', 'turn-right', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Outskirts,\n    playerTarget: RelativeDirection.Right,\n    arms: {\n      south: {\n        stopLine: StopLine.Dashed,\n        sign: {\n          type: Sign.Yield,\n        },\n      },\n      east: {\n        sign: {\n          type: Sign.RightOfWay,\n          offset: -3,\n        },\n      },\n      west: {\n        cars: [{\n          type: 'car',\n          target: RelativeDirection.Straight,\n          distance: 2,\n        }],\n        sign: {\n          type: Sign.RightOfWay,\n          offset: -4,\n        },\n      },\n    },\n  },\n  '3-1': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'forward', 'look', 'forward', 'forward', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Outskirts,\n    playerTarget: RelativeDirection.Straight,\n    arms: {\n      south: {\n        stopLine: StopLine.Hidden,\n      },\n      east: {\n        stopLine: StopLine.Hidden,\n      },\n      west: {\n        stopLine: StopLine.Hidden,\n        cars: [{\n          type: 'car',\n          target: RelativeDirection.Straight,\n          distance: 3,\n        }],\n      },\n      north: {\n        stopLine: StopLine.Hidden,\n      },\n    },\n  },\n  '3-2': {\n    ...baseConfig,\n    suggestedSolution: ['look', 'signal-right', 'forward', 'turn-right', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Suburbs,\n    playerTarget: RelativeDirection.Right,\n    arms: {\n      south: {\n        stopLine: StopLine.Hidden,\n      },\n      east: {\n        stopLine: StopLine.Hidden,\n      },\n      west: {\n        stopLine: StopLine.Hidden,\n        cars: [{\n          type: 'car',\n          target: RelativeDirection.Straight,\n          distance: 3,\n        }],\n      },\n      north: {\n        stopLine: StopLine.Hidden,\n      },\n    },\n  },\n  '3-3': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'forward', 'forward', 'wait', 'forward', 'forward', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Suburbs,\n    playerTarget: RelativeDirection.Straight,\n    arms: {\n      south: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Right,\n        },\n      },\n      east: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Left,\n        },\n      },\n      west: {\n        cars: [{\n          type: 'car',\n          target: RelativeDirection.Left,\n          distance: 2,\n        }],\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Left,\n        },\n      },\n      north: {\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Right,\n        },\n      },\n    },\n  },\n  '3-4': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'look', 'signal-right', 'wait', 'wait', 'wait', 'wait', 'turn-right', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Suburbs,\n    playerTarget: RelativeDirection.Right,\n    arms: {\n      south: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Right,\n        },\n      },\n      east: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Left,\n        },\n      },\n      west: {\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Left,\n            distance: 2,\n          },\n          {\n            type: 'car',\n            target: RelativeDirection.Straight,\n            distance: 3,\n          },\n        ],\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Left,\n        },\n      },\n      north: {\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Right,\n        },\n      },\n    },\n  },\n  '4-1': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'forward', 'forward', 'wait', 'wait', 'wait', 'wait', 'forward', 'forward', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Suburbs,\n    playerTarget: RelativeDirection.Straight,\n    arms: {\n      south: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Right,\n        },\n      },\n      east: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Left,\n        },\n      },\n      west: {\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Straight,\n            distance: 1,\n          },\n          {\n            type: 'car',\n            target: RelativeDirection.Left,\n            distance: 5,\n          },\n        ],\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Left,\n        },\n      },\n      north: {\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Right,\n        },\n      },\n    },\n  },\n  '4-2': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'forward', 'forward', 'wait', 'wait', 'wait', 'forward', 'forward', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Suburbs,\n    playerTarget: RelativeDirection.Straight,\n    arms: {\n      south: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Right,\n        },\n      },\n      east: {\n        stopLine: StopLine.Dashed,\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Right,\n            distance: 2,\n          },\n        ],\n        sign: {\n          type: Sign.Yield,\n          direction: RelativeDirection.Left,\n        },\n      },\n      west: {\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Left,\n            distance: 2,\n          },\n        ],\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Left,\n        },\n      },\n      north: {\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Right,\n        },\n      },\n    },\n  },\n  '4-3': {\n    ...baseConfig,\n    suggestedSolution: ['look', 'signal-right', 'forward', 'wait', 'wait', 'turn-right', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.Suburbs,\n    playerTarget: RelativeDirection.Right,\n    arms: {\n      south: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Right,\n        },\n      },\n      east: {\n        stopLine: StopLine.Solid,\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Right,\n            distance: 2,\n          },\n        ],\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Left,\n        },\n      },\n      west: {\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Straight,\n            distance: 2,\n          },\n        ],\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Left,\n        },\n      },\n      north: {\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Right,\n        },\n      },\n    },\n  },\n  '4-4': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'forward', 'forward', 'wait', 'wait', 'wait', 'wait', 'forward', 'forward', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.City,\n    playerTarget: RelativeDirection.Straight,\n    arms: {\n      south: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Right,\n        },\n      },\n      east: {\n        stopLine: StopLine.Dashed,\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Left,\n            distance: 3,\n          },\n        ],\n        sign: {\n          type: Sign.Yield,\n          direction: RelativeDirection.Left,\n        },\n      },\n      west: {\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Left,\n            distance: 2,\n          },\n          {\n            type: 'car',\n            target: RelativeDirection.Straight,\n            distance: 3,\n          },\n        ],\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Left,\n        },\n      },\n      north: {\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Right,\n        },\n      },\n    },\n  },\n  '4-5': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'look', 'signal-right', 'wait', 'wait', 'wait', 'wait', 'turn-right', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.City,\n    playerTarget: RelativeDirection.Right,\n    arms: {\n      south: {\n        stopLine: StopLine.Solid,\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Right,\n        },\n      },\n      east: {\n        stopLine: StopLine.Solid,\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Left,\n            distance: 3,\n          },\n        ],\n        sign: {\n          type: Sign.Stop,\n          direction: RelativeDirection.Left,\n        },\n      },\n      west: {\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Left,\n            distance: 2,\n          },\n          {\n            type: 'car',\n            target: RelativeDirection.Straight,\n            distance: 3,\n          },\n        ],\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Left,\n        },\n      },\n      north: {\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Right,\n        },\n      },\n    },\n  },\n  '5-1': {\n    ...baseConfig,\n    suggestedSolution: ['look-left', 'signal-left', 'bear-left', 'look', 'forward', 'turn-left', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.City,\n    playerStartPosition: 5,\n    playerTarget: RelativeDirection.Left,\n    arms: {\n      south: {\n        stopLine: StopLine.Hidden,\n      },\n      east: {\n        stopLine: StopLine.Hidden,\n      },\n      west: {\n        stopLine: StopLine.Hidden,\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Straight,\n            distance: 2,\n          },\n        ],\n      },\n      north: {\n        stopLine: StopLine.Hidden,\n      },\n    },\n  },\n  '5-2': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'forward', 'forward', 'wait', 'wait', 'forward', 'forward', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.City,\n    playerTarget: RelativeDirection.Straight,\n    arms: {\n      south: {\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Right,\n        },\n      },\n      east: {\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Left,\n            distance: 2,\n          },\n          {\n            type: 'car',\n            target: RelativeDirection.Right,\n            distance: 3,\n          },\n        ],\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Left,\n        },\n      },\n      north: {\n        stopLine: StopLine.Dashed,\n        sign: {\n          type: Sign.Yield,\n          direction: RelativeDirection.Right,\n        },\n      },\n    },\n  },\n  '5-3': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'look-left', 'signal-left', 'bear-left', 'wait', 'forward', 'turn-left', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.City,\n    playerStartPosition: 5,\n    playerTarget: RelativeDirection.Left,\n    arms: {\n      south: {\n        stopLine: StopLine.Dashed,\n        sign: {\n          type: Sign.Yield,\n        },\n      },\n      east: {\n        sign: {\n          type: Sign.RightOfWay,\n        },\n      },\n      west: {\n        sign: {\n          type: Sign.RightOfWay,\n        },\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Straight,\n            distance: 2,\n          },\n        ],\n      },\n      north: {\n        stopLine: StopLine.Dashed,\n        sign: {\n          type: Sign.Yield,\n        },\n      },\n    },\n  },\n  '5-4': {\n    ...baseConfig,\n    suggestedSolution: ['forward', 'look-left', 'signal-left', 'bear-left', 'wait', 'wait', 'forward', 'turn-left', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.City,\n    playerStartPosition: 5,\n    playerTarget: RelativeDirection.Left,\n    arms: {\n      south: {\n        stopLine: StopLine.Dashed,\n        sign: {\n          type: Sign.Yield,\n        },\n      },\n      east: {\n        sign: {\n          type: Sign.RightOfWay,\n        },\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Straight,\n            distance: 4,\n          },\n        ],\n      },\n      west: {\n        sign: {\n          type: Sign.RightOfWay,\n        },\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Straight,\n            distance: 2,\n          },\n        ],\n      },\n      north: {\n        stopLine: StopLine.Dashed,\n        sign: {\n          type: Sign.Yield,\n        },\n      },\n    },\n  },\n  '5-5': {\n    ...baseConfig,\n    suggestedSolution: ['look-left', 'signal-left', 'bear-left', 'look', 'forward', 'turn-left', 'forward', 'forward'],\n    levelStyle: RisiLevelStyle.City,\n    playerStartPosition: 5,\n    playerTarget: RelativeDirection.Left,\n    arms: {\n      south: {\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Right,\n        },\n      },\n      east: {\n        sign: {\n          type: Sign.RightOfWay,\n          direction: RelativeDirection.Left,\n        },\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Left,\n            distance: 2,\n          },\n        ],\n      },\n      west: {\n        stopLine: StopLine.Dashed,\n        sign: {\n          type: Sign.Yield,\n          direction: RelativeDirection.Left,\n        },\n      },\n      north: {\n        stopLine: StopLine.Dashed,\n        sign: {\n          type: Sign.Yield,\n          direction: RelativeDirection.Right,\n        },\n        cars: [\n          {\n            type: 'car',\n            target: RelativeDirection.Straight,\n            distance: 1,\n          },\n        ],\n      },\n    },\n  },\n};\n","const sharedMoves = [\n  'forward',\n  'turn-right',\n  'turn-left',\n  'bear-left',\n  'bear-right',\n  'signal-left',\n  'signal-right',\n  'wait',\n] as const;\n\nexport const vehicleMoves = [\n  ...sharedMoves,\n] as const;\n\nexport const playerMoves = [\n  ...sharedMoves,\n  'look-left',\n  'look',\n] as const;\n\nexport type VehicleMove = typeof vehicleMoves[number];\nexport type PlayerMove = typeof playerMoves[number];\nexport type ActorMove = VehicleMove | PlayerMove;\n","import { ActorMove, PlayerMove } from './RisiMoves';\n\nexport interface IRisiActor {\n  type: 'car' | 'player';\n  id: string;\n  coords: ICoords;\n  direction: Direction;\n  bearingLeft?: boolean;\n  signalingLeft?: boolean;\n  signalingRight?: boolean;\n  handExtendedLeft?: boolean;\n  handExtendedRight?: boolean;\n  lookingLeft?: boolean;\n  lookingRight?: boolean;\n  lookedBeforeIntersection?: boolean;\n  lookedLeft?: boolean;\n  lookedRight?: boolean;\n  stopping?: boolean;\n}\n\nexport enum RisiStatus {\n  Plan = 'plan',\n  Execute = 'execute',\n  Success = 'success',\n  Fail = 'fail'\n}\n\nexport interface IRisiPlayerMove {\n  type: PlayerMove;\n}\n\nexport enum Direction {\n  Up = 0,\n  Right = 90,\n  Down = 180,\n  Left = 270,\n}\n\nexport enum RelativeDirection {\n  Straight = 'straight',\n  Left = 'left',\n  Right = 'right',\n  Back = 'back',\n}\n\nexport interface ICoords {\n  x: number;\n  y: number;\n}\n\nexport enum Marking {\n  Solid = 'solid',\n  Dashed = 'dashed',\n  DashedCurve = 'dashed-curve',\n}\n\nexport enum StopLine {\n  Hidden = 'hidden',\n  Solid = 'solid',\n  Dashed = 'dashed',\n}\n\nexport enum Sign {\n  Stop = 'stop',\n  RightOfWay = 'row',\n  Yield = 'yield',\n}\n\nexport interface ISign {\n  type: Sign;\n  direction?: RelativeDirection;\n  offset?: number;\n}\n\nexport interface IStreet {\n  directions?: Direction[];\n  stopLine?: StopLine;\n  marking?: Marking;\n  intersection?: Direction;\n  beforeIntersection?: Direction;\n  afterIntersection?: Direction;\n  sign?: ISign;\n}\n\nexport interface ISpace {\n  coords: ICoords;\n  street?: IStreet;\n  goal?: boolean;\n}\n\nexport interface IRisiActorMove {\n  type: ActorMove;\n  actor: IRisiActor;\n}\n\nexport interface IRisiActorUndoMove {\n  move: IRisiActorMove;\n  oldState: IRisiActor;\n}\n\nexport enum RisiLevelStyle {\n  Outskirts = 'outskirts',\n  Suburbs = 'suburbs',\n  City = 'city',\n}\n\nexport interface IRisiConfig {\n  rows: number;\n  columns: number;\n  origin: ICoords;\n  playerStartPosition: number;\n  playerTarget: RelativeDirection;\n  arms: IAbsoluteIntersectionConfig;\n  levelStyle?: RisiLevelStyle;\n  goalDistance: number;\n  suggestedSolution: PlayerMove[];\n  specialRules?: {\n    ignoreMissingLookRight?: boolean;\n  };\n}\n\nexport interface IAbsoluteIntersectionConfig {\n  south?: IIntersectionArmConfig;\n  north?: IIntersectionArmConfig;\n  west?: IIntersectionArmConfig;\n  east?: IIntersectionArmConfig;\n}\n\nexport interface IRelativeIntersectionConfig {\n  back?: IIntersectionArmConfig;\n  straight?: IIntersectionArmConfig;\n  left?: IIntersectionArmConfig;\n  right?: IIntersectionArmConfig;\n}\n\nexport interface ICarConfig {\n  type: 'car';\n  distance?: number;\n  target?: RelativeDirection;\n}\n\nexport interface IIntersectionArmConfig {\n  stopLine?: StopLine;\n  cars?: ICarConfig[];\n  sign?: ISign;\n}\n\nexport enum FailReason {\n  Collision = 'collision',\n  DangerousMove = 'dangerous-move',\n  DidNotLookLeft = 'did-not-look-left',\n  DidNotLook = 'did-not-look',\n  DidNotSignal = 'did-not-signal',\n  FacingOffRoad = 'facing-off-road',\n  GoingAgainstTraffic = 'going-against-traffic',\n  IgnoredCrossingTraffic = 'ignored-crossing-traffic',\n  IgnoredRightOfWay = 'ignored-right-of-way',\n  IgnoredStopSign = 'ignored-stop-sign',\n  InterruptedOncomingLeftTurn = 'interrupted-oncoming-left-turn',\n  NotCorrectlyAligned = 'not-correctly-aligned',\n  OffRoad = 'off-road',\n  Slowpoke = 'slowpoke',\n  UnnecessarySignal = 'unnecessary-signal',\n  ViolatedSafetyDistance = 'violated-safety-distance',\n  ViolatedTrafficFlow = 'violated-traffic-flow',\n  WrongAlignment = 'wrong-alignment',\n  WrongRoute = 'wrong-route',\n  WrongSignal = 'wrong-signal'\n}\n","import {\n  Direction,\n  IAbsoluteIntersectionConfig,\n  ICoords,\n  IRelativeIntersectionConfig,\n  ISpace,\n  RelativeDirection,\n} from './RisiTypes';\n\nexport class RisiUtils {\n  /**\n   * @param coords start coordinates\n   * @param direction \"forward\" direction\n   * @param spacesForward \"walk this amount forward\" (negative value for backward)\n   * @param spacesAcross \"walk this amount to the right\" (negative value for left)\n   *\n   * example:\n   * `getCoordsAhead({x:1, y:2}, Direction.Right, 3, -1)`\n   *\n   * ```\n   * x:start\n   * o:target\n   *\n   * ......\n   * ....o.\n   * .x--^.\n   * ......\n   * ```\n   */\n  public static getCoordsAhead(coords: ICoords, direction: Direction, spacesForward = 1, spacesAcross = 0): ICoords {\n    const firstStep = this.addVectors(coords, this.multiplyVector(this.getDirectionalVector(direction), spacesForward));\n    return spacesAcross === 0\n      ? firstStep\n      : this.addVectors(firstStep, this.multiplyVector(this.getDirectionalVector(this.turnRight(direction)), spacesAcross));\n  }\n\n  public static turnLeft(direction: Direction): Direction {\n    return (direction + 270) % 360;\n  }\n\n  public static turnRight(direction: Direction): Direction {\n    return (direction + 90) % 360;\n  }\n\n  public static turnAround(direction: Direction): Direction {\n    return (direction + 180) % 360;\n  }\n\n  public static turnBy(direction: Direction, relativeDirection: RelativeDirection): Direction {\n    switch(relativeDirection) {\n      case RelativeDirection.Left: return this.turnLeft(direction);\n      case RelativeDirection.Right: return this.turnRight(direction);\n      case RelativeDirection.Back: return this.turnAround(direction);\n      default: return direction;\n    }\n  }\n\n  public static getDirectionalVector(direction: Direction): ICoords {\n    if(direction === Direction.Up) return { x: 0, y: -1 };\n    if(direction === Direction.Right) return { x: 1, y: 0 };\n    if(direction === Direction.Down) return { x: 0, y: 1 };\n    if(direction === Direction.Left) return { x: -1, y: 0 };\n    return { x: 0, y: 0 };\n  }\n\n  public static addVectors(v1: ICoords, v2: ICoords): ICoords {\n    return {\n      x: v1.x + v2.x,\n      y: v1.y + v2.y,\n    };\n  }\n\n  public static multiplyVector(v: ICoords, scalar: number): ICoords {\n    return {\n      x: v.x * scalar,\n      y: v.y * scalar,\n    };\n  }\n\n  public static areCoordsEqual(v1: ICoords, v2: ICoords): boolean {\n    return v1.x === v2.x && v1.y === v2.y;\n  }\n\n  /**\n   * relative intersection config when looking in 'direction'\n   */\n  public static relativeIntersection(absoluteIntersectionArms: IAbsoluteIntersectionConfig, direction: Direction): IRelativeIntersectionConfig {\n    const clockwiseArms = [absoluteIntersectionArms.south, absoluteIntersectionArms.west, absoluteIntersectionArms.north, absoluteIntersectionArms.east];\n\n    if(direction === Direction.Right) clockwiseArms.push(clockwiseArms.shift());\n    if(direction === Direction.Down) clockwiseArms.push(clockwiseArms.shift(), clockwiseArms.shift());\n    if(direction === Direction.Left) clockwiseArms.unshift(clockwiseArms.pop());\n\n    return {\n      back: clockwiseArms[0],\n      left: clockwiseArms[1],\n      straight: clockwiseArms[2],\n      right: clockwiseArms[3],\n    };\n  }\n\n  public static getSpace(spaceMap: ISpace[][], coords: ICoords): ISpace | undefined {\n    return spaceMap[coords.x]?.[coords.y];\n  }\n\n  public static getCardinalNeighboringSpaces(\n    spaceMap: ISpace[][],\n    coords: ICoords,\n  ): Partial<Record<'top' | 'left' | 'right' | 'bottom', ISpace>> {\n    return {\n      top: spaceMap[coords.x]?.[coords.y - 1],\n      right: spaceMap[coords.x + 1]?.[coords.y],\n      bottom: spaceMap[coords.x]?.[coords.y + 1],\n      left: spaceMap[coords.x - 1]?.[coords.y],\n    };\n  }\n\n  public static getDiagonalNeighboringSpaces(\n    spaceMap: ISpace[][],\n    coords: ICoords,\n  ): Partial<Record<'topLeft' | 'bottomLeft' | 'topRight' | 'bottomRight', ISpace>> {\n    return {\n      topLeft: spaceMap[coords.x - 1]?.[coords.y - 1],\n      topRight: spaceMap[coords.x + 1]?.[coords.y - 1],\n      bottomRight: spaceMap[coords.x + 1]?.[coords.y + 1],\n      bottomLeft: spaceMap[coords.x - 1]?.[coords.y + 1],\n    };\n  }\n\n  public static detectInnerCorner(\n    cardinalNeighbors: Partial<Record<'top' | 'left' | 'right' | 'bottom', ISpace>>,\n    isHit: (neighbor1?: ISpace, neighbor2?: ISpace) => boolean,\n  ): Direction | undefined {\n    if(isHit(cardinalNeighbors.top, cardinalNeighbors.left)) return Direction.Up;\n    if(isHit(cardinalNeighbors.top, cardinalNeighbors.right)) return Direction.Right;\n    if(isHit(cardinalNeighbors.bottom, cardinalNeighbors.right)) return Direction.Down;\n    if(isHit(cardinalNeighbors.bottom, cardinalNeighbors.left)) return Direction.Left;\n    return undefined;\n  }\n\n  public static detectOuterCorner(\n    diagonalNeighbors: Partial<Record<'topLeft' | 'bottomLeft' | 'topRight' | 'bottomRight', ISpace>>,\n    isHit: (neighbor?: ISpace) => boolean,\n  ): Direction | undefined {\n    if(isHit(diagonalNeighbors.topLeft)) return Direction.Down;\n    if(isHit(diagonalNeighbors.topRight)) return Direction.Left;\n    if(isHit(diagonalNeighbors.bottomRight)) return Direction.Up;\n    if(isHit(diagonalNeighbors.bottomLeft)) return Direction.Right;\n    return undefined;\n  }\n\n  public static detectEdge(\n    cardinalNeighbors: Partial<Record<'top' | 'left' | 'right' | 'bottom', ISpace>>,\n    isHit: (neighbor?: ISpace) => boolean,\n  ): Direction | undefined {\n    if(isHit(cardinalNeighbors.top)) return Direction.Left;\n    if(isHit(cardinalNeighbors.right)) return Direction.Up;\n    if(isHit(cardinalNeighbors.bottom)) return Direction.Right;\n    if(isHit(cardinalNeighbors.left)) return Direction.Down;\n    return undefined;\n  }\n}\n","import {\n  Direction,\n  ICoords,\n  IRisiActor,\n  RelativeDirection,\n} from '../RisiTypes';\n\nexport function createCar(id: string, coords: ICoords, direction: Direction, target: RelativeDirection): IRisiActor {\n  return {\n    type: 'car',\n    id,\n    coords,\n    direction,\n    bearingLeft: target === RelativeDirection.Left,\n    signalingLeft: target === RelativeDirection.Left,\n    signalingRight: target === RelativeDirection.Right,\n    lookedBeforeIntersection: true,\n  };\n}\n","import { Direction, ICoords, IRisiActor } from '../RisiTypes';\n\nexport function createPlayer(coords: ICoords, direction: Direction): IRisiActor {\n  return {\n    type: 'player',\n    id: 'player',\n    coords,\n    direction,\n    bearingLeft: false,\n  };\n}\n","// this file has be autogenerated by generateIndex.js\n// typechecking disabled because ts can't handle the webpack loader\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport Logo from './Logo.svg';\nimport RisiGameActorsBikerBiker from './actors/biker/biker.svg';\nimport RisiGameActorsBikerSignalingLeft from './actors/biker/signaling-left.svg';\nimport RisiGameActorsBikerSignalingRight from './actors/biker/signaling-right.svg';\nimport RisiGameActorsCarCar from './actors/car/car.svg';\nimport RisiGameBuildingsHouse01Grass from './buildings/house-01-grass.svg';\nimport RisiGameBuildingsHouse01 from './buildings/house-01.svg';\nimport RisiGameBuildingsHouse02Grass from './buildings/house-02-grass.svg';\nimport RisiGameBuildingsHouse02 from './buildings/house-02.svg';\nimport RisiGameBuildingsHouse03Grass from './buildings/house-03-grass.svg';\nimport RisiGameBuildingsHouse03 from './buildings/house-03.svg';\nimport RisiGameFoliage01 from './foliage/01.svg';\nimport RisiGameFoliage02 from './foliage/02.svg';\nimport RisiGameFoliage03 from './foliage/03.svg';\nimport RisiGameFoliage04 from './foliage/04.svg';\nimport RisiGameMainmapObjectsPrerendered from './mainmap-objects.prerendered.svg';\nimport RisiGameSignsSignBack from './signs/sign-back.svg';\nimport RisiGameSignsSignFront from './signs/sign-front.svg';\nimport RisiGameTilesGroundCurb from './tiles/ground/curb.svg';\nimport RisiGameTilesGroundCurbCurve from './tiles/ground/curb_curve.svg';\nimport RisiGameTilesGroundGrassCurve from './tiles/ground/grass-curve.svg';\nimport RisiGameTilesGroundGrassPlain from './tiles/ground/grass-plain.svg';\nimport RisiGameTilesGroundPavementBorderCurve from './tiles/ground/pavement-border-curve.svg';\nimport RisiGameTilesGroundPavementBorder from './tiles/ground/pavement-border.svg';\nimport RisiGameTilesGroundPavementCurveGrass from './tiles/ground/pavement-curve-grass.svg';\nimport RisiGameTilesGroundPavement from './tiles/ground/pavement.svg';\nimport RisiGameTilesGroundPavement2CurveGrass from './tiles/ground/pavement2-curve-grass.svg';\nimport RisiGameTilesGroundPavement2Curve from './tiles/ground/pavement2-curve.svg';\nimport RisiGameTilesGroundPavement2 from './tiles/ground/pavement2.svg';\nimport RisiGameTilesGroundPavementCurve from './tiles/ground/pavement_curve.svg';\nimport RisiGameTilesGroundStreetCurveWide from './tiles/ground/street-curve-wide.svg';\nimport RisiGameTilesGroundStreet from './tiles/ground/street.svg';\nimport RisiGameTilesGroundStreetCurveOuterGrass from './tiles/ground/street_curve-outer-grass.svg';\nimport RisiGameTilesGroundStreetCurvePavement from './tiles/ground/street_curve-pavement.svg';\nimport RisiGameTilesGroundStreetCurve from './tiles/ground/street_curve.svg';\nimport RisiGameTilesMarkingsDashedCurve from './tiles/markings/dashed-curve.svg';\nimport RisiGameTilesMarkingsDashedStraight from './tiles/markings/dashed-straight.svg';\nimport RisiGameTilesMarkingsSolidStraight from './tiles/markings/solid-straight.svg';\nimport RisiGameTilesMarkingsStoplineDashed from './tiles/markings/stopline-dashed.svg';\nimport RisiGameTilesMarkingsStopline from './tiles/markings/stopline.svg';\nimport RisiGameTilesMarkingsZebra from './tiles/markings/zebra.svg';\nimport RisiGameUiActionsBearLeft from './ui/actions/bear-left.svg';\nimport RisiGameUiActionsBearRight from './ui/actions/bear-right.svg';\nimport RisiGameUiActionsForward from './ui/actions/forward.svg';\nimport RisiGameUiActionsLookLeft from './ui/actions/look-left.svg';\nimport RisiGameUiActionsLookRight from './ui/actions/look-right.svg';\nimport RisiGameUiActionsLook from './ui/actions/look.svg';\nimport RisiGameUiActionsSignalLeft from './ui/actions/signal-left.svg';\nimport RisiGameUiActionsSignalRight from './ui/actions/signal-right.svg';\nimport RisiGameUiActionsTurnLeft from './ui/actions/turn-left.svg';\nimport RisiGameUiActionsTurnRight from './ui/actions/turn-right.svg';\nimport RisiGameUiActionsWait from './ui/actions/wait.svg';\nimport RisiGameUiCheck from './ui/check.svg';\nimport RisiGameUiFlag from './ui/flag.svg';\nimport RisiGameUiGoal from './ui/goal.svg';\n// @ts-check\n\nexport const imageNameToString = {\n  Logo,\n  RisiGameActorsBikerBiker,\n  RisiGameActorsBikerSignalingLeft,\n  RisiGameActorsBikerSignalingRight,\n  RisiGameActorsCarCar,\n  RisiGameBuildingsHouse01Grass,\n  RisiGameBuildingsHouse01,\n  RisiGameBuildingsHouse02Grass,\n  RisiGameBuildingsHouse02,\n  RisiGameBuildingsHouse03Grass,\n  RisiGameBuildingsHouse03,\n  RisiGameFoliage01,\n  RisiGameFoliage02,\n  RisiGameFoliage03,\n  RisiGameFoliage04,\n  RisiGameMainmapObjectsPrerendered,\n  RisiGameSignsSignBack,\n  RisiGameSignsSignFront,\n  RisiGameTilesGroundCurb,\n  RisiGameTilesGroundCurbCurve,\n  RisiGameTilesGroundGrassCurve,\n  RisiGameTilesGroundGrassPlain,\n  RisiGameTilesGroundPavementBorderCurve,\n  RisiGameTilesGroundPavementBorder,\n  RisiGameTilesGroundPavementCurveGrass,\n  RisiGameTilesGroundPavement,\n  RisiGameTilesGroundPavement2CurveGrass,\n  RisiGameTilesGroundPavement2Curve,\n  RisiGameTilesGroundPavement2,\n  RisiGameTilesGroundPavementCurve,\n  RisiGameTilesGroundStreetCurveWide,\n  RisiGameTilesGroundStreet,\n  RisiGameTilesGroundStreetCurveOuterGrass,\n  RisiGameTilesGroundStreetCurvePavement,\n  RisiGameTilesGroundStreetCurve,\n  RisiGameTilesMarkingsDashedCurve,\n  RisiGameTilesMarkingsDashedStraight,\n  RisiGameTilesMarkingsSolidStraight,\n  RisiGameTilesMarkingsStoplineDashed,\n  RisiGameTilesMarkingsStopline,\n  RisiGameTilesMarkingsZebra,\n  RisiGameUiActionsBearLeft,\n  RisiGameUiActionsBearRight,\n  RisiGameUiActionsForward,\n  RisiGameUiActionsLookLeft,\n  RisiGameUiActionsLookRight,\n  RisiGameUiActionsLook,\n  RisiGameUiActionsSignalLeft,\n  RisiGameUiActionsSignalRight,\n  RisiGameUiActionsTurnLeft,\n  RisiGameUiActionsTurnRight,\n  RisiGameUiActionsWait,\n  RisiGameUiCheck,\n  RisiGameUiFlag,\n  RisiGameUiGoal,\n} as const;\n"],"names":[],"sourceRoot":""}