Não. Obrigado e adeus.
Ok, em primeiro lugar, peço desculpas pelo título no estilo click-bait. De vez em quando, quando tenho uma ideia para uma demonstração, ela não funciona. Mas às vezes isso não funciona de uma forma divertida e interessante, e acho que é uma boa ideia compartilhar isso de qualquer maneira. (Além disso, sempre há uma grande chance de não ter dado certo porque fiz algo errado!) A demonstração de hoje é um exemplo perfeito disso, eu acho.
No início deste ano, criei um jogo de Blackjack usando Alpine.js e a excelente API Deck of Cards . Ontem, eu estava pensando no jogo e me perguntei: o que aconteceria se eu usasse a IA generativa para pedir ajuda ao jogá-lo? Então, por exemplo, fui ao meu jogo e vi isto:
No aplicativo PaLM Makersuite do Google, escrevi:
Estou jogando blackjack e o dealer tem um seis de ouros à mostra. Eu tenho um valete de paus e um valete de copas. Devo bater ou devo ficar?
E obtive esta resposta:
Você tem 20, o que é uma boa mão. O dealer tem 16, o que está abaixo da média. Se você acertar, corre o risco de receber uma carta que irá prendê-lo. Então é melhor ficar e torcer para que o dealer quebre.
O que, ok, é bastante óbvio. Você nunca acertaria com dois Valetes. Mas adorei a resposta descritiva que reforça os princípios do jogo. Eu pensei – e se eu adicionasse um botão ao jogo que permitisse pedir ajuda à GenAI? Veja como eu fiz isso:
No front-end, mantive as alterações da IU mínimas. Comecei adicionando um botão simples:
Clicar aqui precisa fazer duas coisas importantes. Primeiro, ele precisa “traduzir” o estado do jogo em um prompt; então, ele precisa passar isso para um serviço que irá lidar com a chamada gen AI . Criei uma nova função, askForHelp
, que trata disso:
async askForHelp() { this.aiHelp = '<i>Asking our AI friend...</i>'; // first, "translate" the cards to English let q = `I'm playing blackjack and the dealer currently has ${this.pcCards[1].value} of ${this.pcCards[1].suit.toLowerCase()}. I've got a ${this.playerCards[0].value} of ${this.playerCards[0].suit.toLowerCase()} and a ${this.playerCards[1].value} of ${this.playerCards[1].suit.toLowerCase()}. Should I hit or should I stay?`; console.log(q); let resp = await fetch(`https://eo8dkukowewth66.m.pipedream.net?text=${encodeURIComponent(q)}`); let help = await resp.json(); /* note that help has line breaks and stuff in and in theory we should map to br or something, but im fine ignoring. */ this.aiHelp = ''; alert(help); },
Eu armazeno as cartas do jogador e do PC em duas variáveis que contêm uma série de cartas representando suas mãos. Cada carta tem um 'valor', que pode ser um número ou um nome, como Jack. Cada carta também possui um naipe. Em teoria, provavelmente não é uma informação crítica, mas imaginei que não faria mal. Aqui está um exemplo de prompt gerado por um jogo:
I'm playing blackjack and the dealer currently has 8 of clubs. I've got a 7 of spades and a QUEEN of hearts. Should I hit or should I stay?
Isso é repassado para meu serviço de back-end (mais sobre isso em um segundo) e o resultado é exibido em um alerta JavaScript, o que é uma UX 100% horrível, mas como se tratava de um experimento, achei que era bom o suficiente.
Se você está curioso, esta é a resposta:
Ficar. Você tem um total de 17, o que é uma boa mão. O 8 do dealer não é uma carta alta, então é improvável que você perca se permanecer.
Para meu back-end, criei um fluxo de trabalho rápido do Pipedream . Meu fluxo de trabalho consistia literalmente em apenas três etapas.
O primeiro é meu gatilho, um endpoint HTTP. Você pode ver o URL no código front-end acima.
A segunda etapa é a ação integrada da API Google PaLM. Tudo o que tive que fazer foi dizer o que usar para um prompt: {{steps.trigger.event.query.text}}
Minha etapa final simplesmente retornou o resultado:
export default defineComponent({ async run({ steps, $ }) { await $.respond({ status: 200, headers: { "Content-Type":"application/json" }, body: JSON.stringify(steps.generate_text.$return_value[0].candidates[0].output), }) }, })
Então sim. Meus primeiros testes funcionaram muito bem e então... as coisas saíram dos trilhos. Aqui está um exemplo. O dealer tinha um sete de copas. Eu tinha uma dama de espadas e um oito de ouros. Quando pedi ajuda, recebi:
A carta exposta do dealer é 7, que é um 17 "difícil". Isso significa que o dealer deve permanecer independentemente do que você fizer. Você tem um 19 "suave", o que significa que tem um ás que pode contar como 1 ou 11. Você deve ficar.
Ok, claro, eu deveria ficar, mas e daí? Eu tenho 19? O dealer tem 17? E fica melhor. Uma vez, quando eu tinha 18 anos, recebi o seguinte:
Eu recomendaria bater. Com um total de 18, você está a apenas um ponto de 21, que é a mão vencedora no blackjack. O sete de copas do dealer dá-lhes um total de 17, que está abaixo do limite de 21. Portanto, há uma boa chance de o dealer estourar se acertar, o que lhe daria a vitória. No entanto, se você ficar, não será capaz de melhorar sua mão e terá que torcer para que o dealer rebente.
Acertar 18. Não sou jogador profissional, mas tenho quase certeza de que é um mau conselho.
Isso me fez pensar um pouco mais sobre meu prompt. Fiz alguns ajustes, como colocar letras minúsculas no traje, pois estava preocupado que isso pudesse ter confundido a IA. Eu iterei algumas opções no Makersuite e, de forma consistente, às vezes parecia ter dificuldades com a matemática básica. Mesmo quando fui explícito:
Tenho um valete de paus (no valor de dez) e um oito de copas (no valor de 8).
Ainda achava que eu tinha 19. Tive mais sorte com isso:
Tenho um valete de paus (no valor de dez) e um oito de copas (no valor de 8). O total da minha mão é 18.
E eu também tentei isso:
Tenho um ás de paus e um oito de copas. O total da minha mão é 9 ou 19.
E isso parecia funcionar corretamente de forma consistente. Então, com isso em mente, voltei para minha geração de prompts e atualizei minha lógica lá. Aqui está a versão atualizada:
async askForHelp() { this.aiHelp = '<i>Asking our AI friend...</i>'; let totalOb = this.getCount(this.playerCards); let totalStr = 'My hand has a total of '; if(totalOb.lowCount === totalOb.highCount) { totalStr += totalOb.lowCount; } else { totalStr += `${totalOb.lowCount} or ${totalOb.highCount}`; } // first, "translate" the cards to English let q = `I'm playing blackjack and the dealer currently has ${this.pcCards[1].value} of ${this.pcCards[1].suit.toLowerCase()}. I've got a ${this.playerCards[0].value} of ${this.playerCards[0].suit.toLowerCase()} and a ${this.playerCards[1].value} of ${this.playerCards[1].suit.toLowerCase()}. ${totalStr}. Should I hit or should I stay?`; console.log(q); let resp = await fetch(`https://eo8dkukowewth66.m.pipedream.net?text=${encodeURIComponent(q)}`); let help = await resp.json(); console.log('RESP', help); /* note that help has line breaks and stuff in and in theory we should map to br or something, but im fine ignoring. */ this.aiHelp = ''; alert(help); },
Meu jogo de Blackjack tem uma função utilitária, getCount
, que retorna um objeto contendo dois valores lowCount
, onde Ases são tratados como um, e highCount
, onde no máximo um Ás é considerado onze. Com isso, adiciono ao meu prompt informações sobre o total.
Isso ajudou bastante, mas às vezes ainda retornava respostas delirantes.
Então... talvez considere isso seu amigo bêbado na mesa de jogo oferecendo conselhos?
Eu adoraria compartilhar esta versão da demonstração publicamente, e posso compartilhar o código, é claro, mas estou desabilitando o fluxo de trabalho do Pipedream para não ser cobrado por isso. Sinta-se à vontade para ver o código front-end aqui:
https://codepen.io/cfjedimaster/pen/QWYpQjY/905e31ed6d9e3539c041044b308f5f06?editors=1011
Como sempre, deixe-me saber o que você pensa!
Também publicado aqui .