Aventuras na Terra da Programação: Jogo de cartas King (versão Aloja USP)

Não consigo pensar em forma melhor de aprender do que a prática, o famoso "Olá Mundo" serve bem para introduzir o novato na sintaxe e estrutura básica de uma linguagem, mas tem que meter o pé na porta e fazer algo maior para realmente sentir as limitações e vantagens de uma linguagem. Enfim, decidi implementar meu jogo de cartas favorito, King, para o mundo eletrônico. Visto que o jogo exige quatro jogadores, sendo portanto naturalmente multi-jogadores, eu decidi ter o jogo acessível pelo navegador, onde o mesmo pode ser distribuído e executado em rede naturalmente. Como o projeto havia iniciado a muito tempo, quando ainda estava programando em Python com suporte à rede mas sem interface gráfica, eu preferi manter essa parte até porque permite que eu exercite a integração de múltiplas linguagens, visto que a parte gráfica é em HTML5 (e portanto JavaScript e CSS) e a parte remota desse componente também (usei NodeJS e Express aqui). Lembro a todos que isso não é um serviço comercial, mas assim como este blog, um hobby com uma face pública, portanto, contenham expectativas. Provavelmente tá tudo bugado, o servidor vai crashar e ficar fora do ar e eu ainda não cobri um  monte de arestas, portanto, é fio passando no meio da sala, que já virou varal pra pendurar roupa que provavelmente foi lavada na pia. 

Mas ... enfim, o link abaixo é para jogar, uma vez na página, tem uns botões lá em cima, para 1) logar e 2) juntar-se à uma mesa, ou criar uma caso não haja disponível.

King, Versão Web (http://king.epensieve.com:8086/)

Este outro link é para registrar um problema que você possa ter encontrado e queira me ajudar, notificando o ocorrido.

Reporte um Problema (https://bitbucket.org/raffaine/cardgamehall/issues) 

Ok, pausa com minha tara nerd, como é que se joga King? Como disse, o jogo envolve 4 jogadores, posicionados em um círculo ao redor da mesa, utiliza-se o baralho completo com 52 cartas nos 4 naipes. O jogo então se subdivide em Mãos (definição minha, a gente não dava nome pra isso), sendo 10 ao todo, onde 6 dessas pontuam negativamente e 4 pontuam positivamente. Iniciando-se em um jogador na mesa e rodando no sentido anti-horário (convenção minha) ao final de cada mão, estes jogadores definirão no começo de cada mão qual a Regra a ser aplicada, e esta regra definirá se o jogo pontua positivamente ou negativamente. Antes de descrever as regras, é possível entender a mecânica básica de como cada mão é jogada. Dado que temos 52 cartas e 4 jogadores, temos portanto 13 cartas para cada jogador, iniciando-se no jogador que escolheu a Regra desta mão, e seguindo em sentido horário, ele deverá escolher uma carta de sua mão (inicialmente 13 e reduzindo a cada rodada) e jogar na mesa. Quando a mesa está vazia, qualquer carta pode ser jogada na mesa, exceto se a Regra escolhida disser o contrário, porém, quando a primeira carta foi colocada na mesa, todas as demais cartas na rodada deverão ser do mesmo naipe, exceto quando o jogador não possuir uma carta daquele naipe, nesses casos, qualquer carta pode ser jogada, o que é denominado descarte. A rodada termina quando o turno volta ao jogador que a iniciou, e é considerado o vencedor da rodada aquele que jogou a maior carta do naipe que iniciou a rodada, sendo que o Rank da carta inicia-se em 2, sendo o Ás a maior carta, e Valete (Jack, J), Dama (Queen, Q) e Rei (King, K) são na ordem apresentada (para evitar confusões). O vencedor da rodada inicia a próxima rodada a não ser que ele não possua cartas na mão, nesse caso encerra-se a Mão e computam-se os pontos. Como dito anteriormente, cada Mão tem uma Regra diferente escolhida pelo jogador que iniciará a mão (e isso passa para o próximo ao final da mão), sendo que cada Regra só pode ser escolhida uma vez, exceto Positivas que é uma vez por jogador, são elas:

  • Vaza (No Tricks): Regra mais simples e uma das que considero difíceis de sair ileso, tudo funciona como apresentado na rodada típica, porém, como o nome em Inglês aponta, é a regra que pontua -20 pontos para cada rodada vencida (Trick). Ao final, -260 pontos serão distribuídos.
  • Homens (No Jacks and Kings): Rodada segue como apresentado, porém, -30 pontos por Valete ou Rei que estiver presente na rodada vencida. Ao todo, -240 pontos.
  • Mulheres (No Queens): Sem mudanças na rodada, -50 pontos por Dama presente na rodada vencida. Ao todo, -200 pontos.
  • Copas (No Hearts): A rodada passa a ter uma regra adicional, não pode jogar Copas exceto se for o único naipe em sua mão, ou for descarte (você não tem carta do naipe que iniciou a rodada). Cada carta de Copas na rodada vencida são -20 pontos. Total de -260 pontos.
  • Duas Últimas (No Last Two Tricks): Rodada segue sem mudanças, pontua-se -90 pontos ao vencedor da penúltima rodada e -90 pontos ao vencedor da última. -180 ao todo.
  • King (No King of Hearts): Regra que dá nome ao jogo, assim como em Copas, os jogadores não podem jogar Copas a não ser que seja seu último naipe ou descarte. Adicionalmente, força-se o jogador que possuir o Rei de Copas a descartá-lo imediatamente em seu primeiro descarte. -160 pontos é anotado para o vencedor da rodada contendo o Rei de Copas. A mão pode terminar imediatamente.
  • Positivas (Positive): Cada jogador só tem direito a escolher uma vez essa regra, e o jogador que escolhe a Positiva deve também escolher qual será o naipe trunfo, ou se não haverá um. Antes de escolher o jogador iniciará um leilão pela escolha do naipe, onde os outros jogadores oferecem lances baseados em quantas rodadas vencidas (Tricks) eles darão ao ofertante no final da Mão. Jogadores não são obrigados a fazer lances e o Ofertante não é obrigado a aceitar, mas caso aceite, ele dá ao vencedor do leilão a escolha do naipe trunfo (ou a não existência de um). Escolhido o naipe, iniciam-se as rodadas, começando pelo jogador que escolheu a Regra (não o vencedor do leilão). A rodada segue como apresentado anteriormente com uma alteração, caso o descarte de qualquer jogador seja do naipe trunfo, a maior carta do naipe trunfo vencerá a rodada, porém os outros jogadores devem continuar jogando cartas do naipe que iniciou caso tenham. Cada rodada concede 25 pontos ao vencedor, lembrando que ao final da Mão, quaisquer débitos em aberto devem ser honrados na medida do possível. O total de cada positiva é 325 pontos. (Nas mesas do alojamento a maior vergonha de um jogador é não poder pagar o lance ofertado ao final, o ofertante acaba prejudicado pelo calote, mas é um risco que ele aceitou) 
Ao final de todas as 10 mãos, os jogadores somam seus pontos e o vencedor e demais do pódio são decididos. Para confirmar que não houve pilantragem, é possível verificar o placar somando-se todos os valores que deve resultar em 0 (zero). Como dito, a Positiva só pode ser escolhida uma vez por cada jogador, porém, existem situações onde não existe outra escolha disponível, e nestes casos o jogador é forçado a escolher positiva sendo esta a única exceção. 

Ilustrando com um exemplo: Temos na mesa D2, 3 Horas, Samurai e Ana Maria, sentados nesta mesma ordem, D2 então embaralha e distribui as cartas para os jogadores. Uma vez que D2 conhece suas cartas decide que a melhor Regra para essa Mão é Vaza. D2 inicia a primeira rodada com um 4 de Paus, 3 Horas na sequência vai embaixo com o 2 de Paus, seguido por Samurai com um Ás de Paus e por fim, Ana Maria (um tanto aliviada com o Ás de Samurai) joga um Rei de Paus. Samurai coleta as cartas e anota a rodada, iniciando a próxima com um 3 de Ouros. A Mão segue e ao final, D2 anota 2 rodadas (-40), 3 Horas levou 4 rodadas (-80), Samurai com terríveis 7 rodadas (-140) e Ana Maria blindada pelo guerreiro oriental apenas 1 rodada (-20). 3 Horas assume o baralho então, e uma vez que as cartas foram distribuídas decide que sua melhor opção é a Positiva, uma vez que ele anuncia a Regra, Samurai faz a primeira oferta, e um pouco mais preocupado com a falta de Ouros em sua mão, faz um lance ousado de 3 positivas. Ana Maria e D2 não tem um lance maior do que Samurai para ofertar e 3 Horas precisa decidir se aceita ou não a oferta. Ele observa que possui Naipes balanceados e com Ranks altos em quase todos, portanto decide aceitar a oferta. Samurai, aliviado mas endividado, escolhe Espadas como o Naipe Trunfo, o que claramente não favoreceu D2 visto seu vocal descontentamento. 3 Horas inicia a rodada com um Ás de Paus, seguido por 2 de Paus de Samurai, 8 de Paus de Ana e 3 de Paus de D2, vencendo assim a rodada, coletando as cartas, anotando a rodada e dando inicio a próxima com um Ás de Ouros, que é imediatamente assassinado por um 2 de Espadas descartado por Samurai para decepção de 3 Horas, Ana Maria lança o 3 de Ouros e D2 o 4 de Ouros. Visto que Espadas é o Trunfo, Samurai leva a rodada e de forma agressiva inicia a rodada seguinte com um Ás de Espada, forçando Ana Maria a jogar um 10 de Espadas (uma de duas cartas que possui no Naipe), D2 a jogar um Rei de Espadas (aos prantos) e 3 Horas com um 3 de Espadas, Samurai novamente o vencedor da rodada. Ao final da Mão, D2 não anotou vitórias (no jargão, passou um traço!), Ana conseguiu levar 2 (+50), Samurai venceu 7 ao todo, porém 3 dessas foram entregues à 3 Horas como pagamento por sua oferta, ficando assim com 4 no final (+100) e por fim 3 Horas que venceu 4 rodadas, soma também as 3 que recebeu de Samurai, totalizando 7 (+175). O resultado parcial coloca 3 Horas na frente com 95 pontos, seguido por Ana Maria com 30 pontos e empatados no último lugar estão D2 e Samurai com -40 pontos. Tem muito jogo pela frente ainda, Samurai coleta as cartas para embaralhar e decidirá qual a próxima Regra.

Espero que tenha ficado claro, busquei colocar situações que ilustrem como o jogo funciona e esclarecer a dinâmica do Leilão que precede à Positiva. Alternativamente o leilão poderia ter iniciado com uma oferta baixa de Samurai, 1 Positiva apenas, seguido de um lance maior de Ana Maria ou D2, o que daria novamente a Samurai a chance de escolher se cobre a oferta com um lance maior, como é o caso em leilões em geral. No jogo de mesa, se não me engano, era permitido que depois de uma recusa, o jogador pudesse aumentar sua oferta inicial. Eu não gosto disso, acho que remove o fator risco da história, então, nessa implementação do jogo, caso sua oferta inicial não seja coberta por outros participantes, a decisão será tomada em cima desse valor, sem chance do ofertante aumentar em caso de recusa. Por isso, no exemplo que usei, Samurai já inicia com uma oferta alta pois sabe que por não possuir o Naipe de Ouros e ter Ranks baixo em geral, ele está com um risco alto de passar traço caso o Naipe trunfo escolhido seja Ouros. Honestamente, eu nessa situação, levaria minha oferta até 4 ou mesmo ousadas 5, porque antes pontuar 2 positivas no final do que passar o traço.

Na versão web disponível, o usuário primeiro precisa se autenticar (botão no topo), isso é só para que eu possa registrar os recordes, não use senhas complexas e nem se preocupe com seus dados, a não ser que sua honra como jogador valha tanto. Uma vez autenticado, basta clicar em "Iniciar Partida" para que o sistema busque uma mesa disponível ou crie uma. Agora tem que esperar outros 3 jogadores fazerem a mesma coisa. Quando o número mínimo de jogadores é alcançado, o jogo se iniciará, suas cartas serão reveladas, e o jogador inicial receberá a pergunta sobre qual Regra quer utilizar para a Mão atual. Outros jogadores não vêem nada (desculpa!) e portanto parece um pouco estranho que não dá pra fazer nada, mas não se desespere, uma vez que o jogador inicial decidir a Regra, uma mensagem será enviada para todos informando qual foi a decisão. Para jogar, basta clicar na carta desejada, lembrando que o sistema não permitirá jogadas inválidas, mas também não aparecerão mensagens de erro na tela, se ao clicar a carta não está indo, é provavelmente porque ou não é sua vez, ou porque a carta em questão não pode ser jogada. Aqui eu deixo um aviso, reparei um bug que trava o jogador mesmo sendo o turno dele, parece que o bug acontece quando eu tento clicar nas minhas cartas enquanto a animação do jogador que me antecede ainda está tocando, portanto a dica é ser paciente, esperar as animações pararem e então clicar em qualquer coisa, rs. Caso você esteja em uma partida, seja o seu turno e o jogo não te deixa jogar, você pode tentar o seguinte comando no Console JavaScript do seu navegador (em geral F12), substituindo USERNAME pelo nome escolhido durante autenticação (respeitando case e entre aspas).

    > cur_game.info["TURN"](cur_game, USERNAME);

O jogo que desenvolvi é algo meio 2 em 1, com um serviço de organização de mesas e um cliente gráfico para acessar o tal do serviço, ele possui, portanto, um Servidor que organiza a mesa, embaralhando e distribuindo cartas, além de validando todas as jogadas e movendo o jogo ao longo dos seus possíveis estados. Isso é apresentado aos Clientes por meio de um mecanismo portável em termos de tecnologia, filas de mensagens e um protocolo comum. ZeroMQ é um framework para troca de mensagens com portes em mais linguagens do que eu saberia listar, além de não ter necessidade de um serviço terceiro mediar as trocas de mensagens (como em outros MQs, i.e. Message Queues). A ideia é completamente representada por duas filas, uma do tipo Requisição-Resposta onde os Clientes são os requerentes e o Servidor é quem Responde, como exemplo temos a solicitação das cartas de um jogador no início de cada mão (GETHAND no protocolo), a segunda fila é do tipo Publicador-Assinantes, onde o Servidor é o Publicador e os Clientes os Assinantes, exemplos são as notificações das jogadas dos outros jogadores, onde o servidor notificará que o jogador X jogou a carta Y (PLAY no protocolo), assim como as consequências dessas jogadas (como é o caso quando a rodada acaba). Uma vez que um cliente se conecta nessas duas filas de mensagem, ele usará o protocolo para tentar estabelecer um jogo, e digo tentar pois como dito o jogo necessita de quatro jogadores, portanto um jogo só começa quando o número de jogadores é atingido. Uma vez estabelecido o jogo, ele seguirá usando o protocolo e as duas filas para comunicar aos servidores e demais jogadores cada uma de suas ações até o final do jogo, a única diferença é que ele pode filtrar as mensagens no canal Publicador-Assinante para apenas aquelas destinadas à mesa em que está jogando.

Até onde eu saiba, não consigo usar ZeroMQ no JavaScript direto do navegador, e por isso fiz um cliente em NodeJS que serve como ponte entre os usuários finais em seus navegadores e o Servidor do jogo operando as filas em ZeroMQ. Este servidor NodeJS se comunica com os navegadores por meio de WebSockets, o que permite a comunicação nos dois sentidos, tanto do servidor para o cliente como a típica cliente-servidor (típica pois é como a web normalmente opera). Eu necessito desse segundo canal para o servidor nos notificar das coisas que ocorrem nas filas Publicador-Assinante. Repara-se que nenhuma lógica do jogo está presente no servidor NodeJS, e em verdade, muito pouca lógica do jogo está em qualquer implementação de Clientes para o jogo, inclusive do em HTML5 nos navegadores. Isto se dá pelo fato de que o protocolo não permite jogadas inválidas contendo também instruções de erro na resposta que podem ser usadas pelo Cliente para entender porquê não foi possível fazer tal jogada (ok ok, provavelmente TODO, não me lembro se fiz isso ou não). Visto que a ideia é aprender, vamos colocar mais coisas aqui, eu fiz um serviço de autenticação simples para que eu possa ter uma forma mais formal de representar a identidade dos jogadores, e em cima disso, permitir a manutenção de recordes. Isso era uma das coisas mais legais no jogo, o fato de que montávamos uma tabelinha no papel e ao final do jogo, ela virava uma memória do que foi aquele jogo, e dependendo de quão importante, você guardava com muito carinho a tabela. A ideia de manter os recordes aqui é igual, é de ter a disposição os placares de cada Mão, assim como o placar final e poder no futuro quem sabe fazer um Hall da Fama.

Aqui eu entro com outra ideia para açucarar essa aventura, como o servidor é agnóstico com relação à tecnologia do cliente (lembrem-se, eu já estou mesclando duas aqui), é possível que estes clientes sejam desenvolvidos não na expectativa de interação humana, mas como agentes completamente autônomos, como inteligências artificiais. Eu sem querer, ganhei gratuitamente, a minha implementação do jogo para jogar também contra máquinas. Eu inclusive fiz um cliente em Python, chamado de RandomBot, que como o nome implica não é a mais aguçada inteligência no mercado, mas serve de companhia para uma noite solitária.

Agora já não estou mais no ramo do que existe mas sim do ramo da especulação, projetos futuros e aquele famoso "um dia, quem sabe". O primeiro deles é ajustar a plataforma para registro de robôs, a ideia é que você possa não só criar usuários humanos mas também robôs, estes poderiam escolher usar diretamente o ZeroMQ ou usar o adaptador para WebSockets, e utilizar não só qualquer tecnologia mas também qualquer estratégia para o jogo. Eu gostaria de poder ter um tipo de Registro (similar ao Docker) onde desenvolvedores poderiam "sandbox" a solução deles e eu pudesse ativar o Robô deste desenvolvedor sob demanda. Desta forma, usuários do jogo poderiam criar mesas com robôs de sua escolha, fazendo com que o jogo possa ser usufruído sem outros usuários. Imagina que legal, você pode balancear a sua mesa colocando diferentes tipos de Robôs, e baseando essa decisão no Rank desse Robô no Hall da Fama. O RandomBot é muito tosco, e não chamaria nem de fácil pois tem que ser tão aleatório quanto ele para perder, portanto, estou desenvolvendo um menos aleatório, porém sem estratégia forte (algo como sempre jogue sua maior carta menor que a da mesa) para ter como desafiante e quem sabe o jogo não terá o modo Solitário antes do que se pensava. Sou apaixonado por Haskell também, e já comecei a rabiscar uma implementação nele onde quero tentar fazer um Robô mais esperto, com um verdadeiro algoritmo de Inteligência Artificial, como MinMax ou Aprendizado de Máquina. Oxe, falando em Aprendizado, talvez seja o canal também já pensar em implementações usando R ou mesmo Python pra pirar nas multiplicações matriciais. Eu tenho também o esqueleto de uma implementação em C++ e tinha uma em C#, mas ambas devem estar quebradas e faz muito que não toco. A em C# pretendo retomar para ver se faço uma versão Standalone do jogo, para poder baixar e jogar no PC (ainda vai exigir conexão, mas  para os entusiastas, dá pra instalar Python3 e ZeroMQ em quase todas as plataformas, e rodar o servidor localmente).

Para quem ainda está lendo isso, fica aqui o registro do protocolo, provavelmente não o melhor lugar para ler visto que pode mudar no futuro, mas pode auxiliar à solucionar alguns problemas, pois eu deixei uma interface na tela (O botão C revela ela) que permite enviar comandos diretamente ao servidor, portanto se por alguma razão eu perdi a informação sobre as cartas que tenho disponíveis em minha mão, posso requisitar ao servidor, usando GETHAND. Enfim, serve também para os interessados em fazer seus próprios Clientes para o jogo.

Mensagens não relacionadas ao jogo em si
  • AUTHORIZE <username> <password>
    • Servidor responde com uma chave <channel> representando a conexão atual
  • AVAILABLE <username> <channel>
    • Mensagem utilizada para confirmar sua disposição de ser convidado para partidas, deve ser respondida dentro de um período de tempo pois o servidor espera por apenas 10s.
    • Servidor responde com 'ACK'
  • LISTUSERS
    • Servidor retorna 'ACK', usuário receberá a lista no tópico 'user-list-channel' do canal Assinante.
  • MATCH <username> <channel> <op1> <op2> <op3>
    • Mensagem utilizada para criar uma mesa com os oponentes escolhidos
    • Servidor responde com o nome da mesa criada e envia para o tópico exclusivo de cada usuário (filtrar por <channel>) mensagens de ASKJOIN.
  • TABLE <username> <channel>
    • Servidor responde com o nome <table> da mesa criada
  • LIST
    • Servidor responde com uma lista de Mesas, estas contendo o nome e a lista de jogadores atualmente presente. Retornado no formato JSON (e.g [{'name': "t", 'players': []}])
  • JOIN <username> <channel> <table>
    • Servidor responde com uma chave <secret> exclusiva para o jogador usar nesta mesa.
  • LEAVE <username> <secret>
    • Similar à outras mensagens relativas ao jogo, esta requer a chave exclusiva do jogador, para evitar que outros jogadores possam remover adversários. Servidor responde com 'ACK'.
Mensagens utilizadas pelo cliente para realizar ações no jogo, todas elas devem ser sucedidas por <username> e <secret>, onde <secret> foi obtido em resposta à mensagem JOIN.
  • GETHAND
    • Servidor responde com a lista de cartas (representadas por Rank+Naipe em texto), formato JSON.
  • GETTURN
    • Servidor responde com o nome do jogador cujo turno é o atual.
  • GAME <rule>
    • A Regra <rule> deve ser uma das regras na lista (VAZA, COPAS, 2ULTIMAS, KING, MULHERES, HOMENS, POSITIVA).
    • Servidor responde com 'ACK' 
  • BID <value>
    • Informa o valor da oferta que o jogador está fazendo, 0 para desistir.
    • Servidor responde com 'ACK'
  • DECIDE <True | False>
    • Jogador informa se aceita ou não à oferta vigente.
    • Servidor responde com 'ACK'
  • TRUMP <'H' | 'C' | 'D' | 'S' | ''>
    • Jogador informa qual o naipe trunfo, ou vazio caso não haja (acho que isso é uma má ideia, melhor usar um token qualquer como N ou A).
    • Servidor responde com 'ACK'
  • PLAY <card>
    • Jogador informa qual carta irá jogar, <card> é uma String combinando o Rank (2, ..., T, J, Q, K, A) e o Naipe (H, C, D, S), note que 10 vira T para manter a consistência de um único caractere para o Rank.
    • Servidor responde com 'ACK' 
Mensagens enviadas pelo Servidor no canal Publicador-Assinante, endereçadas com o tópico sendo o nome da mesa (utilizado para filtrar mensagens respectivas a uma mesa)
  • START <p1> <p2> <p3> <p4>
    • Informa o início do jogo para o tópico da mesa, assim como a ordem dos jogadores nesta.
  • STARTHAND <starter> *<choices>
    • Informa ao <starter> quais opções de Regras *<choices> (* para simbolizar uma ou mais) que ele tem para sua mão
  • TURN <player>
    • Informa à mesa que e a vez do jogador <player> jogar uma carta
  • GAME <rule>
    • Informa à mesa que a mão irá começar, seguindo as Regras <rule>
  • BID <player>
    • Informa à mesa que é a vez de <player> informar seu lance
  • BIDS <value>
    • Informa à mesa que foi feito um lance de <value> pela Positiva.
  • DECIDE
    • Informa ao jogador que este deve escolher se aceita à oferta ou não.
  • CHOOSETRUMP <player>
    • Informa à mesa que uma decisão foi tomada e que <player> deve escolher o naipe trunfo
  • PLAY <card>
    • Informa à mesa que a carta <card> foi jogada.
  • ENDROUND <winner> <score>
    • Informa à mesa que a rodada encerrou, sendo <winner> o vencedor, anotando <score> pontos (positivos ou negativos).
  • ENDHAND <score1> <score2> <score3> <score4>
    • Informa à mesa que a Mão se encerrou, além de apresentar o placar final da mão
  • GAMEOVER <score1> <score2> <score3> <score4>
    • Informa à mesa que o Jogo se encerrou, apresenta o placar final.
  • LEAVE <player>
    • Informa à mesa que o Jogador <player> saiu da mesa (Isso provoca o encerramento do jogo, caso esteja em andamento).
Mensagens enviadas pelo Servidor no canal Publicador-Assinante, endereçadas ao tópico especial 'user-list-channel' ou à tópicos exclusivos para um usuário (como CONFIRM_AVAILABLE). Esta parte ainda está bem crua, mas é uma forma de criar um espaço para Organizar Partidas, onde jogadores podem logar no sistema e serem convidados por outros jogadores (ou Robôs) para formarem uma mesa.
  • CONFIRM_AVAILABLE
    • Mensagem enviada diretamente ao tópico exclusivo do usuário <channel>. Esta mensagem deve ser respondida o mais rápido possível com a mensagem AVAILABLE..
  • ASKJOIN <table>
    • Mensagem enviada diretamente ao tópico exclusivo do usuário <channel>. Informa que este foi convidado a juntar-se a mesa <table>. 

Fonte da Imagem:
- https://pixahive.com/photo/king-of-hearts-in-cards/







Comentários