Membuat game tebak-tebakan dengan Prompt API

Dipublikasikan: 10 Oktober 2025

Anak-anak usia sekolah bermain game Guess Who pada tahun 2014.

Game papan klasik, Guess Who?, adalah contoh sempurna dari penalaran deduktif. Setiap pemain memulai dengan papan berisi wajah dan, melalui serangkaian pertanyaan ya atau tidak, mempersempit kemungkinan hingga Anda dapat mengidentifikasi karakter rahasia lawan dengan yakin.

Setelah melihat demo AI bawaan di Google I/O Connect, saya bertanya-tanya: bagaimana jika saya bisa bermain game Tebak Siapa? melawan AI yang ada di browser? Dengan AI sisi klien, foto akan ditafsirkan secara lokal, sehingga game Tebak Siapa? khusus teman dan keluarga akan tetap bersifat pribadi dan aman di perangkat saya.

Latar belakang saya terutama dalam pengembangan UI dan UX, dan saya terbiasa membangun pengalaman yang sempurna secara piksel. Saya berharap dapat melakukan hal itu dengan interpretasi saya.

Aplikasi saya, AI Guess Who?, dibuat dengan React dan menggunakan Prompt API serta model bawaan browser untuk membuat lawan yang sangat mumpuni. Dalam proses ini, saya menemukan bahwa tidak mudah untuk mendapatkan hasil yang "sempurna". Namun, aplikasi ini menunjukkan cara AI dapat digunakan untuk membangun logika game yang matang, dan pentingnya rekayasa perintah untuk menyempurnakan logika ini dan mendapatkan hasil yang Anda harapkan.

Lanjutkan membaca untuk mempelajari integrasi AI bawaan, tantangan yang saya hadapi, dan solusi yang saya temukan. Anda dapat memainkan game dan menemukan kode sumber di GitHub.

Dasar-dasar game: Aplikasi React

Sebelum melihat penerapan AI, kita akan meninjau struktur aplikasi. Saya membuat aplikasi React standar dengan TypeScript, dengan file App.tsx pusat untuk bertindak sebagai pengatur game. File ini berisi:

  • Status game: Enum yang melacak fase game saat ini (seperti PLAYER_TURN_ASKING, AI_TURN, GAME_OVER). Ini adalah bagian status yang paling penting, karena menentukan apa yang ditampilkan antarmuka dan tindakan apa yang tersedia bagi pemain.
  • Daftar karakter: Ada beberapa daftar yang menentukan karakter aktif, setiap karakter rahasia pemain, dan karakter yang telah dieliminasi dari papan.
  • Chat game: Log yang terus berjalan berisi pertanyaan, jawaban, dan pesan sistem.

Antarmuka dibagi menjadi komponen logis:

GameSetup adalah layar awal.
GameBoard menampilkan petak karakter dan kontrol chat untuk menangani semua input pengguna.

Seiring berkembangnya fitur game, kompleksitasnya juga meningkat. Awalnya, seluruh logika game dikelola dalam satu hook React kustom yang besar, useGameLogic, tetapi dengan cepat menjadi terlalu besar untuk dinavigasi dan di-debug. Untuk meningkatkan kemudahan pemeliharaan, saya memfaktorkan ulang hook ini menjadi beberapa hook, yang masing-masing memiliki satu tanggung jawab. Contoh:

  • useGameState mengelola status inti
  • usePlayerActions adalah untuk giliran pemain
  • useAIActions adalah untuk logika AI

Sekarang, hook useGameLogic utama bertindak sebagai composer yang bersih, yang menggabungkan hook yang lebih kecil ini. Perubahan arsitektur ini tidak mengubah fungsi game, tetapi membuat codebase jauh lebih bersih.

Logika game dengan Prompt API

Inti dari project ini adalah penggunaan Prompt API.

Saya menambahkan logika game AI ke builtInAIService.ts. Berikut adalah tanggung jawab utamanya:

  1. Izinkan jawaban biner yang membatasi.
  2. Mengajari model strategi game.
  3. Mengajarkan analisis model.
  4. Membuat model mengalami amnesia.

Mengizinkan jawaban biner yang ketat

Bagaimana pemain berinteraksi dengan AI? Saat pemain bertanya, "Apakah karaktermu memakai topi?", AI perlu "melihat" gambar karakter rahasianya dan memberikan jawaban yang jelas.

Upaya pertama saya berantakan. Responsnya bersifat percakapan: "Tidak, karakter yang saya pikirkan, Isabella, tampaknya tidak memakai topi", alih-alih menawarkan jawaban biner ya atau tidak. Awalnya, saya menyelesaikan masalah ini dengan perintah yang sangat ketat, yang pada dasarnya mendikte model untuk hanya merespons dengan "Ya" atau "Tidak".

Meskipun berhasil, saya mempelajari cara yang lebih baik menggunakan output terstruktur. Dengan memberikan Skema JSON ke model, saya dapat menjamin respons benar atau salah.

const schema = { type: "boolean" };
const result = session.prompt(prompt, { responseConstraint: schema });

Hal ini memungkinkan saya menyederhanakan perintah dan membiarkan kode saya menangani respons dengan andal:

JSON.parse(result) ? "Yes" : "No"

Mengajari model strategi game

Meminta model menjawab pertanyaan jauh lebih sederhana daripada meminta model memulai dan mengajukan pertanyaan. Pemain yang baik dalam permainan Tebak Siapa? tidak mengajukan pertanyaan acak. Mereka mengajukan pertanyaan yang menghilangkan paling banyak karakter sekaligus. Pertanyaan yang ideal akan mengurangi kemungkinan karakter yang tersisa menjadi setengahnya menggunakan pertanyaan biner.

Bagaimana cara mengajari model strategi tersebut? Sekali lagi, rekayasa perintah. Perintah untuk generateAIQuestion() sebenarnya adalah pelajaran singkat dalam teori game Guess Who?.

Awalnya, saya meminta model untuk "mengajukan pertanyaan yang bagus". Hasilnya tidak dapat diprediksi. Untuk meningkatkan hasil, saya menambahkan batasan negatif. Perintah kini menyertakan petunjuk yang mirip dengan:

  • "KRITIS: Hanya tanyakan tentang fitur yang ada"
  • "KRITIS: Jadilah diri sendiri. JANGAN mengulangi pertanyaan".

Batasan ini mempersempit fokus model, mencegahnya mengajukan pertanyaan yang tidak relevan, sehingga menjadikannya lawan yang jauh lebih menyenangkan. Anda dapat meninjau file perintah lengkap di GitHub.

Mengajarkan analisis model

Ini adalah tantangan yang paling sulit dan penting. Saat model mengajukan pertanyaan, seperti, "Apakah karakter Anda memiliki topi," dan pemain menjawab tidak, bagaimana model mengetahui karakter mana di papan mereka yang dieliminasi?

Model harus menghilangkan semua orang yang memakai topi. Upaya awal saya dipenuhi dengan kesalahan logis, dan terkadang model menghilangkan karakter yang salah atau tidak menghilangkan karakter sama sekali. Selain itu, apa yang dimaksud dengan "topi"? Apakah "kupluk" termasuk "topi"? Hal ini, jujur saja, juga dapat terjadi dalam perdebatan antarmanusia. Dan tentu saja, kesalahan umum terjadi. Rambut dapat terlihat seperti topi dari perspektif AI.

Saya mendesain ulang arsitektur untuk memisahkan persepsi dari deduksi kode:

  1. AI bertanggung jawab atas analisis visual. Model ini unggul dalam analisis visual. Saya menginstruksikan model untuk menampilkan pertanyaannya dan analisis mendetail dalam skema JSON yang ketat. Model menganalisis setiap karakter di papan dan menjawab pertanyaan, "Apakah karakter ini memiliki fitur ini?" Model menampilkan objek JSON terstruktur:

    { "character_id": "...", "has_feature": true }
    

    Sekali lagi, data terstruktur adalah kunci keberhasilan.

  2. Kode game menggunakan analisis untuk membuat keputusan akhir. Kode aplikasi memeriksa jawaban pemain ("Ya" atau "Tidak") dan melakukan iterasi melalui analisis AI. Jika pemain mengatakan "Tidak", kode akan tahu untuk menghilangkan setiap karakter di mana has_feature adalah true.

Menurut saya, pembagian tugas ini adalah kunci untuk membangun aplikasi AI yang andal. Gunakan AI untuk kemampuan analisisnya, dan serahkan keputusan biner ke kode aplikasi Anda.

Untuk memeriksa persepsi model, saya membuat visualisasi analisis ini. Hal ini memudahkan untuk mengonfirmasi apakah persepsi model sudah benar.

Rekayasa perintah

Namun, meskipun dengan pemisahan ini, saya melihat bahwa persepsi model masih bisa salah. Mungkin salah menilai apakah suatu karakter memakai kacamata, sehingga menyebabkan eliminasi yang salah dan membuat frustrasi. Untuk mengatasi hal ini, saya bereksperimen dengan proses dua langkah: AI akan mengajukan pertanyaannya. Setelah menerima jawaban pemutar, ia akan melakukan analisis kedua yang baru dengan jawaban sebagai konteks. Teorinya adalah bahwa pemeriksaan kedua dapat menemukan kesalahan dari pemeriksaan pertama.

Berikut cara kerja alur tersebut:

  1. Giliran AI (panggilan API 1): AI bertanya, "Apakah karakter Anda memiliki janggut?"
  2. Giliran pemain: Pemain melihat karakter rahasianya, yang bercukur bersih, dan menjawab, "Tidak".
  3. Giliran AI (panggilan API 2): AI secara efektif meminta dirinya sendiri untuk melihat semua karakter yang tersisa, lagi, dan menentukan karakter mana yang harus dihilangkan berdasarkan jawaban pemain.

Pada langkah kedua, model mungkin masih salah mengartikan karakter dengan janggut tipis sebagai "tidak memiliki janggut" dan gagal menghapusnya, meskipun pengguna mengharapkannya. Error persepsi inti tidak diperbaiki, dan langkah tambahan hanya menunda hasil. Saat bermain melawan lawan manusia, kita dapat menentukan perjanjian atau klarifikasi tentang hal ini; dalam penyiapan saat ini dengan lawan AI, hal ini tidak terjadi.

Proses ini menambahkan latensi dari panggilan API kedua, tanpa mendapatkan peningkatan akurasi yang signifikan. Jika model salah pada percobaan pertama, model juga sering salah pada percobaan kedua. Saya mengembalikan perintah untuk ditinjau hanya sekali.

Tingkatkan kualitas, bukan menambahkan lebih banyak analisis

Saya mengandalkan prinsip UX: Solusinya bukan analisis yang lebih banyak, tetapi analisis yang lebih baik.

Saya berinvestasi besar-besaran dalam menyempurnakan perintah, menambahkan petunjuk eksplisit agar model memeriksa ulang pekerjaannya dan berfokus pada fitur yang berbeda, yang terbukti menjadi strategi yang lebih efektif untuk meningkatkan akurasi. Berikut cara kerja alur saat ini yang lebih andal:

  1. Giliran AI (panggilan API): Model diminta untuk membuat pertanyaan dan analisis internalnya secara bersamaan, sehingga menampilkan satu objek JSON.

    1. Pertanyaan: "Apakah karakter Anda memakai kacamata?"
    2. Analisis (data):
    [
      {character_id: 'brad', has_feature: true},
      {character_id: 'alex', has_feature: false},
      {character_id: 'gina', has_feature: true},
      ...
    ]
    
  2. Giliran pemain: Karakter rahasia pemain adalah Alex (tanpa kacamata), jadi dia menjawab, "Tidak".

  3. Akhir putaran: Kode JavaScript aplikasi mengambil alih. Tidak perlu menanyakan hal lain kepada AI. Langkah ini melakukan iterasi melalui data analisis dari langkah 1.

    1. Pemain berkata "Tidak".
    2. Kode mencari setiap karakter yang memiliki nilai has_feature benar (true).
    3. Brad dan Gina akan berbalik ke bawah. Logikanya bersifat deterministik dan instan.

Eksperimen ini sangat penting, tetapi memerlukan banyak coba-coba. Saya tidak tahu apakah kondisinya akan membaik. Terkadang, kondisinya bahkan lebih buruk. Menentukan cara mendapatkan hasil yang paling konsisten bukanlah ilmu pasti (belum, jika pernah...).

Namun, setelah beberapa putaran dengan lawan AI baru saya, masalah baru yang luar biasa muncul: kebuntuan.

Mengatasi kebuntuan

Jika hanya dua atau tiga karakter yang sangat mirip yang tersisa, model akan terjebak dalam loop. Bot akan mengajukan pertanyaan tentang fitur yang dimiliki semua karakter, seperti, "Apakah karaktermu memakai topi?"

Kode saya akan mengidentifikasi ini dengan benar sebagai giliran yang sia-sia, dan AI akan mencoba fitur lain yang sama luasnya yang juga dimiliki semua karakter, seperti, "Apakah karakter Anda memakai kacamata?"

Saya meningkatkan kualitas perintah dengan aturan baru: jika upaya pembuatan pertanyaan gagal dan ada tiga karakter atau kurang yang tersisa, strategi akan berubah.

Petunjuk baru ini jelas: "Daripada fitur yang luas, Anda harus bertanya tentang fitur visual yang lebih spesifik, unik, atau gabungan untuk menemukan perbedaan." Misalnya, alih-alih menanyakan apakah karakter memakai topi, karakter tersebut diminta untuk menanyakan apakah dia memakai topi bisbol.

Hal ini memaksa model untuk melihat gambar lebih dekat guna menemukan satu detail kecil yang akhirnya dapat menghasilkan terobosan, sehingga strategi akhir gamenya bekerja sedikit lebih baik, sebagian besar waktu.

Membuat model lupa

Kekuatan terbesar model bahasa adalah memorinya. Namun, dalam game ini, kekuatan terbesarnya menjadi kelemahan. Saat saya memulai game kedua, game tersebut akan mengajukan pertanyaan yang membingungkan atau tidak relevan. Tentu saja, lawan AI pintar saya mempertahankan seluruh histori chat dari game sebelumnya. Ia mencoba memahami dua (atau bahkan lebih) pertandingan sekaligus.

Daripada menggunakan kembali sesi AI yang sama, saya sekarang secara eksplisit menghancurkannya di akhir setiap game, yang pada dasarnya membuat AI amnesia.

Saat Anda mengklik Play Again, fungsi startNewGameSession() akan mereset papan dan membuat sesi AI baru. Ini adalah pelajaran menarik dalam mengelola status sesi tidak hanya di aplikasi, tetapi dalam model AI itu sendiri.

Fitur tambahan: Game kustom dan input suara

Untuk membuat pengalaman yang lebih menarik, saya menambahkan dua fitur tambahan:

  1. Karakter kustom: Dengan getUserMedia(), pemain dapat menggunakan kamera mereka untuk membuat set 5 karakter mereka sendiri. Saya menggunakan IndexedDB untuk menyimpan karakter, database browser yang sempurna untuk menyimpan data biner seperti blob gambar. Saat Anda membuat set kustom, set tersebut akan disimpan ke browser Anda, dan opsi pemutaran ulang akan muncul di menu utama.

  2. Input suara: Model sisi klien bersifat multi-modal. Model ini dapat menangani teks, gambar, dan juga audio. Dengan menggunakan MediaRecorder API untuk merekam input mikrofon, saya dapat memasukkan blob audio yang dihasilkan ke model dengan perintah: "Transkripsikan audio berikut...". Hal ini menambahkan cara yang menyenangkan untuk bermain (dan cara yang menyenangkan untuk melihat cara Google menafsirkan aksen Flandria saya). Saya membuat ini sebagian besar untuk menunjukkan fleksibilitas kemampuan web baru ini, tetapi sejujurnya, saya bosan mengetik pertanyaan berulang kali.

Poin penutup

Membangun "AI Guess Who?" jelas merupakan tantangan. Namun, dengan sedikit bantuan dari membaca dokumen dan beberapa AI untuk men-debug AI (ya... Saya melakukannya), ternyata itu adalah eksperimen yang menyenangkan. Hal ini menyoroti potensi besar menjalankan model di browser untuk menciptakan pengalaman yang pribadi, cepat, dan tidak memerlukan internet. Ini masih merupakan eksperimen, dan terkadang lawan tidak bermain dengan sempurna. Tidak sempurna secara piksel atau logika. Dengan AI generatif, hasilnya bergantung pada model.

Daripada berupaya mencapai kesempurnaan, saya akan berupaya meningkatkan hasil.

Project ini juga menggarisbawahi tantangan rekayasa perintah yang terus-menerus. Perintah tersebut menjadi bagian yang sangat penting, dan tidak selalu menjadi bagian yang paling menyenangkan. Namun, pelajaran paling penting yang saya pelajari adalah merancang arsitektur aplikasi untuk memisahkan persepsi dari deduksi, membagi kemampuan AI dan kode. Meskipun dengan pemisahan tersebut, saya mendapati bahwa AI masih dapat membuat kesalahan yang jelas (bagi manusia), seperti mengira tato sebagai riasan atau tidak mengetahui karakter rahasia siapa yang sedang dibahas.

Setiap kali, solusinya adalah membuat perintah menjadi lebih eksplisit, dengan menambahkan petunjuk yang terasa jelas bagi manusia, tetapi penting bagi model.

Terkadang, game terasa tidak adil. Terkadang, saya merasa AI "mengetahui" karakter rahasia sebelumnya, meskipun kode tidak pernah secara eksplisit membagikan informasi tersebut. Hal ini menunjukkan bagian penting dari manusia versus mesin:

Perilaku AI tidak hanya harus benar, tetapi juga harus terasa adil.

Itulah sebabnya saya memperbarui perintah dengan petunjuk yang terus terang, seperti, "Anda TIDAK tahu karakter mana yang telah saya pilih", dan "Jangan curang". Saya mempelajari bahwa saat membangun agen AI, Anda harus meluangkan waktu untuk menentukan batasan, bahkan mungkin lebih banyak daripada petunjuk.

Interaksi dengan model dapat terus ditingkatkan. Dengan menggunakan model bawaan, Anda akan kehilangan sebagian kemampuan dan keandalan model sisi server yang besar, tetapi Anda akan mendapatkan privasi, kecepatan, dan kemampuan offline. Untuk game seperti ini, kompromi tersebut sangat layak untuk dieksperimenkan. Masa depan AI sisi klien kian membaik dari hari ke hari, model juga semakin kecil, dan kami tidak sabar untuk melihat apa yang dapat kami bangun selanjutnya.