Making my own simple bot: Euroton

I’ve always wanted to develop a Go bot, but not a good one. I just want to give it simple rules and incentives, like “avoid 1-1”, “getting out of atari is good”, “start in the corners”, to see what happens for the fun of it. No think-ahead, just vibes.

For anyone reading this and having any form of expectation, this will be much worse than even early days leela or gnugo, as those have had much better heuristics, but also shape/pattern matching, joseki libraries, and think ahead. This will just be a bit better than a random bot (hopefully). If it ever reaches a state of usefullness, I’ll have it play on OGS.

In this thread I’ll blog the development of this bot. The bot is Euroton. (Heuristic Skeleton)
Btw, if there’s a better subcateogry, feel free to move this thread.

Yesterday I started vibe coding, today I have a working bot. The backend is dlgo, the bot runs in Python, and Sabaki hosts games between Euroton and GnuGo.

First game - Euroton as Black vs GnoGo level 10: 381 point loss.
The only functioning rules are:

  • play legal moves
  • get out of atari
  • An incentive to connect two friendly groups

The result is a bot that loved playing 1st and 2nd line moves, probably because they come first in the consideration. Nothing lived in the end. I hoped maybe by accident something would survive, but no.

2nd game - Euroton vs GnuGo level 1 (weakest): 350 point loss
Nothing changed but GnuGo is now weaker. This shot nicely shows the affinity of the bot for 1st and 2nd line moves, the inability to read a ladder, and the latest move is dubious.

3rd game as white vs GnuGo lvl 1: 250pt loss

  • added an incentive for 3rd and 4th line moves
  • punishment for 1st and 2nd line moves if the game is still in the first 30 moves, and less so after that.
  • give it incentive for 1-distance moves from friendly stones, so a 1 space jump or a diagonal, and a smaller incentive for 2-distance moves.

The result is obvious, a love for 3rd/4th line moves, and since tigers-mouths are a diagonal and a one-space jump, those moves score even higher, which is why the whole board is littered witht hem.

This eventually grew to this mesh and a total avoidance of 1st and 2nd line moves.

But the bot doesn’t know or care about points, so it starts filling it’s own territory in… Still, the group survived and the loss is now “just” 250 points.

14 Likes

I have zero interest in building a bot, but this was incredibly entertaining, and I shall be watching for updates XD

8 Likes

Haha I’m glad to hear that!

Game 4 - GnuGo vs Euroton: 403pt Loss

  • Organised the python code neatly in files and folders, instead of 1 all-inclusive file. This accidentally fixed the incentive to connect with friendly stones, which didn’t work before. Very nice
  • “Influence” incentive added: ChatGPT suggested this one, alright go ahead… but it didn’t really understand Influence, and instead made an Incentive to play near the center.

Euroton now starts in the centers of the 3rd line, playing a new big-square fuseki. After filling up the 3rd line, it continues on the 4th line. After the 3rd and 4th line were full, it jumped to the ultimate center: tengen.

The incentive to connect is also going strong, shown by the straight lines and clumping together.

Game 5: GnuGo vs Euroton: 350pt Loss

  • Removed Influence incentive
  • Added Incentive to play in the corners (Which is just the “influence” incentive with a minus in front of it)

Wow, promising opening, grabbing two strong corners. But then it went onto ultra thick play and eventually lost every stone.

Game 6: GnuGo - Euroton - Making somewhat eyespace - 250pt loss

  • Added Incentive to make 1-space-jumps and diagonals, with the goal to make eyeshape and avoid overly thick play.

The new incentive is so strong that it stays close to its friendly stones. This did lead to the starting group surviving (good!), but everything else died (boo). To go further, Euroton needs to learn about Go concepts as dead/alive and territory.

Game 7: GnuGo (black) - Euroton (white) - Understanding liberties! - 350pt loss

  • Added Incentive to extend a friendly 2-liberty-group to as many liberties as possible, the more the better. (1-liberty-group / atari was already implemented)
  • Added Incentive to remove liberties from the opponent, the more the better.

Straight out the gate Euroton follows GnuGo around to fight, cutting everything in sight and not letting go. When GnuGo tenuki´s in the bottom left, Euroton continues in the top right to save its own stones. That is unsuccesfull (1st pic), but later it does neatly connect a weakness (2nd pic). It is glorious to see the bot fighting like a bulldog!

When running this same setup again, it even won the top right corner! (1st pic).
However, since it only responds to 2-or-less liberties, and doesn’t know or care about territory or two-eyes yet, it just gave that top right away until it was too late (2nd pic)..

It’s still far from good, but it does look like someone is trying to play Go, rather than plopping geometrical patterns on the board.

7 Likes

We do miss some more interesting bots for beginners, it looks promising

5 Likes

Game 10 - GnuGo (B) vs Euroton (W) - less attacking (350pt loss)

  • Decreased incentive to reduce opponent liberties

With less incentive to attack, it follows other rules more:

  • Keep a healthy distance from friendly stones (3 spaces in between optimal)
  • Prefer 3rd and 4th line
  • Make sure groups have at least two, preferably 3 liberties.

Eurotons behavior is now to attack a bit, then let go, then attack a bit more somewhere else. GnuGo has no trouble to kill all those little groups off, and in the end nothing remains.

The last move, P11, follows the “stay at a couple stones distance from friendly stones” rule, which is to make eyespace in theory, but is pointless in practice.

At least when it played like a unrelenting bloodhound, it would occasionally win a fight, captures, and automatically make eye-space. I think it played at a much higher level than what we see here. Which makes sense, because liberties are the main concept of Go.

So, rather than removing its fighting spirit, I should give it something better to do.

Game 11 - Making an eye - Gnugo (B) vs Euroton (W): 250pt loss

  • added: logic to recognise almost eyes and incentive to finish them eyes

Euroton now recognises almost eyes, those are points that have 3 of their 4 sides being a friendly stone (or an edge). Euroton will then fill in the remaining stone, and the 4 diagonals. In the beginning of the game it doesn’t care so much about diagonals, but after 100-ish stones it will fill those in too.

Except.. it doesn’t. The game below looks awkward and random. Yes, there is a two-eyed group up top, but that one happened by accident. At no point did it deliberately choose to make an eye there.

Game 12 - Making TWO eyes - Gnugo (B) vs Euroton (W): 250pt loss

  • added: incentive for connecting two eyes
  • added: punishment for filling in own eyes
  • changed: turned incentive to fight back on high.

Since it can’t look ahead to really plan to make eyes, let’s turn on fighting again and hope that it happens more organically. And it does.

It starts with fighting everything, and quickly makes a ponnuki. After that another. Eventually, it turned that into a living group, with no less than four eyes. :partying_face:

It also made two single eyes on the left and attempted a group with two eyes on the right.

4 Likes

What about a score estimator heuristic? Could be as simple as minimum distance to nearest stone + status detection.

Once you have a nice basket of these heuristics you’re interested in, you can have Euroton play itself and let simulated annealing figure out the correct weights.

3 Likes

Also curious how the bot plays on 9x9.

In particular even if the bot wasn’t good based on heuristics, it could still end up being an interesting beginner bot on 9x9, 13x13 or 19x19.

Or if it became too good after a while, one could imagine freezing the heuristics to have different models for different levels.

3 Likes

Looks like a fun bot! Are you going to put it up on OGS? We have nothing between random-move-nixbot (~65k) and amybot-beginner (~25k) right now so there’s a lot of room for this kind of thing.

One tip is that you can use GnuGo as an “ending bot” to help your bot resign or pass, so it can finish games well despite otherwise playing horribly.

3 Likes

Here we go :slight_smile:

Game 12 - 9x9 - GnuGo lvl1 (B) vs Euroton (W)

  • No changes

It still fights, but doesn’t survive GnuGo. On 19x19 the fighting spirit leads to some organic territory, but on 9x9 the space isn’t there. A good example of what goes wrong is the image left.

  • C6 is the chosen move, because of the strong atari bonus, as well as reducing a liberty on the top black group.
  • H4, KataGo’s top move, was ranked 4th best, because while it reduces a liberty of the opponent, it gets a hefty penalty for being too close to its own stones.

At no point did it consider H4 because it would make good territory, because that’s not coded yet. So while that territory/life intelligence is necessary, maybe we can get a little closer by not punishing self-contact.


Game 13 - 9x9 with connections - GnuGo (B) vs Euroton (W)

  • removed: harsh punishment for contacting own stones. A very light incentive for larger jumps (optimum 4) remains.
  • added: “pass when opponent passes” rule

> after bugfix >

F5??? So…

  • The SelfAtari rule didn’t trigger. Its job is to veto self-atari moves, unless a capture is made.
  • The Urgency rule strongly suggested this move, which is strange because its job is to suggest moves to get out of Atari.

These are the only rules not made by me, but called from the dlgo backend. I don’t know how they work, so instead I remade it in Eurotons code. After this, it seems to behave as intended.


Games 14.. - 9x9 bugfixed - GnuGo (B) vs Euroton (W)


Here are some games in its current form. The top left is a great looking result, with black winning only 10 points. The games after that are almost identical. Euroton is deterministic (for now), apparently GnuGo can be close to deterministic as well.


To do: Fix EyeMaker rule

The situation above I’ve seen occur a couple of times now. The white group on the bottom needs G1 to make life. The EyeMaker rule suggests D1 and F1 to make an eye around E2, which is alright, but G1 is not even considered. That’s a bug, because F1 is also an Almost Eye, and thus G1 should at least be considered. Additionally, orthogonal edges of eyes should weigh heavier than diagonal edges, so it should win out.

In the future it may be better to have logic about two groups sharing two false eyes, ending up having two full eyes, or something. Or, more simple, it can be a rule that closes off an area.


Noteworthy behaviors

Below are some moves from a manual game of me (B) vs Euroton (W). Now I must say, it is pretty fun to play. But I noticed some strange moves, that I want to note for later fixing.

Semeai - Liberty race

D3 is not flagged as a dangerous move because it slips through the cracks: the group starts with 3 liberties, so it’s not considered in danger. It doesn’t really self-atari either. So as far Euroton is concerned, it’s reducing liberties of 3 black groups at once, an excellent move indeed!

This one will be more difficult, and maybe not the most important to fix, because it creates aji. It is a good move in the sense that it achieves at least something.

Playing in opponent territory

B4 is… not great? However, it did play a “vital point”, so let’s keep this for now.

These first two noteworthy behaviors may be best fixed by giving the bot something better to do, rather than nerfing this behavior.

Escape to nowhere (ladder)

This is technically a ladder: a series of atari’s, ending in defeat. I’ve noticed before that Euroton will play out a ladder. So there needs to be some basic ladder reading logic.


Yes, that´s a good one. For an optimisation algorithm, is it best to play itself or a better bot?

You mean implementing GnuGo code? Can you point me towards what you mean?

Yes, if it’s reliable and fun I will.

What I do find important is that it must be a good influence on beginners. It can play bad, as long as it’s a reasonable form of bad, and not outright weird. Nor should it be easily exploited in a way that wouldn’t work on a human opponent.

4 Likes

Fix: EyeMaker rule

  • Fixed: incentive to surround a potential eye.

> >

On 9x9 it made a tiny group, and on 19x19it made two living groups (top left & middle right), that’s the best so far.


Understanding territory

To prevent useless fill moves, I implemented @Myrsilos suggestion.

  • added: ray casting in four directions to determine ownership of a point.
  • added: strong incentive to avoid playing on points fully surrounded by the opponent.
  • added: mild incentive to avoid playing on points fully surrounded by friendlies.
  • added: very light incentive for 3rd/4th line moves, otherwise it opens at A1/A9 because there is no other incentive yet.

>>

However, it broke the eye_maker rule, because it didn’t call this move anymore. That’s to fix later.

>>

To visualise the territory: purple is neutral. Eventually it hugs the opponents wall, motivated by reducing liberties.

> >


Game 18 - Understanding Territory - GnuGo lvl1 (B) vs Euroton (W). 17pt loss

  • added: Euroton only passes after opponent passes two times in a row. (Seems better to add a final stone for good measure and try an invasion).

The territory rule has a nice effect. Euroton doesn’t want to play in its own territory, but if it has to, it will attach to the opponent, because of the liberty reduction motivation. This leads to a natural defence of what it considers territory. However, it now completely avoids playing in blacks territory. The territory judgement is too absolute.


Game 19 - Tapering Territory - GnuGo lvl1 (B) vs Euroton (W). 215pt loss

  • Added: tapered Influence / Potential ownership, stones far away from a point exert less “influence” over that point.

Despite the tapered influence, Euroton loves playing in areas that are fully his own. This is because the punishment for playing in opponent territory is still higher.

After every other option is exhausted, including filling in its own territory with single eyes, it will finally invade, unsuccesfully of course. This is a 230 pt loss.

And another game. If the top group didn’t get so viciously penetrated, and the group on the middle right dumbly left to die, this would’ve been a very good result.

Obviously then it needs to invade more. However, increasing the motivation to invade, leads to a known pit… the dreaded ladder.

6 Likes

You want it to play itself, rather than a stronger bot, because playing the stronger bot will only tell you that a modern go AI is stronger than any configuration Euroton, whereas what you are trying to figure out whether weight-vector A of Euroton is stronger or weaker than weight-vector B of Euroton. (Also, I assume the heuristics Euroton uses are much lighter computationally than AI, so you can run lots of trials very rapidly.)

3 Likes

tldr: Euroton is now faster

Ah yes, that´s cool. I hope to get to that point soon. As for the computational lightness, good question. It’s about as slow as full strength GnuGo, and getting slower with each added algorithm. Even though optimisation isn’t a goal yet, I’m getting bored watching the bot play bad moves slowly. So high time to dive into this topic.

First some crude benchmarking. Starting on an empty 19x19, letting bots play themselves in Sabaki and running the stopwatch for 1 minute (on my laptop):

  • GnuGo level 1: 334 moves
  • Euroton old: 118 moves
  • GnuGo level 10: 110 moves

Euroton’s rules are pretty much standalone, each checking the board state for what they need, but that means a lot of double work is done. So to optimise, common tasks of information gathering are now centralised. On its turn, fist basic info is determined for each position, such as

  • is the move is valid/legal
  • how many liberties will the stone or resulting group have
  • does it atari or capture

The results are cached and used by the rules/incentives. These analysis itself is also improved: looking only at the local situation (as necessary), rather than the whole board.

After converting some of the rules to this new approach the speed has already increased significantly:

  • GnuGo level 1: 334 moves
  • Euroton new: 280 moves
  • Euroton old: 118 moves
  • GnuGo level 10: 110 moves
7 Likes
  • added: Eye_Maker rule adjusted to not attempt to finish eyes if the opponent already occupies too many diagonals (2 on a center eye, 1 on an edge).

> fix >

Game 22 - GnuGo lvl 1 (B) vs Euroton (W) - 150pt loss

Game 22 - 19x19 v2.sgf (2.1 KB)

  • added: incentive to increase liberties tapers off when a group has more than 4 liberties, and is very small when a group has 8 or more liberties.

  • added: crude ladder check (placeholder)
    If my group is in atari, virtually add the rescuing stone, and then see:

    • If it ends with 2 liberties, and both are legal moves for the opponent (so the opponent can atari immediately), then consider it a ladder and don’t play there.
    • If it ends with 1 liberty, the move is blocked by the SelfAtari rule.
    • In any other case, it’s a valid move for consideration.

This is ladder code is painfully crude, but I must stop it from playing into every ladder it can find for now. All of this led to the best game so far.

After this it didn’t have a chance to get more points.

5 Likes

Yeah I’m not sure what heuristic it needs to realise that saving one stone is not as good as saving 6 say.

Maybe some connection heuristic for checking if the opponent can cut (say with one move - so just direct cuts) and then counting the number of cut stones.

It would probably have to be a combination of evaluating groups as alive (two eyes and whatever else it can figure out is alive) and then not prioritising connecting alive groups to each, but just when a not alive part can be cut in one move.

2 Likes

Is there a score for playing in open areas of the board?

2 Likes

TL:DR: Today I had a free day and I spent 14 hours on this bot. It has more and better algorithms and many bugfixes. As a result, it now loses harder than ever before.

As the Dutch saying goes: there’s no shine without friction.

Improved: Connectivity rule
Goal: Incentivise friendly connections that are cuttable by the opponent, by

  • Incentive for the connection of two groups
  • Add a bonus if one of the groups is low on liberties (‘save a weak group’).
  • Add a bonus if one or two of the adjacent stones are from the opponent. Basically: Even an fool defends against a peep.

These additional considerations make it weigh more than saving a single stone, though of course this will not always be the correct choice. @shinuito A future step is indeed to make it reward connecting non-alive groups to alive groups.

>

Added: OpenArea rule @Jon_Ko
Goal: motivate playing in open space.

Each candidate move is checked on the emptyness of the surrounding rings. Rings with distance 1 (adjacent) and 2 (one-space-jumps and diagonals) must be completely empty/open. For the rings with distance/radius 3 and further, it measures the percentage emptyness. These values are summed to determine a value of “openness”, with inner rings (radius 3) counting more than the outer rings. The outmost ring checked is capped at radius 6. It works pretty good, and looks as expected.

The gameplay (white) is, if we are really flattering, a cosmic go opening.

But it lacks direction. So

Fixed: CornerBias rule to reward corners
Goal: Motivate corner and edge plays in the opening.

The distance from the board center is calculated, and the further points are awarded more. (Basically 4-4 > 10-4 > Tengen). It is capped at the 4-4 point, so playing deeper inside the corner (3-4, 2-2 etc) is not worth more than 4-4.

The rule stays active for the first 40 moves (the opening), and then abruptly goes to zero. The idea is to taper it off later, this is easier to tweak.

Fixed: LineRule rewards 3rd/4th line, lightly penalises 1st/2nd line
This is a layer on top of the CornerBias rule. This also abruptly ends at move 40.

These help steer the OpenSpace rule in the right direction, and against it’s older self it looks to do that:
Game: Old Euroton (B) vs New Euroton (W) (B won)

But now it will play 5 moves in one corner before moving on to the next. And black won this game.. So added some more rules:

Added: Liberty safety rule now also penalises moves that reduce liberties

>

Locality rule - made but seems not useful for now.
Goal: Either motivate or discourage local plays
Working: It can either reward or punish plays close to recent plays. The number of previous moves to consider is adjustable, and there is both a spatial decay (closer moves weigh more) and a time decay (recent moves weigh more). However, it doesn’t feel intelligent to always incentivise moving away or closer. Maybe it can serve as a tie-breaker when the other algorithms are more dialed in, for now it stays deactivated

Tweaked values to avoid “fake Ko-fights”
GnoGo and Eurobot are repeatedly playing back and forward captures (without consequences, so not really a Ko), which was very annoying. So I made the penalty on filling your own eye exactly as big as the reward to save a stone from Atari incentive, so they cancel out.

Fixes
So. much. bugfixing. Functions silently failing, different coordinate systems (0-based and 1-based used intertwiningly), 3 different play history databases, debug not actually showing what happens… I don’t know how* I accrued so much technical debt in such a short time.
(*It’s vibe coding).

Game 25: Using open spaces - GnuGo(B) - Euroton (W) - 350pt loss

After tuning the weights to account for the new rules, the bot played something that resembles a decent opening:

At this point the bot is already 50pts behind, but the main thing is that it took corners, sides, center in a somewhat natural looking way. However, it doesn’t have two crucial skills necessary to convert this to points:

  • Defend territory. Euroton has no concept or incentive for territory. GnuGo does monkey jumps, and slides and just takes it all away.
  • Connect over distances. All these groups look at least decent, but Euroton only responds to losing liberties and 1 space cut/connect options. It doesn’t realise it’s slowly getting its groups cut off and surrounded.

The new rules make it play too widely to rely on the “captures make eyespace”-tactic that worked well before, and thus, nothing lives:

But on the plus side, the current heuristics are still far from optimally tweaked. This is just the first version that looked alright, so I think it has enough room to improve. However, to really purposefully play the game, it has to learn how to claim and defend territory.

5 Likes

Yeah to be honest it looks like a player that can play the game, but is still just figuring out how to balance priorities, which stones are important, which areas are important etc.

Some cool updates :slight_smile:

1 Like

How does it do on smaller boards? Would be nice seeing how it handles 9x9 ^___^

1 Like

Thanks for this thread. I have considered a very similar project for a long time and you encouraged me starting it. I’m planning to do roughly the same, but with the goal of creating many different engines with slightly different playing strengths and let them play each others. From random (which I have ready as I write this) to as high as I can go.

Not sure yet how much time I will find for this. If it is going anywhere, we can let our bots play each others one day. :slight_smile:

5 Likes

Pretty bad tbh! :cold_face: The new spacing rules are counterproductive on 9x9. Look at black A9. Perfectly spaced from its own stones. I don’t want fix this by discouraging 1st line moves more than I already do, because then we are just playing whack-a-mole with pushing moves here and there.

I spent a lot of time tweaking the Open_area and Territory rule to encourage it to play elsewhere, but the issue is that it wont do it unless you give it priority over the Liberty rules. But if you do that, it’ll tenuki when it shouldn’t. So a Influence/Territory function is the main forcus, however first some more important things needed to be done.

First I made the bot have different rule-parameters for 19x19 and 9x9, so I can tweak per boardsize. I didn’t want to do this because it’s less elegant than a single bot that can just play go anywhere, but I also play differently on 9x9 vs 19x19, so I think that’s fair. And maybe later lessons learned from tweaking on 9x9 will bleed into 19x19 too, who knows.

Liberty_safety and Liberty_attack

Then I tweaked the Liberty functions. They are usually good but still had their weird moments, see black B8 below. You may have to squint, this is pretty dark, I edited a theme for Sabaki for dark mode.

Since the white group is so strong, black wasn’t getting any reward for reducing a liberty. I adjusted the tapering of the functions so that reducing liberties on a strong opponent group, or defending your own strong group, is not worth much, but still preferable over just increasing liberties. In Go, a move must achieve multiple things at once.

In the case below, black didn’t get any extra points for extending its own liberties, for being already strong enough, so it ranked H3 as equally good as G2. After tuning the parameters, it chooses G2.

I do admire that black has no shame in playing such a chunky group, but a “dont play bulky 15”-rule is for another time.

Then there are massive mistakes to fix:

Eye_maker

H9 I wait with fixing this until I have the new influence/territory function, because I want the new function to inform other rules.

So for now, the liberty functions are fixed and the rudimentary territory function is tweaked as good as I could.

For who wants to see the bot play, here are some games. I only set the first move manually, the rest is the bots.
Against GnuGo the result is two games lost by 90pt (annihilation) and four with a 35-50 loss (one group lives).

Game 29 - 9x9 Euroton25-10-08 vs GnuGo lvl 1.sgf (2.4 KB)

It does beat its older version in 11 of the 12 games played:

Game 29b - 9x9 Euroton25-10-08 vs Euroton25-10-07.sgf (3.0 KB)

Game 29c - 9x9 Euroton25-10-07 vs Euroton25-10-08.sgf (2.8 KB)

2 Likes