Awesome! Looking forward to seeing that, and eventually having our bots fight it out. Let me know if I can be of any help.
I added a couple of big things:
-
Inluence map, semi-transparent stones and territory estimate
Every stone now emits influence, tapering off with distance. The bot emits positive influence, the opponent emits negative. At every point on the board, the sum is taken, leading to a positive or negative value. This is usable as a rough territory estimate, although it doesn’t take into account wether stones are dead.Stones “hidden” behind others also emit some influence, though only a fraction of what they’d had with direct line of sight. This is called semi-transparent stones.
Example: There’s empty point A1 and a stone on C2. The stone is 3 steps away from A1 so it emits strong influence on A1. If the shortest path gets completely blocked by two stones on B1 and B2, then it’s influence over A1 will be less. However, if a is only on either B1 or B2, then the influence of the C2 stone on A1 remains unchanged, as the C2 stone can still find an unblocked shortest patch to A1.
[ownership grid with solid stones] (+Black -White) -0.69 ##### ##### +0.37 -0.71 ##### -0.35 -0.22[ownership grid with 50% transparent stones] (+Black -White) -0.64 ##### ##### +0.24 -0.69 ##### -0.35 -0.22 -
Influence delta (the big moves)
Building on the previous step, the total board sum is taken over the influence values per position. Basically one number who has the most influence / rough territory. Then on each empty position a move is simulated, and this inluence sum is recalculated. The move that changes the influence the most (the largest delta), recieves the highest score.Stones in open or unsettled positions change the influence more than in strongly settled ones. So this step identifies the positions that are most effective, or that’s the idea.
The downside is that it can take unfortunately up to 2 minutes on 19x19 on a regular laptop per move. To speed things up this calculation is limited to a certain radius around the virtual stone (now set to 6), which brings it down to an acceptable couple of seconds. This does trade in some precision. With full vision, Euroton (black) will play C6 here, but with the smaller range it finds F3 just as good.
The transparency of stones have an effect in the bottom right below. The bot likes S3, because it increases liberties the most, and it also flips T1 strongly in favor of “black territory”. The rest of the board is blocked off by the white wall, so it doesn’t care about that. However…
… setting all stones to 50% transparent, the black stones in the corner now gain influence over the rest of the board, so there’s no reason to shy away from a wall. It probably would’ve done this move as well without the influence_delta at all, but this way the influence delta is not getting in the way of other good moves.
It does require tweaking to see how this slots in with the existing rules.
Btw, new iron pillar fuseki dropped.
Further added is a very strong “don’t needlessly connect a diagonal connection”-exception to the Connectivity rule. Many moves were wasted on this, but now it’ll only connect if the diagonal is under risk of being cut from an opponents stone (ie there’s a stone in the diagonal).
Now I’m coding a defense against monkey jumps / slides. GnuGo does relentless monkey slides, and the influence delta rule doesn’t identify it as a huge enough risk to defend it. Without hardcoding a block against monkey jumps it’s hard to keep any form of territory or even have a representative demo game.
My current best bot is named Hubert. Hubert knows how to capture and has some knowlegde about good/bad patterns. He has no idea how the game is counted and passes randomly unless there is an attractive move (as per his pattern preferences) left. (I’m using Tromp-Taylor rules.) In other words: Hubert is still incredibly stupid, yet about 2350 Elo points stronger than Rob, who plays truly randomly.
Edit: let me know if posting this in your thread annoys you!
I enjoy you posting here!
How did you make it recognise patterns?
I (vibe-) coded a pattern ‘engine’, so I can define patterns in small txt files. Here is the one for empty triangles:
MM
Xo
X represents the newly played stone, M stands for My stone and o for not opponents stone. I have a few other categories like empty/non empty/board edge.
My ‘players’ (or bots) are ini files in which I can assign float values to patterns. So empty triangle would get a small negative value.
I don’t have much besides these patterns yet. The most important non-pattern feature is detecting if a move captures. But this too gives a (configurable) value per captured stone.
I’ll try with sticking to pure move evals for as long as posssible, as this means I don’t have to copy board states and execute moves. But I’m pretty sure that eventually I’ll have to give up on that.
Here’s an example game of Hubert against Julia, who is about 400 Elo weaker. She’s lacking a few patterns and more often plays a move other than the top candidate (higher randomness): hubert vs. julia
That’s awesome, that looks like a lot of fun to make. I think you can get quite far with not executing moves, although you’ll have the same “if it looks like a ladder then just avoid it entirely” until you have enough patterns to cover your bases. How do you measure elo?
So added the Monkey Jump Defense rule, and it works perfectly, but only in the case with adjacent stones, where the opponent has a stone on the 2nd line diagonally under my 3rd line stone. GnuGo isn’t that easily defeated…
Game 33 - Euroton (B) vs. GnuGo lvl 1 (W)
This corner looks great, the sides are blocked off, finally some points for Euroton.
Oh no..
Game 33b - GnuGo lvl 1 (B) vs Euroton (W) - B+200pt
A great territory on the side with no opponent stones nearby…. gone in 3 stones.
>>So I do still have to make something, preferably something that uses territory information that we have in an effective way.
I have 24 bots by now and they played lots of games against each others. Average ELO distance to neighbors about 100.
I added a debug grid with pretty gradients to see what’s going on in the bot. The top move gets a score of +100 (cyan), and then the rest is scaled from there.
Bot (Black) picks E3, with D4 as #2 move
Bot (white) picks C4
Bot (white) picks F3
The bot has the tendency to make freestanding iron pillars (two adjacent friendly stones), especially when it can “headbutt” into opponent stones, see white below.
This is because this move double-dips in rules; it’s both extending liberties of a friendly group, and reducing opponent liberties.
- added: a simple multiplier that reduces the reward of extending liberties of stones or groups that are free-standing, not touching an opponent
As a result, it again plays a little better than before, getting close to losing by “only” 100pts.
Game 40 - GnuGo (B) vs Euroton (W) - result: B+155pt
I started testing the bot on Beta OGS. The bot renamed to Oroton for easier pronunciation. Feel free to do a game against it.
Note: It doesn’t know when to pass or resign, (it will never stop playing until it runs out of legal moves), but it passes when you do.
I am out of the house for the next few hours, so if it crashes, that’s unfortunately it. But so far it seems to be able to play multiple games at once. Enjoy ![]()
First battle of the bots:
That was Soraya, one of my stronger bots atm. (Yeah, I know, hard to believe!)
Why did she not play C4 instead of passing? Because she’s far too strong for that and knows to avoid empty triangles!!!
If you want to add that the easy way, gtp2ogs can delegate pass and resign to another bot like Gnugo. You can see my config for random-move-nixbot here for example.
ending_bot: {
command: ["${gnugo}/bin/gnugo", "--mode", "gtp"],
moves_to_allow_before_checking_ratio: 0.2,
allowed_resigns: 3,
send_chats: false
},
Ha, even my random player called Bob knows how to pass. ![]()
Guess how he decides!
What is allowed_resigns ?
(I thought a player couldn’t resign more than once per game.
)
- added: territory edge detector to defend our own territory.
A candidate move is now determined to be the edge of our potential territory if it satisfies all conditions:- The position is relatively neutral, not fully owned by either player.
- The position is at the edge of our territory. Positions in a radius around the move are checked, and the sum of our ownership values is taken. This makes it weigh bigger territories as more important.
- Has an adjacent opponent stone, or an open path to strong opponent territory (max distance 3).
Example:
Black has a strong influence / potential territory on the upper half (green, and yellow), and white has it on the lower half (red):
The territory edge detector finds the cells that allow a path into it, as well as the open paths on the side:
On 19x19 it identifies O9 as a hole in the lower right black territory.
There are still many situations where it doesn´t detect well so it needs more work.
Awesome, I was going to ask you to give it a try.
Well it just goes to show that there are so many things we take for granted when we play the game that just ‘make sense’.
Brilliant, thanks for sharing! I saw the option in gtp2ogs indeed.
IIRC it means your bot will wait until Gnugo has resigned 3 times before actually resigning.
I think that could be messing it up sometimes, like when after the first resign it can’t find a legal move.
After many updates, the bot is highly improved in its defensive abilities. Subjectively I think it plays more human-like and blunders less. It is also stronger against GnuGo. On 9x9, the “old” Oct 23 bot (currently online) needed four handicap stones to win; the new version wins with three. Given GnuGo’s estimated 6–7 kyu strength and the handicap scaling of 5-6 ranks per stone on 9x9, it lands around 21-25 kyu. This is just a very rough estimate, take it with a spoonful of salt.
However, much to my chagrin, the Oct 23 version (which is currently online), beats the new version convincingly on 19x19, as white and as black… The new defensive abilities make it play more safe, but also too slow.
Example: New (white) vs old (black). White spends too many stones making territory in the bottom left that by the time it want to move to the center it’s already occupied by black. It also seems like white makes way too many eyes, but those were in fact all captures. Captures that didn’t need to be done, but the bot doesn’t know that yet.
>I’m very annoyed that the new bot which can do everything the old bot can do and more, somehow loses.
But realistically, it is simply a matter of tuning/balancing the new functions with the existing ones.
To improve sealing off its own territory, I rewrote most of how influence / potential territory was calculated and saved. That is mostly about fixing edge cases, such as:
A big improvement is that the Eye-maker rule now understands connected-eyes (probably not the right term). It wouldn’t play A18 below, rather prefering to connect at B18. This is because the old Eye-maker didn’t judge an empty diagonal around an eye as a safe location. But if that empty diagonal is another eye, it should be seen as a safe connection, and now it is.
As you can hopefully tell from grid below, it neatly identifies where to play (white) to create two eyes (the cyan +100 locations). But, it is a overly cautious in the bottom left corner, and in fact it lucks out there. It doesn’t like B3 and C3 because it splits eye space in two, which is what you’d expect. No, the algorithm sees the eye at B1 as an existing eye, and because that eye already exists, the creation of a single eye at B3 or C3 triggers the “this makes two eyes” rule for the group at A1. I can predict that this needs more work in the future..
Some 1st line patterns are hardcoded. In the situation below, there wasn’t really a strong incentive for black to connect on B15 or B11, so that pattern is now codified and always recognised and given a high value. I don’t want to make a pattern matching library, but in this case it’s either pattern matching, or playing out multiple moves to see that it is bad for territory, so I chose the simpler approach.
>>Also added is a rule to prevent the opponent from connecting underneath on 1st or 2nd line.
In this case, the bot (black) will cut white stones to prevent them from sneaking into black territory. It triggers if there are opponent stones on both sides, and 1st or 2nd line is treated as an OR statement.
The downside of this pattern matching approach is that these moves are now also played if there is no territory to defend. This adds to the bot having become slower. But that’s preferable over a bot that has easy exploits.
Next up is to try to tune all the new stuff (I left out more than half) to work together.





























