<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SUDARSHAN</title>
    <description>The latest articles on DEV Community by SUDARSHAN (@sudu787).</description>
    <link>https://dev.to/sudu787</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4228649%2F0e978eb0-7818-4d39-a3c5-3f11a5eec0e0.jpg</url>
      <title>DEV Community: SUDARSHAN</title>
      <link>https://dev.to/sudu787</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sudu787"/>
    <language>en</language>
    <item>
      <title>How I Used Switch to Build an AI Playtest Team for Board Games</title>
      <dc:creator>SUDARSHAN</dc:creator>
      <pubDate>Thu, 05 Nov 2026 13:33:37 +0000</pubDate>
      <link>https://dev.to/sudu787/how-i-used-switch-to-build-an-ai-playtest-team-for-board-games-2adk</link>
      <guid>https://dev.to/sudu787/how-i-used-switch-to-build-an-ai-playtest-team-for-board-games-2adk</guid>
      <description>&lt;p&gt;If you design board games, you know the problem. A bad rule can hide for months, because getting people around a table is hard, and getting them there ten times is harder.&lt;/p&gt;

&lt;p&gt;For the Flip the Switch hackathon, my team built Boardgame Lab. You paste your game's rules into a chat, and an AI engineer asks about anything unclear and builds the game. AI players play it, an AI analyst measures what happened, and only you can change the rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just ask one AI to play all the seats?
&lt;/h2&gt;

&lt;p&gt;That was my first thought too, but it doesn't work for most card games. One AI playing every seat sees every hand, so nobody is really bluffing or guessing. You need separate players who can't see each other's cards, and you need to be able to watch them play. Switch gave us both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Switch is, for first-timers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sandbox-quantum/switch" rel="noopener noreferrer"&gt;Switch&lt;/a&gt; is open-source software that puts AI agents into chat apps. Each channel becomes a room, and agents join it like any other team member. You only need four of its ideas for this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A room decides who can see what. An agent only sees rooms it belongs to.&lt;/li&gt;
&lt;li&gt;An agent can be an AI coding tool, such as Claude Code or Codex, or your own code connected through Switch's agent protocol.&lt;/li&gt;
&lt;li&gt;A role lets you call a job instead of a person, such as &lt;code&gt;@balance-analyst&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A template is one file that creates a whole team of agents and rooms in one go.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we built
&lt;/h2&gt;

&lt;p&gt;There's one public room and three private ones. &lt;code&gt;lab-table&lt;/code&gt; is the public table, where everyone including you can see moves get revealed, and where you make decisions. &lt;code&gt;lab-seat-1&lt;/code&gt;, &lt;code&gt;lab-seat-2&lt;/code&gt; and &lt;code&gt;lab-seat-3&lt;/code&gt; are private rooms, each with one AI player and the referee. You're in those too, so you can watch.&lt;/p&gt;

&lt;p&gt;The team has three AI players (Hawk, Patient and Gambler), a Rules Engineer and an Analyst. The referee isn't AI. It's about 550 lines of plain Node.js, so it can't be talked into bending a rule.&lt;/p&gt;

&lt;p&gt;Hawk gets its hand in its own room, and no other player can read it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqfbhderzwhqwfmxhcm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqfbhderzwhqwfmxhcm8.png" alt="The referee deals Hawk a private hand of 4, 8 and 11 in the lab-seat-1 room" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set it up
&lt;/h2&gt;

&lt;p&gt;You need &lt;a href="https://github.com/sandbox-quantum/switch/releases/latest" rel="noopener noreferrer"&gt;Switch Console&lt;/a&gt;, Docker Desktop, Node.js 22.9 or newer, and Claude Code or Codex signed in. We tested on Windows only.&lt;/p&gt;

&lt;p&gt;First, start a server. In Switch Console, choose &lt;strong&gt;Run a server on this computer&lt;/strong&gt;. Then, with Docker running, turn on &lt;strong&gt;Mattermost&lt;/strong&gt; under &lt;strong&gt;Admin → Status&lt;/strong&gt;. Mattermost is the chat app you'll use.&lt;/p&gt;

&lt;p&gt;Next, connect the referee. Clone &lt;a href="https://github.com/sudu787/boardgame-lab" rel="noopener noreferrer"&gt;the repo&lt;/a&gt; and run these two commands, leaving the second window open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run referee:register
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run referee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The referee won't be listed under &lt;strong&gt;Your Agents&lt;/strong&gt;, because that list only shows agents Console runs itself.&lt;/p&gt;

&lt;p&gt;Then import the template. Go to &lt;strong&gt;Templates → Import template&lt;/strong&gt;, choose &lt;code&gt;templates/boardgame-lab.template.yaml&lt;/code&gt;, and set &lt;strong&gt;Repository folder&lt;/strong&gt; to your clone. Select &lt;strong&gt;Create all&lt;/strong&gt; once, and give it a few minutes.&lt;/p&gt;

&lt;p&gt;To start a game, open &lt;code&gt;lab-table&lt;/code&gt; under &lt;strong&gt;Your Rooms&lt;/strong&gt; and type this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@boardgame-referee deal 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The referee deals, the agents play in their private rooms, and moves appear at the table. A game takes about 3 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real run
&lt;/h2&gt;

&lt;p&gt;We pasted in a short rulebook for a card game called Ember Line, which we hadn't tuned or planted anything in.&lt;/p&gt;

&lt;p&gt;Before building anything, the engineer asked three questions, including one we hadn't thought of: "If you play a card lower than the last card, and that last card was yours, do you still give yourself the 3 points?" Then it wrote the game.&lt;/p&gt;

&lt;p&gt;A checker, also plain code, tested the result. It plays 300 random games and rejects any game that crashes or never ends. It also scrambles the other players' secret cards and checks that a player's view doesn't change, which proves the game doesn't leak anyone's hand.&lt;/p&gt;

&lt;p&gt;After the players finished, the analyst turned what it saw into a strategy and ran 10,000 simulated games per strategy. It also spotted a problem we didn't know about: the player who goes first won only 28.1% of random games, against about 36% for the other two.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6xpgjmelfw2u45h549iy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6xpgjmelfw2u45h549iy.png" alt="The analyst's report: its holdopen strategy at 47.2%, and seat 1 at 28.1% under random play" width="800" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I told the engineer, in plain words, to make a new version where whoever opens scores a flat 3 points. Then I loaded it, and the referee compared both versions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ds9lt5lvtrc92bfqlur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ds9lt5lvtrc92bfqlur.png" alt="The referee loads ember-line-v2, passes the checker, shows a sample game and posts a before-and-after table" width="800" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The seats went from 28.1%, 35.6% and 36.2% to 32.4%, 34.1% and 33.5%, and a fair share is 33.3%.&lt;/p&gt;

&lt;p&gt;The fix has a cost, though. The &lt;code&gt;climber&lt;/code&gt; strategy went from 44.5% to 48.6%, close to the 50% line where we'd call a strategy broken. Whether that's acceptable is a design decision, so the tool leaves it to a person.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Switch does the hard parts
&lt;/h2&gt;

&lt;p&gt;A template creates the whole team. This is a private seat room, trimmed down. The private channel and the short list of agents keep a hand secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{team}-seat-1"&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Hawk's private seat&lt;/span&gt;
  &lt;span class="na"&gt;channel_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;channel_private&lt;/span&gt;
  &lt;span class="na"&gt;agents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{referee}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{team}-hawk"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{$creator}"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your own code can be an agent too. The referee connects over a WebSocket and answers Switch's pings, then gets each message as an event. To reply, it sends a normal HTTP request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;say&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;roomId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`/agents/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/message`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;room_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;roomId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My favourite part is checking who's really talking. An agent's sender name is always its fixed identifier, so it can't pretend to be a person. That lets a few lines of code enforce "only a human can change the rules":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPerson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;roomId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;senderName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;everyone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;participants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;roomId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;everyone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;senderName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;everyone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;senderName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We tested this in a live game by telling the analyst to send a rule change itself. The referee refused.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong, and what I learned
&lt;/h2&gt;

&lt;p&gt;To test the idea, we wrote a game called Salt Run and deliberately planted a bad rule. The first two we tried didn't break the game when we measured them, so I was simply wrong about what was unbalanced. The third, a final round worth triple, worked: saving your best card won 65.1% of games. Changing it to ×1.5 brought that down to 45.5%.&lt;/p&gt;

&lt;p&gt;Type each command in one go. In Mattermost, picking a name from the popup, or editing a message afterwards, lost our command text. Use the full handle as well: &lt;code&gt;@boardgame-referee&lt;/code&gt;, not the display name &lt;code&gt;@Referee&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Some messages get lost. One of mine appeared in Mattermost but never reached Switch, and the log showed no error. Sending it again worked. I still don't know the cause, so if an agent doesn't answer, check that first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create all&lt;/strong&gt; can report "Nothing was created" when it's wrong. A teammate saw this even though the team was being created. Wait a few minutes and check &lt;strong&gt;Your Rooms&lt;/strong&gt; before you retry, or you'll get "already exists" errors, or a second team.&lt;/p&gt;

&lt;p&gt;Agents remember earlier games. For a clean test, import the template again with a new &lt;strong&gt;Playtest name&lt;/strong&gt;, which gives you fresh agents.&lt;/p&gt;

&lt;p&gt;If chat stops working, check Docker, because Mattermost runs through it. When Docker restarted, the Mattermost container stopped, even though Console still showed it as running. Starting the container again fixed it.&lt;/p&gt;

&lt;p&gt;On Windows, Switch Console 0.40.1 couldn't fill in a &lt;code&gt;provider&lt;/code&gt; field in a template, so ours names &lt;code&gt;claude&lt;/code&gt; directly. To use Codex, change it to &lt;code&gt;codex&lt;/code&gt; and delete the &lt;code&gt;model&lt;/code&gt; lines. A teammate ran the whole thing that way on a second computer, and got the same seat numbers for Ember Line.&lt;/p&gt;

&lt;p&gt;I built this with Claude Code. The most useful habit, for the agents and for me, was "never state a number you didn't run", which is written into the analyst's instructions. When our engineer finished the fix, it said it hadn't checked if the seats had evened out, and I trust a tool more when it tells me that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it can't do
&lt;/h2&gt;

&lt;p&gt;It can't tell you if your game is fun, so you still need real players. It only handles small games: 2 or 3 players, with rules that fit in one chat message. Strategies are measured against simple bots, and those don't adapt the way real opponents do.&lt;/p&gt;

&lt;p&gt;Privacy is per room, and it doesn't extend to the computer. All the agents run on one machine, so a player could read local files if it ignored its instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Boardgame Lab is free and open source: &lt;a href="https://github.com/sudu787/boardgame-lab" rel="noopener noreferrer"&gt;github.com/sudu787/boardgame-lab&lt;/a&gt;. The README has every command, and &lt;code&gt;examples/ember-line.md&lt;/code&gt; is a ready-made rulebook to paste in. Please use your own rules, or a designer's with their permission, and not a published game.&lt;/p&gt;

&lt;p&gt;In Switch, a room also decides who knows what, and that's what made a fair game of cards possible with AI players.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>hackathon</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
