September 28, 2026

Pointing game with npc quests

 The game engine generates a random quests like "click on top" or "click on obstacle". The human user has to follow the instruction to get a reward.

import sys, random, pygame

pygame.init()
pygame.font.init()

# Setup grid & display settings
GRID_SIZE, CELL_SIZE = 8, 60
OFF_X, OFF_Y = 160, 60
W, H = 800, 650

SCREEN = pygame.display.set_mode((W, H))
pygame.display.set_caption("Grounded Language: Grid Pointing Game")
CLOCK = pygame.time.Clock()

FONT_MAIN = pygame.font.SysFont("Arial", 20, bold=True)
FONT_UI = pygame.font.SysFont("Arial", 16)

# Colors: BG, Grid, Obstacle, Box BG, Box Border, Text, Success, Error
C_BG, C_GRID, C_OBS = (240, 243, 246), (180, 185, 190), (100, 110, 120)
C_BOX_BG, C_BOX_BDR, C_TXT = (220, 225, 230), (140, 150, 160), (30, 40, 50)
C_SUCC, C_ERR = (46, 204, 113), (231, 76, 60)

# 2x2 Obstacle cells in the middle
OBSTACLE_CELLS = {(3, 3), (3, 4), (4, 3), (4, 4)}

ACTIONS = ["point at", "click on", "select", "target"]

class PointingGame:
    def __init__(self):
        self.score = 0
        self.fb_msg, self.fb_col, self.fb_timer = "", C_TXT, 0
        self.new_quest()

    def new_quest(self):
        act = random.choice(ACTIONS)
        qtype = random.choice(["specific", "obstacle", "top", "bottom", "left", "right"])

        if qtype == "specific":
            x, y = random.randint(0, 7), random.randint(0, 7)
            self.quest = f'NPC Quest: "{act} ({x},{y})"'
            self.check = lambda cx, cy, x=x, y=y: (cx, cy) == (x, y)
        elif qtype == "obstacle":
            self.quest = f'NPC Quest: "{act} the obstacle"'
            self.check = lambda cx, cy: (cx, cy) in OBSTACLE_CELLS
        else: # Top, Bottom, Left, or Right edge (8 cells each)
            self.quest = f'NPC Quest: "{act} {qtype} edge cells"'
            edges = {
                "top": lambda cx, cy: cy == 0,
                "bottom": lambda cx, cy: cy == 7,
                "left": lambda cx, cy: cx == 0,
                "right": lambda cx, cy: cx == 7
            }
            self.check = edges[qtype]

    def handle_click(self, pos):
        mx, my = pos
        if OFF_X <= mx < OFF_X + 480 and OFF_Y <= my < OFF_Y + 480:
            cx, cy = (mx - OFF_X) // CELL_SIZE, (my - OFF_Y) // CELL_SIZE
            if self.check(cx, cy):
                self.score += 10
                self.fb_msg, self.fb_col = f"Correct! +10 pts for cell ({cx},{cy})", C_SUCC
                self.new_quest()
            else:
                self.score = max(0, self.score - 5)
                self.fb_msg, self.fb_col = f"Wrong! Cell ({cx},{cy}) is not the target. (-5)", C_ERR
            self.fb_timer = pygame.time.get_ticks() + 1500

    def draw(self):
        SCREEN.fill(C_BG)
        # Headers & Score
        SCREEN.blit(FONT_MAIN.render("Grounded Language Grid Game", True, C_TXT), (OFF_X, 15))
        SCREEN.blit(FONT_MAIN.render(f"Score: {self.score}", True, C_SUCC), (OFF_X + 320, 15))

        # Axis Labels & Grid
        mx, my = pygame.mouse.get_pos()
        for i in range(8):
            SCREEN.blit(FONT_UI.render(str(i), True, C_TXT), (OFF_X + i * 60 + 26, OFF_Y - 25))
            SCREEN.blit(FONT_UI.render(str(i), True, C_TXT), (OFF_X - 25, OFF_Y + i * 60 + 22))

        for cy in range(8):
            for cx in range(8):
                rect = pygame.Rect(OFF_X + cx * 60, OFF_Y + cy * 60, 60, 60)
                col = C_OBS if (cx, cy) in OBSTACLE_CELLS else (255, 255, 255)
                pygame.draw.rect(SCREEN, col, rect)
                pygame.draw.rect(SCREEN, C_GRID, rect, 1)

                if rect.collidepoint(mx, my):
                    s = pygame.Surface((60, 60), pygame.SRCALPHA)
                    s.fill((52, 152, 219, 100))
                    SCREEN.blit(s, rect.topleft)

        # Quest & Feedback Widget
        box = pygame.Rect(OFF_X - 40, OFF_Y + 505, 560, 85)
        pygame.draw.rect(SCREEN, C_BOX_BG, box, border_radius=8)
        pygame.draw.rect(SCREEN, C_BOX_BDR, box, width=2, border_radius=8)

        SCREEN.blit(FONT_MAIN.render(self.quest, True, C_TXT), (box.x + 20, box.y + 15))
        if pygame.time.get_ticks() < self.fb_timer:
            SCREEN.blit(FONT_UI.render(self.fb_msg, True, self.fb_col), (box.x + 20, box.y + 50))

game = PointingGame()
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
        elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            game.handle_click(event.pos)

    game.draw()
    pygame.display.flip()
    CLOCK.tick(60)
 

No comments:

Post a Comment