You are browsing as a guest. Sign up (or log in) to start making projects!

raczjonathan12

@raczjonathan12

Joined June 8th, 2026

  • 22Devlogs
  • 7Projects
  • 5Ships
  • 35Votes
Ship

I built an AI that plays Ultimate Tic-Tac-Toe, the version where the board is 9 small boards and winning one sends your opponent to a specific one. It learned by playing itself over and over instead of me teaching it moves directly. You can play against it right in your browser, no installs.

The hardest part was the very first approach I tried didn't work. It kept making the same moves no matter what was on the board, so I scrapped it and switched to a different method that actually searches ahead for winning and blocking moves instead of just guessing. That's what ended up working. Getting the trained AI to run inside a browser instead of on a server was its own fight too, closer to translating it into a different language than just copying it over.

If you want to try it, just open the link and play a game. There's also a scoreboard now that keeps track of wins, losses, and draws between you and it.

  • 5 devlogs
  • 15h
Try project → See source code →
Open comments for this post

19m 30s logged

Added a scoreboard to the browser game today.

Now it tracks wins, losses, and draws between me and the AI, shown right above the board.

It saves to local storage too, so the score sticks around even if I close the tab or hit New Game.

0
0
4
Open comments for this post

3h 18m 19s logged

Got the game playable in an actual browser tab today.

Moved the whole thing over from Python to a real webpage, board, AI opponent, all of it, so anyone can just open a link and play.

Had a hard time getting the trained model working in the browser, but got it working and double checked it gives the same results as the original.

Then spent a while making it actually feel good to play. The AI’s moves were popping in too suddenly, so I slowed that down and added a marker so you can see what it just played. Cleaned up the spacing and added a New Game button too.

Deployed it, thought it looked fine, then realized it was actually still showing an old broken version because of caching. Tracked that down and fixed it, also caught one more small thing where the board looked clickable during the AI’s turn when it shouldn’t have been.

You can try it at: https://raczjonathan12.github.io/super-tic-tac-toe/

0
0
2
Open comments for this post

19m 21s logged

Rebuilt the whole training setup around Monte Carlo tree search instead of DQN, and it’s actually finding forced wins now.

Wrote a real spec and plan before touching code this time. Built the game engine again first, same rules as before, no changes needed, then added the value and policy network, then layered the tree search on top, all with tests along the way.

Writing the tests for the tree search caught a genuine sign bug right away. The code assumed the turn always switches after a move, but it doesn’t when a move wins the game, so the value was flipped for exactly the moves that mattered most. Also realized my own test scenario was wrong, it only completed a small board instead of the actual game. Fixed both, and the search now finds a forced win or a forced block on its own, even with a completely untrained network, since it’s real search finding the outcome, not a guess.

Got self-play, training, and evaluation working, ran a short validation loop and it actually worked, loss came down and it was already beating random play early on. Scaled it up for a real run.

Then the real run barely printed anything for ten minutes. Turned out it was running fine, just slow, because the tree search was calling the network one board at a time for every single simulation instead of batching them together, the exact same slowness from the DQN version, just in new code that never got the fix. Built batched search for self-play and evaluation both, checked it’s actually faster with a real timing test, and added logging that writes to a file and prints a lot more often so it’s obvious it’s alive.

Kicked off a real training run right after with the fix in, that’s going now.

0
0
3
Open comments for this post

7h 29m 50s logged

Spent a few hours trying to get a DQN to learn the game and it never really worked.

Got the DQN model built and training against itself. Found a real bug early where the training math treated every move as if the same player went again next instead of the opponent, and fixed it.

After that it just wouldn’t budge. Tried a handful of fixes: better reward shaping, a much faster version of the game engine so it could run way more games at once, features that pointed out immediate wins, playing against older versions of itself with separate buffers so wins didn’t get drowned out by everyday moves. None of it stuck. The network kept settling back into picking almost the same move no matter what the board looked like.

Decided the algorithm itself was the problem, not the tuning, and switched to Monte Carlo tree search with a value and policy network instead, closer to how AlphaZero plays these kinds of games. Kept the DQN code in git history instead of deleting it.

I have to admit, I gave up on coding and debugging this manually so I used Claude Code… Next devlog soon with the actual changes.

0
0
2
Open comments for this post

3h 30m 55s logged

Started the super tic-tac-toe project.
Wrote the first version of the game engine in train.py. There’s a Game class that tracks the 9x9 board, whose turn it is, and the status of each of the 9 sub-boards.
It handles the annoying part of this game: winning a small board, checking if that wins the whole meta-board, and figuring out which sub-board the other player is forced to play in next (or opening up the whole board if that one’s already finished).Also pulled in TensorFlow/Keras imports up top since the plan is to train a DQN on it once the game logic is solid.
Nothing runs as an actual game yet, just the class and its rules.

0
0
2
Ship

I built a tic-tac-toe game with an agent trained using Q-learning instead of just scripting the obvious optimal-move logic. It started as a plain random-move simulator in Python, then I built out the training loop (state encoding, epsilon-greedy move choice, reward updates after wins, losses, and draws) and trained it over 500,000 games. From there I moved the game into the browser, no backend, it just fetches the trained Q-table as JSON and picks moves with it client side. Added a turn indicator, a short delay before the agent moves, and a style pass so it holds up on any screen size, plus a small chance for the agent to play a random move so it's not fully predictable every game.

What I'm most proud of is that the agent actually plays well. It's not hardcoded to know the best tic-tac-toe strategy, it earned that by playing itself half a million times.

  • 5 devlogs
  • 9h
  • 7.78x multiplier
  • 73 Stardust
Try project → See source code →
Open comments for this post

2h 14m 43s logged

Made the agent a little less predictable and gave the site an actual style pass.
The agent now plays a random move 7.5% of the time instead of always going for its best known move, so games don’t play out the same way every time.
Then did the styling I mentioned wanting to do: added a “your turn / agent’s turn” indicator, a short pause before the agent’s move shows up on the board (with a click guard so you can’t sneak in an extra move during that pause), and reworked the colors and spacing on the board, the reset button, and the title.
Starting to feel like a real little site instead of just a board with squares.

0
0
3
Open comments for this post

2h 52m 16s logged

Moved the game into the browser.
Built a web version with HTML, CSS, and JS, it loads the trained Q-table straight from the JSON file and picks the agent’s move client side, no server needed. Click a cell to play as X, the agent answers as O.
Also retrained the table with a small tweak, new board states now start at -0.1 instead of 0, and wrapped the training code so importing train.py doesn’t kick off a training run by accident. The old CLI script is retired now that the browser version covers the same thing.
It works but looks plain right now, next up is actually styling it properly.

0
0
3
Open comments for this post

24m logged

You can now actually play against the trained agent, from the terminal.
Added a CLI script where a human plays against the agent, the agent picks its moves greedily off the Q-table instead of exploring. Cleaned up the board printing with separators so it’s easier to read between turns while playing.
Right now it’s just a command line game. A full UI is the plan for later.

0
0
1
Open comments for this post

29m 41s logged

Swapped the random-move players for actual Q-learning.
Instead of both sides just picking randomly, moves now come from an epsilon-greedy policy: mostly pick the best known move for the current board state, occasionally pick a random one to keep exploring. Added state encoding (board from the current player’s perspective, so X and O see it the same way), a Q-value table, and updates after every move based on the reward (win, loss, or draw).
Also added saving and loading the trained table to q_table.json so training runs don’t have to start from zero, train.py now loads it in on startup.
Next step is actually building a playable UI.

0
0
3
Open comments for this post

3h 22m 5s logged

Started a tic-tac-toe project with a random-move simulator.
Built the basic board logic first: a 9-cell board, printing it out, checking valid moves, and checking for a winner across all 8 win lines.
Then wired up two players who just pick random moves and play until someone wins or it’s a draw. Ran it for 10 games in a row and printed the result of each one.
Nothing fancy yet, just the scaffolding to build the real thing on top of.

0
0
1
Open comments for this post

46m 17s logged

Added /ship, the last skill in the set, that checks whether a project is actually ready to submit before you hit send.

It goes through the things a Shipwright reviewer actually checks: a public GitHub repo, a README that isn’t just boilerplate, a live demo link that actually responds, a devlog entry newer than the last time you shipped, and Hackatime tracking.It only reports gaps, doesn’t try to fix anything itself, just points you at /readme, /devlog, or the right gh command depending on what’s missing.

Tried it on a throwaway repo that had never shipped, then with old and fresh ship timestamps, to check the devlog comparison actually flips the right way. Also pointed it at a genuinely broken demo link and a fake GitHub remote, just to make sure it reports a real failure instead of quietly assuming things are fine.

0
0
2
Open comments for this post

24m 50s logged

Added a third skill, /commit, that drafts real commit messages instead of leaving you to type “fix” again.

It looks at your actual staged and unstaged diff, checks whether it’s really one coherent change or a few things mashed together, and only then writes the message, imperative mood, specific about what changed, no vague one-word placeholders.

It always asks before actually running git commit, even if you say you’re in a hurry, since that’s exactly when a wrong commit slips through unnoticed.

Right after, went back and added it to the README, so it shows up in Features and Quick Start next to /devlog and /readme instead of getting left out the way /readme almost was last time.

0
0
2
Open comments for this post

57m 52s logged

Built a second skill, /readme, then had to fix it twice after it actually missed things.

Added /readme so it writes a real README straight from the repo, checking manifests and existing docs instead of guessing.

Ran it on this project and it only wrote up /devlog. Turned out the skill was trusting an old doc that said there was just one skill, instead of looking at what was actually in the skills folder. Fixed that and had it always check the real directory listing over any doc’s claim.

Then noticed the README still just mentioned /readme in passing instead of giving it a proper section. Made the rule stricter: every component found has to get its own labeled group in Features, no exceptions.

Went one more round after that. The detection was written around “skills” specifically, so it wouldn’t have caught other kinds of components like agents or commands. Reworded it to catch any repeated pattern of the same kind of thing, then tested it against a fake plugin with a skill and an agent side by side, and it gave both their own sections this time.

0
0
1
Open comments for this post

22m 45s logged

Added a rule letting devlogs actually sound excited or annoyed instead of staying flat and careful. Went through the devlog skill again and loosened it up.

Before, the tone rules kept everything even no matter how a session actually went. Now it can sound genuinely pumped if the work went well, or genuinely annoyed if it didn’t, instead of forcing a flat middle voice.

Also switched the default format to short lines with blank lines between them instead of one dense paragraph, closer to how someone actually jots down what they did.

Added a bit about not listing more than three things in a row like a changelog, and about swapping in big words for plain ones when a small one says the same thing.

0
0
3
Open comments for this post

1h 21m 14s logged

Spent the day building out a devlog skill for Claude Code and kept finding new ways it could go wrong. Started by dropping the CLI-and-backend plan and turning this into a plugin instead, a plugin.json plus a first SKILL.md that has Claude read your git history and write the entry right there, no server needed. A few hours later I went back through it and added a bunch of edge cases I hadn’t thought about the first time, things like a huge diff, a binary file, a commit from someone else, work sitting uncommitted, a diff with something that looks like a secret in it, that kind of thing. I also added a self-check pass so the draft gets reread against a short list of AI-writing tics before it’s shown to anyone.Then near the end of the day I caught something in my own rules. The skill was telling itself to sound honest and grounded, but it had no rule against describing a design decision as if it were a bug that actually happened, which is exactly the kind of thing this tool is supposed to catch. I added that as a third rule sitting above everything else in the file, next to the no-inventing-details rule, and cleaned up a duplicate rule while I was in there. I also pulled an old demo devlog.md entry that had been sitting in the repo from testing.Right now I’m still in the middle of another pass, adding a rule about writing in plain, everyday language instead of anything that sounds like an essay or a spec, and one about calling out jargon words like “guardrail” or “architecture” that only make sense with full context. Not committed yet, still checking whether the wording actually reads like something a person would say.

0
0
2
Open comments for this post

1h 32m 26s logged

I thought that a qwen model would be enough for generating good quality devlogs but it turns out it isn’t, so I’m switching approaches.

I’m going to build a plugin for Claude that generates high quality and human devlogs.
P.S. here is the output I managed to get with the qwen model:

I added a note flag to the devlog command so I can hand-write details when the automated git log feels too sparse. The CLI now sends requests to the backend over HTTP. I swapped the model to qwen3.6-flash when I hit rate limits and added a fallback just in case that one locks up too. File-change stats get calculated and injected into the prompt alongside the commit data.

0
0
3
Open comments for this post

1h 35m 28s logged

Built a CLI that reads git commits and a backend that turns those commits into AI-written devlogs.

The CLI uses Commander to pull git log history and hand it off to the backend. The backend takes that commit data and generates a devlog through OpenRouter.

Lost a good chunk of time to two bugs that failed silently instead of erroring properly: the SDK wanted the request wrapped inside a field called chatRequest, not documented anywhere obvious. Then I had baseURL instead of serverURL in the client config, so requests were quietly going to the wrong server the whole time.

Next: wiring the CLI to actually call the backend.

0
0
2
Loading more…

Followers

Loading…