NET FORGE DOCS

How to make an online card battler with AI opponents

Last updated Checked against @netforge/engine 0.2.0View as Markdown

Net Forge, the open-source multiplayer browser game engine built for AI coding agents, is published on npm under @netforge with source at github.com/netnetdev/net-forge.

Run npm create netforge@latest my-cards -- --template cards --yes and you have an online card battler: two players duel with a deck, a hand, mana that rises each turn, creatures and spells, and when you play alone an AI opponent takes the other seat. Decks are shuffled on the server and a hand is only ever sent to its owner, so hidden information is handled for you.

npm create netforge@latest my-cards -- --template cards --yes
cd my-cards
npm run play

You change the game by declaring cards and the table in src/game.ts. The turn structure, the shuffle, targeting, guards and rush, the draft, the AI, the netcode and the 3D desk are the template's.

What you get

PartWhereNotes
Turns and timercardTable({ turnSeconds })A coin decides who goes first; the second player draws one extra card; the timer ends a slow turn.
Hidden informationroom serverThe deck order never leaves the server. A hand and a draft offer go only to their owner.
Cardscard({...})4 to 40 units and spells. Effects: damage, heal, draw, buff, with targets such as enemy, all_enemies, own_unit.
AI opponentscardTable({ ai })1 to 12 tiers of { health, smart }; duel n against the AI uses tier n.
Seat fillingroomA person alone for 3 seconds gets an AI opponent; it leaves when a second person joins.
DraftcardTable({ duels })After each duel but the last, pick one of three cards into your deck.
Playtestnpm testFour playtests that together simulate 17 minutes of duels finish in under half a second (Apple M2 Pro, Node 22).

1. Start the template

npm create netforge@latest my-cards -- --template cards --yes
cd my-cards
npm run play -- --players 2

With two tabs you duel yourself; with one, an AI scribe sits down after three seconds.

2. Add a card

src/game.ts
import { card } from "@netforge/engine";

card("mirror_moth", {
  model: "style.lowpoly.jelly_flyer",
  kind: "unit",
  cost: 3,
  attack: 2,
  health: 2,
  effect: { kind: "damage", amount: 1, target: "all_enemies" },
});
card("ink_storm", { model: "style.lowpoly.meteor", kind: "spell", cost: 4, effect: { kind: "damage", amount: 2, target: "all_enemies" } });

Add the card's name to src/names.json under weapons ({ "name": "Mirror Moth", "line": "..." }). The draft offers every declared card unless the table's pool lists some.

3. Shape the table and the AI

src/game.ts
import { cardTable } from "@netforge/engine";

cardTable({
  health: 20,
  startHand: 3,
  handMax: 7,
  boardMax: 5,
  manaMax: 10,
  turnSeconds: 30,
  draftSeconds: 15,
  duels: 4,
  starter: ["blot_imp", "blot_imp", "quill_sprite", "quill_sprite", "ink_frog", "ink_frog", "margin_bat", "ledger_crab", "serif_fox", "splash", "splash", "mirror_moth"],
  ai: [
    { health: 14, smart: 0.3 },
    { health: 16, smart: 0.55 },
    { health: 20, smart: 0.75 },
    { health: 24, smart: 0.9 },
  ],
});

Lower health makes faster duels and more duels a longer run. Raise each tier's health and smart for a harder gauntlet. The starter deck must hold 8 to 30 declared cards, at least one unit and some cards costing 2 or less; the build tells you which rule a deck breaks.

4. Reward the right things

src/game.ts
import { earn } from "@netforge/engine";

earn.rule("ink_master", { when: "ai.beaten", n: 3, pay: 15, oncePer: "run" });

Events are duel.won (against a person, never a forfeit), ai.beaten and run.won. The AI never earns. Locally every payout is play money.

5. Test it

npm test

The playtest plays duels between two players and one person against the AI, and checks that duels are decided, a run ends and starts over, and no softlock happens.

How it works

The card battler is a rules genre: a deterministic module with integer state and a seeded random number generator, run by the room at 30 Hz. A play, an attack, an end of turn and a draft pick are actions the server validates (whose turn it is, the card in that slot, the mana, guards). Your cursor is predicted so pointing never waits; everything else comes from the server. The room sends each player their own private state (their hand, their draft offer) separately from the public board, so another player's client never receives your cards. Because every shuffle comes from the room's seeded generator, a whole run replays exactly from its inputs. See Make a turn-based multiplayer game for the rules-genre model, and the card battler template page for every file.

Alternatives

  • boardgame.io is an MIT-licensed engine for turn-based games in JavaScript. It keeps game state in sync across clients, server and storage, supports game phases with different rules and turn orders per phase, generates bots that can play your game, and includes a lobby for matchmaking. It is view-layer agnostic, so you bring your own rendering. Its latest npm release is 0.50.2, published on 2022-11-10 (npm registry). Pick it when you want a small, proven state model for a 2D board or card game and are happy to write the UI.
  • Colyseus is an authoritative Node.js room server with matchmaking and binary delta state sync. Pick it when you want to write the card rules as server code yourself and use any client.

Pick Net Forge when you want a finished, rendered card battler with AI opponents and server-side hidden information, and a declarative file your agent can extend.

FAQ

How do I keep players from seeing each other's hands?

The template does it on the server: the deck is shuffled by the room's seeded generator, the order never leaves the server, and each hand is sent only to its owner. Nothing in src/game.ts can change that.

Can players play against the computer?

Yes. A person alone gets an AI opponent after three seconds. Its strength per duel comes from cardTable({ ai }), from 1 to 12 tiers of { health, smart }.

Is there a turn timer?

Yes, turnSeconds (30 in the template). When it runs out, the turn ends.

Can I make it two players only, with no AI?

The table seats two players. With a second person in the room the AI leaves; a duel against a person is the only one that counts as duel.won.

Can I make a deckbuilder roguelike instead?

The template already drafts one of three cards after each duel of a run. Raise duels and tune the AI tiers for a longer climb.

Does the AI cheat by seeing my hand?

No. The AI picks its moves from its own hand and the cards on the board; its code does not read the other player's hand or deck. It plays through the same rules module and never earns.