Durak
Durak, the Russian card game: a Python version I built with a search-based AI opponent (not public), and my attempts to teach a neural network to play it by itself.
I built this because I've played Durak and I wanted to know whether a computer could beat my friends, my family, or me. I haven't played it in a while, so I can't tell you much from my own games, and I have no real answer to that question yet. What I do have is a working version of the game, a computer opponent that plays it, and a second, more ambitious opponent that is still an experiment.
Durak is a Russian card game, and the name means "fool". You play with 36 cards. One player attacks by putting a card on the table, and the player next to them has to beat it with a higher card of the same suit, or with a trump, which is a suit that outranks all the others for that game. If you can't beat it, you pick up everything on the table. In the end, whoever is left holding cards is the durak.
I built my own version in Python over about a week in March 2026. It has the rules, a text-only version, a window with cards you click, and a helper for real cards: you type in your hand and what is on the table, and it tells you what to play. It handles two to four players.
The computer opponent has one big problem, which is that it can't see your hand, just like you can't see its hand. So it guesses. It deals out the cards it can't see in a way that fits everything it has watched so far, thinks a few moves ahead as if that guess were true, and then does it again with a different guess. The hardest search setting does that up to 300 times for every move. The move that comes out best across all the guesses is the one it plays. It is a bit like a card player who imagines a few hundred possible hands for the other person before deciding what to do.
The part I was really after was a second kind of player: a neural network that learns Durak by playing against itself, the way the best Go-playing programs learned. First I had it copy my computer players, mostly the simpler rule-based one and a smaller share from the search one, and it picked the same move as its teacher more than nine times out of ten. Copying is not the same as playing well, though. So I let it play itself. After each batch of games, a new version had to beat the previous one over 100 games to take its place.
That is where it went wrong. Every time I started from a network that was already trained and let it learn mostly from its own games, the new version never got past the old one, in any of the 30 generations I ran. Starting from scratch with games played by my other players looked better, and two runs ended up with 24 replacements between them. Two other from-scratch runs that learned only from their own games never got a single replacement. But the bar was 52 wins out of 100, and two players of exactly equal strength clear that bar about one time in three by luck alone. The number of replacements I got is about what luck would give, so I can't say those networks got better.
I never measured how the network does against my search AI, which is the test that would tell me how good it is. So the honest summary is that the game and the search AI run, and the network is an experiment I haven't finished.
What's next
A proper match between the network and the search AI, over enough games to mean something, and eventually the original question: whether any of it can beat people I know. After that, there is one likely mistake to fix: while training, the search, and the network inside it, gets to see the other player's real hand, but the examples it is trained on only show what a real player could see. They may be learning from slightly different games. I haven't tested whether that matters.
The problem
I've played Durak, though not recently, and it made me curious whether an AI could beat my friends, my family or me. I have no result to report on that, since I haven't measured it, and what follows is the part I did build.
Durak hides information. You see your own hand, the table and the discard pile, and you know how many cards the other player holds, but not which. That rules out searching the whole game tree the way you would in chess. I wanted a computer opponent that plays properly under that uncertainty, and then I wanted to see whether a neural network could learn to do the same without being handed rules of thumb. The project went in three steps: a search player, a network cloned from my computer players (mostly the rule-based medium one), and then AlphaZero-style self-play loops where the network learns only from wins and losses.
Around the AI is the game itself. The rules engine handles two to four players, there is a terminal version and a Pygame window (with an optional rule that lets the defender pass an attack on, called perevodnoy), and an advisor for playing with real cards: you type in your hand and the table and it tells you what to play. It is all Python, written over about a week in March 2026. The training only covers two-player games.
How it works
The search AI is Perfect Information Monte Carlo (PIMC). It samples possible opponent hands that are consistent with what it has seen, runs an alpha-beta search on each sampled world with a hand-written evaluation, and plays the move with the best average score. Before searching it ranks the moves with a heuristic and only searches the top three to five. The levels are easy (random legal moves), medium (greedy rules), hard (25 worlds, depth 4) and expert (300 worlds, depth 6). Once the deck is empty the hidden cards follow by elimination, so there is only one possible world and it searches four plies deeper instead.
What is Perfect Information Monte Carlo?
Search of the kind chess programs use needs to see every piece. Durak hides the other hand, so PIMC cheats in a controlled way: it deals the unseen cards out at random into a "world" that fits everything seen so far, searches that world as if it were fully visible, then repeats with a new deal and averages the results. Alpha-beta is the standard trick for skipping branches of that search that cannot change the answer. The blind spot is that inside each world it acts as if it knows the hidden cards, so it does not value hiding or revealing information.
The network is a small MLP: 235 inputs, three hidden layers of 256, 256 and 128, and two heads on top. One head gives a score for each of 37 actions (the 36 cards, plus one for pass or take), the other gives a win probability. Click a block below to see what each part of the input holds.
| Block | Slots | What it holds |
|---|---|---|
| Own hand | 36 | The cards in my hand. |
| Seen taken | 36 | Cards I have watched the opponent pick up from the table. |
| Table attack | 36 | The attack cards on the table right now. |
| Table defense | 36 | The cards that have been played to beat them. |
| Discard pile | 36 | Cards that are already out of the game. |
| Unseen cards | 36 | Every card that is not in my hand, on the table or in the discard pile: the opponent's hand plus whatever is left in the deck. Once the deck is empty this is exactly the opponent's hand. |
| Trump suit | 4 | Which of the four suits is trump. |
| Trump rank | 9 | The rank of the face-up trump card, from 6 to ace. |
| Six single values | 6 | Deck size, the opponent's hand size, whether I am the attacker, whether it is the defend phase, whether the deck is empty, and whether I am the defender. |
What do the policy and value heads do?
The policy head says which move looks best: it outputs a score for each of the 37 possible actions, and illegal ones get ignored. The value head says how likely the player to move is to win from this position, as a number between 0 and 1. In AlphaZero-style search the first guides which moves are worth exploring, and the second stands in for playing a position out to the end.
I trained the network to copy the existing players first. 547,764 of the 586,163 samples came from 10,000 games of the medium AI, and 38,399 from 500 games of a lighter hard setting (15 worlds, depth 3, not the 25 worlds and depth 4 described above), which generated games at 0.2 a second. The medium one managed about 700. The clone agreed with its teacher's move 94.2% of the time. That is agreement, not strength: it shows the network can imitate, not that it plays well.
The self-play loop is the AlphaZero one. Games are played with Monte Carlo tree search using the network's priors, and each position is stored with the search's visit distribution and the final win or loss. A candidate network trains on those with a cross-entropy plus value loss, plays the current model, and replaces it only above 52% wins.
What is AlphaZero-style self-play?
The network plays against itself, but with a tree search on top that looks several moves ahead and uses the network's guesses to decide which branches to explore. How often the search visited each move becomes the training target, which is usually a better move than the network would have picked on its own. The network trains on that and on who won, the improved network searches better, and the loop repeats.
Results
There is no result here I would call strong. The only thing I measured was a candidate against the model it came from, never against a fixed outside opponent. Across all the AlphaZero-style runs, 24 generations were promoted, all in two runs. I wrote a script that plays the network against the search AI, but I have no result from it. I numbered the AlphaZero-style runs az, az2, az3 and so on up to az8, and az7 and az8 had several phases.
| Run | Start and data | Generations | Promoted | Candidate win rate |
|---|---|---|---|---|
| az | Started from the saved network; self-play, 50 sims | 10 | 0 | 22.5 to 38.0% |
| az3, az4 | Random weights; self-play | 3 and 8 | 0 | 0 to 9% |
| az5 | Random weights; games played by an earlier network cloned from the search AI | 35 | 13 | 36.0 to 92.0% |
| az6 | Started from the saved network; self-play | 2 | 0 | 7.2 to 10.5% |
| az7 phase 1 | Random weights; games played by the hard search AI | 40 | 11 | 36.0 to 58.8% |
| az7 phase 2 | Phase 1 model; self-play | 12 | 0 | 9.2 to 17.2% |
| az8 phase 3 | Started from the saved network; 70% self-play, 30% against the hard AI | 6 | 0 | 30.0 to 50.0% |
The table above has the numbers by run. The control below lets you pick a run, scrub through its generations and move the promotion threshold, and next to it is the arithmetic for two models of exactly equal strength. It needs JavaScript.
A 200-game match has a standard error near 3.5 points, so two equal models pass a 52% gate roughly a quarter to a third of the time: about 26% at 200 games, about 31% at 100, ignoring draws. az5 promoted 13 of 35 and az7 phase 1 promoted 11 of 40, against roughly 11 and 10 expected from coin flips. The first az5 generation (92%) was against a network with random weights, and the second (77%) against the one that had just replaced it, so those look real. The rest I read as the gate letting noise through, not steady improvement.
What is a standard error here?
If two equal players play 200 games, the number of wins wobbles around 100 the same way heads wobble over 200 coin flips. A typical wobble is about 7 games, which is 3.5 points of win rate. A gate at 52% is only 2 points above even, well inside that wobble, so it lets equal models through fairly often.
What didn't work
Self-play never beat its starting model. Every run that started from an already trained network and used mostly self-play data (az, az6, az7 phase 2 and az8) failed to promote once, over 30 generations. In az7 phase 2 the candidates won 9 to 17% of games against the model they started from. In az3 and az4 the trained candidates lost between 91 and 100 games out of 100 to the random-weight network they started from. I never found out why. One suspect is the mismatch in the diagram above: inside the search, both the tree and the network's evaluations see the opponent's real hand, but the training examples I store show only the cards seen being taken plus the mask of cards that could still be in the opponent's hand (which equals the hand once the deck is empty). I haven't tested that.
GPU batching did not buy the speed I expected. I wrote the batched search expecting a 5 to 10 times speed-up. 300 games at 100 simulations took 264 to 549 seconds per generation on the GPU (264 to 306 for the first seven), against roughly 224 to 410 seconds on ten or twelve CPU workers. These were different runs and different models, so it is not a controlled comparison.
The pipeline was fragile. Along the way I hit a mistyped command line, a CPU/GPU device error in the evaluation step, and four process-pool crashes (az5 twice, az6, az7 phase 2). The input size changed more than once during the project, from 199 numbers to the 235 it has now, so the league rounds of the first cloning script repeatedly failed to load their opponents, and some trained on only a few hundred samples.
What is unfinished
A fixed benchmark is the missing piece: the network against the PIMC player, over enough games to mean something. Beyond that sits the question I started with, whether either player can beat people I know, which I haven't tried to answer. After that, the obvious experiment is to search over sampled hidden hands during training, so the targets match what the network can see. Training is two-player only. The Pygame interface has the optional transfer rule with a simple AI rule for it, but neither the search nor the network models transfers. There are no automated tests.
- Stack
- Python, PyTorch, Pygame, trained on a laptop RTX 4070
- Status
- Prototype
- Code
- Not public