Semi-random starting position

Yet another idea:

As we randomly place each stone, exclude points within certain radius from the candidates.

How do we decide the radius? For example, 19 x 19 board with stone-free margin equal to 2 has 15 x 15 = 225 candidate points. If we are to place 3 stones each, total of 6 stones, the space we can give to each stone is 225 / 6 = 37.5 . Area of taxicab circle with radius r is 1 + 4 ( r + (r - 1) + (r - 2) + … + 1) . With radius 3, the area is 25, with 4, it’s 41. Since 37.5 is between the two, we choose the smaller one, 3.

EDIT:
I’m starting to think a separate ladder is unnecessary. We could add an option “randomize handicap stones”. For even games, pie rule (white can choose to switch colors on their first move) might work. Komi auction/bidding is also an option.

EDIT2:
It occurred to me only accepting points outside twice the radius would use the space better than excluding points within a radius. Then I wonder if it’s possible that not all the stones can be placed.

Here’s the euclidean version of a similar problem:

1 Like

What are you trying to accomplish here? I assume you want to avoid the same memorized opening patterns. But do the moves have to be random?
How about a fixed, predetermined set of 10000 different starting positions. First, place the stones somewhat randomly, then use katago or leelazero to ensure the positions are balanced (50/50).
So basically Chess960 but for Go instead of chess.
I think having the openings be fixed in advance is useful because then we could give the bots some millions of playouts to be extra certain the positions are balanced. No human can memorize the meta for 10000 completely different openings anyway, at least not like they memorize the common 4-4 patterns.

1 Like

Tell that to the guy who’s learning 5-style.

But I like the idea; seems a good one.

Proposal: New game mode on OGS
1 white and 1 black stones are placed randomly but with equal chances
then, when all possible combinations of above played 1 time each by anyone,
2 white and 2 black stones are placed randomly but with equal chances
And so on …
So each game would be unique with minimal number of random stones, forever

1 Like

I think the idea is that if you have stones all over the board your forced into fighting, so you learn more about how to make good shape, assess the strength of groups, etc. But I think optimally you’d want more then 3 stones each at the start.

2 Likes

Procuring a pool of fair positions using AI would be great if viable. I think the problem of (exhaustively?) generating fair configurations for the other color given a configuration for one color is interesting. Also, a method for modifying a close candidate into a solution might speed up the process.

For casual play, the configuration might only need to be roughly fair, say within 60:40. In that case, it might be possible to generate a configuration on the fly and check (and modify?) it, if we could come up with a generation method that gives close enough candidates often enough.

Fischerrandom (chess960), randbats in pokemon showdown, and drafting in mtg are things I can remember that inspired the idea of randomization in Go. I somehow find it more light-hearted and fun to be given a few ingredients and try to figure out how to effectively use them than to be given complete freedom. It might have something to do with me not getting stronger in Go in two decades.

1 Like

position may be 100% random
all we need is special komi for each game

1 Like

I just saw this thread for the first time. It reminds me that on a games site called Maldoo (which doesn’t seem to exist any more) there was a version of Go on a small board with a random-ish starting position (I can’t remember how many stones but the same number of each colour and well more than 3). Once you saw the position both players had a very short time for each move (a couple of seconds?) and if you didn’t move in time then you lost your move / passed.

Children really seemed to love this! Of course it was for a completely different purpose than the one the original poster in this thread is looking for.

The reason I wanted this is for fun so I think it’s the same as children enjoying the website you mentioned. It’s sort of like training wheels on my creativity.

continues here:

This is a reference post summarizing ideas for improving the current model of generating varied starting positions, created by runarberg:

This new thread was created as the first one got pretty long (could someone add “(old)” at the end of the name and lock the previous thread?thanks @AdamR):
https://forums.online-go.com/t/semi-random-starting-position/27801

  • What constitutes an interesting/uninteresting starting position? We currently have stone-free margin (no stones near edges) and spreading apart. Is there any other factor?

  • To reduce clustering there are two types of ideas so far, each with a few specific models.

    1. Biased placing of stones
    • adaptive probability field: table of probabilities over the board that changes as stones are placed.
    • loose circle packing: guaranteeing certain length of minimum separation between stones. (edit: this feels too restrictive alone; perhaps combine?)
    1. Post-processing a randomly generated configuration
    • repulsive random walk: probability to move in each direction varies with crowdedness.
    • reluctant walker: accept or reject a candidate move according to relative crowdedness of current and candidate location (aka Metropolis-Hastings algorithm).
  • There are multiple possible definitions for crowdedness.

    • distance to nearest stone.
    • each stone weighted according to distance: diluted contributions.
    • number of stones in a given radius: neighbor counting.
  • Taxicab geometry might be suitable: kosumi and one-space jump are considered the same distance.

  • I’m thinking of relearning programming to attempt simulating and comparing those models. I’m looking at ipython as it seems simplest but let me know if anyone knows a language better suited for this (grid based simulation).

  • The only method for comparison so far is visual inspection of generated configurations.

  • Using AI to ensure certain degree of fairness, which can be circumvented somewhat by some form of I-cut-you-choose at beginning of game. Bonus problem: optimal placement of a hole, which works like the edge, to balance the configuration of stones.

  • There is an unrelated OGS group with a similar idea:
    https://online-go.com/group/1557

4 Likes

I don’t think the other thread was too long to manage. There are also drawbacks to splitting the discussion like this:

  1. It’s more cumbersome to quote and reference things that were already said in the previous thread.
  2. The older content in the other thread may be less likely to be read, and hence they become diminished.

Since @runarberg has already built a prototype (with open source code here: GitHub - runarberg/random-go-stone-placements: Generate a random starting position for your go games), you can play with that and try modifying the javascript.

This is a highly subjective question. With komi betting, perhaps any random position could be considered interesting, since it would be up to the players to judge possibly bizarre positions. With a fixed komi, maybe “interesting” should be something judged to have a roughly 50-50 win-rate (maybe within plus/minus 5%) by analysis with a strong AI engine.

There are many ways to try to quantify crowdedness, but I don’t think that alone can capture “interesting”.

2 Likes

Oh yeah, I forgot about this. I was also working on something, but I’ve never finished it… Perhaps today or tomorrow

2 Likes

It’s been a while since I played around with probabilistic programing, but I do recommend ipython (or jupyter). Python in particular has plenty of good libraries and tutorials for implementing different random walks or probability fields. I used Julia and R a lot in school (jupyter supports all three) but they might not be as approachable for beginners—if only for the fact that python has plenty of tutorials aimed for beginners.

As a bonus, if your notebook is clear enough (and the license is liberal enough) I might be able to read it and translate the algorithm to JavaScript and add as an option to my app.

Good luck, and foremost, have fun. Programing (as with all other crafts) is mostly about having fun making nice things, and sharing the things you’ve made.

1 Like

@yebellz

I’m not opposed to merging the two threads and I wasn’t aware of the consequences you mentioned. I was just thinking it might make it easier for people who haven’t been following the original thread to contribute.

Should we merge the threads?

  • merge
  • keep them separate

0 voters

Regarding javascript, my plan is to experiment in an easier language (or one that is natural to the problem) and worry about translating later.

Regarding the evaluation of generated positions, I was thinking of something like generating and mixing large number of configurations and vote like/dislike (or possibly finer options) without knowing which model they belong to, but it’s started to dawn on me that the difference might be too multifaceted to decide which model is better.

Even when working with AI, we might need a way to generate promissing candidates as I assume AI-based evaluation is the costly part of the process.

Also, if possible, I feel the swap rule (white can choose to swap colors on first move) is more elegant than komi-betting, although the configurations need to be in certain band of fairness as black’s best first move needs to be good enough.

1 Like

Came up with a potential name: 散碁(さんご sango). 散る(ちる) means to scatter and is also used to describe falling of leaves in autumn or of cherry blossom petals in spring. It didn’t seem to be taken judging by casual web searching.

It could also be written 3go if we restrict the number of stones per player to 3, as 3 is pronounced “san” in japanese. I feel 3 is a natural candidate for an even game but that might be confusing.

EDIT:
ちらし碁, analogous to ちらし寿司, has the advantage of having less homophones.

2 Likes

Or just “35” regarless of how manyn stones are used. :smiley:

1 Like

(loose circle packing + random walk) starts out with even coverage over the board and adds variation.

The question is: Is there a way to set this up so that configurations are fair with the swap rule?

EDIT:
loose circle packing with radius r;
r-step random walk, introducing stone-free margin here (stone-free margin < r);
with komi 3.5, is there a move for black that AI can play that makes the game approximately even?;
if not, swap colors, check the existance of a move again;
if not, start from beginning

1 Like

The following file (scatter-go.py), which couldn’t be attached, contains (circle packing + random walk) part of the problem.
EDIT: https://pastebin.com/ejMPBDt1

scatter-go.py
DEBUG_MODE = False  # whether to print intermediate states

N_STONE = 3  # number of stones per player, only supports 3
SIZE_BD = 19  # size of board, only supports 19
MARGIN = 2  # stone-free margin

# packing radius was estimated as follows:
# from https://en.wikipedia.org/wiki/Circle_packing_in_a_square
# which is about the euclidean (as opposed to taxicab) version of the problem
# when n = 6, side length = 5.328...
# rounding off 19 / 5.328 yields 3
R_PACKING = 3

stones = []

# print stone
def print_stone(stone):
  print("({0}, {1})".format(stone[0], chr(96 + stone[1])))

# print stones
def print_stones(stones):
  for a in range(len(stones)):
    print_stone(stones[a])

# print the board
def print_board(stones):
  # create 19 x 19 empty board
  board = [' . a b c d e f g h i j k l m n o p q r s']
  for a in range(1, SIZE_BD + 1):
    board.append('{:>2} + + + + + + + + + + + + + + + + + + +'.format(a))
  # place stones by replacing a character
  for a in range(len(stones)):
    x, y = stones[a][0], stones[a][1]
    if a < len(stones)/2:  # black stone
      # I couldn't do: board[x][1 + 2*y] = '@'
      # so I found the following work-around online
      board[x] = '{0}{1}{2}'.format(
        board[x][:1 + 2*y], '@', board[x][1 + 2*y + 1:])
    else:  # white stone
      board[x] = '{0}{1}{2}'.format(
        board[x][:1 + 2*y], '0', board[x][1 + 2*y + 1:])
  # print board
  for a in range(len(board)):
    print(board[a])

# randomly place stones on the board
from random import randint
while True:
  candidate = [randint(1, SIZE_BD), randint(1, SIZE_BD)]
  if candidate in stones:
    continue
  else:
    stones.append(candidate)
    if len(stones) == N_STONE * 2:
      break

# remove duplicates in list of lists
def remove_duplicates(l_orig):
  l_new = []
  for a in range(len(l_orig)):
    if l_orig[a] not in l_new:
      l_new.append(l_orig[a])
  return l_new

# find all points that are at given taxicab distance from given origin
def points_at_distance(origin, distance):
  x, y = origin[0], origin[1]
  points = []
  # from square expanded by going distance from origin in 4 cardinal directions
  for a in range(distance + 1):
    for b in range(distance + 1):
      if a + b == distance:  # taxicab distance
        points.append([x - a, y - b])
        points.append([x - a, y + b])
        points.append([x + a, y - b])
        points.append([x + a, y + b])
  return remove_duplicates(points)

# intersection of two lists of lists
def intersection_ll(l_1, l_2):
  l_new = []
  for a in range(len(l_1)):
    for b in range(len(l_2)):
      if l_1[a] == l_2[b]:
        l_new.append(l_1[a])
        break
  return l_new

# find all stones that are at given taxicab distance from given origin
def stones_at_distance(origin, distance, stones):
  points = points_at_distance(origin, distance)
  return intersection_ll(points, stones)

# find all points, just outside the board, that are at given taxicab distance from given origin,
#   which is used as proxy for edges
def edges_at_distance(origin, distance):
  points = points_at_distance(origin, distance)
  edges = []
  for a in range(len(points)):
    x, y = points[a][0], points[a][1]
    if x < 1 or x > SIZE_BD or y < 1 or y > SIZE_BD:
      edges.append([x, y])
  return edges  

from enum import Enum
Direction = Enum('Direction', 'w n s e')

# find directions that are least crowded
def find_steps(stone, points_repulsive):
  x, y = stone[0], stone[1]
  density = {k:0 for k in [Direction.w, Direction.n, Direction.s, Direction.e]}
  for a in range(len(points_repulsive)):
    s, t = points_repulsive[a][0], points_repulsive[a][1]
    if s < x:  # somewhere in west
      if t < y:  # NW
        density[Direction.n] += 0.5
        density[Direction.w] += 0.5
      elif t == y:  # exactly west
        density[Direction.w] += 1
      else:  # SW
        density[Direction.s] += 0.5
        density[Direction.w] += 0.5
    elif s == x:  # exactly north or south
      if t < y:  # north
        density[Direction.n] += 1
      else:  # south
        density[Direction.s] += 1
    else:  # somewhere in east
      if t < y:  # NE
        density[Direction.n] += 0.5
        density[Direction.e] += 0.5
      elif t == y:  # exactly east
        density[Direction.e] += 1
      else:  # SE
        density[Direction.s] += 0.5
        density[Direction.e] += 0.5
  density_min = min(density.values())
  return [k for k in density if density[k] == density_min]

# apply step randomly chosen from candidate directions,
#   making sure not to step out of bounds
from random import choice
def apply_step(stone, directions, margin):
  x, y = stone[0], stone[1]
  lower_bound, upper_bound = 1 + margin, SIZE_BD - margin
  # pick a direction and step in that direction  
  while True:
    if directions == []:
      break
    pick = choice(directions)
    if pick == Direction.w:
      if x == lower_bound:
        directions.remove(Direction.w)
      else:
        x -= 1
        break
    elif pick == Direction.n:
      if y == lower_bound:
        directions.remove(Direction.n)
      else:
        y -= 1
        break
    elif pick == Direction.s:
      if y == upper_bound:
        directions.remove(Direction.s)
      else:
        y += 1
        break
    elif pick == Direction.e:
      if x == upper_bound:
        directions.remove(Direction.e)
      else:
        x += 1
        break
  return [x, y]

# modify the configuration so that stones are at least 2*R_PACKING apart
#   and edges are at least R_PACKING away (circle packing)
counter = len(stones)
while True:
  for a in range(len(stones)):
    if counter == 0:
      break
    if DEBUG_MODE:
      print_stone(stones[a])
    for b in range(1, 2*R_PACKING + 1):
      # find stones at given taxicab distance, if any
      points_repulsive = stones_at_distance(stones[a], b, stones)
      if b <= R_PACKING:  # detect edges only within one packing radius
        points_repulsive.extend(edges_at_distance(stones[a], b))
      if points_repulsive == []:  # none found
        if b == 2*R_PACKING:  # all clear
          counter -= 1
          break
      else:  # found
        # reset counter as moving a stone might put it within another stone's range
        counter = len(stones)
        stones[a] = apply_step(stones[a], find_steps(stones[a], points_repulsive), 0)
        break
    if DEBUG_MODE:
      print_stone(stones[a])
      print_board(stones)
      print("")
  if counter == 0:
    break

print("after circle packing with radius {}".format(R_PACKING))
print_board(stones)

# random walk, taking into account stone-free margin
directions_all = [e for e in Direction]
for a in range(len(stones)):
  for b in range(R_PACKING):
    stones[a] = apply_step(stones[a], directions_all, MARGIN)

print("")
print("after {0}-step random walk with stone-free margin of {1}".format(R_PACKING, MARGIN))
print_board(stones)

The circle packing algorithm works as follows. Start with randomly scattered stones. Pick one stone, then scan for stones/edges at distance 1. If found, nudge the stone in one of the least crowded directions and move on to another stone. If not found, increase the distance unless it’s already twice the packing radius for stones and single time the packing radius for edges. If we hit that point consequtively for the number of stones on the board, it’s done.

It seems to be working although I can’t tell if it succeeded in making all packing solutions equiprobable, or if all packing solutions are reachable at all.

Let me know somewhere it can be made more efficient or easier to understand.

On the other hand, does anyone know if checking the existance of an “even” move in AI -part is reasonable?

EDIT:
The file got formatted weirdly.

EDIT2:
I reformatted the code according to runarberg’s advice.

I realized that, currently, the circle packing part does not distinguish a stone at SSE (1, 2) and ESE (2, 1) when scanning radius is 3, for example.

Also, there’s a possibility to add “staying still” option in random walk.