Source: Commands/Tictactoe/index.js

/* eslint-disable max-len */
/* eslint-disable default-case */
/* eslint-disable indent */

/* Requires */
const fs = require('fs');
const { Sticker } = require('wa-sticker-formatter');
const tttagent = require('tictactoe-agent');
const Indexer = require('../../index');

/* JSON's | Utilidades */
const envInfo = JSON.parse(fs.readFileSync(`${__dirname}/utils.json`));

/* Define os jogos, apenas em memória, TTT é pra ser jogos rápidos afinal */
const inMemoryGame = {
    score: {},
};

/**
 * Retorna todos os detalhes do ambiente (`envInfo`).
 *
 * @returns {Object} O objeto `envInfo`, que contém os detalhes do ambiente da execução.
 */
function ambientDetails() {
    /* Retorna a envData */
    return envInfo;
}

/* Função para verificar o resultado do jogo em um tabuleiro */
function checkGameResult(board, playerOne, playerTwo) {
    /* Inicializa o resultado como vazio */
    let result = false;

    /* Define as combinações vencedoras por linhas, colunas e diagonais */
    const winCombinations = [
        ['a1', 'a2', 'a3'],
        ['b1', 'b2', 'b3'],
        ['c1', 'c2', 'c3'],
        ['a1', 'b1', 'c1'],
        ['a2', 'b2', 'c2'],
        ['a3', 'b3', 'c3'],
        ['a1', 'b2', 'c3'],
        ['a3', 'b2', 'c1'],
    ];

    /* Verificações por linhas, colunas e diagonais */
    for (let i = 0; i < winCombinations.length; i += 1) {
        /* Define as combinações */
        const [a, b, c] = winCombinations[i];

        /* Baseado na posição */
        if (board[a] === 'X' && board[b] === 'X' && board[c] === 'X') {
            /* Define o jogador 1 como vencedor */
            result = playerOne;

            /* Se o jogador 1 não venceu, verifica o jogador 2 */
        } else if (board[a] === 'O' && board[b] === 'O' && board[c] === 'O') {
            /* Define o jogador 2 como vencedor */
            result = playerTwo;
        }
    }

    /* Verifica empate por meio de todas as jogadas serem feitas */
    if (!result && Object.values(board).every((cell) => cell !== false)) {
        /* Define como Draw */
        result = 'draw';
    }

    /* Se nada acima foi acionado, retorna padrão */
    return result;
}

/* Função de jogo caso a Íris seja a player 2 */
function irisPlaying(board, chatId, user, difficulty, typePlayer, lvpc) {
    /* Define a jogada */
    let finalStep = '';

    /* Só executa se tiver jogadas */
    if (Object.values(board).includes(false)) {
        /* Baseado na dificuldade, Easy */
        if (difficulty === '1') {
            /* Só aleatoriza */
            finalStep = Indexer('array').extract(Object.keys(board).filter((j) => board[j] === false)).value;
            finalStep = Object.keys(board).indexOf(finalStep);

            /* Medium/Normal/Hardcore */
        } else {
            /* Define o modelo da board */
            const model = new tttagent.Model(Object.values(board).map((d) => d || '-').join(''), typePlayer);

            /* Obtém uma recomendação */
            const recommendation = model.getRecommendation();

            /* Jogada padrão */
            finalStep = recommendation.possibilities.filter((d) => d.index !== recommendation.index).reduce((big, now) => (now.score > big.score ? now : big), { score: -Infinity }).index;

            /* Se lvpc for maior que 50 ou Hardcore */
            if (lvpc > 50 || difficulty === '3') {
                /* Faz a jogada perfeita */
                finalStep = recommendation.index;
            }
        }

        /* Faz a jogada */
        inMemoryGame[chatId][user].board[Object.keys(board)[finalStep]] = typePlayer;

        /* Define como vez do player 1 */
        inMemoryGame[chatId][user].currentStep = inMemoryGame[chatId][user].playerOne;

        /* Define o novo tabuleiro */
        inMemoryGame[chatId][user].board = board;
    }

    /* Retorna algo */
    return board;
}

/* Cria a função de comando */
async function playVelha(
    kill = envInfo.functions.exec.arguments.kill.value,
    env = envInfo.functions.exec.arguments.env.value,
) {
    /* Define um resultado padrão */
    envInfo.results.value = false;

    /* Define o sucesso */
    envInfo.results.success = false;

    /* Try-Catch para casos de erro */
    try {
        /* Se recebeu tudo corretamente, se der ruim, não fará nada */
        if (typeof kill === 'object' && typeof env === 'object') {
            /* Constrói os parâmetros */
            const {
                chatId,
                arks,
                mentionedJidList,
                user,
                userFormated,
                isOwner,
                argl,
                stickerConfig,
                reply,
                lvpc,
            } = env.value;

            /* Define o alias na envInfo */
            envInfo.alias = env.value.alias;

            /* Tira o próprio user da lista de menções e adiciona a BOT */
            const mentionsPlayer = mentionedJidList.filter((d) => d !== user);
            mentionsPlayer.push(irisNumber);

            /* Menu de ajuda DEV */
            if (arks.includes('--help-dev') && isOwner === true) {
                /* Manda a mensagem de ajuda de dev */
                envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Helper', 'Developer', true, true, envInfo).value }, reply);

                /* Menu de ajuda normal */
            } else if (arks.includes('--help') || argl.length === 0 || !['-play', '-create', '-cancel', '-board', '-placar'].includes(argl[0]) || (argl[0] === '-create' && !/[1-3]/.test(argl[1] || '')) || (argl[0] === '-play' && !/[0-9]/.test(argl[1] || ''))) {
                /* Não inclui informações secretas */
                envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Helper', 'User', true, true, envInfo).value }, reply);

                /* Sistema de TTT */
            } else {
                /* Define um jogo para o grupo */
                inMemoryGame[chatId] = inMemoryGame[chatId] || {};

                /* Localiza o jogo da pessoa */
                let gameCreator = user;
                if (Object.keys(inMemoryGame[chatId]).length > 0) {
                    /* Verifica se algum tem a pessoa */
                    gameCreator = Object.keys(inMemoryGame[chatId]).filter((g) => inMemoryGame[chatId][g].playerOne === user || inMemoryGame[chatId][g].playerTwo === user);

                    /* Se teve algum */
                    if (gameCreator.length !== 0) {
                        /* Verifica novamente se for para jogo especifico */
                        const alterPlayer = gameCreator.filter((d) => mentionsPlayer.includes(d));

                        /* Define o criador do jogo */
                        gameCreator = alterPlayer[0] || gameCreator[0];
                    }
                }

                /* Define o que fazer por nome de comando */
                switch (argl[0]) {
                    /* Envia a tabela */
                    case '-board':
                        /* Verifica se não tem jogos */
                        if (!Object.keys(inMemoryGame[chatId]).includes(gameCreator)) {
                            /* Diz que já pode criar */
                            await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Games', 'Uncreated', true, true, env.value).value }, reply);

                            /* Se tiver */
                        } else {
                            /* Cria o tabuleiro */
                            const canvaBoard = await Indexer('cards').ttt(inMemoryGame[chatId][gameCreator].board);

                            /* Constrói o sticker */
                            const sticker = new Sticker(canvaBoard.value, {
                                ...stickerConfig,
                                type: 'default',
                                quality: 100,
                            });

                            /* Define como formato Baileys */
                            const sendSticker = await sticker.toMessage();

                            /* Envia a board */
                            await kill.sendMessage(chatId, sendSticker, reply);
                        }
                    break;

                    /* Envia o placar */
                    case '-placar':
                        /* Verifica se não tem jogos */
                        if (Object.keys(inMemoryGame.score).length === 0) {
                            /* Diz que já pode criar */
                            await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Games', 'NoPlacar', true, true, env.value).value }, reply);

                            /* Se tiver */
                        } else {
                            /* Cria o placar */
                            const scoreboard = Object.keys(inMemoryGame.score).sort((a, b) => inMemoryGame.score[b].win - inMemoryGame.score[a].win).map((player) => {
                                /* Define o número para marcar */
                                let playerNumber = Indexer('sql').get('personal', player, chatId);
                                playerNumber = playerNumber.value.name.text === 'default' ? player.replace(/@s.whatsapp.net/gi, '') : playerNumber.value.name.text;

                                /* Define os atributos */
                                const { win, lost, draw } = inMemoryGame.score[player];

                                /* Define e retorna a string */
                                return `👤 ${playerNumber}: 🏆 ${win} | 😞 ${lost} | 🤝 ${draw}`;
                            });

                            /* Avisa que apagou */
                            await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Games', 'Placar', true, true, { scoreboard: scoreboard.join('\n\n') }).value }, reply);
                        }
                    break;

                    /* Criar um jogo */
                    case '-create':
                        /* Verifica se a pessoa está em um jogo */
                        if (Object.keys(inMemoryGame[chatId]).includes(gameCreator)) {
                            /* Diz para ela cancelar o jogo antes de iniciar outro */
                            await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Games', 'Created', true, true, env.value).value, mentions: [user, mentionsPlayer[0]] }, reply);

                            /* Se não estiver, prossegue em criar */
                        } else {
                            /* Define o jogo padrão */
                            inMemoryGame[chatId][gameCreator] = {
                                playerOneChar: 'X',
                                playerTwoChar: 'O',
                                playerOneFormatted: false,
                                playerTwoFormatted: false,
                                playerOne: false,
                                playerTwo: false,
                                difficulty: 1,
                                currentStep: 'playerOne',
                                board: {
                                    a1: false,
                                    a2: false,
                                    a3: false,
                                    b1: false,
                                    b2: false,
                                    b3: false,
                                    c1: false,
                                    c2: false,
                                    c3: false,
                                },
                            };
                            inMemoryGame[chatId][gameCreator].playerOne = gameCreator;
                            [inMemoryGame[chatId][gameCreator].playerTwo] = [mentionsPlayer[0]];
                            [inMemoryGame[chatId][gameCreator].difficulty] = [argl[1]];
                            inMemoryGame[chatId][gameCreator].currentStep = gameCreator;
                            inMemoryGame[chatId][gameCreator].playerOneFormatted = gameCreator.replace(/@s.whatsapp.net/gi, '');
                            inMemoryGame[chatId][gameCreator].playerTwoFormatted = mentionsPlayer[0].replace(/@s.whatsapp.net/gi, '');
                            inMemoryGame.score[gameCreator] = {
                                win: 0,
                                lost: 0,
                                draw: 0,
                            };
                            inMemoryGame.score[mentionsPlayer[0]] = {
                                win: 0,
                                lost: 0,
                                draw: 0,
                            };

                            /* Avisa que criou */
                            await kill.sendMessage(chatId, { text: `${Indexer('sql').languages(region, 'Games', 'Init', true, true, { userFormated }).value}\n\n🔢 Moves: 0-9\n\n👤 Player 1: @${userFormated}\n\n👤 Player 2: @${inMemoryGame[chatId][gameCreator].playerTwoFormatted}\n\n🎲 Board:\n\`\`\`+---+---+---+\n| 0 | 3 | 6 |\n+---+---+---+\n| 1 | 4 | 7 |\n+---+---+---+\n| 2 | 5 | 8 |\n+---+---+---+\`\`\``, mentions: [user, mentionsPlayer[0]] }, reply);
                        }
                    break;

                    /* Apagar um jogo */
                    case '-cancel':
                        /* Verifica se não tem jogos */
                        if (!Object.keys(inMemoryGame[chatId]).includes(gameCreator)) {
                            /* Diz que já pode criar */
                            await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Games', 'Uncreated', true, true, env.value).value }, reply);

                            /* Se tiver */
                        } else {
                            /* Deleta */
                            delete inMemoryGame[chatId][gameCreator];

                            /* Avisa que apagou */
                            await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Games', 'Delete', true, true, env.value).value }, reply);
                        }
                    break;

                    /* Fazer uma jogada */
                    case '-play':
                        /* Verifica se não tem jogos */
                        if (!Object.keys(inMemoryGame[chatId]).includes(gameCreator)) {
                            /* Diz que já pode criar */
                            await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Games', 'Uncreated', true, true, env.value).value }, reply);

                            /* Se tiver */
                        } else {
                            /* Define o jogador atual */
                            const actualPlayer = (inMemoryGame[chatId][gameCreator].playerOne === inMemoryGame[chatId][gameCreator].currentStep ? inMemoryGame[chatId][gameCreator].currentStep : inMemoryGame[chatId][gameCreator].playerTwo);
                            const playerFormatted = actualPlayer.replace(/@s.whatsapp.net/gi, '');

                            /* Define o player rival - Uso: Íris */
                            const rivalPlayer = actualPlayer === inMemoryGame[chatId][gameCreator].playerOne ? inMemoryGame[chatId][gameCreator].playerTwo : inMemoryGame[chatId][gameCreator].playerOne;

                            /* Define a index que o player enviou */
                            const playerStep = Object.keys(inMemoryGame[chatId][gameCreator].board)[Number(argl[1])];
                            let availableSteps = Object.keys(inMemoryGame[chatId][gameCreator].board).filter((p) => inMemoryGame[chatId][gameCreator].board[p] === false).map((key) => Object.keys(inMemoryGame[chatId][gameCreator].board).indexOf(key)).join(', ');

                            /* Define o operador do jogador */
                            const playerOperator = inMemoryGame[chatId][gameCreator].playerOne === user ? inMemoryGame[chatId][gameCreator].playerOneChar : inMemoryGame[chatId][gameCreator].playerTwoChar;

                            /* Define o operador rival - Uso: Íris */
                            const rivalOperator = playerOperator === 'X' ? 'O' : 'X';

                            /* Mas não for a vez */
                            if (inMemoryGame[chatId][gameCreator].currentStep !== user) {
                                /* Avisa que não é a vez */
                                await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Games', 'NotTurn', true, true, { playerFormatted }).value, mentions: [inMemoryGame[chatId][gameCreator].playerOne, inMemoryGame[chatId][gameCreator].playerTwo] }, reply);

                                /* Se for a vez, mas a jogada não for válida */
                            } else if (inMemoryGame[chatId][gameCreator].board[playerStep] !== false) {
                                /* Avisa que não é a vez */
                                await kill.sendMessage(chatId, { text: `${Indexer('sql').languages(region, 'Games', 'Invalid', true, true, { movements: availableSteps }).value}\n\n🎲 Board:\n\`\`\`+---+---+---+\n| 0 | 3 | 6 |\n+---+---+---+\n| 1 | 4 | 7 |\n+---+---+---+\n| 2 | 5 | 8 |\n+---+---+---+\`\`\`` }, reply);

                                /* Se a jogada estiver certa */
                            } else {
                                /* Faz ela no board */
                                inMemoryGame[chatId][gameCreator].board[playerStep] = playerOperator;
                                inMemoryGame[chatId][gameCreator].currentStep = rivalPlayer;

                                /* Verifica se venceu */
                                let isVictory = checkGameResult(inMemoryGame[chatId][gameCreator].board, inMemoryGame[chatId][gameCreator].playerOne, inMemoryGame[chatId][gameCreator].playerTwo);

                                /* Se é um jogo contra a Íris */
                                if (rivalPlayer === irisNumber && isVictory === false) {
                                    /* Executa a jogada da Íris */
                                    irisPlaying(inMemoryGame[chatId][gameCreator].board, chatId, user, inMemoryGame[chatId][gameCreator].difficulty, rivalOperator, lvpc);
                                }

                                /* Tabuleiro */
                                const canvaBoard = await Indexer('cards').ttt(inMemoryGame[chatId][gameCreator].board);

                                /* Constrói o sticker */
                                const sticker = new Sticker(canvaBoard.value, {
                                    ...stickerConfig,
                                    type: 'default',
                                    quality: 100,
                                });

                                /* Define como formato Baileys */
                                const sendSticker = await sticker.toMessage();

                                /* Envia */
                                await kill.sendMessage(chatId, sendSticker, reply);

                                /* Verifica por vitorias */
                                isVictory = checkGameResult(inMemoryGame[chatId][gameCreator].board, inMemoryGame[chatId][gameCreator].playerOne, inMemoryGame[chatId][gameCreator].playerTwo);

                                /* Se for draw */
                                if (isVictory === 'draw') {
                                    /* Avisa o fim */
                                    await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Games', 'Draw', true, true, { gameend: 'DRAW' }).value, mentions: [inMemoryGame[chatId][gameCreator].playerOne, inMemoryGame[chatId][gameCreator].playerTwo] });

                                    /* Adiciona os pontos */
                                    inMemoryGame.score[inMemoryGame[chatId][gameCreator].playerOne].draw += 1;
                                    inMemoryGame.score[inMemoryGame[chatId][gameCreator].playerTwo].draw += 1;

                                    /* Deleta o jogo */
                                    delete inMemoryGame[chatId][gameCreator];

                                    /* Se for vitoria */
                                } else if (isVictory === inMemoryGame[chatId][gameCreator].playerOne || isVictory === inMemoryGame[chatId][gameCreator].playerTwo) {
                                    /* Formata o jogador vitorioso */
                                    const formattedWinner = isVictory.replace(/@s.whatsapp.net/gi, '');

                                    /* Define quem perdeu */
                                    const looserUser = isVictory === inMemoryGame[chatId][gameCreator].playerOne ? inMemoryGame[chatId][gameCreator].playerTwo : inMemoryGame[chatId][gameCreator].playerOne;

                                    /* Avisa o fim */
                                    await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Games', 'Victory', true, true, { gameend: 'K.O', winner: formattedWinner.replace(/@s.whatsapp.net/gi, ''), looser: rivalPlayer.replace(/@s.whatsapp.net/gi, '') }).value, mentions: [inMemoryGame[chatId][gameCreator].playerOne, inMemoryGame[chatId][gameCreator].playerTwo] });

                                    /* Adiciona os pontos */
                                    inMemoryGame.score[isVictory].win += 1;
                                    inMemoryGame.score[looserUser].lost += 1;

                                    /* Ganha icoins */
                                    Indexer('sql').update('leveling', isVictory, chatId, 'coin', envInfo.parameters.coinsWin.value);

                                    /* Perde icoins */
                                    Indexer('sql').update('leveling', looserUser, chatId, 'coin', envInfo.parameters.coinsLost.value);

                                    /* Deleta o jogo */
                                    delete inMemoryGame[chatId][gameCreator];

                                    /* Se nada, continua */
                                } else {
                                    /* Formata o user para mencionar */
                                    const mentionRival = inMemoryGame[chatId][gameCreator].currentStep.replace(/@s.whatsapp.net/gi, '');

                                    /* Atualiza as jogadas possiveis */
                                    availableSteps = Object.keys(inMemoryGame[chatId][gameCreator].board).filter((p) => inMemoryGame[chatId][gameCreator].board[p] === false).map((key) => Object.keys(inMemoryGame[chatId][gameCreator].board).indexOf(key)).join(', ');

                                    /* Avisa quem deve jogar agora */
                                    await kill.sendMessage(chatId, { text: `${Indexer('sql').languages(region, 'Games', 'Play', true, true, { mentionRival }).value}\n\n🔢 Moves:\n${availableSteps}\n\n🎲 Board:\n\`\`\`+---+---+---+\n| 0 | 3 | 6 |\n+---+---+---+\n| 1 | 4 | 7 |\n+---+---+---+\n| 2 | 5 | 8 |\n+---+---+---+\`\`\``, mentions: [inMemoryGame[chatId][gameCreator].playerOne, inMemoryGame[chatId][gameCreator].playerTwo] });
                                }
                            }
                        }
                    break;
                }
            }
        }

        /* Define o resultado como a board, achei mais útil */
        envInfo.results.value = inMemoryGame;

        /* Define o sucesso */
        envInfo.results.success = true;

        /* Caso de algum erro */
    } catch (error) {
        /* Insere tudo na envInfo */
        logging.echoError(error, envInfo, __dirname);

        /* Avisa que deu erro enviando o comando e data atual pro sistema SER */
        await kill.sendMessage(env.value.chatId, {
            text: Indexer('sql').languages(region, 'S.E.R', error, true, true, {
                command: 'TICTACTOE',
                time: (new Date()).toLocaleString(),
            }).value,
        }, env.value.reply);
    }

    /* Retorna os resultados */
    return logging.postResults(envInfo);
}

/**
 * Restaura o ambiente e atualiza as exportações do módulo com a funcionalidade principal
 * @param {Object} [changeKey={}] - Chaves personalizadas para atualizar o envInfo
 * @param {Object} [envFile=envInfo] - Objeto com informações do ambiente
 * @param {string} [dirname=__dirname] - Caminho do diretório atual
 * @returns {Object} Exportações do módulo com todas as funções configuradas
 */
/* eslint-disable-next-line no-return-assign */
const resetLocal = (
    changeKey = {},
    envFile = envInfo,
    dirname = __dirname,
) => module.exports = logging.resetAmbient({
    functions: {
        [envInfo.exports.env]: { value: ambientDetails },
        [envInfo.exports.messedup]: { value: logging.echoError },
        [envInfo.exports.poswork]: { value: logging.postResults },
        [envInfo.exports.reset]: { value: resetLocal },
        [envInfo.exports.exec]: { value: playVelha },
    },
    parameters: {
        location: { value: __filename },
    },
}, envFile, changeKey, dirname);
resetLocal();