frontispiece / packages / ink-scriptwriter / src / index.ts
index.ts
Raw
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

import { InkProcessorEngine } from '@a-morphous/frontispiece-ink-processor'
import {
	VisualInkChoice,
	VisualInkLineOrCommand,
} from '@a-morphous/frontispiece-ink-processor/src/types'
import { Story } from 'inkjs/engine/Story'
import inquirer, { DistinctChoice } from 'inquirer'

type LineParser = (inkLine: VisualInkLineOrCommand) => string
type ChoiceParser = (inkChoice: VisualInkChoice) => string
type ChoiceEngine = (choices: VisualInkChoice[], engine: InkProcessorEngine) => Promise<number>

type ScriptOptions = {
	customEngine?: InkProcessorEngine // can override the story
	lineParser: LineParser
	choiceParser: ChoiceParser
	choiceEngine: ChoiceEngine

	// set this to do something different than the default `engine.makeChoice`. Useful mostly for storylets.
	overrideMakeChoice?: (
		engine: InkProcessorEngine,
		choices: VisualInkChoice[],
		choiceResult: number
	) => void
	verbose: boolean
}

export const defaultLineParser: LineParser = (inkLine: VisualInkLineOrCommand): string => {
	if (inkLine.type === 'command') {
		return ''
	}
	return inkLine.text + '\n'
}

export const defaultChoiceParser: ChoiceParser = (inkChoice: VisualInkChoice): string => {
	return '> ' + inkChoice.text + '\n\n'
}

export const defaultChoiceEngine: ChoiceEngine = async (choices: VisualInkChoice[], engine) => {
	const choiceResult = await inquirer.prompt([
		{
			name: 'choice',
			type: 'list',
			message: engine.activeLine.type === 'line' ? engine.activeLine.text : '',
			choices: () => {
				const cArray: DistinctChoice[] = []
				for (let choice of choices) {
					cArray.push({
						key: (choice.index + 1).toString(),
						name: choice.text,
						value: choice.index,
					})
				}
				return cArray
			},
		},
	])
	return choiceResult.choice
}

const defaultOptions: ScriptOptions = {
	lineParser: defaultLineParser,
	choiceParser: defaultChoiceParser,
	choiceEngine: defaultChoiceEngine,
	verbose: false,
}

export const createScript = async (
	story: Story,
	options: Partial<ScriptOptions> = {}
): Promise<string> => {
	let finalString = ''
	let mergedOptions = { ...defaultOptions, ...options }

	const engine = mergedOptions.customEngine ?? new InkProcessorEngine(story)

	while (engine.canAdvance || engine.choices.length > 0) {
		if (engine.canAdvance) {
			engine.advance()
			if (mergedOptions.verbose) {
				console.log(mergedOptions.lineParser(engine.getActiveLine()))
			}

			finalString += mergedOptions.lineParser(engine.getActiveLine())
		} else {
			const choiceResult = await mergedOptions.choiceEngine(engine.choices, engine)
			finalString += mergedOptions.choiceParser(engine.choices[choiceResult])
			if (mergedOptions.overrideMakeChoice) {
				mergedOptions.overrideMakeChoice(engine, engine.choices, choiceResult)
			} else {
				engine.makeChoice(choiceResult)
				engine.advance()
			}
		}
	}

	return finalString
}