Advertisement
Guest User

scatter-go.py

a guest
Jun 22nd, 2020
108
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.93 KB | None | 0 0
  1. DEBUG_MODE = False  # whether to print intermediate states
  2.  
  3. N_STONE = 3  # number of stones per player, only supports 3
  4. SIZE_BD = 19  # size of board, only supports 19
  5. MARGIN = 2  # stone-free margin
  6.  
  7. # packing radius was estimated as follows:
  8. # from https://en.wikipedia.org/wiki/Circle_packing_in_a_square
  9. # which is about the euclidean (as opposed to taxicab) version of the problem
  10. # when n = 6, side length = 5.328...
  11. # rounding off 19 / 5.328 yields 3
  12. R_PACKING = 3
  13.  
  14. stones = []
  15.  
  16. # print stone
  17. def print_stone(stone):
  18.   print("({0}, {1})".format(stone[0], chr(96 + stone[1])))
  19.  
  20. # print stones
  21. def print_stones(stones):
  22.   for a in range(len(stones)):
  23.     print_stone(stones[a])
  24.  
  25. # print the board
  26. def print_board(stones):
  27.   # create 19 x 19 empty board
  28.   board = [' . a b c d e f g h i j k l m n o p q r s']
  29.   for a in range(1, SIZE_BD + 1):
  30.     board.append('{:>2} + + + + + + + + + + + + + + + + + + +'.format(a))
  31.   # place stones by replacing a character
  32.   for a in range(len(stones)):
  33.     x, y = stones[a][0], stones[a][1]
  34.     if a < len(stones)/2:  # black stone
  35.       # I couldn't do: board[x][1 + 2*y] = '@'
  36.       # so I found the following work-around online
  37.       board[x] = '{0}{1}{2}'.format(
  38.         board[x][:1 + 2*y], '@', board[x][1 + 2*y + 1:])
  39.     else:  # white stone
  40.       board[x] = '{0}{1}{2}'.format(
  41.         board[x][:1 + 2*y], '0', board[x][1 + 2*y + 1:])
  42.   # print board
  43.   for a in range(len(board)):
  44.     print(board[a])
  45.  
  46. # randomly place stones on the board
  47. from random import randint
  48. while True:
  49.   candidate = [randint(1, SIZE_BD), randint(1, SIZE_BD)]
  50.   if candidate in stones:
  51.     continue
  52.   else:
  53.     stones.append(candidate)
  54.     if len(stones) == N_STONE * 2:
  55.       break
  56.  
  57. # remove duplicates in list of lists
  58. def remove_duplicates(l_orig):
  59.   l_new = []
  60.   for a in range(len(l_orig)):
  61.     if l_orig[a] not in l_new:
  62.       l_new.append(l_orig[a])
  63.   return l_new
  64.  
  65. # find all points that are at given taxicab distance from given origin
  66. def points_at_distance(origin, distance):
  67.   x, y = origin[0], origin[1]
  68.   points = []
  69.   # from square expanded by going distance from origin in 4 cardinal directions
  70.   for a in range(distance + 1):
  71.     for b in range(distance + 1):
  72.       if a + b == distance:  # taxicab distance
  73.         points.append([x - a, y - b])
  74.         points.append([x - a, y + b])
  75.         points.append([x + a, y - b])
  76.         points.append([x + a, y + b])
  77.   return remove_duplicates(points)
  78.  
  79. # intersection of two lists of lists
  80. def intersection_ll(l_1, l_2):
  81.   l_new = []
  82.   for a in range(len(l_1)):
  83.     for b in range(len(l_2)):
  84.       if l_1[a] == l_2[b]:
  85.         l_new.append(l_1[a])
  86.         break
  87.   return l_new
  88.  
  89. # find all stones that are at given taxicab distance from given origin
  90. def stones_at_distance(origin, distance, stones):
  91.   points = points_at_distance(origin, distance)
  92.   return intersection_ll(points, stones)
  93.  
  94. # find all points, just outside the board, that are at given taxicab distance from given origin,
  95. #   which is used as proxy for edges
  96. def edges_at_distance(origin, distance):
  97.   points = points_at_distance(origin, distance)
  98.   edges = []
  99.   for a in range(len(points)):
  100.     x, y = points[a][0], points[a][1]
  101.     if x < 1 or x > SIZE_BD or y < 1 or y > SIZE_BD:
  102.       edges.append([x, y])
  103.   return edges  
  104.  
  105. from enum import Enum
  106. Direction = Enum('Direction', 'w n s e')
  107.  
  108. # find directions that are least crowded
  109. def find_steps(stone, points_repulsive):
  110.   x, y = stone[0], stone[1]
  111.   density = {k:0 for k in [Direction.w, Direction.n, Direction.s, Direction.e]}
  112.   for a in range(len(points_repulsive)):
  113.     s, t = points_repulsive[a][0], points_repulsive[a][1]
  114.     if s < x:  # somewhere in west
  115.       if t < y:  # NW
  116.         density[Direction.n] += 0.5
  117.         density[Direction.w] += 0.5
  118.       elif t == y:  # exactly west
  119.         density[Direction.w] += 1
  120.       else:  # SW
  121.         density[Direction.s] += 0.5
  122.         density[Direction.w] += 0.5
  123.     elif s == x:  # exactly north or south
  124.       if t < y:  # north
  125.         density[Direction.n] += 1
  126.       else:  # south
  127.         density[Direction.s] += 1
  128.     else:  # somewhere in east
  129.       if t < y:  # NE
  130.         density[Direction.n] += 0.5
  131.         density[Direction.e] += 0.5
  132.       elif t == y:  # exactly east
  133.         density[Direction.e] += 1
  134.       else:  # SE
  135.         density[Direction.s] += 0.5
  136.         density[Direction.e] += 0.5
  137.   density_min = min(density.values())
  138.   return [k for k in density if density[k] == density_min]
  139.  
  140. # apply step randomly chosen from candidate directions,
  141. #   making sure not to step out of bounds
  142. from random import choice
  143. def apply_step(stone, directions, margin):
  144.   x, y = stone[0], stone[1]
  145.   lower_bound, upper_bound = 1 + margin, SIZE_BD - margin
  146.   # pick a direction and step in that direction  
  147.   while True:
  148.     if directions == []:
  149.       break
  150.     pick = choice(directions)
  151.     if pick == Direction.w:
  152.       if x == lower_bound:
  153.         directions.remove(Direction.w)
  154.       else:
  155.         x -= 1
  156.         break
  157.     elif pick == Direction.n:
  158.       if y == lower_bound:
  159.         directions.remove(Direction.n)
  160.       else:
  161.         y -= 1
  162.         break
  163.     elif pick == Direction.s:
  164.       if y == upper_bound:
  165.         directions.remove(Direction.s)
  166.       else:
  167.         y += 1
  168.         break
  169.     elif pick == Direction.e:
  170.       if x == upper_bound:
  171.         directions.remove(Direction.e)
  172.       else:
  173.         x += 1
  174.         break
  175.   return [x, y]
  176.  
  177. # modify the configuration so that stones are at least 2*R_PACKING apart
  178. #   and edges are at least R_PACKING away (circle packing)
  179. counter = len(stones)
  180. while True:
  181.   for a in range(len(stones)):
  182.     if counter == 0:
  183.       break
  184.     if DEBUG_MODE:
  185.       print_stone(stones[a])
  186.     for b in range(1, 2*R_PACKING + 1):
  187.       # find stones at given taxicab distance, if any
  188.       points_repulsive = stones_at_distance(stones[a], b, stones)
  189.       if b <= R_PACKING:  # detect edges only within one packing radius
  190.         points_repulsive.extend(edges_at_distance(stones[a], b))
  191.       if points_repulsive == []:  # none found
  192.         if b == 2*R_PACKING:  # all clear
  193.           counter -= 1
  194.           break
  195.       else:  # found
  196.         # reset counter as moving a stone might put it within another stone's range
  197.         counter = len(stones)
  198.         stones[a] = apply_step(stones[a], find_steps(stones[a], points_repulsive), 0)
  199.         break
  200.     if DEBUG_MODE:
  201.       print_stone(stones[a])
  202.       print_board(stones)
  203.       print("")
  204.   if counter == 0:
  205.     break
  206.  
  207. print("after circle packing with radius {}".format(R_PACKING))
  208. print_board(stones)
  209.  
  210. # random walk, taking into account stone-free margin
  211. directions_all = [e for e in Direction]
  212. for a in range(len(stones)):
  213.   for b in range(R_PACKING):
  214.     stones[a] = apply_step(stones[a], directions_all, MARGIN)
  215.  
  216. print("")
  217. print("after {0}-step random walk with stone-free margin of {1}".format(R_PACKING, MARGIN))
  218. print_board(stones)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement