"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
// tslint:disable:no-console
const chalk_1 = require("chalk");
const commander = require("commander");
const font_ascii_1 = require("font-ascii");
const fs_1 = require("fs");
const glob = require("glob");
const inquirer = require("inquirer");
const autocomplete = require("inquirer-autocomplete-prompt");
const path = require("path");
const pify = require("pify");
const supportsColor = require("supports-color");
const __1 = require("..");
const recordCoverage_1 = require("../recordCoverage");
const terminal_1 = require("../ui/terminal");
const solvedGames_1 = require("./solvedGames");
const titleFonts_1 = require("./titleFonts");
solvedGames_1.default.add(`Skipping Stones to Lonely Homes`);
solvedGames_1.default.add(`Spikes 'n' Stuff`);
var CLI_SPRITE_SIZE;
(function (CLI_SPRITE_SIZE) {
    CLI_SPRITE_SIZE["LARGE"] = "large";
    CLI_SPRITE_SIZE["SMALL"] = "small";
})(CLI_SPRITE_SIZE || (CLI_SPRITE_SIZE = {}));
// Use require instead of import so we can load JSON files
const pkg = require('../../package.json'); // tslint:disable-line:no-var-requires
function sleep(ms) {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise((resolve) => setTimeout(resolve, ms));
    });
}
function first2Lines(filePath) {
    const opts = {
        encoding: 'utf8',
        lineEnding: '\n'
    };
    return new Promise((resolve, reject) => {
        const rs = fs_1.createReadStream(filePath, { encoding: opts.encoding });
        let acc = '';
        let pos = 0;
        let index;
        rs
            .on('data', (chunk) => {
            index = chunk.indexOf(opts.lineEnding);
            acc += chunk;
            if (index === -1) {
                pos += chunk.length;
            }
            else {
                index = chunk.indexOf(opts.lineEnding, index + 1);
                if (index >= 0) {
                    pos += index;
                    rs.close();
                }
                else {
                    pos += chunk.length;
                }
            }
        })
            .on('close', () => resolve(acc.slice(acc.charCodeAt(0) === 0xFEFF ? 1 : 0, pos)))
            .on('error', (err) => reject(err));
    });
}
// keypress(process.stdin)
// keypress.enableMouse(process.stdout)
function toUnicode(theString) {
    let unicodeString = '';
    for (let i = 0; i < theString.length; i++) {
        let theUnicode = theString.charCodeAt(i).toString(16).toUpperCase();
        while (theUnicode.length < 4) {
            theUnicode = '0' + theUnicode;
        }
        theUnicode = '\\u' + theUnicode;
        unicodeString += theUnicode;
    }
    return unicodeString;
}
commander
    .version(pkg.version)
    .option('--no-ui', 'just output text instead of renndering the puzzles (for accessibility)')
    .option('-g, --game <title>', 'play a specific game matching this title regexp or "random" to pick a random one')
    .option('-s, --size <largeOrSmall>', 'Specify the sprite size (either "large" or "small")')
    .option('-n, --new', 'start a new game')
    .option('-l, --level <num>', 'play a specific level', ((arg) => parseInt(arg, 10)))
    .option('-r, --resume', 'resume the level from last save')
    .option('--nosound', 'disable sound')
    .parse(process.argv);
run().then(() => { process.exit(0); }, (err) => {
    console.error(err);
    process.exit(111);
});
function run() {
    return __awaiter(this, void 0, void 0, function* () {
        inquirer.registerPrompt('autocomplete', autocomplete);
        const gists = yield pify(glob)(path.join(__dirname, '../../gists/*/script.txt'));
        const cliOptions = commander.opts();
        const { ui: cliUi, game: cliGameTitle, nosound } = cliOptions;
        let { level: cliLevel, resume: cliResume } = cliOptions;
        const gamePathInitial = commander.args[0];
        if (gamePathInitial) {
            yield startPromptsAndPlayGame(gamePathInitial, null, cliResume, cliLevel, nosound);
            return; // we are only playing one game
        }
        const games = [];
        const titleRegexp = /title/i;
        for (const filePath of gists) {
            const lines = yield first2Lines(filePath);
            const linesAry = lines.split('\n');
            let title;
            if (titleRegexp.test(linesAry[0])) {
                title = linesAry[0].replace(/title /i, '');
            }
            else if (titleRegexp.test(linesAry[1])) {
                title = linesAry[1].replace(/title /i, '');
            }
            else {
                throw new Error(`BUG: Game does not have a title section in the 1st 2 lines`);
            }
            games.push({
                id: path.basename(path.dirname(filePath)),
                title,
                filePath
            });
        }
        if (!cliGameTitle) {
            const possibleFonts = titleFonts_1.default.filter(({ minWidth }) => minWidth <= (process.stdout.columns || 80));
            const font = possibleFonts[Math.floor(Math.random() * possibleFonts.length)]; // pick a random one
            console.log(``);
            console.log(`Let's play some`);
            console.log(``);
            if (font) {
                font_ascii_1.default('Puzzle Games', { typeface: font.name });
            }
            else {
                console.log('Puzzle Games');
            }
            console.log(``);
            console.log(`(${chalk_1.default.bold.whiteBright(`${games.length}`)} games to choose from)`);
            console.log(``);
            console.log(``);
            console.log(`${chalk_1.default.dim('Games that are')} ${chalk_1.default.bold.whiteBright('white')} ${chalk_1.default.dim('are great to start out,')} ${chalk_1.default.bold.white('gray')} ${chalk_1.default.dim('are fun and run, and')} ${chalk_1.default.bold.dim('dark')} ${chalk_1.default.dim('may or may not run.')}`); // tslint:disable-line:max-line-length
            console.log(``);
        }
        // loop indefinitely
        let wantsToPlayAgain = false;
        do {
            const { filePath: gamePath, id: gistId } = yield promptGame(games, cliGameTitle);
            yield startPromptsAndPlayGame(gamePath, gistId, cliResume, cliLevel, nosound);
            if (!cliGameTitle) {
                wantsToPlayAgain = yield promptPlayAnother();
            }
            // clear the level and resume Options since if they play another game the options may not apply
            cliResume = undefined;
            cliLevel = undefined;
        } while (wantsToPlayAgain);
        if (cliUi) {
            console.log(``);
            console.log(``);
            console.log(`Thanks for playing PuzzleScript Games! Check out these links for more:`);
            console.log(``);
            console.log(`- ${chalk_1.default.blueBright('https://puzzlescript.net')} : The PuzzleScript homepage`);
            console.log(`- ${chalk_1.default.blueBright(pkg.homepage)} : Code for this program (${chalk_1.default.green('Help improve our code!')})`);
            console.log(``);
        }
    });
}
function startPromptsAndPlayGame(gamePath, gistId, cliResume, cliLevel, nosound) {
    return __awaiter(this, void 0, void 0, function* () {
        const cliOptions = commander.opts();
        const { ui: cliUi, size: cliSpriteSize, new: cliNewGame } = cliOptions;
        const absPath = path.resolve(gamePath);
        const code = fs_1.readFileSync(absPath, 'utf-8');
        const { data } = __1.Parser.parse(code);
        console.log('');
        console.log(`Opened Game: "${chalk_1.default.blueBright(data.title)}"`);
        console.log('');
        showControls();
        // check to see if the terminal is too small
        yield promptPixelSize(data, cliUi ? cliSpriteSize : CLI_SPRITE_SIZE.SMALL);
        // Load the solutions file (if it exists) so we can append to it
        const solutionsPath = path.join(__dirname, `../../gist-solutions/${gistId}.json`);
        let recordings;
        if (gistId && fs_1.existsSync(solutionsPath)) {
            recordings = JSON.parse(fs_1.readFileSync(solutionsPath, 'utf-8'));
        }
        else {
            recordings = { version: 1, solutions: [], title: data.title, totalLevels: data.levels.length, totalMapLevels: data.levels.filter((l) => l.isMap()).length }; // default
        }
        const currentLevelNum = yield promptChooseLevel(recordings, data, cliNewGame ? 0 : cliLevel);
        // Allow the user to resume from where they left off
        let ticksToRunFirst = '';
        if (recordings.solutions[currentLevelNum] && (recordings.solutions[currentLevelNum].partial || recordings.solutions[currentLevelNum].solution)) {
            let shouldResume;
            if (cliResume !== undefined) {
                shouldResume = cliResume;
            }
            else {
                shouldResume = (yield inquirer.prompt({
                    type: 'confirm',
                    name: 'shouldResume',
                    message: 'Would you like to resume where you left off?'
                })).shouldResume;
            }
            if (shouldResume) {
                ticksToRunFirst = recordings.solutions[currentLevelNum].partial || recordings.solutions[currentLevelNum].solution || '';
            }
        }
        // Prepare the keyboard handler
        if (process.stdin.setRawMode) {
            process.stdin.setRawMode(true);
        }
        else {
            throw new Error(`ERROR: stdin does not allow setting setRawMode (we need that for keyboard input`);
        }
        process.stdin.resume();
        process.stdin.setEncoding('utf8');
        terminal_1.default.clearScreen();
        yield playGame(data, currentLevelNum, recordings, ticksToRunFirst, absPath, solutionsPath, cliUi, cliLevel !== undefined /*only run one level if specified*/, nosound);
    });
}
function playGame(data, currentLevelNum, recordings, ticksToRunFirst, absPath, solutionsPath, cliUi, onlyOneLevel, nosound) {
    return __awaiter(this, void 0, void 0, function* () {
        if (process.env.LOG_LEVEL === 'debug') {
            console.error(`Start playing "${data.title}". Level ${currentLevelNum}`);
        }
        const ticksToRunFirstAry = ticksToRunFirst.split('');
        terminal_1.default.setHasVisualUi(cliUi);
        const level = data.levels[currentLevelNum];
        if (!level) {
            throw new Error(`BUG: Could not find level ${currentLevelNum}`);
        }
        const engine = new __1.GameEngine(data);
        engine.on('loading-cells', ({ cellStart, cellEnd, cellTotal }) => {
            // UI.writeDebug(`Loading cells ${cellStart}-${cellEnd} of ${cellTotal}. SpriteKey="${key}"`)
            const loading = `Loading... [`;
            const barChars = '                    ';
            terminal_1.default.writeDebug(loading + barChars, 1);
            const offset = loading.length + 1;
            const barLength = barChars.length;
            const percentStartYellow = Math.floor(barLength * cellStart / cellTotal);
            const percentStartBlack = Math.floor(barLength * (cellStart + cellEnd) / cellTotal);
            for (let i = 0; i < barLength; i++) {
                let fgColor = '#707070';
                const char = '█';
                if (i <= percentStartYellow) {
                    fgColor = '#00ff00'; // green
                }
                else if (i <= percentStartBlack) {
                    fgColor = '#ffff00'; // yellow
                    // char = '▒' // ░▒▓
                }
                terminal_1.default._drawPixel(i + offset, 0, fgColor, null, char);
            }
        });
        terminal_1.default.clearScreen();
        engine.setLevel(data.levels.indexOf(level));
        let keypresses = []; // set later once we walk through all the existing partial keys
        let pendingKey = null;
        let tickNum = 0;
        let shouldExitGame = false;
        function restartLevel() {
            engine.pressRestart();
            terminal_1.default.renderScreen(true);
            keypresses = []; // clear key history
        }
        // https://stackoverflow.com/a/30687420
        process.stdin.on('data', handleKeyPress);
        function handleKeyPress(key) {
            if (process.env.NODE_ENV === 'developer' && !terminal_1.default.getHasVisualUi()) {
                console.log(`${chalk_1.default.dim(`Pressed:`)} ${chalk_1.default.whiteBright(key)}`);
            }
            switch (key) {
                case 'W':
                case 'w':
                case '\u001B\u005B\u0041': // UP-ARROW
                    pendingKey = 'W';
                    break;
                case 'S':
                case 's':
                case '\u001B\u005B\u0042': // DOWN-ARROW
                    pendingKey = 'S';
                    break;
                case 'A':
                case 'a':
                case '\u001B\u005B\u0044': // LEFT-ARROW
                    pendingKey = 'A';
                    break;
                case 'D':
                case 'd':
                case '\u001B\u005B\u0043': // RIGHT-ARROW
                    pendingKey = 'D';
                    break;
                case 'X':
                case 'x':
                case ' ':
                case '\u000D':
                    pendingKey = 'X';
                    break;
                case 'R':
                case 'r':
                    return restartLevel();
                case 'U':
                case 'Z':
                case 'u':
                case 'z':
                    // update keypresses so that it does not contain the most-recent key
                    while (keypresses.length > 0) {
                        const lastKey = keypresses[keypresses.length - 1];
                        keypresses.pop();
                        if (!(lastKey === '.' || lastKey === ',')) {
                            break;
                        }
                    }
                    engine.pressUndo();
                    terminal_1.default.renderScreen(false);
                    return;
                case 'C':
                case 'c':
                    terminal_1.default.clearScreen();
                    terminal_1.default.renderScreen(false);
                    return;
                case 'I':
                case 'i':
                    terminal_1.default.moveInspector(__1.RULE_DIRECTION.UP);
                    return;
                case 'J':
                case 'j':
                    terminal_1.default.moveInspector(__1.RULE_DIRECTION.LEFT);
                    return;
                case 'K':
                case 'k':
                    terminal_1.default.moveInspector(__1.RULE_DIRECTION.DOWN);
                    return;
                case 'L':
                case 'l':
                    terminal_1.default.moveInspector(__1.RULE_DIRECTION.RIGHT);
                    return;
                case 'P':
                case 'p':
                    const players = data.getPlayer().getCellsThatMatch();
                    if (players.size === 1) {
                        terminal_1.default.moveInspectorTo([...players][0]);
                    }
                    else {
                        console.log(`There are ${players.size} players. Use tab to select which one`);
                    }
                    return;
                case '\u0003': // Ctrl+C
                    __1.closeSounds();
                    return process.exit(1);
                case '\u001B': // Escape
                    recordCoverage_1.saveCoverageFile(data, absPath, `${path.basename(path.dirname(absPath))}-playgame`);
                    // Save the partially-completed steps
                    if (keypresses.join('').replace(/\./g, '').length > 0) { // skip just empty ticks
                        recordings.solutions[currentLevelNum] = recordings.solutions[currentLevelNum] || {};
                        recordings.solutions[currentLevelNum].partial = keypresses.join('');
                        fs_1.writeFileSync(solutionsPath, JSON.stringify(recordings, null, 2));
                    }
                    __1.closeSounds();
                    shouldExitGame = true;
                    return;
                case '1':
                    if (process.env.NODE_ENV === 'development') {
                        pendingKey = '[pause]';
                    }
                    return;
                case '2':
                    if (process.env.NODE_ENV === 'development') {
                        pendingKey = '[continue]';
                    }
                    return;
                case '9':
                    // Save State
                    if (process.env.NODE_ENV === 'development') {
                        const cellState = engine.saveSnapshotToJSON();
                        recordings.solutions[currentLevelNum].snapshot = { tickNum, cellState };
                        fs_1.writeFileSync(solutionsPath, JSON.stringify(recordings, null, 2));
                    }
                    return;
                case '0':
                    // Load most-recent state
                    if (process.env.NODE_ENV === 'development') {
                        const { snapshot } = recordings.solutions[currentLevelNum];
                        if (snapshot) {
                            const { tickNum: savedTickNum, cellState } = snapshot;
                            engine.loadSnapshotFromJSON(cellState);
                            tickNum = savedTickNum;
                        }
                    }
                    return;
                default:
                    terminal_1.default.writeDebug(`pressed....: "${toUnicode(key)}"`, 1);
            }
        }
        let isPaused = false;
        function doPress(key, recordPress) {
            switch (key) {
                case 'W':
                    engine.pressUp();
                    break;
                case 'S':
                    engine.pressDown();
                    break;
                case 'A':
                    engine.pressLeft();
                    break;
                case 'D':
                    engine.pressRight();
                    break;
                case 'X':
                    engine.pressAction();
                    break;
                case 'b': // so we can set a breakpoint in the playback
                case '[pause]':
                    isPaused = true;
                    break;
                case '[continue]':
                    isPaused = false;
                    break;
                case '.':
                case ',':
                    // just .tick()
                    break;
                default:
                    throw new Error(`BUG: Invalid keypress character "${ticksToRunFirstAry[tickNum]}"`);
            }
            if (recordPress) {
                keypresses.push(key);
            }
        }
        // engine.on('cell:updated', cell => {
        //   UI.drawCellAt(data, cell, cell.rowIndex, cell.colIndex, false)
        // })
        terminal_1.default.setGameEngine(engine);
        terminal_1.default.clearScreen();
        terminal_1.default.renderScreen(false);
        terminal_1.default.writeDebug(`"${data.title}"`, 1);
        let currentlyPlayingSoundPromise = null; // stack the sounds so we know if one is playing
        // Run a bunch of ticks in case the user partially played a level
        let maxTickAndRenderTime = -1;
        for (tickNum = 0; tickNum < ticksToRunFirstAry.length; tickNum++) {
            let key = ticksToRunFirstAry[tickNum];
            const startTime = Date.now();
            if (isPaused && !pendingKey) {
                tickNum--;
                yield sleep(1000);
                continue;
            }
            if (pendingKey) {
                key = pendingKey;
                ticksToRunFirstAry.splice(tickNum, 0, key);
                pendingKey = null;
                if (key === '[continue]') {
                    console.log('New Game Code:');
                    console.log(ticksToRunFirstAry.join(''));
                }
            }
            doPress(key, false);
            const { changedCells, soundToPlay, didLevelChange, messageToShow } = engine.tick();
            // if (changedCells.size === 0 && !messageToShow && 'WSAD'.includes(key)) {
            //     isPaused = true
            //     ticksToRunFirstAry.splice(keyNum, 0, '[PAUSED]')
            // }
            if (soundToPlay && !nosound) {
                if (!currentlyPlayingSoundPromise) {
                    currentlyPlayingSoundPromise = __1.playSound(soundToPlay).then(() => {
                        currentlyPlayingSoundPromise = null;
                        return;
                    });
                }
            }
            if (messageToShow) {
                terminal_1.default.renderMessageScreen(messageToShow);
            }
            // UI.renderScreen(data, engine.currentLevel)
            // Draw any cells that moved
            terminal_1.default.drawCells(changedCells, false);
            if (didLevelChange) {
                if (onlyOneLevel) {
                    return;
                }
                currentLevelNum = engine.getCurrentLevelNum();
                terminal_1.default.clearScreen();
                terminal_1.default.renderScreen(false);
            }
            if (tickNum > 1) { // Skip the 1st couple because they might be cleaning up the level
                maxTickAndRenderTime = Math.max(maxTickAndRenderTime, Date.now() - startTime);
            }
            const tickHistory = ticksToRunFirstAry.slice(Math.max(tickNum - 25, 0), tickNum + 1).join('');
            const msg = `Playback ${tickNum}/${ticksToRunFirstAry.length} of "${data.title}" (took ${Date.now() - startTime}ms) ${tickHistory}`;
            terminal_1.default.writeDebug(msg.substring(0, 160), 1);
            yield sleep(1); // sleep long enough to play sounds
            // await sleep(Math.max(100 - (Date.now() - startTime), 0))
        }
        keypresses = [...ticksToRunFirstAry];
        while (true) {
            let maxSleepTime = 50;
            // Exit the game if the user pressed escape
            if (shouldExitGame) {
                break; // so we can detach key listeners
            }
            if (isPaused && !pendingKey) {
                yield sleep(maxSleepTime);
                continue;
            }
            let didHandleKeyPress = false;
            if (pendingKey && !engine.hasAgain()) {
                doPress(pendingKey, true /*record*/);
                pendingKey = null;
                didHandleKeyPress = true;
            }
            const startTime = Date.now();
            const { changedCells, soundToPlay, messageToShow, didLevelChange, wasAgainTick } = engine.tick();
            if (soundToPlay && !nosound) {
                if (!currentlyPlayingSoundPromise) {
                    currentlyPlayingSoundPromise = __1.playSound(soundToPlay).then(() => {
                        currentlyPlayingSoundPromise = null;
                        return;
                    });
                }
            }
            if (didLevelChange) {
                if (!supportsColor.stdout) {
                    console.log('You beat the level!');
                }
                // Save the solution
                recordings.solutions[currentLevelNum] = { solution: keypresses.join('') };
                fs_1.writeFileSync(solutionsPath, JSON.stringify(recordings, null, 2));
                keypresses = [];
                pendingKey = null;
                currentLevelNum = engine.getCurrentLevelNum();
                terminal_1.default.clearScreen();
                terminal_1.default.renderScreen(true);
                continue;
            }
            // do this after cells are rendered (so they don't cover the message)
            if (messageToShow) {
                terminal_1.default.renderMessageScreen(messageToShow);
            }
            else {
                // Draw any cells that moved
                terminal_1.default.drawCells(changedCells, false);
            }
            const msg = `Tick: ${tickNum} took ${Date.now() - startTime}ms. Moves: ${[...keypresses].reverse().join('').substring(0, 20)}`;
            terminal_1.default.writeDebug(msg.substring(0, 160), 1);
            if (wasAgainTick) {
                keypresses.push(',');
            }
            else {
                if (!didHandleKeyPress && changedCells.size > 0) {
                    keypresses.push('.'); // Add a "tick"
                    if (process.env.NODE_ENV === 'development') {
                        maxSleepTime = 500; // Slow down when every "." matters (for recording)
                    }
                }
                pendingKey = null;
            }
            yield sleep(Math.max(maxSleepTime - (Date.now() - startTime), 0));
            tickNum++;
        }
        // Some versions of node do not offer this method (e.g. maybe v8.11.1)
        if (process.stdin.off) {
            process.stdin.off('data', handleKeyPress);
        }
    });
}
function promptPlayAnother() {
    return __awaiter(this, void 0, void 0, function* () {
        const { playAnotherGame } = yield inquirer.prompt({
            type: 'confirm',
            name: 'playAnotherGame',
            message: 'Would you like to play another game?'
        });
        return playAnotherGame;
    });
}
var START_MODE;
(function (START_MODE) {
    START_MODE["NEW_GAME"] = "New Game";
    START_MODE["CONTINUE"] = "Continue";
    START_MODE["CHOOSE_LEVEL"] = "Choose a Level";
})(START_MODE || (START_MODE = {}));
function promptChooseLevel(recordings, data, cliLevel) {
    return __awaiter(this, void 0, void 0, function* () {
        const levels = data.levels;
        const firstUncompletedLevel = levels
            .indexOf(levels
            .filter((l, index) => !(recordings.solutions[index] && recordings.solutions[index].solution))[0]);
        if (cliLevel !== undefined && cliLevel !== null) {
            return cliLevel;
        }
        // First ask if they want to Continue, Start a new Game, or Choose a Level
        const startModeOptions = [];
        if (firstUncompletedLevel > 0) {
            startModeOptions.push(START_MODE.CONTINUE);
        }
        startModeOptions.push(START_MODE.NEW_GAME);
        startModeOptions.push(new inquirer.Separator());
        startModeOptions.push(START_MODE.CHOOSE_LEVEL);
        const { startMode } = yield inquirer.prompt([{
                type: 'list',
                name: 'startMode',
                message: 'What would you like to do?',
                choices: startModeOptions
            }]);
        if (startMode === START_MODE.NEW_GAME) {
            return 0;
        }
        else if (startMode === START_MODE.CONTINUE) {
            return firstUncompletedLevel;
        }
        else if (startMode !== START_MODE.CHOOSE_LEVEL) {
            throw new Error(`BUG: Invalid startMode: ${startMode}`);
        }
        const { currentLevelNum } = yield inquirer.prompt([{
                type: 'list',
                name: 'currentLevelNum',
                message: 'Which Level would you like to play?',
                default: firstUncompletedLevel,
                pageSize: Math.max(15, terminal_1.getTerminalSize().rows - 15),
                choices: levels.map((levelMap, index) => {
                    const hasSolution = recordings.solutions[index] && recordings.solutions[index].solution;
                    if (levelMap.isMap()) {
                        const levelRows = levelMap.getRows();
                        const cols = levelRows[0];
                        let width = cols.length;
                        let height = levelRows.length;
                        // If flickscreen or zoomscreen is enabled, then change the level size
                        // that is reported
                        const { zoomscreen, flickscreen } = data.metadata;
                        if (flickscreen) {
                            height = flickscreen.height;
                            width = flickscreen.width;
                        }
                        if (zoomscreen) {
                            height = zoomscreen.height;
                            width = zoomscreen.width;
                        }
                        const { columns, rows } = terminal_1.getTerminalSize();
                        const isTooWide = columns < width * 5 * terminal_1.default.PIXEL_WIDTH;
                        const isTooTall = rows < height * 5 * terminal_1.default.PIXEL_HEIGHT;
                        let message = '';
                        if (isTooWide && isTooTall) {
                            message = `(too tall & wide for your terminal)`;
                        }
                        else if (isTooWide) {
                            message = `(too wide for your terminal)`;
                        }
                        else if (isTooTall) {
                            message = `(too tall for your terminal)`;
                        }
                        if (hasSolution) {
                            return {
                                name: `${chalk_1.default.green(`${index}`)} ${chalk_1.default.dim(`(${width} x ${height}) ${chalk_1.default.green('(SOLVED)')}`)} ${chalk_1.default.yellowBright(message)}`,
                                value: index
                            };
                        }
                        else {
                            if (message) {
                                return {
                                    name: `${chalk_1.default.whiteBright(`${index}`)} ${chalk_1.default.red(`(${width} x ${height})`)} ${chalk_1.default.yellowBright(message)}`,
                                    value: index
                                };
                            }
                            else {
                                return {
                                    name: `${chalk_1.default.whiteBright(`${index}`)} ${chalk_1.default.green(`(${width} x ${height})`)}`,
                                    value: index
                                };
                            }
                        }
                    }
                    else {
                        const message = levelMap.getMessage();
                        let snippet = message.split('\n')[0]; // just use the 1st line
                        if (snippet.length > 40) {
                            snippet = `${snippet.substring(0, 40)}...`;
                        }
                        return {
                            name: chalk_1.default.dim(`... "${snippet}"`),
                            value: index
                        };
                    }
                })
            }]);
        return currentLevelNum;
    });
}
function promptPixelSize(data, cliSpriteSize) {
    return __awaiter(this, void 0, void 0, function* () {
        if (cliSpriteSize === CLI_SPRITE_SIZE.SMALL) {
            terminal_1.default.setSmallTerminal(true);
        }
        else if (cliSpriteSize === CLI_SPRITE_SIZE.LARGE) {
            // do nothing since the terminal size is large by default
        }
        else if (!terminal_1.default.willAllLevelsFitOnScreen(data)) {
            // Draw some example sprites
            console.log('Some of the levels in this game are too large for your terminal.');
            console.log('You can resize your terminal or use compact sprites.');
            console.log('There may be some graphical artifacts if you use compact sprites.');
            console.log('Below are examples of a compact sprite and a non-compacted sprite:');
            console.log('');
            const b = chalk_1.default.bgBlueBright(' ');
            const y = chalk_1.default.bgYellowBright(' ');
            const k = chalk_1.default.bgBlack(' ');
            const by = chalk_1.default.bgBlueBright.yellowBright('▄');
            const yb = chalk_1.default.bgYellowBright.blueBright('▄');
            const yk = chalk_1.default.black.bgYellowBright('▄');
            const ky = chalk_1.default.bgBlack.yellowBright('▄');
            const bk = chalk_1.default.bgBlueBright.black('▄');
            console.log(b + b + b + b + b + b + b + b + b + b + b + b + b + b);
            console.log(b + b + b + b + y + y + y + y + y + y + b + b + b + b);
            console.log(b + b + y + y + k + k + y + y + k + k + y + y + b + b);
            console.log(b + b + k + k + y + y + y + y + y + y + k + k + b + b);
            console.log(b + b + y + y + k + k + k + k + k + k + y + y + b + b);
            console.log(b + b + b + b + y + y + y + y + y + y + b + b + b + b);
            console.log(b + b + b + b + b + b + b + b + b + b + b + b + b + b);
            console.log('');
            console.log('');
            console.log(b + b + by + by + by + b + b);
            console.log(b + yk + ky + y + ky + yk + b);
            console.log(b + yb + ky + ky + ky + yb + b);
            console.log(bk + bk + bk + bk + bk + bk + bk);
            const { useCompressedCharacters } = yield inquirer.prompt({
                type: 'confirm',
                name: 'useCompressedCharacters',
                default: process.env.NODE_ENV !== 'development',
                message: 'Would you like to use small characters when rendering the game?'
            });
            if (useCompressedCharacters) {
                terminal_1.default.setSmallTerminal(true);
            }
        }
    });
}
// https://stackoverflow.com/a/12646864
function shuffleArray(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]]; // eslint-disable-line no-param-reassign
    }
    return array;
}
function percentComplete(game) {
    const solutionsPath = path.join(__dirname, `../../gist-solutions/${game.id}.json`);
    let message = process.env.NODE_ENV === 'development' ? chalk_1.default.bold.red('(unstarted)') : '';
    if (fs_1.existsSync(solutionsPath)) {
        const recordings = JSON.parse(fs_1.readFileSync(solutionsPath, 'utf-8'));
        const numerator = recordings.solutions.filter((s) => s && s.solution && s.solution.length > 1).length;
        const denominator = recordings.totalMapLevels;
        const percent = 100 * numerator / denominator;
        let colorFn = chalk_1.default.bold.red;
        if (numerator === denominator) {
            colorFn = chalk_1.default.blueBright;
        }
        else if (percent > 75) {
            colorFn = chalk_1.default.greenBright;
        }
        else if (percent > 25) {
            colorFn = chalk_1.default.yellowBright;
        }
        else if (percent > 0) {
            colorFn = chalk_1.default.redBright;
        }
        message = chalk_1.default.gray(`(${colorFn(`${numerator}/${denominator}`)})`);
    }
    return message;
}
function promptGame(games, cliGameTitle) {
    return __awaiter(this, void 0, void 0, function* () {
        // Sort games by games that are fun and should be played first
        const firstGames = [
            'Pot Wash Panic',
            'PUSH',
            'Beam Islands',
            'Pants, Shirt, Cap',
            'Entanglement - Chapter One',
            'Mirror Isles',
            'IceCrates',
            'Boxes & Balloons',
            'Boxes Love Bloxing Gloves',
            'Cyber-Lasso',
            'Pushcat Jr',
            'Skipping Stones to Lonely Homes',
            'Hack the Net',
            'Garten der Medusen',
            'SwapBot',
            'Separation',
            'Roll those Sixes',
            'Spacekoban',
            "Spikes 'n' Stuff",
            'Rock, Paper, Scissors (v0.90 = v1.alpha)',
            'Spooky Pumpkin Game',
            'Vacuum',
            'Miss Direction',
            'Alien Disco',
            'Some lines were meant to be crossed',
            'Flying Kick⁣',
            'Memories Of Castlemouse',
            'Bubble Butler: CMD REORGANIZE',
            'Sokoboros',
            'Sleepy players',
            // rosden games
            'Islands',
            'bomb n ice',
            'compressed',
            'consumed to 1',
            'duality',
            'dup-block',
            'fire in winter',
            'fuse to green',
            'inbetween',
            'infected',
            'interconnection',
            'Path lines',
            'pull and push',
            'purple',
            'rows and columns',
            'shifting',
            'sticky',
            'Teleporters',
            'The art of storage',
            'the big dig',
            'the copying',
            'the walls you leave behind',
            'tile step',
            'using pushers',
            // End rosden games
            'Airport Aggression',
            'Coin Counter',
            'Dangerous Dungeon',
            'Easy Enigma',
            "Spider's Hollow",
            'MazezaM',
            'pretender to the crown',
            'Vines',
            'Unconventional Guns⁣',
            'Dungeon Janitor',
            'It Dies In The Light',
            'Collapse',
            'JAM3 Game',
            'Collapsable Sokoban',
            'Element Walkers',
            'Telefrag',
            'Count Mover',
            'ESL Puzzle Game -- CHALLENGE MODE アダムのパズルゲーム'
        ];
        function getGameIndexForSort(gameInfo) {
            let gameIndex = firstGames.indexOf(gameInfo.title);
            if (gameIndex < 0) {
                gameIndex = firstGames.length;
            }
            return gameIndex;
        }
        games = games.sort((a, b) => {
            return getGameIndexForSort(a) - getGameIndexForSort(b);
        });
        const question = {
            type: 'autocomplete',
            name: 'gameTitle',
            message: 'Which game would you like to play?',
            pageSize: Math.max(15, terminal_1.getTerminalSize().rows - 15),
            source: (answers, input) => __awaiter(this, void 0, void 0, function* () {
                let filteredGames;
                if (!input) {
                    filteredGames = games;
                }
                else {
                    filteredGames = games.filter(({ id, title, filePath }) => {
                        return title.toLowerCase().indexOf(input.toLowerCase()) >= 0;
                    });
                }
                return Promise.resolve(filteredGames.map((game) => {
                    // dim the games that are not recommended
                    const index = firstGames.indexOf(game.title);
                    if (index <= 10) {
                        return chalk_1.default.bold.whiteBright(`\u2063${game.title}\u2063 ${percentComplete(game)}`);
                    }
                    else if (solvedGames_1.default.has(game.title)) {
                        // add an invisible unicode character so we can unescape the title later
                        return chalk_1.default.white(`\u2063${game.title}\u2063 ${percentComplete(game)}`);
                    }
                    else {
                        // add an invisible unicode character so we can unescape the title later
                        return chalk_1.default.dim(`\u2063${game.title}\u2063 ${percentComplete(game)}`);
                    }
                }));
            })
            // choices: games.map(({id, title, filePath}) => {
            //     return {
            //         name: `${title} ${chalk.dim(`(${id})`)}`,
            //         value: filePath,
            //         short: id,
            //     }
            // })
            // coercing because we use the autcomplete plugin and it defines a `source:` object
        }; // tslint:disable-line:no-object-literal-type-assertion
        let chosenGame;
        if (cliGameTitle) {
            if (cliGameTitle.toUpperCase() === 'RANDOM') {
                chosenGame = shuffleArray([...games])[0];
            }
            else {
                // try to match the title exactly, then try a substring
                chosenGame = games.find((g) => {
                    return g.title.toLowerCase() === cliGameTitle.toLowerCase();
                });
                if (!chosenGame) {
                    chosenGame = games.find((g) => {
                        return g.title.toLowerCase().indexOf(cliGameTitle.toLowerCase()) >= 0;
                    });
                }
            }
            if (!chosenGame) {
                console.error(`ERROR: Could not find game with title "${cliGameTitle}"`);
                process.exit(111);
                throw new Error('Could not find game');
            }
        }
        else {
            const gameTitle = (yield inquirer.prompt([question])).gameTitle;
            // Filter out the DIM escape codes (to give the game titles a color)
            const firstInvisible = gameTitle.indexOf('\u2063');
            const lastInvisible = gameTitle.lastIndexOf('\u2063');
            let uncoloredGameTitle;
            if (firstInvisible >= 0) {
                uncoloredGameTitle = gameTitle.substring(firstInvisible + 1, lastInvisible);
            }
            else {
                uncoloredGameTitle = gameTitle;
            }
            chosenGame = games.filter((game) => game.title === uncoloredGameTitle)[0];
            if (!chosenGame) {
                throw new Error(`BUG: Could not find game "${uncoloredGameTitle}"`);
            }
        }
        return chosenGame;
    });
}
function showControls() {
    function prettyKey(keyCode) {
        return chalk_1.default.whiteBright.bgWhite(`[${chalk_1.default.black(keyCode)}]`);
    }
    console.log(`-------------------------------------`);
    console.log(`Controls:`);
    console.log(`  ${prettyKey('W')} or ${prettyKey('up')}    : Move Up`);
    console.log(`  ${prettyKey('S')} or ${prettyKey('down')}  : Move Down`);
    console.log(`  ${prettyKey('A')} or ${prettyKey('left')}  : Move Left`);
    console.log(`  ${prettyKey('D')} or ${prettyKey('right')} : Move Right`);
    console.log(`  ${prettyKey('X')} or ${prettyKey('space')} : Perform Action`);
    console.log(`  ${prettyKey('Z')} or ${prettyKey('U')}     : Undo`);
    console.log(`  ${prettyKey('R')}            : Restart the current level`);
    console.log(`  ${prettyKey('C')}            : Clear and redraw the screen`);
    console.log(`  ${prettyKey('esc')}          : Exit the Game`);
    console.log(`-------------------------------------`);
    console.log(`Accessibility Controls: (for inspecting which sprites are in the puzzle)`);
    console.log(`  ${prettyKey('I')}            : Move Inspector Up`);
    console.log(`  ${prettyKey('K')}            : Move Inspector Down`);
    console.log(`  ${prettyKey('J')}            : Move Inspector Left`);
    console.log(`  ${prettyKey('L')}            : Move Inspector Right`);
    console.log(`  ${prettyKey('P')}            : Move Inspector onto the Player`);
    console.log(`-------------------------------------`);
}
//# sourceMappingURL=playGame.js.map