402 Agency · AI research since 2017 · Bachelor's thesis project, Technical University of Sofia, February – December 2017
TU.Diplomna
A two-player pygame arena and a Deep Q-Network agent whose Q-function is a multilayer perceptron written in numpy by hand. “Diplomna” is Bulgarian for thesis; it is the repository's own name. Written up in 2026 from the code and the 35 commits.
- Arena
- 960 × 512 px
30 × 16 tiles of 32 px - Observation
- 26 features
13 per unit, absolute x, y - Actions
- 4: up, down, left, right
no shoot action - Q-network
- 26 → 10 → 6 → 6 → 4
tanh on every layer - Training
- 300,000 steps, headless
batch 1, γ 0.2, lr 1e-4 - Code
- ~1,500 lines Python, 15 modules
numpy, pygame, matplotlib - History
- 35 commits
17 Feb – 4 Dec 2017 - Source
- Bitbucket: donald_ovcharov / tu.diplomna
public repository, 672 MB - Video
- Learned agent vs bot
as linked in the README, not re-verified - The thesis
- English showcase edition
the 48-page document, with its figures and experiments
1An assessment
- Impressive for a bachelor's thesis in 2017?
- Yes. One student built every layer by hand in ten months, physics engine to backpropagation, at a time when the ingredients were online but nothing joined them.
- A good implementation of the DeepMind paper?
- Partly. The idea is there and it works well enough to produce a learned behaviour. The two mechanisms the paper introduced for stability are missing, and two core lines of the final commit are wrong.
The reasoning below comes from the code and the commit history, not from recollection. Terms are explained in section 2.
What was available in 2017
The DeepMind paper had been in Nature for two years, and DeepMind had published its reference code, in Lua for the Torch framework. OpenAI Gym had launched in 2016; OpenAI's own DQN baseline arrived in May 2017. Both were built on TensorFlow, whose 1.0 release came in February 2017; PyTorch was a months-old beta. Tutorials existed but were few and framework-based: a handful of blog posts implementing DQN in Keras or TensorFlow on Gym's CartPole and Atari environments. From-scratch reinforcement-learning walkthroughs were rare; the best known, Karpathy's 2016 Pong-from-pixels post, used a different method (policy gradients). For plain neural networks there were two well-known numpy walkthroughs, Matt Mazur's step-by-step backpropagation example and Andrew Trask's eleven-line network, and the first versions of this repository's network use their vocabulary: neth1, outh1, Etotal, a nonlin(x, deriv) helper, np.random.seed(1).
So the halves existed separately: a paper and framework code on one side, from-scratch tutorials for plain networks on the other. No code assistant, and a thin Stack Overflow on reinforcement learning. A student who wanted to understand the method rather than call a library had to join the halves from the paper, the calculus and trial and error. The 35 commits are a record of that.
What was built, and what stands out
Everything in the pipeline is hand-written: a 2-D physics engine with vector force summation, per-tile friction and momentum-based knockback; a game with terrain, swords, bullets and health; an environment in the shape of Gym's step and reset; a scripted opponent; a 26-number state; a four-layer neural network with its own backpropagation; a Q-learning loop with exploration, action repeat and a Bellman target. About 1,500 lines, one author, no learning library at any point.
- The method was validated in stages. A Q-table on a five-state version of the problem first. The network fitted to known functions (a square root, a sine) before it was trusted with Q-values. Backpropagation derived twice, once in scalars and once in matrices, and the two compared; the comparison caught an index error.
- The debugging is legible. Commits go from “not working” to “working model” through identifiable fixes: a flipped error sign, a deleted weight update, biases that were not summed over the batch. Thousands of cost-curve images were generated to watch training and then removed.
- The choices show understanding of the mechanics, not just the recipe. Learning only from exploratory moves, a clean use of the fact that Q-learning is off-policy. Holding a random action for five frames so it has a physical effect at thirty steps per second. Feeding absolute coordinates so the network has to learn geometry. Diagnosing that one shared learning rate starved the output side of a tanh stack, and fixing it with per-layer step sizes. Collapsing the Q-table's state space until the table could fill.
- Reproducibility habits unusual in student code. A seeded random generator “so random gets deterministic”, and numeric warnings promoted to exceptions.
- A learned behaviour at the end. Dodging, in a two-player game with physics, against an opponent with perfect aim, as the README reports; a video of it is linked from the README.
Against the paper
As an implementation of the DeepMind recipe it is partial. The core is present: a network standing in for the Q-table; exploration by sometimes acting at random; a target that copies the network's own prediction and overwrites one action's entry with the reward plus a discounted estimate of the future; gradient descent on the squared error. Two mechanisms are missing, and they are the two the paper introduced to make this stable: experience replay, learning from a random sample of remembered moments rather than the latest one, and a target network, a frozen copy used to estimate the future so that the goal does not move with every update. Without them the method is the online neural Q-learning that predates the paper, and which the paper describes as prone to instability.
The final commit also has faults in the two lines the algorithm hinges on: the discounted-future term uses the position of the best next action instead of its value, and the derivative used in backpropagation is taken one step off. The reward is computed from the opponent's side. What the network could learn from, in that commit, was the immediate reward; the README reports dodging, and the repository holds no evaluation data to say more. Section 10 lists each of these with the line numbers.
Verdict
| Scope, for one student in ten months | Wide. Physics, game, environment, network, backpropagation and learner, all written by hand. |
| Understanding of the method | Shown in the design choices and in how each failure was diagnosed and fixed. |
| Faithfulness to the DeepMind paper | Partial. The idea, not the stabilising recipe; replay and target network absent; two core lines wrong. |
| Rigour of evaluation | Low. No metrics, no saved weights, no baseline; one video and one README sentence. |
| Code quality | Research code. Configuration by editing source, dead code kept as a record, a virtualenv committed. |
For 2017, for one student working from a paper and scattered tutorials, this is substantial work: the parts most people skip, the calculus and the debugging, are exactly the parts done by hand. It is not a faithful DQN. Read it as evidence of understanding, not as a reference implementation.
2The terms, explained simply
Everything on this page can be followed with these. Each entry gives the plain idea first.
The learning problem
- Reinforcement learning
- Learning by trial and error, the way you would train a dog with treats. The learner tries things, gets points for good outcomes, and does more of what earned points. Nobody shows it the right answer; it has to find out.
- Agent, environment
- The agent is the learner (here, the unit the network controls). The environment is the world it acts in (the arena, the opponent, the physics).
- State
- A snapshot of the situation, as a list of numbers. Here: where both units are, their health, what tile they stand on, where the bullets are. 26 numbers in all.
- Action
- One of the moves the agent can make. Here: up, down, left, right. It cannot shoot.
- Reward
- The points for one step. Here: most of it for not standing on lava, the rest for not having been hit much. The agent's job is to collect as much reward as it can over time.
- Episode
- One game, from the start until a unit's health reaches zero.
- Policy
- The agent's rulebook: which move to make in which situation. The whole point of training is to end up with a good one.
- Hyperparameters
- Settings a person chooses before training rather than something the agent learns: how fast to learn, how much to care about the future, how big the network is. “Hand-tuned” means chosen by trying values.
The scores
- Q-value
- A score for “how good is it to make move A in situation S”, counting the reward that follows. Q is for quality. Once you have good Q-values, the policy is simple: in each situation, pick the move with the highest score.
- Q-table
- A spreadsheet of Q-values: one row per situation, one column per move. It works when situations are few. It cannot work when situations are described by 26 continuous numbers, because there are effectively infinitely many rows.
- Q-learning
- The rule for improving the scores from experience: after each move, nudge the score you had toward “the reward I just got, plus a share of the best score available from where I ended up”.
- Bellman update, bootstrap
- That nudge, named after the mathematician who wrote it down. “Bootstrapping” means updating a guess using another guess: the value of now is estimated from the value you expect next, which is itself an estimate.
- Discount, γ (gamma)
- How much future points count compared with points now. 0.9 is patient: rewards ten steps away still matter. 0.2, the value used here, is short-sighted: only the next step or two count.
- Learning rate, α (alpha)
- How far to move toward the new estimate each time. Too large and the scores swing wildly; too small and learning takes forever. The schedule of lowering it during training is the same idea as taking smaller steps as you get close.
- Exploration, ε-greedy
- Mostly make the move you currently think is best (“greedy”), but sometimes make a random one so you discover moves you had underrated. ε (epsilon) is the chance of the random move. The 2017 code used the letter for the opposite chance; the page says so where it matters.
- Off-policy
- Learning about the best possible rulebook while behaving by a different one, for instance mostly at random. Q-learning can do this because its update asks “what is the best move from here”, not “what move did I actually make next”.
- Action repeat
- Holding a chosen move for several frames instead of re-deciding thirty times a second, so that a move has a visible effect before it is judged.
The network
- Neural network
- A large adjustable formula with many knobs, that turns a list of input numbers into a list of output numbers. Learning means turning the knobs, a little at a time, so the outputs get closer to what they should be. The network here has 406 knobs.
- Weights, biases
- The knobs. Weights scale each input on its way to the next layer; biases shift the result. “Initialised uniform in (−1, 1)” means every knob starts at a random setting in that range.
- Layer, hidden layer
- The network is built in stages. Inputs go into the first stage, its outputs into the next, and so on. The stages between input and output are “hidden”. This one has three: 10, 6 and 6 units wide.
- Activation, tanh
- After each stage, every number is passed through a squashing function so values stay in a sane range and the network can represent curves, not just straight lines. tanh squashes anything into −1 to 1. Using it on the output too means every Q-value is capped at 1.
- Function approximation
- Using a formula (the network) to estimate the scores instead of storing them in a table. The formula can give an answer for situations it has never seen exactly, which a table cannot.
- Loss, cost
- A single number for how wrong the network's output is compared with the target. Training pushes it down. “Squared error” means the wrongness is squared, so big mistakes count much more than small ones.
- Gradient descent
- The method for turning the knobs: work out, for each knob, which direction lowers the loss, then move every knob a small step that way. Repeat many thousands of times.
- Backpropagation
- The bookkeeping that finds those directions, working backwards from the output through each stage using the chain rule from calculus. Frameworks like TensorFlow and PyTorch do this automatically. Here it is written out by hand, which is the unusual part.
- Vanishing gradient
- In a deep stack of squashing stages, the “which way to turn” signal gets smaller at every stage it passes back through, so some layers barely learn. The per-layer step sizes in this network (×1, ×10, ×100, ×1000) are a hand-made fix for that.
- Features, feature engineering
- The numbers a person chooses to hand to the network. “Hand-picked features” means the author decided that position, health, tile type and bullet positions are what matters, and scaled each to a sensible range. The alternative, which DeepMind used, is to hand over raw pixels and let the network find the features.
- Batch, minibatch
- How many examples the network learns from in one knob-turn. One example at a time is “batch size 1”; several averaged together is a minibatch.
The DQN parts
- Deep Q-Network (DQN)
- Q-learning where the spreadsheet is replaced by a neural network. The DeepMind version, published in Nature in 2015, learned to play Atari games from the screen pixels alone.
- Experience replay
- Keep a diary of past moments (situation, move, reward, what came next) and study random pages from it instead of only the most recent moment. This stops lessons being distorted by whatever happened to occur in a row. Not used in this project.
- Target network
- When estimating “the best score from where I ended up”, use a frozen copy of the network that is refreshed only now and then, so the target does not shift every time the network learns. Not used in this project.
- Headless
- Running the game without drawing it, so training goes as fast as the computer allows instead of thirty frames a second.
- numpy, pygame, Gym
- numpy is Python's library for fast arithmetic on arrays of numbers, the tool the network is written with. pygame is a library for making 2-D games. OpenAI Gym is a standard way to package a game so learning code can plug into it with two calls, reset and step; this project copies that shape.
Live demo · tabular Q-learning · built in 2026 for this page, not the 2017 code
Q-values on a smaller arena
- episode
- 0
- steps
- 0
- last return
- –
- last 50 episodes
- –
- map seed
- 1
Arena focused: Space run / pause · S step · R reset
The 2017 agent used a neural network over 26 continuous features. This table over 120 cells makes every update visible: one state per tile, four actions, and the readout shows the Bellman update with the actual numbers. The heat-map is the 2017 visualiser's mapping, mean Q per tile, green positive and red negative; arrows show the greedy action. The ε here is the probability of exploring, the usual convention. The 2017 variable eps was the probability of acting greedily. The terms are explained in section 2 above.
3The game
Two units on a 960 × 512 px arena divided into 30 × 16 tiles of 32 px. The outer three columns and two rows are lava; the interior is grass, with a one-in-ten chance of ice per tile. One sword lies on the map at a time. Walking over it adds 5 to the mass of your loaded bullet and spawns a new sword elsewhere. Bullets fly at 300 px/s with a 0.6 s cooldown. A hit applies a knockback force of bullet mass × 4000 × the victim's hit factor, then raises that hit factor by 0.2, so every hit makes the next one push harder. Lava drains 15 health per second. You win by pushing the opponent onto the lava.
Physics
- Units: mass 10, health 100, hit factor 1, control speed 200 px/s.
- Forces are composed by vector addition; kinetic friction per tile: grass 40, lava 40, ice 0.5, so ice really slides.
- Fixed timestep of 1/30 s. Training runs the same physics without a frame clock.
- Bullets are units with
isFlyingset: no friction, no lava damage, removed when they leave the screen.
The environment interface
The environment is written in the shape of OpenAI Gym, which was a year old at the time. env.step(events) returns (state, reward), env.restart() resets, env.checkWin() ends an episode when either unit's health reaches zero. Rendering lives in a separate DrawEngine, so training runs headless at whatever speed the CPU allows and the game is drawn only afterwards. The Gym loop is pasted into game_engine.py as a TODO comment, which is an honest description of how far the resemblance goes: there is no done flag and no info.
The opponent
bot.py is 25 lines. It walks along whichever axis is farther from the sword and fires at the learner's centre on every frame, limited only by the cooldown. It never dodges. Because it monopolises the swords, its bullets get heavier over a game. The learner is UNIT_2, has no shoot action, and can only move.
4What the learner sees
The state is 13 hand-picked features per unit, for both units, in a fixed order: 26 numbers. Positions are fed as absolute x and y rather than as distances. The README states why:
“I am feeding the NN with (X,Y) values instead of distances so that the NN has to understand the concept of map positions.”
unit_state.py). μ is the friction coefficient of the tile under the unit; b1 and b2 are the unit's own two nearest bullets, sorted by distance, with their heading. Bullet x and y are appended in raw pixels; everything else is scaled to about 0 – 1.The learner never fires, so its own six bullet features stay at zero. The bot's bullets enter through the bot's block, sorted by distance to the bot, which is where they were just launched from; that is the signal a dodging policy has to work with.
Four actions, encoded as a bitfield (up 8, down 4, left 2, right 1). No diagonals are used and there is no shoot action. The reward for one unit is
reward = 0.7 · (1 − dps/15)
+ 0.3 · (1 − clip(hit_factor, 0, 30)/30)
# health and sword-mass terms exist, weighted 0
# unit_state.py:61-68
which is 0.7 for not standing on lava plus a share that decays as the unit accumulates hits. The environment reward is reward(unit 1) − reward(unit 2). There is no terminal bonus and no done flag.
5The Q-function
No framework. nn.py is a numpy class with four weight matrices, four bias rows and a hand-written backward pass. Every layer, including the output, is tanh, so each Q-value is bounded to (−1, 1). Weights start uniform in (−1, 1) under np.random.seed(1), with the comment “Important for debug, random gets deterministic!”; np.seterr(all='raise') turns any numeric warning into an exception. Loss is half the squared error; the optimiser is plain gradient descent.
nn.py, commit ffa4186f). Weights and biases start uniform in (−1, 1) with seed 1; loss ½ Σ (target − output)²; plain gradient descent with base learning rate 1e-4 and a per-layer multiplier on the step.The per-layer multipliers are the last change made to the network:
# nn.py, lines 97-105
self.wx1 += deltaW0*self.lr
self.w12 += deltaW1*self.lr*10
self.w23 += deltaW2*self.lr*100
self.w3y += deltaW3*self.lr*1000
With one shared learning rate, the backpropagated error shrinks through each tanh layer and the output side barely moves. Scaling the step by 1, 10, 100 and 1000 from input to output compensates by hand. The base rate was cut to 1e-4 and the net narrowed to 26 → 10 → 6 → 6 → 4 in the same commit (11 Sep 2017). The commit message calls it removing weight decay; there was never an explicit decay term in the code, so what was removed is the implicit decay of the step size with depth.
Before the class was trusted with Q-values it was validated as a plain regressor on √(x² + y²), on a sine over [−20, 20] and on x/2; the harnesses survive as comments at the bottom of nn.py. Earlier still, network.py derives backprop for a 2-4-1 net by hand, one partial derivative per line with the chain rule written out in comments, then repeats the computation in matrix form directly beside it and compares the two. That comparison caught an index error in the scalar version.
6The training loop
main_nn.py. The network is updated only on the frames where a random action was chosen, from that single transition.The variable eps is the probability of taking the greedy action, the opposite of the usual convention: 20 % greedy for the first quarter of training, 30 % after, 100 % once training ends. A random action is held for five frames so that it has a visible physical effect at 1/30 s per step; the network is updated only on those frames. So the behaviour policy is mostly random, the update is toward the greedy value, and the net learns only from exploratory transitions: off-policy in the strict sense.
A 20-sample minibatch was tried on 17 August and dropped two days later. The commit message, in Bulgarian, records the reason: on batches the updates average out, without them the network “corrects on every move”. With only the taken action's entry carrying new information, summing twenty targets does smear the signal across all four outputs.
| steps | 300,000, headless (the README says 200k; the code says 300k) |
| greedy probability (eps) | 0.2 → 0.3 at step 75,000 → 1.0 after training |
| action repeat | random action held 5 frames |
| update | only on exploratory frames; batch 1 |
| target | predicted Q(s,·) with target[a] = r + γ · bootstrap |
| γ | 0.2 |
| learning rate | 1e-4 base; ×0.5 at 150k, ×0.5 at 225k, ×0.1 at 262.5k; per-layer ×1 / ×10 / ×100 / ×1000 |
| replay buffer, target network | none |
| logging | step and last cost every 10,000 steps; cost curve shown at the end; nothing saved to disk |
Cost is printed every 10,000 steps and plotted with matplotlib when training ends; then the loop keeps going with rendering on, AI against bot at 30 FPS. Nothing is saved: the weights live only in that process. No cost curve is reproduced here. 5,898 curve images from runs past 10.8 million steps were committed and deleted the same day (commit 810a5427), and no numbers survive in the repository.
7Before the network: the Q-table
The first learner, in April 2017, was a Q-table. 120 position states aliased and failed. 480 states, one per tile, with a terrain reward and count-based exploration, failed too. What worked on 3 April was collapsing the state to five values: which lava band the unit is in, or the interior (lr 0.1, γ 0.5, ε 0.5). A visualiser coloured every tile by the mean Q of its state, green positive and red negative, which is the display the live demo above re-creates.
Five states cannot express a bullet, an opponent or a sword, which is what pushed the project to function approximation. The README, later:
“Initially I have implemented it with Q-table but wasn't sufficient due to big state space, so I have decided to go for NN to replace the Q-Table.”
A scikit-learn MLPRegressor was tried first, in July, and replaced by the hand-written network in August.
8Result
From the README, December 2017:
“My success with the AI is that it learns to avoid bullets. Can't shoot yet.”
A video of the learned agent is linked from the README on Google Drive; the link is reproduced as recorded and was not re-verified for this page. There is a mode to play against the trained agent, switched on by editing main_nn.py.
What the repository does not contain: saved weights, tests, a command-line interface, or a thesis document. Of its 672 MB, almost all is a committed virtualenv and the Dungeon Crawl Stone Soup tileset (3,054 files) used for the sprites; the Python is about 1,500 lines, comments included.
9The commit history, as a lab notebook
Thirty-five commits between 17 February and 4 December 2017. Messages are paraphrased; dates and hashes are as recorded. The two marked commits are the ones that made the network learn.
Game engine
17 Feb – 31 Mar · 10 commits02f379fcRepository created: a pygame event loop and a blank window26cc6c72Physics: forces, mass, acceleration, a fixed 1/30 s timestep2a8b6169Forces composed by vector addition; movement in all directionscdb44fc7Impulse model for forces; input handling split into UnitControlf2617c9eBullets fly; a mouse click fires toward the cursord8d46c58Bullet collision detection; off-screen bullets removedd6bd914bMap grid of tiles; a hit becomes knockback6524fc64Dungeon Crawl Stone Soup tileset added (3,054 files); arena set to 30 × 16 tiles6a046754Health; friction and damage per tile type: lava, grass, ice2d0289edRefactor; swords on the map; collected mass enters the knockback equation
Q-table
1 – 3 Apr · 4 commitsbb71b6dbGym-shaped split into step and render; first Q-learning loop, not yet runnablea68c57b6Loop runs; 120 position states alias and learning fails444992c4480 tile states, terrain reward, count-based exploration; still fails1edadedbWorks: state collapsed to five lava bands; lr 0.1, γ 0.5, ε 0.5
A network from scratch
26 May – 17 Jun · 2 commits636b2cdcBackprop derived by hand for a 2-4-1 net, one partial derivative per line; Q heat-map visualisereee8e2beThe same network in matrix form, computed beside the scalar version to verify it
DQN
8 Jul – 19 Aug · 14 commits58053333Rendering moved to DrawEngine so training runs headless; UnitState defines observation and rewardd6827cd9scikit-learn MLPRegressor replaces the Q-table9a33e10aLarger sklearn net, replay-style buffer, continuous distance features; not workingff7417e8Custom numpy network started alongside the sklearn one077f9e86Custom network replaces sklearn; not working (gradient sign, ReLU derivative)164f9d7dRow-major rewrite; cost plotting32dac213Backprop correct: error sign, second-layer update restored, biases summed over the batch7844ea13tanh activations, 100 hidden units; passes a sine regression test1801654726-feature two-player state, differential reward, scripted opponent, episodes, 20-sample minibatch, learning-rate annealing591d6117Configuration for a long run: 3,000,000 steps, hidden layers of 14 and 825bb5a16Breakthrough: batch size 1; four-layer net 26 → 40 → 40 → 40 → 4; the two nearest bullets as features810a54275,898 cost-curve images from runs past 10.8 million steps removed731942f0“Good config”: 26 → 20 → 15 → 10 → 4
Final tuning
10 – 11 Sep · 2 commitsb497db29Sprites for the sword, the bullet and player 2ffa4186fPer-layer learning-rate multipliers 1 / 10 / 100 / 1000; net 26 → 10 → 6 → 6 → 4; lr 1e-4; Bellman bootstrap term added, γ 0.2
Write-up
4 Dec · 2 commits328b6a31README written in the Bitbucket editor; a formatting edit follows as 57f20020
10Reading it back in 2026
A read-through of the final commit, nine years on. The first table is what the code does differently from the 2015 DeepMind paper it cites; the second is what a code review would flag.
Against the Nature DQN
| Technique | In the paper | Here |
|---|---|---|
| Experience replay | uniform sampling from a buffer of past transitions | none: the buffer is flushed after every sample (main_nn.py:120) |
| Target network | a separate, periodically copied network for the bootstrap | none: the live network predicts s′ (main_nn.py:114) |
| Minibatch | 32 | 1; a batch of 20 was tried and dropped |
| Input | four stacked frames into a convolutional net | 26 hand-picked features into a fully connected net |
| Loss | squared error with the error clipped to ±1; rewards clipped to ±1 | squared error; rewards already in [−1, 1] |
| Exploration | ε from 1.0 to 0.1 over the first million frames | greedy probability 0.2 → 0.3 → 1.0; random actions held 5 frames; updates only on those frames |
What a code review would flag
| What the code does | Effect | Where |
|---|---|---|
max_future = np.argmax(a1_predicted), then r + y*max_future | the bootstrap term is the index of the best next action, 0 to 3, not its value; the target carries no value information beyond r | main_nn.py:115-116 |
tanh_deriv(a) called on activations, where a = tanh(z) | the slope is computed as 1 − tanh(tanh z)² instead of 1 − tanh(z)²; the sign is right, the magnitude is not | nn.py:67-70, 124-125 |
reward = reward(unit 1) − reward(unit 2); the learner is unit 2 | the learner is rewarded for the opponent's advantage; nothing in the loop flips the sign | game_engine.py, unit_state.py:61-68 |
| bullet x and y appended in raw pixels, 0 to 960, beside inputs scaled to about 1 | whenever a bullet is in flight the first tanh layer saturates on those inputs | unit_state.py:45, 47 |
env.restart() on a win; no done flag in the target | the transition that spans a restart is fitted like any other, and no terminal value is ever assigned | main_nn.py:60-62, 116 |
None of this changes what the repository shows: a physics engine, an environment, a tabular learner, a network derived by hand and a Q-learning loop around it, built and debugged in one year with the failures left in the history.
Source: bitbucket.org/donald_ovcharov/tu.diplomna · Bachelor's thesis, Computer and Software Engineering, Technical University of Sofia, 2017. The thesis document itself is presented in English at Visualising Game AI.
Written in 2026 from the repository's code and commit history. The live demo is a tabular re-illustration built for this page, not the 2017 program.
Donald Ovcharov, founder of 402. AI research and implementation since 2017; today it is Claude Code sessions, LangGraph pipelines and agents in production. Talk to us about AI →