No. Gracias y adiós.
Bien, antes que nada, me disculpo por el título con estilo de cebo de clics. De vez en cuando, cuando se me ocurre una idea para una demostración, no funciona. Pero a veces no funciona de una manera divertida e interesante, y creo que es una buena idea compartirlo de todos modos. (Además, siempre existe la gran posibilidad de que no funcionó porque hice algo mal). Creo que la demostración de hoy es un ejemplo perfecto de eso.
A principios de este año, creé un juego de Blackjack usando Alpine.js y la excelente API Deck of Cards . Ayer estaba pensando en el juego y me pregunté: ¿qué pasaría si usara IA generativa para pedir ayuda al jugarlo? Entonces, por ejemplo, fui a mi juego y vi esto:
En la aplicación PaLM Makersuite de Google, escribí:
Estoy jugando al blackjack y el crupier muestra un seis de diamantes. Tengo una jota de tréboles y una jota de corazones. ¿Debo golpear o debo quedarme?
Y obtuve esta respuesta:
Tienes 20, lo cual es una buena mano. El dealer tiene 16, lo que está por debajo del promedio. Si aciertas, corres el riesgo de recibir una carta que te arruinará. Así que es mejor quedarse y esperar que el dealer quiebre.
Lo cual, bueno, es bastante obvio. Nunca pedirías con dos jotas. Pero me encantó la respuesta descriptiva que refuerza los principios del juego. Pensé: ¿qué pasaría si agregara un botón al juego que te permita pedir ayuda a GenAI? Así es como lo hice:
En la interfaz, mantuve los cambios en la interfaz de usuario al mínimo. Comencé agregando un botón simple:
Al hacer clic aquí es necesario hacer dos cosas importantes. Primero, necesita "traducir" el estado del juego en un mensaje; luego, debe pasar esto a un servicio que manejará la llamada gen AI . Creé una nueva función, askForHelp
, que lo maneja:
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); },
Almaceno las tarjetas del jugador y del PC en dos variables que contienen una serie de tarjetas que representan sus manos. Cada carta tiene un "valor", que es un número o un nombre, como Jack. Cada carta también tiene un palo. En teoría, probablemente esa no sea información crítica, pero pensé que no vendría mal. A continuación se muestra un mensaje de ejemplo generado por un juego:
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?
Esto se pasa a mi servicio backend (más sobre esto en un segundo) y el resultado luego se muestra en una alerta de JavaScript, que es una experiencia de usuario 100% horrible, pero como se trataba de un experimento, pensé que era lo suficientemente bueno.
Si tienes curiosidad, esta fue la respuesta:
Permanecer. Tienes un total de 17, lo cual es una buena mano. El 8 del crupier no es una carta alta, por lo que es poco probable que pierdas si te quedas.
Para mi back-end, preparé un flujo de trabajo rápido de Pipedream . Mi flujo de trabajo constaba literalmente de sólo tres pasos.
El primero es mi disparador, un punto final HTTP. Puede ver la URL en el código de interfaz de usuario de arriba.
El segundo paso es la acción API Google PaLM integrada. Todo lo que tenía que hacer era decirle qué usar para un mensaje: {{steps.trigger.event.query.text}}
Mi último paso simplemente arrojó el 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), }) }, })
Así que sí. Mis primeras pruebas funcionaron muy bien, y luego... las cosas se descarrilaron. He aquí un ejemplo. El crupier tenía un siete de corazones. Tenía una reina de espadas y un ocho de diamantes. Cuando pedí ayuda, obtuve:
La carta descubierta del crupier es 7, que es un 17 "duro". Eso significa que el crupier debe plantarse independientemente de lo que usted haga. Tienes un 19 "suave", lo que significa que tienes un as que puede contar como 1 o 11. Deberías quedarte.
Vale, claro, debería quedarme, pero ¿qué diablos pasa? ¿Tengo 19? ¿El distribuidor tiene 17? Y se pone mejor. Una vez, cuando tenía 18 programas, obtuve lo siguiente:
Yo recomendaría golpear. Con un total de 18, estás a sólo un punto de 21, que es la mano ganadora en el blackjack. El siete de corazones del crupier les da un total de 17, que está por debajo del umbral de 21. Por lo tanto, existe una buena probabilidad de que el crupier se pase si acierta, lo que le daría la victoria. Sin embargo, si te quedas, no podrás mejorar tu mano y tendrás que esperar que el crupier se pase.
Acertar con 18. No soy un jugador profesional, pero estoy bastante seguro de que es un mal consejo.
Esto me hizo pensar un poco más en mi mensaje. Hice algunos ajustes, como poner el traje en minúsculas, porque me preocupaba que pudiera haber confundido a la IA. Repasé algunas opciones en Makersuite y, consistentemente, a veces parecía tener problemas con las matemáticas básicas. Incluso cuando fui explícito:
Tengo una sota de tréboles (que vale diez) y un ocho de corazones (que vale 8).
Todavía pensaba que tenía 19. Tuve más suerte con esto:
Tengo una sota de tréboles (que vale diez) y un ocho de corazones (que vale 8). El total de mi mano es 18.
Y también probé esto:
Tengo un as de tréboles y un ocho de corazones. El total de mi mano es 9 o 19.
Y eso pareció funcionar correctamente de manera consistente. Entonces, con eso en mente, volví a mi generación rápida y actualicé mi lógica allí. Aquí está la versión actualizada:
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); },
Mi juego de Blackjack tiene una función de utilidad, getCount
, que devuelve un objeto que contiene dos valores lowCount
, donde los Ases se tratan como uno, y highCount
, donde un As como máximo se considera once. Con esto, agrego a mi mensaje información sobre el total.
Esto me ayudó bastante, pero a veces todavía respondía con respuestas delirantes.
Entonces... ¿tal vez lo consideres tu amigo borracho en la mesa de juego ofreciéndote consejos?
Me encantaría compartir públicamente esta versión de la demostración y puedo compartir el código, por supuesto, pero estoy deshabilitando el flujo de trabajo de Pipedream para que no me cobren por ello. No dudes en consultar el código de interfaz aquí:
https://codepen.io/cfjedimaster/pen/QWYpQjY/905e31ed6d9e3539c041044b308f5f06?editors=1011
Como siempre, ¡déjame saber lo que piensas!
También publicado aquí .