Source: randomPrinter.js

import {browserToPrinter} from "./receipt.js";
import PrintHandler from "./PrintHandler.js";
import BasketWeave from "./sprite/pattern/BasketWeave.js";
import DiamondPattern from "./sprite/pattern/DiamondPattern.js";
import DotPattern from "./sprite/pattern/DotPattern.js";
import GradientFloor from "./sprite/pattern/GradientFloor.js";
import HerringBone from "./sprite/pattern/HerringBone.js";
import SeedStitch from "./sprite/pattern/SeedStitch.js";
import TimeLines from "./sprite/pattern/TimeLines.js";
import TimestampWaves from "./sprite/pattern/TimestampWaves.js";
import ArrowTime from "./sprite/ArrowTime.js";
import Bird from "./sprite/Bird.js";
import Bug from "./sprite/Bug.js";
import Chandeliers from "./sprite/Chandeliers.js";
import Chevron from "./sprite/Chevron.js";
import Cloud from "./sprite/Cloud.js";
import DiamondButterfly from "./sprite/DiamondButterfly.js";
import Inkblot from "./sprite/Inkblot.js";
import MarioCoinBox from "./sprite/MarioCoinBox.js";
import Panda from "./sprite/Panda.js";
import Peteca from "./sprite/Peteca.js";
import Rope from "./sprite/Rope.js";
import Starburst from "./sprite/Starburst.js";
import {Triangle1, Triangle2} from "./sprite/triangles.js";
import TwinkleBanner from "./sprite/TwinkleBanner.js";

/**
 * used to continuously print random sprites
 * @author Michael Crockett
 */

/**
 * config
 * @type {{randomIntervalBounds: number[], numLinesBounds: number[]}}
 */
const config = {
    randomIntervalBounds: [2 * 60 * 1000, 7 * 60 * 1000], // 2-7 minutes in milliseconds
    numLinesBounds: [3, 30],
    numSprites: 5, // the number of sprites to generate at once (eg. you can see 5 diamonds at once in browser)
    fillEmptySpace: true, // if true, any leftover "rows" after a sprite is finished printing will be filled in with blank space
}

/**
 * list of sprites to choose from
 */

const sprites = [
    BasketWeave,
    DiamondPattern,
    DotPattern,
    GradientFloor,
    HerringBone,
    SeedStitch,
    TimeLines,
    TimestampWaves,
    ArrowTime,
    Bird,
    Bug,
    Chandeliers,
    Chevron,
    Cloud,
    DiamondButterfly,
    Inkblot,
    MarioCoinBox,
    Panda,
    Peteca,
    Rope,
    Starburst,
    Triangle1,
    Triangle2,
    TwinkleBanner,
];

/**
 * used to store information about incomplete sprites.
 * each sprite class maps to an object that holds key information.
 * @type {Map<Sprite, object>}
 */
const spriteHolder = new Map();

// reset the sprite function in spriteHolder with a clean slate
const resetSprite = sprite => spriteHolder.set(sprite, {
    inProgress: false,  // are we in the middle of making this sprite
    browserSprite: null, // generated sprite in browser-friendly form
    printerSprite: null, // an array of each row of the sprite (printer-friendly form)
    startFrom: 0, // if we're in the middle of making a sprite, this is the row where we should start back
});

// add each sprite function to spriteHolder with clean slate
sprites.forEach(resetSprite);

const setReceiptInBrowser = (printNum, {startFrom, browserSprite}) => {
    $('#receipt-content').html(browserSprite);
    $('#print-from-row').html(printNum === -1 ? `_____` : `${startFrom} - ${startFrom + printNum - 1}`);
}
/**
 * convert milliseconds to human-readable
 */
const msToTime = ms => {
    let seconds = Math.floor((ms / 1000) % 60),
        minutes = Math.floor((ms / (1000 * 60)) % 60),
        hours = Math.floor((ms / (1000 * 60 * 60)) % 24);

    hours = (hours < 10) ? "0" + hours : hours;
    minutes = (minutes < 10) ? "0" + minutes : minutes;
    seconds = (seconds < 10) ? "0" + seconds : seconds;

    return hours + ":" + minutes + ":" + seconds;
}

// object to handle connecting to printer and printing
const printHandler = new PrintHandler();

const getNewSprite = type => {
    const sprite = new type();
    //chance to flip
    if (Math.random() < 0.5) sprite.flipHorizontal();
    // chance to change margin
    const marginChoices = ["-", "_", ".", "\xa0"];
    const left = marginChoices[Math.floor(Math.random()*marginChoices.length)];
    const right = marginChoices[Math.floor(Math.random()*marginChoices.length)];
    if (Math.random() < 0.25) sprite.setMarginFill(left, right);
    // chance to change alignment
    if (Math.random() < 0.2) sprite.setAlign("random");
    return sprite.toString();
}

/**
 *  an object to handle the loop.
 *  I know this is a weird way to do this, but i'm just exploring and learning right now
 */
const printLoop = {
    looping: false,
    countDown: {
        id: null, // interval id
        timeLeft: null, // ms
    },
    wholeSprites: false,

    // the actual thing we're repeating
    action() {
        // pick random sprite
        const spriteType = sprites[Math.floor(Math.random()*sprites.length)];
        const spriteInfo = spriteHolder.get(spriteType);

        // are we printing whole sprites at a time?
        if (printLoop.wholeSprites) {
            const browserSprite = getNewSprite(spriteType);
            setReceiptInBrowser(-1, {browserSprite});
            printHandler.setLines(browserToPrinter(browserSprite));

        } else { // partial sprites
            // check to see if we had a leftover
            if (!spriteInfo.inProgress) {
                spriteInfo.inProgress = true;
                spriteInfo.browserSprite = "";
                for (let i = 0; i < config.numSprites; i++) spriteInfo.browserSprite += getNewSprite(spriteType); // add sprites
                spriteInfo.printerSprite = browserToPrinter(spriteInfo.browserSprite);
                spriteInfo.startFrom = 0;
            }

            // pick num of rows to print (any extra are printed as blank lines)
            const [min, max] = config.numLinesBounds;
            let printNum = Math.floor(Math.random() * (max - min + 1) + min);

            // display what we are printing
            setReceiptInBrowser(printNum, spriteInfo);
            // prepare to print what we are printing.
            for (let i = spriteInfo.startFrom; i < spriteInfo.startFrom + printNum; i++) {
                if (i >= spriteInfo.printerSprite.length) {
                    // we're out of bounds
                    if (config.fillEmptySpace) printHandler.addLine('\n'); // print blank line
                    else break;
                } else printHandler.addLine(spriteInfo.printerSprite[i]); // print row
            }

            // we are no longer in progress
            if (spriteInfo.startFrom + printNum >= spriteInfo.printerSprite.length) resetSprite(spriteType);
            // we are still in progress
            else spriteInfo.startFrom += printNum;
        }
        //submit print
        printHandler.submitPrint(true);

        // repeat action if looping
        if (printLoop.looping) {
            // find wait time
            const [min, max] = config.randomIntervalBounds;
            const timeout = Math.floor(Math.random() * (max - min + 1) + min);
            printLoop.countDown.timeLeft = timeout;
            // create interval
            clearInterval(printLoop.countDown.id);
            printLoop.countDown.id = setInterval(() => {
                printLoop.countDown.timeLeft -= 1000;
                $(`#update-time`).html(msToTime(printLoop.countDown.timeLeft));
            }, 1000); // every second
            // wait
            printLoop.timeoutID = setTimeout(printLoop.action, timeout);
        }
    },

    // switch between partial sprites and whole sprites
    changePrintMode() {
        const wasLooping = printLoop.looping;
        printLoop.stop();
        printLoop.wholeSprites = !printLoop.wholeSprites;
        if (wasLooping) printLoop.start();
    },

    start() { // start the loop
        printLoop.looping = true;
        clearTimeout(printLoop.timeoutID);
        clearInterval(printLoop.countDown.id);
        printLoop.action();
    },

    stop() { // stop the loop and reset everything
        printLoop.looping = false;
        printLoop.nextUpdateTime = null;
        clearTimeout(printLoop.timeoutID);
        clearInterval(printLoop.countDown.id);
        setReceiptInBrowser(-1, {browserSprite: ""});
        $(`#update-time`).html("_____");
    },
};

// add listener to print button
$(`#start-print-btn`).on("click", printLoop.start);
$(`#stop-print-btn`).on("click", printLoop.stop);
// toggle print mode when checkbox is clicked
$(`#toggleFullSprite`).on('change', printLoop.changePrintMode);