Skip to content
    SlidingPuzzleJS@jpedroribeiro
    .editorconfig
    .gitattributes
    .gitignore
    README.md
    sliding-puzzle.js
    Packager files
    package-lock.json
    package.json
    Config files
    .replit
    replit.nix
    const DEFAULT_CONFIG = {
    rows: 4,
    cols: 4,
    blockSize: 4,
    blankCharacter: " ",
    difficultyLevel: 10, // The higher the number, more random moves are made on startup
    directions: ["Up", "Down", "Left", "Right"],
    };

    let moves = 0;

    function increaseMoves() {
    moves++;
    }

    function clearScreen() {
    console.log("\n".repeat(process.stdout.rows));
    }

    function outputBoard(board, config, error = "", eventGenerator = "bot") {
    clearScreen();
    let output = `
    +---------------------------------------------------+
    | SLIDING PUZZLE GAME |
    | |
    | Instructions: |
    | Use the arrow keys to move the blocks around. |
    | Try to get the blocks in order. |
    | |
    | Press Ctrl + C to quit at anytime. |
    | |
    +---------------------------------------------------+
    `;

    for (let i = 0; i < config.cols; i++) {
    output = output.concat(
    `${i === 0 ? "| " : ""}${Array.from(
    { length: config.blockSize },
    () => "-"
    ).join("")} | ${i === config.cols - 1 ? "\n" : ""}`
    );
    }

    board.map((entry, index) => {
    output = output.concat(
    `${
    entry.label.toString().length === 1
    ? `| ${entry.label}`
    : `| ${entry.label}`
    }${index % config.cols === config.cols - 1 ? " |\n" : " "}`
    );
    if (index - 1 === config.cols) output.concat("/n");
    });

    for (let i = 0; i < config.cols; i++) {
    output = output.concat(
    `${i === 0 ? "| " : ""}${Array.from(
    { length: config.blockSize },
    () => "-"
    ).join("")} | ${i === config.cols - 1 ? "\n" : ""}`
    );
    }

    output = output.concat(error + "\n");

    // Print board to terminal
    console.log(output);

    // Won?
    if (eventGenerator === "user" && checkIfUserWon(board, config) === true) {
    console.log(`You win! 🎉 (Took ${moves} moves)`);
    process.exit();
    }
    }

    function checkIfUserWon(board, config) {
    const { board: originalBoard } = createBoard(config);
    for (let rowIndex = 0; rowIndex < config.rows; rowIndex++) {
    for (let colIndex = 0; colIndex < config.cols; colIndex++) {
    if (
    board.find((entry) => entry.row === rowIndex && entry.col === colIndex)
    .label !==
    originalBoard.find(
    (entry) => entry.row === rowIndex && entry.col === colIndex
    ).label
    )
    return false;
    }
    }

    return true;
    }

    function handleArrowEvent(board, config, direction, eventGenerator = "bot") {
    const blankBlock = board.find(
    (entry) => entry.label === DEFAULT_CONFIG.blankCharacter
    );
    let error = "";

    switch (direction) {
    case "Up":
    const upBlock = board.find(
    (entry) =>
    entry.row === blankBlock.row - 1 && entry.col === blankBlock.col
    );
    if (upBlock) {
    blankBlock.label = upBlock.label;
    upBlock.label = DEFAULT_CONFIG.blankCharacter;
    if (eventGenerator === "user") increaseMoves();
    } else {
    error = "No block above";
    }
    break;
    case "Down":
    const downBlock = board.find(
    (entry) =>
    entry.row === blankBlock.row + 1 && entry.col === blankBlock.col
    );
    if (downBlock) {
    blankBlock.label = downBlock.label;
    downBlock.label = DEFAULT_CONFIG.blankCharacter;
    if (eventGenerator === "user") increaseMoves();
    } else {
    error = "No block below";
    }
    break;
    case "Left":
    const leftBlock = board.find(
    (entry) =>
    entry.row === blankBlock.row && entry.col === blankBlock.col - 1
    );
    if (leftBlock) {
    blankBlock.label = leftBlock.label;
    leftBlock.label = DEFAULT_CONFIG.blankCharacter;
    if (eventGenerator === "user") increaseMoves();
    } else {
    error = "No block to the left";
    }
    break;
    case "Right":
    const rightBlock = board.find(
    (entry) =>
    entry.row === blankBlock.row && entry.col === blankBlock.col + 1
    );
    if (rightBlock) {
    blankBlock.label = rightBlock.label;
    rightBlock.label = DEFAULT_CONFIG.blankCharacter;
    if (eventGenerator === "user") increaseMoves();
    } else {
    error = "No block to the right";
    }
    break;
    }

    outputBoard(
    board,
    config,
    eventGenerator === "system" ? "" : error,
    eventGenerator
    );
    }

    function setKeyboardEvents(board, config) {
    var stdin = process.stdin;

    // without this, we would only get streams once enter is pressed
    stdin.setRawMode(true);

    // resume stdin in the parent process (node app won't quit all by itself
    // unless an error or process.exit() happens)
    stdin.resume();

    // i don't want binary, do you?
    stdin.setEncoding("utf8");

    // on any data into stdin
    stdin.on("data", function (key) {
    switch (key) {
    case "\u001B\u005B\u0041":
    handleArrowEvent(board, config, "Up", "user");
    break;
    case "\u001B\u005B\u0042":
    handleArrowEvent(board, config, "Down", "user");
    break;
    case "\u001B\u005B\u0044":
    handleArrowEvent(board, config, "Left", "user");
    break;
    case "\u001B\u005B\u0043":
    handleArrowEvent(board, config, "Right", "user");
    break;
    }

    // ctrl-c
    if (key === "\u0003") {
    process.exit();
    }
    });
    }

    function createBoard(startUpConfig) {
    const config = { ...DEFAULT_CONFIG, ...startUpConfig };
    const { rows, cols } = config;
    const board = [];

    // Setup board
    for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
    for (let colIndex = 0; colIndex < cols; colIndex++) {
    board.push({
    label:
    rows * rowIndex + colIndex + 1 === rows * cols
    ? DEFAULT_CONFIG.blankCharacter
    : rows * rowIndex + colIndex + 1,
    row: rowIndex,
    col: colIndex,
    });
    }
    }

    return { config, board };
    }

    function randomizeBoard(board, config) {
    const numberOfMoves = Math.floor(config.difficultyLevel * Math.random() + 1);
    for (let i = 0; i < numberOfMoves; i++) {
    const randomDirection =
    config.directions[Math.floor(config.directions.length * Math.random())];
    handleArrowEvent(board, config, randomDirection);
    }
    }

    function start(startUpConfig = {}) {
    const { config, board } = createBoard(startUpConfig);

    randomizeBoard(board, config);
    outputBoard(board, config);
    setKeyboardEvents(board, config);
    }

    start();