Examples/Blackjack

Blackjack

Created from a prompt

This workspace was written by OpenCode (DeepSeek V4 Pro via OpenRouter) inside a Vercel Sandbox. The agent only received the starter (AGENTS.md, serve.py, player/) and the prompt below. It did not receive the hand-built blackjack demo from env_and_react_demo, and Cursor did not edit the environment code.

2026-10-15 · task 25SRcMnmxMWb · opencode · openrouter/deepseek/deepseek-v4-pro-0813

The prompt

Build blackjack. I start with two cards and the dealer shows one. I can take another card or stand. Over 21 loses at once. When I stand, the dealer draws until 17 or more, then the higher total wins and a tie is a draw. The dealer's other card stays hidden until the hand is over. Winning should score. Not going over should help, and a total closer to 21 should help. Several shoes, and the same shoe should deal the same cards if I make the same choices.

Live environment

Running the prompt-created workspace on this machine — not a mock. Save an episode, then open Replay below to scrub the timeline.

opening · 0 ms

Generated workspace

Exact files the agent wrote — not rewritten by hand.

from __future__ import annotations

import random


def _hand_value(cards):
    total = sum(cards)
    aces = cards.count(1)
    while aces > 0 and total + 10 <= 21:
        total += 10
        aces -= 1
    return total


def _is_soft(cards):
    total = sum(cards)
    aces = cards.count(1)
    while aces > 0 and total + 10 <= 21:
        total += 10
        aces -= 1
    return aces > 0 and cards.count(1) > 0


class Blackjack:
    """A single-deck blackjack environment with deterministic replay.

    The shoe is shuffled from a seed derived from ``task_id``. The same
    ``task_id`` by construction replays the exact same card order, so identical
    choices yield identical outcomes across resets.
    """

    GAME_CLASS = "Blackjack"

    def __init__(self, num_decks: int = 1) -> None:
        self.num_decks = num_decks
        self.rng: random.Random | None = None
        self.deck: list[int] = []
        self.discarded: list[int] = []
        self.player_cards: list[int] = []
        self.dealer_cards: list[int] = []
        self.phase: str = "player"
        self.done: bool = False
        self.last_move: str | None = None
        self.reward: float = 0.0
        self.final_result: str | None = None

    def _build_deck(self) -> list[int]:
        deck: list[int] = []
        for _ in range(self.num_decks):
            ranks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10]
            deck.extend(ranks * 4)
        return deck

    def _fill_shoe(self) -> list[int]:
        remaining = self.deck
        if len(remaining) < 15:
            remaining = remaining + self._build_deck()
            self.discarded = []
        return remaining

    def _draw(self) -> int:
        self.deck = self._fill_shoe()
        card = self.deck.pop(0)
        self.discarded.append(card)
        return card

    def reset(self, task_id: str = "") -> dict:
        seed = 0
        for ch in task_id:
            seed = (seed * 131 + ord(ch)) & 0xFFFFFFFF
        self.rng = random.Random(seed if task_id else random.randint(0, 2**32 - 1))
        self.deck = self._build_deck()
        self.rng.shuffle(self.deck)
        self.discarded = []
        self.player_cards = [self._draw(), self._draw()]
        self.dealer_cards = [self._draw(), self._draw()]
        self.phase = "player"
        self.done = False
        self.last_move = None
        self.reward = 0.0
        self.final_result = None
        return self._obs()

    def step(self, action):
        move = getattr(action, "move", None)
        if not isinstance(move, str) or not move:
            return self._obs(), False, "move is required"
        self.last_move = move
        if self.done:
            return self._obs(), False, "game is over"

        if move == "hit":
            self.player_cards.append(self._draw())
            value = _hand_value(self.player_cards)
            if value > 21:
                self._finish()
            return self._obs(), True, None

        if move == "stand":
            self._dealer_play()
            self._finish()
            return self._obs(), True, None

        return self._obs(), False, f"unknown move: {move}"

    def _dealer_play(self) -> None:
        while _hand_value(self.dealer_cards) < 17:
            self.dealer_cards.append(self._draw())

    def _finish(self) -> None:
        self.done = True
        self.phase = "over"
        player = _hand_value(self.player_cards)
        dealer = _hand_value(self.dealer_cards)
        if player > 21:
            self.final_result = "bust"
            self.reward = -1.0
        elif dealer > 21:
            self.final_result = "win"
            self.reward = 1.0
        elif player > dealer:
            self.final_result = "win"
            self.reward = 1.0
        elif player < dealer:
            self.final_result = "lose"
            self.reward = -1.0
        else:
            self.final_result = "push"
            self.reward = 0.0

    def _obs(self) -> dict:
        if self.done:
            legal_moves: list[str] = []
        elif self.phase == "player":
            legal_moves = ["hit", "stand"]
        else:
            legal_moves = []

        if self.done:
            dealer_visible = self.dealer_cards
        else:
            dealer_visible = [self.dealer_cards[0], None]

        return {
            "reward": self.reward,
            "done": self.done,
            "legal_moves": legal_moves,
            "last_move": self.last_move,
            "phase": self.phase,
            "player_cards": list(self.player_cards),
            "player_total": _hand_value(self.player_cards),
            "dealer_cards": dealer_visible,
            "dealer_total": _hand_value(self.dealer_cards) if self.done else None,
            "final_result": self.final_result,
        }