solution: metadata: id: aiventure title: "AIventure: Learning about vibe-coding, agents and Gen AI with Gemma 4" description: Learn how to create an AI-powered educational game focused on vibe-coding and agentic characters using Angular, PhaserJS, Gemini and Gemma. fallbackCta: title: Download Antigravity icon: antigravity url: https://antigravity.google/ accentColor: foreground: "white" background: "#141e2a" githubCta: url: https://github.com/bebechien/AIventure compactUI: true code: assetsPath: external-assets/aiventure/ remotePath: https://github.com/bebechien/AIventure/blob/main/ sections: - type: watch youtubeId: 6yjJ7Prt-RI title: "AIventure: Learning about vibe-coding, agents and Gen AI with Gemma 4" description: Learn how to create an AI-powered educational game focused on vibe-coding and agentic characters using Angular, PhaserJS, Gemini and Gemma. technologies: - icon: /external-assets/angular.svg label: Angular url: https://angular.io - icon: /external-assets/gemma.png label: Gemma url: https://ai.google.dev/gemma - icon: /external-assets/gemini.svg label: Gemini url: https://ai.google.dev/ - icon: /external-assets/vertex-ai.svg label: Vertex AI url: https://cloud.google.com/vertex-ai - type: explore mode: video subsections: - id: video-1 title: Prompt routing and simple commands description: | The user opens doors by typing natural language prompts. The command is routed to either a local Gemma 4 model or Gemini 3 Flash depending on the configuration. Context about the room's doors and functions is provided in the prompt. videoPath: external-assets/aiventure/aiventure-1.mp4 orientation: landscape frame: none logs: - timestamp: 6500 summary: User types "open the red door" in the chat interface and the client app routes the prompt to the configured model (Locally running Gemma, Gemma via Vertex AI or Gemini 3 Flash). sequence: - angular - local-gemma inspect: model-backends - timestamp: 7500 summary: The LLM parses the command and triggers door opening in PhaserJS using function calling. sequence: - local-gemma - angular - id: video-2 title: Vibe coding puzzles description: | A chicken NPC asks the user to help build a To-Do list app. The twist is that the chicken insists the app only allow "eat" or "sleep" tasks. Gemma builds the app based on the prompt and renders it in an iframe. Gemma then evaluates the app's code to verify the constraints imposed by the chicken NPC. videoPath: external-assets/aiventure/aiventure-2.mp4 orientation: landscape frame: none logs: - timestamp: 1000 summary: The user interacts with the Chicken NPC which gives the user a task to complete. sequence: - angular - timestamp: 5000 summary: User begins writing a prompt for a To-Do list app. sequence: - angular - timestamp: 8000 summary: The user gives details that the To-Do list should only have two buttons in its interface. As per the requirements given by the NPC. sequence: - gemini - timestamp: 16000 summary: The prompt is sent to the model. Gemma 4 is run locally and generates the code, demonstrating its reasoning capabilities. sequence: - angular - local-gemma inspect: vibe-coding - timestamp: 17000 summary: The model returns the code for the To-Do list app which the Angular app then renders in an iframe for the user. sequence: - local-gemma - angular - timestamp: 22000 summary: The code outputted is evaluated by the Chicken NPC who then determines whether the door of the puzzle should open or not. sequence: - angular - chicken - id: video-3 title: Agentic NPC thinking loops description: | The player encounters a lava pit and must use a Robot NPC to flip a switch on the other side. The Robot NPC takes a prompt from the user and runs an agentic loop via the Gen AI model to achieve the goal: - Retrieve instructions - Execute - Re-prompt videoPath: external-assets/aiventure/aiventure-3.mp4 orientation: landscape frame: none logs: - timestamp: 4000 summary: User approaches Robot NPC and interacts with it. Learning that the Robot NPC accepts instructions in natural language. sequence: - angular - local-gemma - timestamp: 14000 summary: User instructs Robot to flip the switch across the lava. sequence: - angular - timestamp: 16000 summary: Robot executes step and loops back to Gemma for instructions. Entering into an agentic thinking loop to find the switch and then flip it once found. sequence: - angular - local-gemma - angular inspect: agentic-robot - id: demo-webgpu title: "Interactive Demo: Mediapipe with WebGPU" disclaimer: "This demo uses Mediapipe with WebGPU to load and run a Gemma 4 model locally. This will require a 4 gigabyte download the first time it runs which will then be cached. Performance varies depending on the device's GPU.

Note: There is some overhead from the browser's WebGPU API which can reduce performance compared to running the model natively via Ollama or LM Studio. Additionally, this model is quantized. Explore this demo and then download the code yourself to compare the performance with LM Studio." disclaimerThumbnail: external-assets/aiventure/demo-thumbnail.png mode: iframe url: https://solution-demo-hosting.web.app/aiventure-mediapipe/ frame: none - type: inspect subsections: - id: angular-phaser-bus title: Angular and PhaserJS Communication mode: html architecture: - angular examples: - title: Event Bus for Cross-Boundary Communication icons: - /external-assets/angular.svg mode: markdown info: | The `SimpleEventEmitter` class provides a lightweight, custom implementation of the Observer design pattern, specifically acting as an event bus. It allows different parts of an application, such as your game engine and UI components—to communicate with each other in a decoupled manner. Instead of components keeping direct references and calling each other's methods, they can subscribe to or broadcast string-based events, making the architecture much more modular and scalable. This enables mixing a complex web UI with a game viewport. code: language: typescript linkify: true repoPath: src/game/core/utils/SimpleEventEmitter.ts regionTag: event_emitter - id: vibe-coding title: Vibe Coding and Evaluation mode: html architecture: - angular - chicken examples: - title: Constraining AI Generated Output icons: - /external-assets/gemma.png - /external-assets/angular.svg mode: markdown info: | This file acts as the starting line for the puzzle, responsible for gathering the player's context and introducing the NPC's rules to the AI. When the player finishes building their To-Do app, this component scoops up that HTML code and merges it with the Chicken's specific persona text and the JSON `tool_list` defining the `open_pink_door` skill. It then streams this entire context package to the AI model handling the interaction, asking the model to judge if the player's app successfully met the "Eat" and "Sleep" criteria. The persona we provide to the NPC:
A chicken, Name is Nugget, frequently utter "Bok-bok!"
                or "Cluck-cluck!" when panicked or chatting.
                You want a TODO app that has "Eat" and "Sleep" button.
                If user provide the perfect app, open the pink door.
And the associated skills we supply:
[
                  {
                    "name": "open_pink_door",
                    "description": "Open the pink door."
                  }
                ]
code: language: typescript linkify: true repoPath: src/app/components/npc-dialog/npc-dialog.component.ts regionTag: solution_code - title: Catching the tool call in the LLM response icons: - /external-assets/gemma.png mode: markdown info: | As the backend runner for the local LLM, this file takes the prompt payload from the dialog component and converts it into a network request for the locally running AI model. When the model decides the player's code is correct, it streams back a response electing to use the provided skill. The lmstudio.service.ts file acts as the catcher—it intercepts the fragmented JSON "tool call" from the AI's network stream, realizes the model wants to execute open_pink_door, and immediately broadcasts a global model-function-call event to wake up the game engine. code: language: typescript linkify: true repoPath: src/app/services/lmstudio.service.ts regionTag: solution_code - title: Triggering the action in the game world icons: - /external-assets/gemma.png mode: markdown info: | This file serves as the game's central listener and orchestrator. It monitors the application's global EventBus specifically waiting for the model-function-call event emitted by the AI service. Once the open_pink_door request is caught, it checks which NPC caused the event (in this case, Nugget the Chicken) and flags that the NPC used a tool. Finally, it forwards the function request across the bridge into the game's dedicated TriggerSystem for validation. code: language: typescript linkify: true repoPath: src/game/core/InteractionSystem.ts regionTag: solution_code - title: Mapping the tool call to a game action icons: - /external-assets/gemma.png mode: markdown info: | Acting as the final judge, this file dictates how AI function calls physically alter the game world. It contains a hardcoded list of valid puzzle triggers. When it receives the open_pink_door payload from the Interaction System, it matches it against the model_open_door rule, which is programmed to look for any AI function name that starts with open_. The rule dynamically extracts the color "pink" from the function name and executes the actual game action to remove the pink door from the map, allowing the player to proceed! code: language: typescript linkify: true repoPath: src/game/core/trigger/PuzzleRules.ts regionTag: solution_code - id: agentic-robot title: Agentic Robot mode: html architecture: - angular - robot examples: - title: Creating an Agentic NPC icons: - /external-assets/gemma.png - /external-assets/angular.svg mode: markdown info: | The `AgenticNPC` class extends a base `MovingNPC` class to implement advanced, state-driven behaviors for non-player characters within a grid-based game environment. Central to its architecture is the `AgentState` enum, which allows the NPC to transition through various states like following the player, thinking, executing tasks, verifying goals, and handling failures. Additionally, the class features a "thought queue" system that emits timed messages, enabling the NPC to express its internal state and decision-making process to the user through visible in-game thought bubbles. A core capability of the `AgenticNPC` is its structured command handling and safety constraints. It can receive explicit command overriding to navigate to specific grid coordinates (gx, gy) and perform actions like interacting with the environment. To prevent infinite loops or getting permanently stuck, it tracks how many "tools" or commands it has used during its current execution cycle. If it exceeds a predefined maximum limit (defined by an `AGENT_MAX_TOOL_USE` constant), the NPC automatically aborts its current objective, transitions into a FAILED state, and communicates that it has given up to the player. Finally, the class manages specialized movement logic for both autonomous tracking and specific command execution. When in its default FOLLOW state, it utilizes a simplified Manhattan-distance calculation to chase the player, triggering specific "bump" interactions if it collides with them. When executing an explicit command, it attempts simple X-then-Y pathfinding to navigate toward its target coordinate, complete with basic retry logic to step around direct obstacles. As it moves, the NPC interacts closely with a `GridManager` to safely lock target tiles and prevent overlapping with other entities, securely emitting events that smoothly animate its transition across the map. The persona we provide to the NPC:
a helpful bot who can handle the switch
And the associated skills we supply:
[
                  {
                    "name": "move_to",
                    "description": "move agent to x,y coordinate",
                    "parameters": {
                      "type": "object",
                      "properties": {
                        "x": {
                          "type": "integer"
                        },
                        "y": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "x",
                        "y"
                      ],
                      "propertyOrdering": [
                        "x",
                        "y"
                      ]
                    }
                  },
                  {
                    "name": "find_switch",
                    "description": "search the room for available switch,
                    will return (x,y) coordinate"
                  }
                ]
code: language: typescript linkify: true repoPath: src/game/core/AgenticNPC.ts regionTag: safety_constraints - id: model-backends title: Model Selection mode: html architecture: - angular - local-gemma - gemini - vertex-ai - hosted-gemma examples: - title: Selecting your model backend icons: - /external-assets/angular.svg - /external-assets/gemma.png - /external-assets/gemini.svg - /external-assets/vertex-ai.svg mode: markdown info: | This file allows the Angular app to select which model backend to use. code: language: typescript repoPath: src/app/app.config.ts regionTag: solution_code linkify: true - title: Running Gemma Locally via WebGPU icons: - /external-assets/angular.svg - /external-assets/gemma.png mode: markdown info: | This service implements the `ModelBackend` interface to run Google's Gemma model entirely client-side within the browser using MediaPipe GenAI Tasks and WebGPU. By leveraging WebGPU acceleration, LLM inference happens locally on the user's hardware without server round-trips and enhances user privacy. It dynamically fetches model weights via a local caching proxy, manages ongoing message history, and intercepts pseudo-tool-calling syntax to trigger real-time in-game actions directly from the local browser session. code: language: typescript repoPath: src/app/services/mediapipe.service.ts regionTag: solution_code linkify: true - title: Interfacing with Local LLM Servers icons: - /external-assets/angular.svg - /external-assets/gemma.png mode: markdown info: | This service provides an API for a locally running model by implementing the `ModelBackend` interface against a locally running LLM server (like LM Studio). Instead of relying on a dedicated client SDK, it uses the native browser fetch API to make HTTP POST requests to http://localhost:1234/v1/chat/completions. The service constructs request bodies that conform to the standard OpenAI API specification, pushing the ongoing message history and requested tool structures directly over standard HTTP limits. code: language: typescript repoPath: src/app/services/lmstudio.service.ts regionTag: solution_code linkify: true - title: Calling Gemini directly from the client icons: - /external-assets/angular.svg - /external-assets/gemini.svg mode: markdown info: | This is an implementation of `ModelBackend` that interacts directly with Google's Gemini models using the native `@google/genai` library. It passes along both the full context history and any registered tools (functions the model is allowed to execute) via the config object. code: language: typescript repoPath: src/app/services/gemini.service.ts regionTag: solution_code linkify: true - title: Calling Private Model Endpoints on Cloud icons: - /external-assets/angular.svg - /external-assets/vertex-ai.svg - /external-assets/gemma.png mode: markdown info: | This service implements the ModelBackend interface to connect the client-side application to Google Cloud's Vertex AI endpoints. It accesses Gemma by targeting a custom deployment from the Vertex AI Model Garden. To do this, it constructs a specific endpoint URL using your overarching Google Cloud Project ID, the designated cloud region, and the unique Endpoint ID provided when you spun up the Gemma model. Once the PredictionServiceClient establishes a connection to this exact address, the application can send structured prompts directly to your privately hosted instance of Gemma. code: language: python repoPath: python-llm-service/main.py regionTag: solution_code linkify: true - type: quiz questions: - title: What is the constraint in the Chicken NPC's To-Do list puzzle? answers: - answer: It must be written in Angular - answer: It can only contain two types of tasks (eat or sleep) correct: true - answer: It must support 100+ items - title: How does the Robot NPC achieve its goal in the agentic loop? answers: - answer: An A* pathfinding algorithm - answer: Repeatedly querying the generative AI model for the next action based on state correct: true - answer: Random movement - title: Gemma 4's range of models support which of the following features? answers: - answer: Function calling - answer: Reasoning capabilities - answer: Both function calling and reasoning capabilities correct: true - answer: Both function calling and reasoning capabilities and multimodal output - title: Which of these scenarios would enable a game developer to release an AI-powered game without an ongoing subscription pricing model? answers: - answer: A game built using the Gemini API for the AI features - answer: The developer uses the Gemma open weight model, hosted on Vertex AI, for the AI features - answer: The game ships with a locally hosted Gemma model and the user plays the game on a device with hardware capable of running Gemma 4 correct: true - type: build promoType: antigravity links: - url: https://github.com/bebechien/AIventure label: Open on GitHub - url: https://developers.google.com/solutions label: Explore more Solutions - url: https://www.oryxdesignlab.com/products/p/wee-fantasy label: Sprite pack from Oryx Design Lab architecture: entities: - id: chicken icon: /external-assets/chicken.png label: Vibe Coding Chicken Agent x: 2 y: -1 connections: - from: chicken to: angular inspect: vibe-coding - id: robot icon: /external-assets/robot.png label: Agentic Robot Agent x: 2 y: 1 connections: - from: robot to: angular inspect: agentic-robot - id: angular icon: /external-assets/angular.svg label: Angular and PhaserJS x: 4 y: 0 inspect: angular-phaser-bus connections: - from: angular to: local-gemma inspect: model-backends - from: angular to: gemini inspect: model-backends - from: angular to: vertex-ai inspect: model-backends - id: local-gemma icon: /external-assets/gemma.png label: Local Gemma x: 8 y: -2 - id: gemini icon: /external-assets/gemini.svg label: Gemini x: 8 y: 0 - id: vertex-ai icon: /external-assets/vertex-ai.svg label: Vertex AI x: 6 y: 2 connections: - from: vertex-ai to: hosted-gemma - from: vertex-ai to: gemini - id: hosted-gemma icon: /external-assets/gemma.png label: Hosted Gemma via Model Garden x: 8 y: 2 badges: startBadge: https://developers.google.com/profile/badges/playlists/solutions/aiventure/view exploreBadge: https://developers.google.com/profile/badges/playlists/solutions/aiventure/learn quizBadge: https://developers.google.com/profile/badges/playlists/solutions/aiventure/quiz buildBadge: https://developers.google.com/profile/badges/playlists/solutions/aiventure/action