Source: Commands/Chess/index.js

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

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

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

/* Define os jogos, apenas em memória */
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;
}

/**
 * Verifica o estado do jogo e atualiza o resultado final (vitória, empate, etc.).
 * @async
 * @param {Object} chessg - Objeto da biblioteca de xadrez (chess.js).
 * @param {Object} kill - Função para enviar mensagens no chat.
 * @param {string} chatId - ID do chat onde o jogo está sendo jogado.
 * @param {string} gameCreator - ID do criador do jogo.
 * @param {Object} reply - Objeto de configuração de resposta para o envio de mensagens.
 * @param {string} nowPlayer - Jogador atual.
 * @param {string} rivalPlayer - Jogador adversário.
 * @returns {string|boolean} - Retorna o resultado do jogo (como 'draw', 'checkmate', etc.) ou `false` caso o jogo ainda esteja em andamento.
 */
async function checkGaming(chessg, kill, chatId, gameCreator, reply, nowPlayer, rivalPlayer) {
    /* Inicializa o resultado como vazio */
    let result = false;

    /* Verifica se o jogo não é nulo */
    if (inMemoryGame[chatId][gameCreator] !== null) {
        /* Define o dialogo e user */
        let dialogue = '';
        const userPlayer = `${nowPlayer}@s.whatsapp.net`;

        /* Verifica diferentes cenários de fim do jogo | Draw */
        if (chessg.isDraw()) {
            /* Define como draw e insere o dialogo dele */
            result = 'draw';

            /* Afogamento */
        } else if (chessg.isStalemate()) {
            /* Define como afogamento (ingles) e insere o dialogo dele */
            result = 'stalemate';

            /* Não tem mais nada a jogar */
        } else if (chessg.isInsufficientMaterial()) {
            /* Define como empate e insere o dialogo dele */
            result = 'nomoreactions';

            /* Xeque Mate */
        } else if (chessg.isCheckmate()) {
            /* Define como checkmate e insere o dialogo dele */
            result = 'checkmate';

            /* Threefold Repetition */
        } else if (chessg.isThreefoldRepetition()) {
            /* Define como check e insere o dialogo dele */
            result = 'threefoldrepetition';

            /* Outros finais de jogo */
        } else if (chessg.isGameOver()) {
            /* Define como gameover e insere o dialogo dele */
            result = 'gameover';
        }

        /* Define qual ponto inserir */
        if (['nomoreactions', 'stalemate', 'draw', 'gameover', 'threefoldrepetition'].includes(result)) {
            /* Adiciona os pontos de empate */
            inMemoryGame.score[inMemoryGame[chatId][gameCreator].playerOne].draw += 1;
            inMemoryGame.score[inMemoryGame[chatId][gameCreator].playerTwo].draw += 1;

            /* Define o dialogo */
            dialogue = Indexer('sql').languages(region, 'Games', 'Draw', true, true, { gameend: result }).value;

            /* Se for vitoria */
        } else if (result !== false) {
            /* Adiciona os pontos */
            inMemoryGame.score[userPlayer].win += 1;
            inMemoryGame.score[rivalPlayer].lost += 1;

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

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

            /* Define o dialogo */
            dialogue = Indexer('sql').languages(region, 'Games', 'Victory', true, true, { gameend: result, winner: userPlayer.replace(/@s.whatsapp.net/gi, ''), looser: rivalPlayer.replace(/@s.whatsapp.net/gi, '') }).value;
        }

        /* Se houve algo a dizer e jogo acabou */
        if (result !== false) {
            /* Envia uma mensagem marcando o jogador */
            await kill.sendMessage(chatId, { text: `${dialogue}\n\n📊 FEN:\n${inMemoryGame[chatId][gameCreator].fen}`, mentions: [inMemoryGame[chatId][gameCreator].playerOne, inMemoryGame[chatId][gameCreator].playerTwo] }, reply);

            /* Reseta ele */
            delete inMemoryGame[chatId][gameCreator];
        }
    }

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

/**
 * Realiza a jogada automática da Iris, escolhendo um movimento válido.
 * @param {Object} chessg - Objeto da biblioteca de xadrez (chess.js).
 * @param {string} chatId - ID do chat onde o jogo está sendo jogado.
 * @param {string} user - ID do jogador atual.
 * @returns {string} - Retorna o FEN (posição do tabuleiro em formato FEN) após a jogada.
 */
function irisPlaying(chessg, chatId, user) {
    /* Define os movimentos */
    const cMoves = chessg.moves();

    /* Só executa se tiver jogadas */
    if (cMoves.length > 0) {
        /* Define a jogada como random */
        let finalStep = cMoves;

        /* Define um segundo resultado alternativo */
        let findStep = false;

        /* Procura um xeque-mate */
        findStep = cMoves.filter((h) => h.includes('#'));

        /* Se não tiver, procura por comer peça */
        findStep = findStep[0] != null ? findStep : cMoves.filter((h) => h.includes('x'));

        /* Define qual usar */
        findStep = findStep[0] != null ? findStep : cMoves;

        /* Define a jogada 'perfeita' */
        finalStep = Indexer('array').extract(findStep).value;

        /* Faz a jogada */
        chessg.move(finalStep);

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

        /* Define o novo fen */
        inMemoryGame[chatId][user].fen = chessg.fen();
    }

    /* Retorna algo */
    return inMemoryGame[chatId][user].fen;
}

/**
 * Função principal do xadrez, gerencia os comandos e interações com o jogo.
 * @async
 * @param {Object} kill - Conjunto de funções para enviar mensagens e gerenciar o WhatsApp.
 * @param {Object} env - Objeto contendo as variáveis de ambiente e dados do comando.
 * @returns {Object} - Retorna o estado final do jogo e o resultado.
 */
async function chessDogs(
    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,
            } = 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', '-pgn', '-raw', '-fen', '-moves'].includes(argl[0])) {
                /* 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 Chess */
            } 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 {
                            /* Importa o Chess */
                            const createChess = new chess.Chess(inMemoryGame[chatId][gameCreator].fen);

                            /* Cria o tabuleiro */
                            const canvaBoard = await Indexer('cards').chess(inMemoryGame[chatId][gameCreator].fen);

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

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

                            /* Avisa que apagou */
                            await kill.sendMessage(chatId, sendSticker, reply);
                            await kill.sendMessage(chatId, { image: { url: `${__dirname}/chess.png` }, caption: `🔢 Moves: ${createChess.moves().join(', ')}\n\n📊 FEN:\n${inMemoryGame[chatId][gameCreator].fen}` }, reply);
                        }
                    break;

                    /* Define o envio do FEN apenas, copiar e colar */
                    case '-fen':
                        /* 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, envia somente o FEN */
                        } else await kill.sendMessage(chatId, { text: inMemoryGame[chatId][gameCreator].fen }, reply);
                    break;

                    /* Define o envio do PGN apenas, copiar e colar, não é usado na Íris, mas em muitos apps */
                    case '-pgn':
                        /* 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 {
                            /* Importa o Chess */
                            const createChess = new chess.Chess(inMemoryGame[chatId][gameCreator].fen);

                            /* Envia somente o PGN */
                            await kill.sendMessage(chatId, { text: createChess.pgn() }, reply);
                        }
                    break;

                    /* Define o envio do tabuleiro como formato RAW (ASCII) */
                    case '-raw':
                        /* 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 {
                            /* Importa o Chess */
                            const createChess = new chess.Chess(inMemoryGame[chatId][gameCreator].fen);

                            /* Envia somente o PGN */
                            await kill.sendMessage(chatId, { text: `\`\`\`${createChess.ascii()}\`\`\`` }, reply);
                        }
                    break;

                    /* Define o envio do moves apenas */
                    case '-moves':
                        /* 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 {
                            /* Importa o Chess */
                            const createChess = new chess.Chess(inMemoryGame[chatId][gameCreator].fen);

                            /* Envia somente o moves */
                            await kill.sendMessage(chatId, { text: createChess.moves().join(', ') }, 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 {
                            /* Cria o novo Chess */
                            const createChess = new chess.Chess();

                            /* Define o jogo padrão */
                            inMemoryGame[chatId][gameCreator] = {
                                playerOneFormatted: false,
                                playerTwoFormatted: false,
                                playerOne: false,
                                playerTwo: false,
                                currentStep: 'playerOne',
                                fen: createChess.fen(),
                            };
                            inMemoryGame[chatId][gameCreator].playerOne = gameCreator;
                            [inMemoryGame[chatId][gameCreator].playerTwo] = [mentionsPlayer[0]];
                            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, { image: { url: `${__dirname}/chess.png` }, caption: `${Indexer('sql').languages(region, 'Games', 'Init', true, true, { userFormated }).value}\n\n🔢 Moves: ${createChess.moves().join(', ')}\n\n👤 Player 1: @${userFormated}\n\n👤 Player 2: @${inMemoryGame[chatId][gameCreator].playerTwoFormatted}\n\n📊 FEN:\n${inMemoryGame[chatId][gameCreator].fen}`, 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 {
                            /* Cria o novo Chess */
                            const chessGame = new chess.Chess(inMemoryGame[chatId][gameCreator].fen);

                            /* 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 availableSteps = chessGame.moves();
                            let playerStep = availableSteps.map((d) => d.toLowerCase()).includes(argl[1]);

                            /* 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 (playerStep === false) {
                                /* Envia que a jogada é invalida e fornece ajuda */
                                await kill.sendMessage(chatId, { image: { url: `${__dirname}/chess.png` }, caption: Indexer('sql').languages(region, 'Games', 'Invalid', true, true, { movements: chessGame.moves().join(', ') }).value }, reply);

                                /* Se a jogada estiver certa */
                            } else {
                                /* Ajusta o jogo */
                                playerStep = playerStep ? availableSteps.filter((d) => d.toLowerCase() === argl[1])[0] : playerStep;

                                /* Define como a vez do jogador rival */
                                inMemoryGame[chatId][gameCreator].currentStep = rivalPlayer;

                                /* Faz a jogada */
                                chessGame.move(playerStep);

                                /* Se é um jogo contra a Íris */
                                if (rivalPlayer === irisNumber) {
                                    /* Executa a jogada da Íris */
                                    irisPlaying(chessGame, chatId, gameCreator);
                                }

                                /* Atualiza a FEN */
                                inMemoryGame[chatId][gameCreator].fen = chessGame.fen();

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

                                /* 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 se venceu */
                                const isVictory = await checkGaming(chessGame, kill, chatId, gameCreator, reply, playerFormatted, rivalPlayer);

                                /* Se for draw */
                                if (isVictory === false) {
                                    /* Formata o user para mencionar */
                                    const mentionRival = inMemoryGame[chatId][gameCreator].currentStep.replace(/@s.whatsapp.net/gi, '');

                                    /* Avisa quem deve jogar agora */
                                    await kill.sendMessage(chatId, { text: `${Indexer('sql').languages(region, 'Games', 'Play', true, true, { mentionRival }).value}\n\n🔢 Moves:\n${chessGame.moves().join(', ')}\n\n📊 FEN:\n${inMemoryGame[chatId][gameCreator].fen}\n\n🎲 Board:\n\`\`\`${chessGame.ascii()}\`\`\``, mentions: [inMemoryGame[chatId][gameCreator].currentStep] });
                                }
                            }
                        }
                    break;
                }
            }
        }

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

        /* Define o sucesso, se seu comando der erro isso jamais será chamado, então o success automaticamente será false em falhas */
        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 S.E.R (Send/Special Error Report) */
        await kill.sendMessage(env.value.chatId, {
            text: Indexer('sql').languages(region, 'S.E.R', error, true, true, {
                command: 'CHESS',
                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: chessDogs },
    },
    parameters: {
        location: { value: __filename },
    },
}, envFile, changeKey, dirname);
resetLocal();