Browse Source

V2 - Blob Simulation

Loïc Vanden Bemden 6 years ago
parent
commit
d775bdfaef
49 changed files with 939 additions and 2409 deletions
  1. 74 0
      blob-simulation/compare.py
  2. 22 1
      blob-simulation/detection/config.json
  3. 162 164
      blob-simulation/detection/detect.py
  4. 108 21
      blob-simulation/detection/setup.py
  5. 1 1
      blob-simulation/detection/utils.py
  6. 0 63
      blob-simulation/python-pathfinding-master/.gitignore
  7. 0 21
      blob-simulation/python-pathfinding-master/.travis.yml
  8. 0 22
      blob-simulation/python-pathfinding-master/LICENSE
  9. 0 16
      blob-simulation/python-pathfinding-master/Pipfile
  10. 0 148
      blob-simulation/python-pathfinding-master/Pipfile.lock
  11. 0 136
      blob-simulation/python-pathfinding-master/README.md
  12. 0 204
      blob-simulation/python-pathfinding-master/pathfinding.ipynb
  13. 0 1
      blob-simulation/python-pathfinding-master/pathfinding/__init__.py
  14. 0 1
      blob-simulation/python-pathfinding-master/pathfinding/core/__init__.py
  15. 0 8
      blob-simulation/python-pathfinding-master/pathfinding/core/diagonal_movement.py
  16. 0 188
      blob-simulation/python-pathfinding-master/pathfinding/core/grid.py
  17. 0 36
      blob-simulation/python-pathfinding-master/pathfinding/core/heuristic.py
  18. 0 52
      blob-simulation/python-pathfinding-master/pathfinding/core/node.py
  19. 0 132
      blob-simulation/python-pathfinding-master/pathfinding/core/util.py
  20. 0 2
      blob-simulation/python-pathfinding-master/pathfinding/finder/__init__.py
  21. 0 89
      blob-simulation/python-pathfinding-master/pathfinding/finder/a_star.py
  22. 0 37
      blob-simulation/python-pathfinding-master/pathfinding/finder/best_first.py
  23. 0 77
      blob-simulation/python-pathfinding-master/pathfinding/finder/bi_a_star.py
  24. 0 35
      blob-simulation/python-pathfinding-master/pathfinding/finder/breadth_first.py
  25. 0 16
      blob-simulation/python-pathfinding-master/pathfinding/finder/dijkstra.py
  26. 0 166
      blob-simulation/python-pathfinding-master/pathfinding/finder/finder.py
  27. 0 128
      blob-simulation/python-pathfinding-master/pathfinding/finder/ida_star.py
  28. 0 2
      blob-simulation/python-pathfinding-master/setup.cfg
  29. 0 22
      blob-simulation/python-pathfinding-master/setup.py
  30. 0 6
      blob-simulation/python-pathfinding-master/test/csv_file.csv
  31. 0 40
      blob-simulation/python-pathfinding-master/test/csv_pandas_test.py
  32. 0 85
      blob-simulation/python-pathfinding-master/test/grid_test.py
  33. 0 111
      blob-simulation/python-pathfinding-master/test/path_test.py
  34. 0 84
      blob-simulation/python-pathfinding-master/test/path_test_scenarios.json
  35. 0 48
      blob-simulation/python-pathfinding-master/test/util_test.py
  36. 0 15
      blob-simulation/python-pathfinding-master/tox.ini
  37. 83 40
      blob-simulation/simulation/board.py
  38. 13 0
      blob-simulation/simulation/default/blob.json
  39. 5 0
      blob-simulation/simulation/default/player.json
  40. 44 28
      blob-simulation/simulation/interface.py
  41. 0 16
      blob-simulation/simulation/logic/actions.py
  42. 60 0
      blob-simulation/simulation/logic/advanced_scouter.py
  43. 3 4
      blob-simulation/simulation/logic/dumb_scouter.py
  44. 33 28
      blob-simulation/simulation/logic/fsm_ant.py
  45. 76 28
      blob-simulation/simulation/logic/gatherer.py
  46. 86 29
      blob-simulation/simulation/logic/main.py
  47. 106 19
      blob-simulation/simulation/logic/sensing_scouter.py
  48. 21 16
      blob-simulation/simulation/play.py
  49. 42 23
      blob-simulation/simulation/player.py

+ 74 - 0
blob-simulation/compare.py

@@ -0,0 +1,74 @@
+import argparse
+from simulation import board
+import pygame
+from pygame.locals import QUIT
+
+
+def main():
+    ap = argparse.ArgumentParser()
+    ap.add_argument("--first", required=True, help="first board file")
+    ap.add_argument("--second", required=True, help="second board file")
+    ap.add_argument("-s", "--scale", type=float, default=10, help="scale display (default: x10")
+    ap.add_argument("-o", "--output", type=str, help="give a name to save the jpeg file")
+    args = ap.parse_args()
+
+    board_1 = board.Board(0,0)
+    board_1.load(args.first)
+
+    board_2 = board.Board(0,0)
+    board_2.load(args.second)
+
+    board_comp = board_1.compare(board_2)
+
+    if board_comp is None:
+        return
+
+    pygame.init()
+
+    width = int(board_1.width * args.scale)
+    height = int(board_1.height * args.scale)
+    window = pygame.display.set_mode((width, height))
+
+    game_surface = pygame.Surface((board_1.width, board_1.height))
+    pixel_array = pygame.PixelArray(game_surface)
+    for x in range(board_1.width):
+        for y in range(board_1.height):
+            pixel_array[x,y] = (0, 0, 0)
+
+            val = max(-255, min(board_comp.get_blob(x, y), 255))
+            if val < 0:
+                val = - val # int(-val/2) + 125
+                pixel_array[x, y] = (val/4, val/4, val)
+            elif val > 0:
+                val = val # int(val/2) + 125
+                pixel_array[x, y] = (val, val/4, val/4)
+            else:
+                if not board_comp.is_touched(x, y):
+                    if board_1.is_touched(x, y):
+                        pixel_array[x, y] = (75, 75, 125)
+                    else:
+                        pixel_array[x, y] = (125, 75, 75)
+
+            if board_comp.has_food(x, y):
+                 pixel_array[x, y] = (0, board_comp.foods[x, y], 0)
+
+    del pixel_array
+
+    game_window = pygame.transform.scale(game_surface, (width, height))
+
+    window.blit(game_window, (0, 0))
+    pygame.display.flip()
+
+    ended = False
+    while not ended:
+        pygame.time.wait(10)
+        for event in pygame.event.get():
+            if event.type == QUIT:
+                ended = True
+
+    if args.output is not None:
+        pygame.image.save(window, args.output)
+
+
+if __name__ == "__main__":
+    main()

+ 22 - 1
blob-simulation/detection/config.json

@@ -1 +1,22 @@
-{"Aspect Ratio": 0.4, "Discrete Height": 40, "Discrete Width": 100, "Limits": [[136, 268], [5484, 208], [5452, 3296], [136, 3308]], "Low Food Color": [92, 140, 168], "High Food Color": [202, 226, 238]}
+{
+    "Aspect Ratio": 0.4,
+    "Discrete Height": 240,
+    "Discrete Width": 600,
+    "High Food Color": [
+        214,
+        230,
+        237
+    ],
+    "Limits": [
+		[136, 268],
+		[5484, 208],
+		[5452, 3296], 
+		[136, 3308]
+	],
+    "Low Food Color": [
+        150,
+        185,
+        198
+    ],
+	"Min Food Size": 60
+}

+ 162 - 164
blob-simulation/detection/detect.py

@@ -5,192 +5,190 @@ from os.path import splitext, basename, join
 
 
 
 
 def main():
 def main():
-	ap = argparse.ArgumentParser()
-	ap.add_argument("-i", "--input", required=True, help="input image")
-	ap.add_argument("-s", "--scale", type=float, default=0.1, help="scale images by this factor (default: x0.1)")
-	ap.add_argument("-c", "--config", type=str, default="config.json",
-					help="name file to load config (default: config.json)")
-	ap.add_argument("-o", "--output", type=str, help="give a directory name to save the game files in it")
-	ap.add_argument("--hide", action='store_true', default=False, help="hide images")
+    ap = argparse.ArgumentParser()
+    ap.add_argument("-i", "--input", required=True, help="input image")
+    ap.add_argument("-s", "--scale", type=float, default=0.10, help="scale images by this factor (default: x0.1)")
+    ap.add_argument("-c", "--config", type=str, default="config.json",
+                    help="name file to load config (default: config.json)")
+    ap.add_argument("-o", "--output", type=str, help="give a directory name to save the game files in it")
+    ap.add_argument("--hide", action='store_true', default=False, help="hide images")
 
 
-	args = vars(ap.parse_args())
-	with open(args['config'], 'r') as file:
-		config = json.load(file)
+    args = ap.parse_args()
+    with open(args.config, 'r') as file:
+        config = json.load(file)
 
 
-	orig, blob_mask, blob, food_mask, food_img, food_list = detect(args['input'], config)
-	discrete_img, discrete_blob, discrete_food_list, known_food = discretize(blob, food_list, config['Discrete Width'],
-																			 config['Discrete Height'])
+    orig, blob_mask, blob, food_mask, food_img = detect(args.input, config)
+    discrete_img, discrete_blob, discrete_food_list = discretize(blob, food_mask, config['Discrete Width'],
+                                                                             config['Discrete Height'])
 
 
-	if args['output'] is not None:
-		filename = splitext(basename(args['input']))[0]
-		dir = args['output']
-		save(join(dir, filename), discrete_img, discrete_blob, discrete_food_list, known_food)
+    if args.output is not None:
+        filename = splitext(basename(args.input))[0]
+        dir = args.output
+        save(join(dir, filename), discrete_img, discrete_blob, discrete_food_list)
 
 
-	if not args['hide']:
-		print_results(orig, blob_mask, blob, food_mask, food_img, discrete_img, args['scale'])
+    if not args.hide:
+        print_results(orig, blob_mask, blob, food_mask, food_img, discrete_img, args.scale)
 
 
 
 
 def detect(input_file, config):
 def detect(input_file, config):
 
 
-	img = cv2.imread(input_file)
-	height, width, _ = img.shape
+    img = cv2.imread(input_file)
+    height, width, _ = img.shape
 
 
-	aspect_ratio = config["Aspect Ratio"]
+    aspect_ratio = config["Aspect Ratio"]
 
 
-	height = int(width*aspect_ratio)
+    height = int(width*aspect_ratio)
 
 
-	""" Resize image to limits in config file """
-	limits = np.array(config['Limits'])
-	transform_mat = cv2.getPerspectiveTransform(np.float32(limits), np.float32(
-		[[0, 0], [width, 0], [width, height], [0, height]]))
-	img = cv2.warpPerspective(img, transform_mat, (width, height))
+    """ Resize image to limits in config file """
+    limits = np.array(config['Limits'])
+    transform_mat = cv2.getPerspectiveTransform(np.float32(limits), np.float32(
+        [[0, 0], [width, 0], [width, height], [0, height]]))
+    img = cv2.warpPerspective(img, transform_mat, (width, height))
 
 
-	""" Prepare upper and lower mask board """
-	upper_mask = np.zeros(img.shape[0:2], np.uint8)
-	lower_mask = np.zeros(img.shape[0:2], np.uint8)
+    """ Prepare upper and lower mask board """
+    upper_mask = np.zeros(img.shape[0:2], np.uint8)
+    lower_mask = np.zeros(img.shape[0:2], np.uint8)
 
 
-	upper_mask = cv2.rectangle(upper_mask, (0, 0), (width, int(height/2)), 255, thickness=cv2.FILLED)
-	lower_mask = cv2.rectangle(lower_mask, (0, int(height / 2)), (width, height), 255, thickness=cv2.FILLED)
+    upper_mask = cv2.rectangle(upper_mask, (0, 0), (width, int(height/2)), 255, thickness=cv2.FILLED)
+    lower_mask = cv2.rectangle(lower_mask, (0, int(height / 2)), (width, height), 255, thickness=cv2.FILLED)
 
 
-	""" Find blob """
-	sat = saturation(img)
-	# sum = mean_image([satA, satB])
-	blob_mask = find_blob(sat)
-	blob = cv2.bitwise_and(img, img, mask=blob_mask)
+    """ Find blob """
+    sat = saturation(img)
+    # sum = mean_image([satA, satB])
+    blob_mask = find_blob(sat)
+    blob = cv2.bitwise_and(img, img, mask=blob_mask)
 
 
-	""" Print blob information """
-	print("Blob covering:")
-	print("\t{:.2f}% of the board.".format(mean_percent_value(blob_mask)))
-	print("\t{:.2f}% of the upper board.".format(
-		mean_percent_value(cv2.bitwise_and(blob_mask, blob_mask, mask=upper_mask), img_ratio=0.5)))
-	print("\t{:.2f}% of the lower board.".format(
-		mean_percent_value(cv2.bitwise_and(blob_mask, blob_mask, mask=lower_mask), img_ratio=0.5)))
+    """ Print blob information """
+    print("Blob covering:")
+    print("\t{:.2f}% of the board.".format(mean_percent_value(blob_mask)))
+    print("\t{:.2f}% of the upper board.".format(
+        mean_percent_value(cv2.bitwise_and(blob_mask, blob_mask, mask=upper_mask), img_ratio=0.5)))
+    print("\t{:.2f}% of the lower board.".format(
+        mean_percent_value(cv2.bitwise_and(blob_mask, blob_mask, mask=lower_mask), img_ratio=0.5)))
 
 
-	""" Find food """
-	food_list, food_mask, food_img = find_food(img, 100, config['Low Food Color'], config['High Food Color'])
+    """ Find food """
+    food_list, food_mask, food_img = find_food(img, config['Min Food Size'], config['Low Food Color'], config['High Food Color'])
 
 
-	""" Print food information """
-	print("Total food discovered: " + str(len(food_list)))
-	for i, food in enumerate(food_list):
-		print("\tFood N°" + str(i) + ": " + str(food))
+    """ Print food information """
+    print("Total food discovered: " + str(len(food_list)))
+    # for i, food in enumerate(food_list):
+        # print("\tFood N°" + str(i) + ": " + str(food))
 
 
-	return img, blob_mask, blob, food_mask, food_img, food_list
+    return img, blob_mask, blob, food_mask, food_img
 
 
 
 
 def print_results(orig, blob_mask, blob, food_mask, food, discrete, scale=1.0):
 def print_results(orig, blob_mask, blob, food_mask, food, discrete, scale=1.0):
-		padding = 35
-		nbr_width = 2
-		nbr_height = 3
-		font = cv2.FONT_HERSHEY_SIMPLEX
-		fontsize = 0.45
-		thickness = 1
-
-		scaled_height = int(orig.shape[0]*scale)
-		scaled_width = int(orig.shape[1]*scale)
-
-		pad = np.zeros((scaled_height, padding, orig.shape[2]), dtype=np.uint8)
-		line_pad = np.zeros((padding, (scaled_width + padding) * nbr_width + padding, orig.shape[2]), dtype=np.uint8)
-		print_img = cv2.resize(orig, (scaled_width, scaled_height))
-
-		middle = ((0, int(scaled_height/2)), (scaled_width, int(scaled_height/2)))
-		cv2.line(print_img, middle[0], middle[1], (0, 255, 0), thickness=1)
-		cv2.putText(print_img, 'Mid Line', (middle[0][0] + 5, middle[0][1] - 5),
-					font, fontsize, (0, 255, 0), thickness, cv2.LINE_AA)
-
-		print_blob_mask = cv2.resize(cv2.cvtColor(blob_mask, cv2.COLOR_GRAY2BGR), (scaled_width, scaled_height))
-		print_blob = cv2.resize(blob, (scaled_width, scaled_height))
-		print_food_mask = cv2.resize(cv2.cvtColor(food_mask, cv2.COLOR_GRAY2BGR), (scaled_width, scaled_height))
-		print_food = cv2.resize(food, (scaled_width, scaled_height))
-		print_discrete = cv2.resize(discrete, (scaled_width, scaled_height))
-
-		concat_line1 = np.concatenate((pad, print_img, pad, print_discrete, pad), axis=1)
-		concat_line2 = np.concatenate((pad, print_blob_mask, pad, print_blob, pad), axis=1)
-		concat_line3 = np.concatenate((pad, print_food_mask, pad, print_food, pad), axis=1)
-
-		aggregate = np.concatenate((line_pad, concat_line1, line_pad, concat_line2, line_pad, concat_line3, line_pad))
-
-		cv2.putText(aggregate, 'Original:',
-					(0 * (scaled_width + padding) + padding + 5, 0 * (scaled_height + padding) + padding - 5),
-					font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
-		cv2.putText(aggregate, 'Discrete:',
-					(1 * (scaled_width + padding) + padding + 5, 0 * (scaled_height + padding) + padding - 5),
-					font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
-		cv2.putText(aggregate, 'Blob Mask:',
-					(0 * (scaled_width + padding) + padding + 5, 1 * (scaled_height + padding) + padding - 5),
-					font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
-		cv2.putText(aggregate, 'Blob:',
-					(1 * (scaled_width + padding) + padding + 5, 1 * (scaled_height + padding) + padding - 5),
-					font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
-		cv2.putText(aggregate, 'Food Mask:',
-					(0 * (scaled_width + padding) + padding + 5, 2 * (scaled_height + padding) + padding - 5),
-					font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
-		cv2.putText(aggregate, 'Food Regions:',
-					(1 * (scaled_width + padding) + padding + 5, 2 * (scaled_height + padding) + padding - 5),
-					font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
-
-		cv2.imshow("Results", aggregate)
-		print("\nPress any key...")
-		cv2.waitKey(0)
-
-
-def discretize(blob_img, food_list, width, height):
-	img_height, img_width, _ = blob_img.shape
-
-	discrete_blob = cv2.resize(blob_img, (width, height), interpolation=cv2.INTER_NEAREST)
-
-	discrete_food_list = []
-	for food in food_list:
-		x = int((food[0] + food[2]/2) / img_width * width)
-		y = int((food[1] + food[3]/2) / img_height * height)
-		discrete_food_list.append((x,y))
-
-	height, width, _ = discrete_blob.shape
-	discrete_blob = cv2.cvtColor(discrete_blob, cv2.COLOR_BGR2GRAY)
-
-	known_food = []
-	for food in discrete_food_list:
-		if discrete_blob[food[1], food[0]] != 0:
-			known_food.append([food[0], food[1]])
-
-	discrete_blob_bgr = cv2.cvtColor(discrete_blob, cv2.COLOR_GRAY2BGR)
-	discrete_img = cv2.resize(discrete_blob_bgr, (0, 0), fx=10, fy=10, interpolation=cv2.INTER_NEAREST)
-	for (x, y) in discrete_food_list:
-		cv2.rectangle(discrete_img, (x * 10, y * 10), ((x + 1) * 10, (y + 1) * 10), (0, 255, 0), thickness=cv2.FILLED)
-
-	for (x, y) in known_food:
-		cv2.drawMarker(discrete_img, (x * 10 + 5, y * 10 + 5), (255, 255, 255), thickness=2, markerSize=9,
-						markerType=cv2.MARKER_TILTED_CROSS)
-
-	return discrete_img, discrete_blob, discrete_food_list, known_food
-
-
-def save(filename, discrete_img, discrete_blob, discrete_food_list, known_food):
-
-	height, width = discrete_blob.shape
-
-	board_str = str(width) + ' ' + str(height) + '\n'
-	for x in range(height):
-		for y in range(width):
-			board_str += format(discrete_blob[x, y] != 0, 'd') + "," + format((y, x) in discrete_food_list, 'd') \
-						+ "," + str(discrete_blob[x, y]) + " "
-		board_str = board_str[:-1]
-		board_str += "\n"
-	board_str = board_str[:-1]
-
-	with open(filename + ".board", 'w') as board_file:
-		board_file.write(board_str)
-
-	with open(filename + ".blob", 'w') as blob_file:
-		knowledge = dict()
-		knowledge['food'] = known_food
-		knowledge['max_scouters'] = len(known_food)
-		json.dump(knowledge, blob_file)
+    padding = 35
+    nbr_width = 2
+    nbr_height = 3
+    font = cv2.FONT_HERSHEY_SIMPLEX
+    fontsize = 0.45
+    thickness = 1
+
+    scaled_height = int(orig.shape[0]*scale)
+    scaled_width = int(orig.shape[1]*scale)
+
+    pad = np.zeros((scaled_height, padding, orig.shape[2]), dtype=np.uint8)
+    line_pad = np.zeros((padding, (scaled_width + padding) * nbr_width + padding, orig.shape[2]), dtype=np.uint8)
+    print_img = cv2.resize(orig, (scaled_width, scaled_height))
+
+    middle = ((0, int(scaled_height/2)), (scaled_width, int(scaled_height/2)))
+    cv2.line(print_img, middle[0], middle[1], (0, 255, 0), thickness=1)
+    cv2.putText(print_img, 'Mid Line', (middle[0][0] + 5, middle[0][1] - 5),
+                font, fontsize, (0, 255, 0), thickness, cv2.LINE_AA)
+
+    print_blob_mask = cv2.resize(cv2.cvtColor(blob_mask, cv2.COLOR_GRAY2BGR), (scaled_width, scaled_height))
+    print_blob = cv2.resize(blob, (scaled_width, scaled_height))
+    print_food_mask = cv2.resize(cv2.cvtColor(food_mask, cv2.COLOR_GRAY2BGR), (scaled_width, scaled_height))
+    print_food = cv2.resize(food, (scaled_width, scaled_height))
+    print_discrete = cv2.resize(discrete, (scaled_width, scaled_height))
+
+    concat_line1 = np.concatenate((pad, print_img, pad, print_discrete, pad), axis=1)
+    concat_line2 = np.concatenate((pad, print_blob_mask, pad, print_blob, pad), axis=1)
+    concat_line3 = np.concatenate((pad, print_food_mask, pad, print_food, pad), axis=1)
+
+    aggregate = np.concatenate((line_pad, concat_line1, line_pad, concat_line2, line_pad, concat_line3, line_pad))
+
+    cv2.putText(aggregate, 'Original:',
+                (0 * (scaled_width + padding) + padding + 5, 0 * (scaled_height + padding) + padding - 5),
+                font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
+    cv2.putText(aggregate, 'Discrete:',
+                (1 * (scaled_width + padding) + padding + 5, 0 * (scaled_height + padding) + padding - 5),
+                font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
+    cv2.putText(aggregate, 'Blob Mask:',
+                (0 * (scaled_width + padding) + padding + 5, 1 * (scaled_height + padding) + padding - 5),
+                font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
+    cv2.putText(aggregate, 'Blob:',
+                (1 * (scaled_width + padding) + padding + 5, 1 * (scaled_height + padding) + padding - 5),
+                font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
+    cv2.putText(aggregate, 'Food Mask:',
+                (0 * (scaled_width + padding) + padding + 5, 2 * (scaled_height + padding) + padding - 5),
+                font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
+    cv2.putText(aggregate, 'Food Regions:',
+                (1 * (scaled_width + padding) + padding + 5, 2 * (scaled_height + padding) + padding - 5),
+                font, fontsize, (255, 255, 255), thickness, cv2.LINE_AA)
+
+    cv2.imshow("Results", aggregate)
+    print("\nPress any key...")
+    cv2.waitKey(0)
+
+
+def discretize(blob_img, food_mask, width, height):
+    img_height, img_width, _ = blob_img.shape
+
+    discrete_blob = cv2.resize(blob_img, (width, height), interpolation=cv2.INTER_NEAREST)
+
+    discrete_food = cv2.resize(food_mask, (width, height), interpolation=cv2.INTER_NEAREST)
+
+    discrete_food_list = []
+    for x in range(height):
+        for y in range(width):
+            if discrete_food[x, y] != 0:
+                discrete_food_list.append((y, x))
+
+    height, width, _ = discrete_blob.shape
+    discrete_blob = cv2.cvtColor(discrete_blob, cv2.COLOR_BGR2GRAY)
+
+    # If discrete blob has to be connected, used this :
+    # contours, hierarchy = cv2.findContours(discrete_blob, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
+    # c = max(contours, key=cv2.contourArea)
+    # mask = np.zeros(discrete_blob.shape, np.uint8)
+    # cv2.drawContours(mask, [c], -1, 255, cv2.FILLED)
+    # discrete_blob = cv2.bitwise_and(discrete_blob, discrete_blob, mask=mask)
+
+    discrete_blob_bgr = cv2.cvtColor(discrete_blob, cv2.COLOR_GRAY2BGR)
+    discrete_img = cv2.resize(discrete_blob_bgr, (0, 0), fx=10, fy=10, interpolation=cv2.INTER_NEAREST)
+    for (x, y) in discrete_food_list:
+        cv2.rectangle(discrete_img, (x * 10, y * 10), ((x + 1) * 10, (y + 1) * 10), (0, 255, 0), thickness=cv2.FILLED)
+        if discrete_blob[y, x] != 0:
+            cv2.drawMarker(discrete_img, (x * 10 + 5, y * 10 + 5), (255, 255, 255), thickness=2, markerSize=9,
+                           markerType=cv2.MARKER_TILTED_CROSS)
+
+    return discrete_img, discrete_blob, discrete_food_list
+
+
+def save(filename, discrete_img, discrete_blob, discrete_food_list):
+
+    height, width = discrete_blob.shape
+
+    board_str = str(width) + ' ' + str(height) + '\n'
+
+    discrete_food = np.zeros(discrete_blob.shape, dtype=bool)
+    for (x, y) in discrete_food_list:
+        discrete_food[y, x] = True
+
+    for x in range(height):
+        for y in range(width):
+            board_str += "{:d},{:d},{} ".format(discrete_blob[x, y] != 0, discrete_food[x,y], discrete_blob[x, y])
+        board_str = board_str[:-1]
+        board_str += "\n"
+    board_str = board_str[:-1]
 
 
-	with open(filename + ".player", 'w') as player_file:
-		player_file.write("0")
-
-	cv2.imwrite(filename + ".jpg", discrete_img)
+    with open(filename + ".board", 'w') as board_file:
+        board_file.write(board_str)
+
+    cv2.imwrite(filename + ".jpg", discrete_img)
 
 
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":
-	main()
+    main()

+ 108 - 21
blob-simulation/detection/setup.py

@@ -3,9 +3,11 @@ import cv2
 import json
 import json
 import datetime
 import datetime
 import time
 import time
+import numpy as np
 from shutil import copyfile
 from shutil import copyfile
 from os.path import exists
 from os.path import exists
 from os import makedirs
 from os import makedirs
+from math import sqrt
 
 
 
 
 def main():
 def main():
@@ -30,6 +32,7 @@ def main():
 
 
     food_color = FoodColor(img, scale, window_name)
     food_color = FoodColor(img, scale, window_name)
     board_setup = BoardLimits(img, scale, window_name)
     board_setup = BoardLimits(img, scale, window_name)
+    food_limits = FoodLimits(img, scale, window_name)
 
 
     done = False
     done = False
     state = 0
     state = 0
@@ -94,6 +97,13 @@ def main():
 
 
                 show_menu()
                 show_menu()
 
 
+            elif key == "5":
+                state = 3
+                food_limits.clear()
+                food_limits.help()
+                cv2.setMouseCallback(window_name, food_limits.on_mouse)
+
+
             elif key == "s":
             elif key == "s":
                 ts = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H.%M.%S-')
                 ts = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H.%M.%S-')
                 if exists(args["config"]):
                 if exists(args["config"]):
@@ -102,7 +112,8 @@ def main():
                     copyfile(args["config"], "bkp/" + ts + args["config"])
                     copyfile(args["config"], "bkp/" + ts + args["config"])
 
 
                 with open(args["config"], "w") as file:
                 with open(args["config"], "w") as file:
-                    json.dump({**setup_vars, **board_setup.toJSON(), **food_color.toJSON()}, file)
+                    json.dump({**setup_vars, **board_setup.toJSON(), **food_color.toJSON(), **food_limits.toJSON()},
+                              file, indent=4, sort_keys=True)
 
 
                 done = True
                 done = True
 
 
@@ -122,6 +133,12 @@ def main():
                 state = 0
                 state = 0
                 show_menu()
                 show_menu()
 
 
+        elif state == 3:
+            food_limits.draw()
+            if food_limits.done:
+                state = 0
+                show_menu()
+
 
 
 def show_menu():
 def show_menu():
     print("\nCommands: ")
     print("\nCommands: ")
@@ -129,6 +146,7 @@ def show_menu():
     print("\tEnter '2' to setup food color")
     print("\tEnter '2' to setup food color")
     print("\tEnter '3' to insert image aspect ratio")
     print("\tEnter '3' to insert image aspect ratio")
     print("\tEnter '4' to insert discrete image height and width")
     print("\tEnter '4' to insert discrete image height and width")
+    print("\tEnter '5' to setup food limits")
     print("\tEnter 's' to save & quit")
     print("\tEnter 's' to save & quit")
     print("\tEnter 'q' to quit without saving")
     print("\tEnter 'q' to quit without saving")
 
 
@@ -201,34 +219,105 @@ class BoardLimits:
             self.help()
             self.help()
 
 
 
 
-class FoodColor:
+class FoodLimits:
 
 
-    def __init__(self, img, scale, window_name, max_qt=0):
-        self.colors = []
+    def __init__(self, img, scale, window_name):
+        self.limits = []
+        self.max_limits = 4
         self.orig = img
         self.orig = img
         self.img = img.copy()
         self.img = img.copy()
         self.scale = scale
         self.scale = scale
-        self.max_qt = max_qt
         self.window_name = window_name
         self.window_name = window_name
         self.done = False
         self.done = False
+        self.limits_drawn = False
+        self.min_dist = 5
 
 
-    def add(self, x, y):
+    def add_limit(self, x, y):
         x_img = int(x / self.scale)
         x_img = int(x / self.scale)
         y_img = int(y / self.scale)
         y_img = int(y / self.scale)
-        self.colors.append(self.orig[y_img, x_img])
+        self.limits.append((x_img, y_img))
         cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
         cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
 
 
     def draw(self):
     def draw(self):
+        if len(self.limits) == self.max_limits and not self.limits_drawn:
+            for i, limit in enumerate(self.limits):
+                cv2.line(self.img, self.limits[i-1], limit, (0, 0, 255), thickness=3)
+            self.limits_drawn = True
+
         cv2.imshow(self.window_name, cv2.resize(self.img, (0, 0), fx=self.scale, fy=self.scale))
         cv2.imshow(self.window_name, cv2.resize(self.img, (0, 0), fx=self.scale, fy=self.scale))
+
         if self.enough_data():
         if self.enough_data():
             self.confirm()
             self.confirm()
 
 
     def enough_data(self):
     def enough_data(self):
-        if self.max_qt == 0:
-            key = cv2.waitKey(10)
-            return key == 13  # Enter
+        return len(self.limits) == self.max_limits
+
+    def compute(self):
+        # TODO Improve with warp perspective...
+        self.min_dist = self.img.shape[0]
+        for i, (x,y) in enumerate(self.limits):
+            dist = sqrt((x - self.limits[i-1][0])**2 + (y - self.limits[i-1][1])**2)
+            self.min_dist = dist if dist < self.min_dist else self.min_dist
+        return self.min_dist
+
+    def toJSON(self):
+        return {'Min Food Size': self.min_dist}
+
+    def help(self):
+        print("--- Food Setup: Click on the {} corners of the tiniest food".format(self.max_limits))
+
+    def clear(self):
+        self.limits = []
+        self.limits_drawn = False
+        self.img = self.orig.copy()
+        self.done = False
+
+    def on_mouse(self, event, x, y, flags, param):
+        if event == cv2.EVENT_LBUTTONUP and not self.enough_data() :
+            if len(self.limits) < self.max_limits:
+                self.add_limit(x, y)
+
+    def confirm(self):
+        print("--- Food Setup: Press enter if you're ok with data or any other key if you want to restart setup...")
+        key = cv2.waitKey(0)
+        if key == 13:  # Enter
+            print("--- Food Setup: " + str(self.compute()))
+            self.done = True
         else:
         else:
-            return len(self.colors) == self.max_qt
+            self.clear()
+            self.help()
+
+
+class FoodColor:
+
+    def __init__(self, img, scale, window_name):
+        self.colors = []
+        self.orig = img
+        self.img = img.copy()
+        self.scale = scale
+        self.window_name = window_name
+        self.done = False
+
+    def add(self, x, y):
+        x_img = int(x / self.scale)
+        y_img = int(y / self.scale)
+        self.colors.append(self.orig[y_img, x_img])
+        self.show_selected()
+
+    def show_selected(self):
+        if len(self.colors) >= 2:
+            low, high = self.compute()
+            mask = cv2.inRange(self.img, np.array(low, dtype=np.uint8), np.array(high, dtype=np.uint8))
+            maskrgb = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
+
+            selected = np.zeros(self.img.shape, dtype=np.uint8)
+            selected[:, :, 2] = mask
+
+            self.img = cv2.add(cv2.subtract(self.img, maskrgb), selected)
+
+    def draw(self):
+        cv2.imshow(self.window_name, cv2.resize(self.img, (0, 0), fx=self.scale, fy=self.scale))
+        self.confirm()
 
 
     def compute(self):
     def compute(self):
         low_color = [255, 255, 255]
         low_color = [255, 255, 255]
@@ -254,10 +343,7 @@ class FoodColor:
         return {'Low Food Color': l, 'High Food Color': h}
         return {'Low Food Color': l, 'High Food Color': h}
 
 
     def help(self):
     def help(self):
-        if self.max_qt == 0:
-            print("--- Color Setup: Click several times on foods to setup food color and then press enter.")
-        else:
-            print("--- Color Setup: Click {} times on food to setup food color".format(self.max_qt))
+        print("--- Color Setup: Click several times on foods to setup food color and then press enter.")
 
 
     def clear(self):
     def clear(self):
         self.colors = []
         self.colors = []
@@ -265,18 +351,19 @@ class FoodColor:
         self.done = False
         self.done = False
 
 
     def on_mouse(self, event, x, y, flags, param):
     def on_mouse(self, event, x, y, flags, param):
-        if event == cv2.EVENT_LBUTTONUP and not self.enough_data() :
+        if event == cv2.EVENT_LBUTTONUP:
             self.add(x, y)
             self.add(x, y)
 
 
     def confirm(self):
     def confirm(self):
-        print("--- Color Setup: Press enter if you're ok with data or any other key if you want to restart setup...")
-        key = cv2.waitKey(0)
+        key = cv2.waitKey(10)
         if key == 13:  # Enter
         if key == 13:  # Enter
             print("--- Color Setup: " + str(self.compute()))
             print("--- Color Setup: " + str(self.compute()))
             self.done = True
             self.done = True
-        else:
-            self.clear()
-            self.help()
+        elif key == 0 and len(self.colors) > 0:  # Delete
+            del self.colors[len(self.colors)-1]
+            self.img = self.orig.copy()
+            self.show_selected()
+            print("Last color removed. {} remaining(s).".format(len(self.colors)))
 
 
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":

+ 1 - 1
blob-simulation/detection/utils.py

@@ -30,7 +30,7 @@ def find_food(img, min_food_size, lower_color_boundary, upper_color_boundary, ke
     mask = cv2.inRange(img, lower, upper)
     mask = cv2.inRange(img, lower, upper)
 
 
     if kernel is None:
     if kernel is None:
-        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
+        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (int(min_food_size/2), int(min_food_size/2)))
 
 
     cleaned = mask
     cleaned = mask
     cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel)
     cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel)

+ 0 - 63
blob-simulation/python-pathfinding-master/.gitignore

@@ -1,63 +0,0 @@
-.tox
-.idea
-.coverage
-.pypirc
-
-
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-env/
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-
-# PyInstaller
-#  Usually these files are written by a python script from a template
-#  before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.cache
-nosetests.xml
-coverage.xml
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# ipython notebook
-.ipynb_checkpoints

+ 0 - 21
blob-simulation/python-pathfinding-master/.travis.yml

@@ -1,21 +0,0 @@
-language: python
-sudo: false
-python:
-  - 2.7
-  - 3.5
-  - 3.6
-
-matrix:
-  include:
-    - python: 3.5
-      env: TOXENV=cov
-install:
-    - pip install --quiet tox-travis flake8
-script:
-    - tox
-    - flake8
-after_script:
-    - if [ $TOXENV == "cov" ]; then
-      pip install --quiet coveralls;
-      coveralls;
-      fi

+ 0 - 22
blob-simulation/python-pathfinding-master/LICENSE

@@ -1,22 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2015-2018 Andreas Bresser
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-

+ 0 - 16
blob-simulation/python-pathfinding-master/Pipfile

@@ -1,16 +0,0 @@
-[[source]]
-name = "pypi"
-url = "https://pypi.org/simple"
-verify_ssl = true
-
-[dev-packages]
-pytest = "*"
-numpy = "*"
-pandas = "*"
-pathfinding = {path = "."}
-
-[packages]
-pathfinding = {path = "."}
-
-[requires]
-python_version = "3.7"

+ 0 - 148
blob-simulation/python-pathfinding-master/Pipfile.lock

@@ -1,148 +0,0 @@
-{
-    "_meta": {
-        "hash": {
-            "sha256": "616310d414c92baa0415ab2450336690c8a30e7b288dd439050a467b72bf68eb"
-        },
-        "pipfile-spec": 6,
-        "requires": {
-            "python_version": "3.7"
-        },
-        "sources": [
-            {
-                "name": "pypi",
-                "url": "https://pypi.org/simple",
-                "verify_ssl": true
-            }
-        ]
-    },
-    "default": {
-        "pathfinding": {
-            "path": "."
-        }
-    },
-    "develop": {
-        "atomicwrites": {
-            "hashes": [
-                "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
-                "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
-            ],
-            "version": "==1.3.0"
-        },
-        "attrs": {
-            "hashes": [
-                "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
-                "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
-            ],
-            "version": "==19.1.0"
-        },
-        "more-itertools": {
-            "hashes": [
-                "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7",
-                "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a"
-            ],
-            "markers": "python_version > '2.7'",
-            "version": "==7.0.0"
-        },
-        "numpy": {
-            "hashes": [
-                "sha256:1980f8d84548d74921685f68096911585fee393975f53797614b34d4f409b6da",
-                "sha256:22752cd809272671b273bb86df0f505f505a12368a3a5fc0aa811c7ece4dfd5c",
-                "sha256:23cc40313036cffd5d1873ef3ce2e949bdee0646c5d6f375bf7ee4f368db2511",
-                "sha256:2b0b118ff547fecabc247a2668f48f48b3b1f7d63676ebc5be7352a5fd9e85a5",
-                "sha256:3a0bd1edf64f6a911427b608a894111f9fcdb25284f724016f34a84c9a3a6ea9",
-                "sha256:3f25f6c7b0d000017e5ac55977a3999b0b1a74491eacb3c1aa716f0e01f6dcd1",
-                "sha256:4061c79ac2230594a7419151028e808239450e676c39e58302ad296232e3c2e8",
-                "sha256:560ceaa24f971ab37dede7ba030fc5d8fa173305d94365f814d9523ffd5d5916",
-                "sha256:62be044cd58da2a947b7e7b2252a10b42920df9520fc3d39f5c4c70d5460b8ba",
-                "sha256:6c692e3879dde0b67a9dc78f9bfb6f61c666b4562fd8619632d7043fb5b691b0",
-                "sha256:6f65e37b5a331df950ef6ff03bd4136b3c0bbcf44d4b8e99135d68a537711b5a",
-                "sha256:7a78cc4ddb253a55971115f8320a7ce28fd23a065fc33166d601f51760eecfa9",
-                "sha256:80a41edf64a3626e729a62df7dd278474fc1726836552b67a8c6396fd7e86760",
-                "sha256:893f4d75255f25a7b8516feb5766c6b63c54780323b9bd4bc51cdd7efc943c73",
-                "sha256:972ea92f9c1b54cc1c1a3d8508e326c0114aaf0f34996772a30f3f52b73b942f",
-                "sha256:9f1d4865436f794accdabadc57a8395bd3faa755449b4f65b88b7df65ae05f89",
-                "sha256:9f4cd7832b35e736b739be03b55875706c8c3e5fe334a06210f1a61e5c2c8ca5",
-                "sha256:adab43bf657488300d3aeeb8030d7f024fcc86e3a9b8848741ea2ea903e56610",
-                "sha256:bd2834d496ba9b1bdda3a6cf3de4dc0d4a0e7be306335940402ec95132ad063d",
-                "sha256:d20c0360940f30003a23c0adae2fe50a0a04f3e48dc05c298493b51fd6280197",
-                "sha256:d3b3ed87061d2314ff3659bb73896e622252da52558f2380f12c421fbdee3d89",
-                "sha256:dc235bf29a406dfda5790d01b998a1c01d7d37f449128c0b1b7d1c89a84fae8b",
-                "sha256:fb3c83554f39f48f3fa3123b9c24aecf681b1c289f9334f8215c1d3c8e2f6e5b"
-            ],
-            "index": "pypi",
-            "version": "==1.16.2"
-        },
-        "pandas": {
-            "hashes": [
-                "sha256:071e42b89b57baa17031af8c6b6bbd2e9a5c68c595bc6bf9adabd7a9ed125d3b",
-                "sha256:17450e25ae69e2e6b303817bdf26b2cd57f69595d8550a77c308be0cd0fd58fa",
-                "sha256:17916d818592c9ec891cbef2e90f98cc85e0f1e89ed0924c9b5220dc3209c846",
-                "sha256:2538f099ab0e9f9c9d09bbcd94b47fd889bad06dc7ae96b1ed583f1dc1a7a822",
-                "sha256:366f30710172cb45a6b4f43b66c220653b1ea50303fbbd94e50571637ffb9167",
-                "sha256:42e5ad741a0d09232efbc7fc648226ed93306551772fc8aecc6dce9f0e676794",
-                "sha256:4e718e7f395ba5bfe8b6f6aaf2ff1c65a09bb77a36af6394621434e7cc813204",
-                "sha256:4f919f409c433577a501e023943e582c57355d50a724c589e78bc1d551a535a2",
-                "sha256:4fe0d7e6438212e839fc5010c78b822664f1a824c0d263fd858f44131d9166e2",
-                "sha256:5149a6db3e74f23dc3f5a216c2c9ae2e12920aa2d4a5b77e44e5b804a5f93248",
-                "sha256:627594338d6dd995cfc0bacd8e654cd9e1252d2a7c959449228df6740d737eb8",
-                "sha256:83c702615052f2a0a7fb1dd289726e29ec87a27272d775cb77affe749cca28f8",
-                "sha256:8c872f7fdf3018b7891e1e3e86c55b190e6c5cee70cab771e8f246c855001296",
-                "sha256:90f116086063934afd51e61a802a943826d2aac572b2f7d55caaac51c13db5b5",
-                "sha256:a3352bacac12e1fc646213b998bce586f965c9d431773d9e91db27c7c48a1f7d",
-                "sha256:bcdd06007cca02d51350f96debe51331dec429ac8f93930a43eb8fb5639e3eb5",
-                "sha256:c1bd07ebc15285535f61ddd8c0c75d0d6293e80e1ee6d9a8d73f3f36954342d0",
-                "sha256:c9a4b7c55115eb278c19aa14b34fcf5920c8fe7797a09b7b053ddd6195ea89b3",
-                "sha256:cc8fc0c7a8d5951dc738f1c1447f71c43734244453616f32b8aa0ef6013a5dfb",
-                "sha256:d7b460bc316064540ce0c41c1438c416a40746fd8a4fb2999668bf18f3c4acf1"
-            ],
-            "index": "pypi",
-            "version": "==0.24.2"
-        },
-        "pathfinding": {
-            "path": "."
-        },
-        "pluggy": {
-            "hashes": [
-                "sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f",
-                "sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746"
-            ],
-            "version": "==0.9.0"
-        },
-        "py": {
-            "hashes": [
-                "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
-                "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
-            ],
-            "version": "==1.8.0"
-        },
-        "pytest": {
-            "hashes": [
-                "sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523",
-                "sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4"
-            ],
-            "index": "pypi",
-            "version": "==4.3.1"
-        },
-        "python-dateutil": {
-            "hashes": [
-                "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
-                "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
-            ],
-            "version": "==2.8.0"
-        },
-        "pytz": {
-            "hashes": [
-                "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
-                "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
-            ],
-            "version": "==2018.9"
-        },
-        "six": {
-            "hashes": [
-                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
-                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
-            ],
-            "version": "==1.12.0"
-        }
-    }
-}

File diff suppressed because it is too large
+ 0 - 136
blob-simulation/python-pathfinding-master/README.md


File diff suppressed because it is too large
+ 0 - 204
blob-simulation/python-pathfinding-master/pathfinding.ipynb


+ 0 - 1
blob-simulation/python-pathfinding-master/pathfinding/__init__.py

@@ -1 +0,0 @@
-__all__ = ['core', 'finder']

+ 0 - 1
blob-simulation/python-pathfinding-master/pathfinding/core/__init__.py

@@ -1 +0,0 @@
-__all__ = ['diagonal_movement', 'grid', 'heuristic', 'node', 'util']

+ 0 - 8
blob-simulation/python-pathfinding-master/pathfinding/core/diagonal_movement.py

@@ -1,8 +0,0 @@
-# -*- coding: utf-8 -*-
-
-
-class DiagonalMovement:
-    always = 1
-    never = 2
-    if_at_most_one_obstacle = 3
-    only_when_no_obstacle = 4

+ 0 - 188
blob-simulation/python-pathfinding-master/pathfinding/core/grid.py

@@ -1,188 +0,0 @@
-# -*- coding: utf-8 -*-
-from .node import Node
-try:
-    import numpy as np
-    USE_NUMPY = True
-except ImportError:
-    USE_NUMPY = False
-from pathfinding.core.diagonal_movement import DiagonalMovement
-
-
-def build_nodes(width, height, matrix=None, inverse=False):
-    """
-    create nodes according to grid size. If a matrix is given it
-    will be used to determine what nodes are walkable.
-    :rtype : list
-    """
-    nodes = []
-    use_matrix = (isinstance(matrix, (tuple, list))) or \
-        (USE_NUMPY and isinstance(matrix, np.ndarray) and matrix.size > 0)
-
-    for y in range(height):
-        nodes.append([])
-        for x in range(width):
-            # 1, '1', True will be walkable
-            # while others will be obstacles
-            # if inverse is False, otherwise
-            # it changes
-            weight = int(matrix[y][x]) if use_matrix else 1
-            walkable = weight <= 0 if inverse else weight >= 1
-
-            nodes[y].append(Node(x=x, y=y, walkable=walkable, weight=weight))
-    return nodes
-
-
-class Grid(object):
-    def __init__(self, width=0, height=0, matrix=None, inverse=False):
-        """
-        a grid represents the map (as 2d-list of nodes).
-        """
-        self.width = width
-        self.height = height
-        if isinstance(matrix, (tuple, list)) or (
-                USE_NUMPY and isinstance(matrix, np.ndarray) and
-                matrix.size > 0):
-            self.height = len(matrix)
-            self.width = self.width = len(matrix[0]) if self.height > 0 else 0
-        if self.width > 0 and self.height > 0:
-            self.nodes = build_nodes(self.width, self.height, matrix, inverse)
-        else:
-            self.nodes = [[]]
-
-    def node(self, x, y):
-        """
-        get node at position
-        :param x: x pos
-        :param y: y pos
-        :return:
-        """
-        return self.nodes[y][x]
-
-    def inside(self, x, y):
-        """
-        check, if field position is inside map
-        :param x: x pos
-        :param y: y pos
-        :return:
-        """
-        return 0 <= x < self.width and 0 <= y < self.height
-
-    def walkable(self, x, y):
-        """
-        check, if the tile is inside grid and if it is set as walkable
-        """
-        return self.inside(x, y) and self.nodes[y][x].walkable
-
-    def neighbors(self, node, diagonal_movement=DiagonalMovement.never):
-        """
-        get all neighbors of one node
-        :param node: node
-        """
-        x = node.x
-        y = node.y
-        neighbors = []
-        s0 = d0 = s1 = d1 = s2 = d2 = s3 = d3 = False
-
-        # ↑
-        if self.walkable(x, y - 1):
-            neighbors.append(self.nodes[y - 1][x])
-            s0 = True
-        # →
-        if self.walkable(x + 1, y):
-            neighbors.append(self.nodes[y][x + 1])
-            s1 = True
-        # ↓
-        if self.walkable(x, y + 1):
-            neighbors.append(self.nodes[y + 1][x])
-            s2 = True
-        # ←
-        if self.walkable(x - 1, y):
-            neighbors.append(self.nodes[y][x - 1])
-            s3 = True
-
-        if diagonal_movement == DiagonalMovement.never:
-            return neighbors
-
-        if diagonal_movement == DiagonalMovement.only_when_no_obstacle:
-            d0 = s3 and s0
-            d1 = s0 and s1
-            d2 = s1 and s2
-            d3 = s2 and s3
-        elif diagonal_movement == DiagonalMovement.if_at_most_one_obstacle:
-            d0 = s3 or s0
-            d1 = s0 or s1
-            d2 = s1 or s2
-            d3 = s2 or s3
-        elif diagonal_movement == DiagonalMovement.always:
-            d0 = d1 = d2 = d3 = True
-
-        # ↖
-        if d0 and self.walkable(x - 1, y - 1):
-            neighbors.append(self.nodes[y - 1][x - 1])
-
-        # ↗
-        if d1 and self.walkable(x + 1, y - 1):
-            neighbors.append(self.nodes[y - 1][x + 1])
-
-        # ↘
-        if d2 and self.walkable(x + 1, y + 1):
-            neighbors.append(self.nodes[y + 1][x + 1])
-
-        # ↙
-        if d3 and self.walkable(x - 1, y + 1):
-            neighbors.append(self.nodes[y + 1][x - 1])
-
-        return neighbors
-
-    def cleanup(self):
-        for y_nodes in self.nodes:
-            for node in y_nodes:
-                node.cleanup()
-
-    def grid_str(self, path=None, start=None, end=None,
-                 border=True, start_chr='s', end_chr='e',
-                 path_chr='x', empty_chr=' ', block_chr='#',
-                 show_weight=False):
-        """
-        create a printable string from the grid using ASCII characters
-
-        :param path: list of nodes that show the path
-        :param start: start node
-        :param end: end node
-        :param border: create a border around the grid
-        :param start_chr: character for the start (default "s")
-        :param end_chr: character for the destination (default "e")
-        :param path_chr: character to show the path (default "x")
-        :param empty_chr: character for empty fields (default " ")
-        :param block_chr: character for blocking elements (default "#")
-        :param show_weight: instead of empty_chr show the cost of each empty
-                            field (shows a + if the value of weight is > 10)
-        :return:
-        """
-        data = ''
-        if border:
-            data = '+{}+'.format('-'*len(self.nodes[0]))
-        for y in range(len(self.nodes)):
-            line = ''
-            for x in range(len(self.nodes[y])):
-                node = self.nodes[y][x]
-                if node == start:
-                    line += start_chr
-                elif node == end:
-                    line += end_chr
-                elif path and ((node.x, node.y) in path or node in path):
-                    line += path_chr
-                elif node.walkable:
-                    # empty field
-                    weight = str(node.weight) if node.weight < 10 else '+'
-                    line += weight if show_weight else empty_chr
-                else:
-                    line += block_chr  # blocked field
-            if border:
-                line = '|'+line+'|'
-            if data:
-                data += '\n'
-            data += line
-        if border:
-            data += '\n+{}+'.format('-'*len(self.nodes[0]))
-        return data

+ 0 - 36
blob-simulation/python-pathfinding-master/pathfinding/core/heuristic.py

@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-import math
-from .util import SQRT2
-
-
-def null(dx, dy):
-    """
-    special heuristic for Dijkstra
-    return 0, so node.h will always be calculated as 0,
-    distance cost (node.f) is calculated only from
-    start to current point (node.g)
-    """
-    return 0
-
-
-def manhatten(dx, dy):
-    """manhatten heuristics"""
-    return dx + dy
-
-
-def euclidean(dx, dy):
-    """euclidean distance heuristics"""
-    return math.sqrt(dx * dx + dy * dy)
-
-
-def chebyshev(dx, dy):
-    """ Chebyshev distance. """
-    return max(dx, dy)
-
-
-def octile(dx, dy):
-    f = SQRT2 - 1
-    if dx < dy:
-        return f * dx + dy
-    else:
-        return f * dy + dx

+ 0 - 52
blob-simulation/python-pathfinding-master/pathfinding/core/node.py

@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-class Node(object):
-    """
-    basic node, saves X and Y coordinates on some grid and determine if
-    it is walkable.
-    """
-    def __init__(self, x=0, y=0, walkable=True, weight=1):
-        # Coordinates
-        self.x = x
-        self.y = y
-
-        # Whether this node can be walked through.
-        self.walkable = walkable
-
-        # used for weighted algorithms
-        self.weight = weight
-
-        # values used in the finder
-        self.cleanup()
-
-    def __lt__(self, other):
-        """
-        nodes are sorted by f value (see a_star.py)
-
-        :param other: compare Node
-        :return:
-        """
-        return self.f < other.f
-
-    def cleanup(self):
-        """
-        reset all calculated values, fresh start for pathfinding
-        """
-        # cost from this node to the goal
-        self.h = 0.0
-
-        # cost from the start node to this node
-        self.g = 0.0
-
-        # distance from start to this point (f = g + h )
-        self.f = 0.0
-
-        self.opened = 0
-        self.closed = False
-
-        # used for backtracking to the start point
-        self.parent = None
-
-        # used for recurion tracking of IDA*
-        self.retain_count = 0
-        # used for IDA* and Jump-Point-Search
-        self.tested = False

+ 0 - 132
blob-simulation/python-pathfinding-master/pathfinding/core/util.py

@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-import math
-import copy
-
-
-# square root of 2 for diagonal distance
-SQRT2 = math.sqrt(2)
-
-
-def backtrace(node):
-    """
-    Backtrace according to the parent records and return the path.
-    (including both start and end nodes)
-    """
-    path = [(node.x, node.y)]
-    while node.parent:
-        node = node.parent
-        path.append((node.x, node.y))
-    path.reverse()
-    return path
-
-
-def bi_backtrace(node_a, node_b):
-    """
-    Backtrace from start and end node, returns the path for bi-directional A*
-    (including both start and end nodes)
-    """
-    path_a = backtrace(node_a)
-    path_b = backtrace(node_b)
-    path_b.reverse()
-    return path_a + path_b
-
-
-def raytrace(coords_a, coords_b):
-    line = []
-    x0, y0 = coords_a
-    x1, y1 = coords_b
-
-    dx = x1 - x0
-    dy = y1 - y0
-
-    t = 0
-    grid_pos = [x0, y0]
-    t_for_one = \
-        abs(1.0 / dx) if dx > 0 else 10000, \
-        abs(1.0 / dy) if dy > 0 else 10000
-
-    frac_start_pos = (x0 + .5) - x0, (y0 + .5) - y0
-    t_for_next_border = [
-      (1 - frac_start_pos[0] if dx < 0 else frac_start_pos[0]) * t_for_one[0],
-      (1 - frac_start_pos[1] if dx < 0 else frac_start_pos[1]) * t_for_one[1]
-    ]
-
-    step = \
-        1 if dx >= 0 else -1, \
-        1 if dy >= 0 else -1
-
-    while t <= 1:
-        line.append(copy.copy(grid_pos))
-        index = 0 if t_for_next_border[0] <= t_for_next_border[1] else 1
-        t = t_for_next_border[index]
-        t_for_next_border[index] += t_for_one[index]
-        grid_pos[index] += step[index]
-    return line
-
-
-def bresenham(coords_a, coords_b):
-    '''
-    Given the start and end coordinates, return all the coordinates lying
-    on the line formed by these coordinates, based on Bresenham's algorithm.
-    http://en.wikipedia.org/wiki/Bresenham's_line_algorithm#Simplification
-    '''
-    line = []
-    x0, y0 = coords_a
-    x1, y1 = coords_b
-    dx = abs(x1 - x0)
-    dy = abs(y1 - y0)
-    sx = 1 if x0 < x1 else -1
-    sy = 1 if y0 < y1 else -1
-    err = dx - dy
-
-    while True:
-        line += [[x0, y0]]
-        if x0 == x1 and y0 == y1:
-            break
-        e2 = err * 2
-        if e2 > -dy:
-            err = err - dy
-            x0 = x0 + sx
-        if e2 < dx:
-            err = err + dx
-            y0 = y0 + sy
-
-    return line
-
-
-def expand_path(path):
-    '''
-    Given a compressed path, return a new path that has all the segments
-    in it interpolated.
-    '''
-    expanded = []
-    if len(path) < 2:
-        return expanded
-    for i in range(len(path)-1):
-        expanded += bresenham(path[i], path[i + 1])
-    expanded += [path[:-1]]
-    return expanded
-
-
-def smoothen_path(grid, path, use_raytrace=False):
-    x0, y0 = path[0]
-
-    sx, sy = path[0]
-    new_path = [[sx, sy]]
-
-    interpolate = raytrace if use_raytrace else bresenham
-    last_valid = path[1]
-    for coord in path[2:-1]:
-        line = interpolate([sx, sy], coord)
-        blocked = False
-        for test_coord in line[1:]:
-            if not grid.walkable(test_coord[0], test_coord[1]):
-                blocked = True
-                break
-        if not blocked:
-            new_path.append(last_valid)
-            sx, sy = last_valid
-        last_valid = coord
-
-    new_path.append(path[-1])
-    return new_path

+ 0 - 2
blob-simulation/python-pathfinding-master/pathfinding/finder/__init__.py

@@ -1,2 +0,0 @@
-__all__ = ['a_star', 'best_first', 'bi_a_star', 'breadth_first', 'dijkstra',
-           'finder', 'ida_star']

+ 0 - 89
blob-simulation/python-pathfinding-master/pathfinding/finder/a_star.py

@@ -1,89 +0,0 @@
-# -*- coding: utf-8 -*-
-import heapq  # used for the so colled "open list" that stores known nodes
-from pathfinding.core.heuristic import manhatten, octile
-from pathfinding.core.util import backtrace, bi_backtrace
-from pathfinding.core.diagonal_movement import DiagonalMovement
-from .finder import Finder, TIME_LIMIT, MAX_RUNS, BY_END
-
-
-class AStarFinder(Finder):
-    def __init__(self, heuristic=None, weight=1,
-                 diagonal_movement=DiagonalMovement.never,
-                 time_limit=TIME_LIMIT,
-                 max_runs=MAX_RUNS):
-        """
-        find shortest path using A* algorithm
-        :param heuristic: heuristic used to calculate distance of 2 points
-            (defaults to manhatten)
-        :param weight: weight for the edges
-        :param diagonal_movement: if diagonal movement is allowed
-            (see enum in diagonal_movement)
-        :param time_limit: max. runtime in seconds
-        :param max_runs: max. amount of tries until we abort the search
-            (optional, only if we enter huge grids and have time constrains)
-            <=0 means there are no constrains and the code might run on any
-            large map.
-        """
-        super(AStarFinder, self).__init__(
-            heuristic=heuristic,
-            weight=weight,
-            diagonal_movement=diagonal_movement,
-            time_limit=time_limit,
-            max_runs=max_runs)
-
-        if not heuristic:
-            if diagonal_movement == DiagonalMovement.never:
-                self.heuristic = manhatten
-            else:
-                # When diagonal movement is allowed the manhattan heuristic is
-                # not admissible it should be octile instead
-                self.heuristic = octile
-
-    def check_neighbors(self, start, end, grid, open_list,
-                        open_value=True, backtrace_by=None):
-        """
-        find next path segment based on given node
-        (or return path if we found the end)
-        """
-        # pop node with minimum 'f' value
-        node = heapq.nsmallest(1, open_list)[0]
-        open_list.remove(node)
-        node.closed = True
-
-        # if reached the end position, construct the path and return it
-        # (ignored for bi-directional a*, there we look for a neighbor that is
-        #  part of the oncoming path)
-        if not backtrace_by and node == end:
-            return backtrace(end)
-
-        # get neighbors of the current node
-        neighbors = self.find_neighbors(grid, node)
-        for neighbor in neighbors:
-            if neighbor.closed:
-                # already visited last minimum f value
-                continue
-            if backtrace_by and neighbor.opened == backtrace_by:
-                # found the oncoming path
-                if backtrace_by == BY_END:
-                    return bi_backtrace(node, neighbor)
-                else:
-                    return bi_backtrace(neighbor, node)
-
-            # check if the neighbor has not been inspected yet, or
-            # can be reached with smaller cost from the current node
-            self.process_node(neighbor, node, end, open_list, open_value)
-
-        # the end has not been reached (yet) keep the find_path loop running
-        return None
-
-    def find_path(self, start, end, grid):
-        """
-        find a path from start to end node on grid using the A* algorithm
-        :param start: start node
-        :param end: end node
-        :param grid: grid that stores all possible steps/tiles as 2D-list
-        :return:
-        """
-        start.g = 0
-        start.f = 0
-        return super(AStarFinder, self).find_path(start, end, grid)

+ 0 - 37
blob-simulation/python-pathfinding-master/pathfinding/finder/best_first.py

@@ -1,37 +0,0 @@
-from .a_star import AStarFinder, MAX_RUNS, TIME_LIMIT
-from pathfinding.core.diagonal_movement import DiagonalMovement
-
-
-class BestFirst(AStarFinder):
-    """
-    Similar to the default A* algorithm from a_star.
-    """
-    def __init__(self, heuristic=None, weight=1,
-                 diagonal_movement=DiagonalMovement.never,
-                 time_limit=TIME_LIMIT,
-                 max_runs=MAX_RUNS):
-        """
-        find shortest path using BestFirst algorithm
-        :param heuristic: heuristic used to calculate distance of 2 points
-            (defaults to manhatten)
-        :param weight: weight for the edges
-        :param diagonal_movement: if diagonal movement is allowed
-            (see enum in diagonal_movement)
-        :param time_limit: max. runtime in seconds
-        :param max_runs: max. amount of tries until we abort the search
-            (optional, only if we enter huge grids and have time constrains)
-            <=0 means there are no constrains and the code might run on any
-            large map.
-        """
-        super(BestFirst, self).__init__(
-            heuristic=heuristic,
-            weight=weight,
-            diagonal_movement=diagonal_movement,
-            time_limit=time_limit,
-            max_runs=max_runs)
-
-        self.weighted = False
-
-    def apply_heuristic(self, node_a, node_b, heuristic=None):
-        return super(BestFirst, self).apply_heuristic(
-            node_a, node_b, heuristic) * 1000000

+ 0 - 77
blob-simulation/python-pathfinding-master/pathfinding/finder/bi_a_star.py

@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-import time
-from .finder import TIME_LIMIT, MAX_RUNS, BY_START, BY_END
-from .a_star import AStarFinder
-from pathfinding.core.diagonal_movement import DiagonalMovement
-
-
-class BiAStarFinder(AStarFinder):
-    """
-    Similar to the default A* algorithm from a_star.
-    """
-    def __init__(self, heuristic=None, weight=1,
-                 diagonal_movement=DiagonalMovement.never,
-                 time_limit=TIME_LIMIT,
-                 max_runs=MAX_RUNS):
-        """
-        find shortest path using Bi-A* algorithm
-        :param heuristic: heuristic used to calculate distance of 2 points
-            (defaults to manhatten)
-        :param weight: weight for the edges
-        :param diagonal_movement: if diagonal movement is allowed
-            (see enum in diagonal_movement)
-        :param time_limit: max. runtime in seconds
-        :param max_runs: max. amount of tries until we abort the search
-            (optional, only if we enter huge grids and have time constrains)
-            <=0 means there are no constrains and the code might run on any
-            large map.
-        """
-        super(BiAStarFinder, self).__init__(
-            heuristic=heuristic,
-            weight=weight,
-            diagonal_movement=diagonal_movement,
-            time_limit=time_limit,
-            max_runs=max_runs)
-
-        self.weighted = False
-
-    def find_path(self, start, end, grid):
-        """
-        find a path from start to end node on grid using the A* algorithm
-        :param start: start node
-        :param end: end node
-        :param grid: grid that stores all possible steps/tiles as 2D-list
-        :return:
-        """
-        self.start_time = time.time()  # execution time limitation
-        self.runs = 0  # count number of iterations
-
-        start_open_list = [start]
-        start.g = 0
-        start.f = 0
-        start.opened = BY_START
-
-        end_open_list = [end]
-        end.g = 0
-        end.f = 0
-        end.opened = BY_END
-
-        while len(start_open_list) > 0 and len(end_open_list) > 0:
-            self.runs += 1
-            self.keep_running()
-            path = self.check_neighbors(start, end, grid, start_open_list,
-                                        open_value=BY_START,
-                                        backtrace_by=BY_END)
-            if path:
-                return path, self.runs
-
-            self.runs += 1
-            self.keep_running()
-            path = self.check_neighbors(end, start, grid, end_open_list,
-                                        open_value=BY_END,
-                                        backtrace_by=BY_START)
-            if path:
-                return path, self.runs
-
-        # failed to find path
-        return [], self.runs

+ 0 - 35
blob-simulation/python-pathfinding-master/pathfinding/finder/breadth_first.py

@@ -1,35 +0,0 @@
-from .finder import Finder, TIME_LIMIT, MAX_RUNS
-from pathfinding.core.util import backtrace
-from pathfinding.core.diagonal_movement import DiagonalMovement
-
-
-class BreadthFirstFinder(Finder):
-    def __init__(self, heuristic=None, weight=1,
-                 diagonal_movement=DiagonalMovement.never,
-                 time_limit=TIME_LIMIT,
-                 max_runs=MAX_RUNS):
-        super(BreadthFirstFinder, self).__init__(
-            heuristic=heuristic,
-            weight=weight,
-            weighted=False,
-            diagonal_movement=diagonal_movement,
-            time_limit=time_limit,
-            max_runs=max_runs)
-        if not diagonal_movement:
-            self.diagonalMovement = DiagonalMovement.never
-
-    def check_neighbors(self, start, end, grid, open_list):
-        node = open_list.pop(0)
-        node.closed = True
-
-        if node == end:
-            return backtrace(end)
-
-        neighbors = self.find_neighbors(grid, node)
-        for neighbor in neighbors:
-            if neighbor.closed or neighbor.opened:
-                continue
-
-            open_list.append(neighbor)
-            neighbor.opened = True
-            neighbor.parent = node

+ 0 - 16
blob-simulation/python-pathfinding-master/pathfinding/finder/dijkstra.py

@@ -1,16 +0,0 @@
-from .a_star import AStarFinder, MAX_RUNS, TIME_LIMIT
-from pathfinding.core.diagonal_movement import DiagonalMovement
-from pathfinding.core.heuristic import null
-
-
-class DijkstraFinder(AStarFinder):
-    def __init__(self, weight=1,
-                 diagonal_movement=DiagonalMovement.never,
-                 time_limit=TIME_LIMIT,
-                 max_runs=MAX_RUNS):
-        super(DijkstraFinder, self).__init__(
-            heuristic=null,
-            weight=weight,
-            diagonal_movement=diagonal_movement,
-            time_limit=time_limit,
-            max_runs=max_runs)

+ 0 - 166
blob-simulation/python-pathfinding-master/pathfinding/finder/finder.py

@@ -1,166 +0,0 @@
-# -*- coding: utf-8 -*-
-import heapq  # used for the so colled "open list" that stores known nodes
-import time  # for time limitation
-from pathfinding.core.util import SQRT2
-from pathfinding.core.diagonal_movement import DiagonalMovement
-
-
-# max. amount of tries we iterate until we abort the search
-MAX_RUNS = float('inf')
-# max. time after we until we abort the search (in seconds)
-TIME_LIMIT = float('inf')
-
-# used for backtrace of bi-directional A*
-BY_START = 1
-BY_END = 2
-
-
-class ExecutionTimeException(Exception):
-    def __init__(self, message):
-        super(ExecutionTimeException, self).__init__(message)
-
-
-class ExecutionRunsException(Exception):
-    def __init__(self, message):
-        super(ExecutionRunsException, self).__init__(message)
-
-
-class Finder(object):
-    def __init__(self, heuristic=None, weight=1,
-                 diagonal_movement=DiagonalMovement.never,
-                 weighted=True,
-                 time_limit=TIME_LIMIT,
-                 max_runs=MAX_RUNS):
-        """
-        find shortest path
-        :param heuristic: heuristic used to calculate distance of 2 points
-            (defaults to manhatten)
-        :param weight: weight for the edges
-        :param diagonal_movement: if diagonal movement is allowed
-            (see enum in diagonal_movement)
-        :param weighted: the algorithm supports weighted nodes
-            (should be True for A* and Dijkstra)
-        :param time_limit: max. runtime in seconds
-        :param max_runs: max. amount of tries until we abort the search
-            (optional, only if we enter huge grids and have time constrains)
-            <=0 means there are no constrains and the code might run on any
-            large map.
-        """
-        self.time_limit = time_limit
-        self.max_runs = max_runs
-        self.weighted = weighted
-
-        self.diagonal_movement = diagonal_movement
-        self.weight = weight
-        self.heuristic = heuristic
-
-    def calc_cost(self, node_a, node_b):
-        """
-        get the distance between current node and the neighbor (cost)
-        """
-        if node_b.x - node_a.x == 0 or node_b.y - node_a.y == 0:
-            # direct neighbor - distance is 1
-            ng = 1
-        else:
-            # not a direct neighbor - diagonal movement
-            ng = SQRT2
-
-        # weight for weighted algorithms
-        if self.weighted:
-            ng *= node_b.weight
-
-        return node_a.g + ng
-
-    def apply_heuristic(self, node_a, node_b, heuristic=None):
-        """
-        helper function to apply heuristic
-        """
-        if not heuristic:
-            heuristic = self.heuristic
-        return heuristic(
-            abs(node_a.x - node_b.x),
-            abs(node_a.y - node_b.y))
-
-    def find_neighbors(self, grid, node, diagonal_movement=None):
-        '''
-        find neighbor, same for Djikstra, A*, Bi-A*, IDA*
-        '''
-        if not diagonal_movement:
-            diagonal_movement = self.diagonal_movement
-        return grid.neighbors(node, diagonal_movement=diagonal_movement)
-
-    def keep_running(self):
-        """
-        check, if we run into time or iteration constrains.
-        :returns: True if we keep running and False if we run into a constraint
-        """
-        if self.runs >= self.max_runs:
-            raise ExecutionRunsException(
-                '{} run into barrier of {} iterations without '
-                'finding the destination'.format(
-                    self.__class__.__name__, self.max_runs))
-
-        if time.time() - self.start_time >= self.time_limit:
-            raise ExecutionTimeException(
-                '{} took longer than {} seconds, aborting!'.format(
-                    self.__class__.__name__, self.time_limit))
-
-    def process_node(self, node, parent, end, open_list, open_value=True):
-        '''
-        we check if the given node is path of the path by calculating its
-        cost and add or remove it from our path
-        :param node: the node we like to test
-            (the neighbor in A* or jump-node in JumpPointSearch)
-        :param parent: the parent node (the current node we like to test)
-        :param end: the end point to calculate the cost of the path
-        :param open_list: the list that keeps track of our current path
-        :param open_value: needed if we like to set the open list to something
-            else than True (used for bi-directional algorithms)
-
-        '''
-        # calculate cost from current node (parent) to the next node (neighbor)
-        ng = self.calc_cost(parent, node)
-
-        if not node.opened or ng < node.g:
-            node.g = ng
-            node.h = node.h or \
-                self.apply_heuristic(node, end) * self.weight
-            # f is the estimated total cost from start to goal
-            node.f = node.g + node.h
-            node.parent = parent
-
-            if not node.opened:
-                heapq.heappush(open_list, node)
-                node.opened = open_value
-            else:
-                # the node can be reached with smaller cost.
-                # Since its f value has been updated, we have to
-                # update its position in the open list
-                open_list.remove(node)
-                heapq.heappush(open_list, node)
-
-    def find_path(self, start, end, grid):
-        """
-        find a path from start to end node on grid by iterating over
-        all neighbors of a node (see check_neighbors)
-        :param start: start node
-        :param end: end node
-        :param grid: grid that stores all possible steps/tiles as 2D-list
-        :return:
-        """
-        self.start_time = time.time()  # execution time limitation
-        self.runs = 0  # count number of iterations
-        start.opened = True
-
-        open_list = [start]
-
-        while len(open_list) > 0:
-            self.runs += 1
-            self.keep_running()
-
-            path = self.check_neighbors(start, end, grid, open_list)
-            if path:
-                return path, self.runs
-
-        # failed to find path
-        return [], self.runs

+ 0 - 128
blob-simulation/python-pathfinding-master/pathfinding/finder/ida_star.py

@@ -1,128 +0,0 @@
-import time
-from pathfinding.core.heuristic import manhatten, octile
-from pathfinding.core.diagonal_movement import DiagonalMovement
-from pathfinding.core.node import Node
-from .finder import Finder, TIME_LIMIT, MAX_RUNS
-
-
-class IDAStarFinder(Finder):
-    """
-    Iterative Deeping A Star (IDA*) path-finder.
-
-    Recursion based on:
-       http://www.apl.jhu.edu/~hall/AI-Programming/IDA-Star.html
-
-    Path retracing based on:
-     V. Nageshwara Rao, Vipin Kumar and K. Ramesh
-     "A Parallel Implementation of Iterative-Deeping-A*", January 1987.
-     ftp://ftp.cs.utexas.edu/.snapshot/hourly.1/pub/AI-Lab/tech-reports/
-     UT-AI-TR-87-46.pdf
-
-    based on the JavaScript implementation by Gerard Meier
-    (www.gerardmeier.com)
-    """
-    def __init__(self, heuristic=None, weight=1,
-                 diagonal_movement=DiagonalMovement.never,
-                 time_limit=TIME_LIMIT,
-                 max_runs=MAX_RUNS,
-                 track_recursion=True):
-        super(IDAStarFinder, self).__init__(
-            heuristic=heuristic, weight=weight,
-            diagonal_movement=diagonal_movement,
-            weighted=False,
-            time_limit=time_limit,
-            max_runs=max_runs)
-        self.track_recursion = track_recursion
-        if not heuristic:
-            if diagonal_movement == DiagonalMovement.never:
-                self.heuristic = manhatten
-            else:
-                # When diagonal movement is allowed the manhattan heuristic is
-                # not admissible it should be octile instead
-                self.heuristic = octile
-
-    def search(self, node, g, cutoff, path, depth, end, grid):
-        self.runs += 1
-        self.keep_running()
-
-        self.nodes_visited += 1
-
-        f = g + self.apply_heuristic(node, end) * self.weight
-
-        # We've searched too deep for this iteration.
-        if f > cutoff:
-            return f
-
-        if node == end:
-            if len(path) < depth:
-                path += [None] * (depth - len(path) + 1)
-            path[depth] = node
-            return node
-
-        neighbors = self.find_neighbors(grid, node)
-
-        # Sort the neighbors, gives nicer paths. But, this deviates
-        # from the original algorithm - so I left it out
-        # TODO: make this an optional parameter
-        #    def sort_neighbors(a, b):
-        #        return self.apply_heuristic(a, end) - \
-        #            self.apply_heuristic(b, end)
-        #    sorted(neighbors, sort_neighbors)
-        min_t = float('inf')
-        for neighbor in neighbors:
-            if self.track_recursion:
-                # Retain a copy for visualisation. Due to recursion, this
-                # node may be part of other paths too.
-                neighbor.retain_count += 1
-                neighbor.tested = True
-
-            t = self.search(neighbor, g + self.calc_cost(node, neighbor),
-                            cutoff, path, depth + 1, end, grid)
-
-            if isinstance(t, Node):
-                if len(path) < depth:
-                    path += [None] * (depth - len(path) + 1)
-                path[depth] = node
-                return t
-
-            # Decrement count, then determine whether it's actually closed.
-            if self.track_recursion:
-                neighbor.retain_count -= 1
-                if neighbor.retain_count == 0:
-                    neighbor.tested = False
-
-            if t < min_t:
-                min_t = t
-
-        return min_t
-
-    def find_path(self, start, end, grid):
-        self.start_time = time.time()  # execution time limitation
-        self.runs = 0  # count number of iterations
-
-        self.nodes_visited = 0  # for statistics
-
-        # initial search depth, given the typical heuristic contraints,
-        # there should be no cheaper route possible.
-        cutoff = self.apply_heuristic(start, end)
-
-        while True:
-            path = []
-
-            # search till cut-off depth:
-            t = self.search(start, 0, cutoff, path, 0, end, grid)
-
-            if isinstance(t, bool) and not t:
-                # only when an error occured we return "False"
-                break
-
-            # If t is a node, it's also the end node. Route is now
-            # populated with a valid path to the end node.
-            if isinstance(t, Node):
-                return [(node.x, node.y) for node in path], self.runs
-
-            # Try again, this time with a deeper cut-off. The t score
-            # is the closest we got to the end node.
-            cutoff = t
-
-        return [], self.runs

+ 0 - 2
blob-simulation/python-pathfinding-master/setup.cfg

@@ -1,2 +0,0 @@
-[metadata]
-description-file = README.md

+ 0 - 22
blob-simulation/python-pathfinding-master/setup.py

@@ -1,22 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from setuptools import setup, find_packages
-
-with open("README.md", "r") as fh:
-    long_description = fh.read()
-
-setup(
-    name="pathfinding",
-    description="Pathfinding algorithms (based on Pathfinding.JS)",
-    long_description=long_description,
-    long_description_content_type="text/markdown",
-    url="https://github.com/brean/python-pathfinding",
-    version="0.0.4",
-    license="MIT",
-    author="Andreas Bresser",
-    packages=find_packages(),
-    tests_require=["numpy", "pandas"],
-    include_package_data=True,
-    install_requires=[],
-)

+ 0 - 6
blob-simulation/python-pathfinding-master/test/csv_file.csv

@@ -1,6 +0,0 @@
-1,1,1,1
-1,0,0,1
-1,1,0,0
-0,1,1,1
-0,1,0,1
-1,1,1,1

+ 0 - 40
blob-simulation/python-pathfinding-master/test/csv_pandas_test.py

@@ -1,40 +0,0 @@
-import os
-import pandas
-import numpy as np
-
-from pathfinding.core.diagonal_movement import DiagonalMovement
-from pathfinding.core.grid import Grid
-from pathfinding.finder.a_star import AStarFinder
-
-BASE_PATH = os.path.abspath(os.path.dirname(__file__))
-CSV_FILE = os.path.join(BASE_PATH, 'csv_file.csv')
-
-
-def _find(matrix):
-    grid = Grid(matrix=matrix)
-    print(matrix)
-
-    start = grid.node(0, 0)
-    end = grid.node(2, 4)
-
-    finder = AStarFinder(diagonal_movement=DiagonalMovement.never)
-    path, runs = finder.find_path(start, end, grid)
-
-    print('operations:', runs, 'path length:', len(path))
-    print(grid.grid_str(path=path, start=start, end=end))
-
-    assert path == [(0, 0), (0, 1), (1, 1), (1, 2), (1, 3), (1, 4), (2, 4)]
-
-
-def test_csv_pandas_str():
-    """
-    test to load a csv file using pandas (as string).
-    """
-    _find(np.array(pandas.io.parsers.read_csv(CSV_FILE)).astype("str"))
-
-
-def test_csv_pandas_int():
-    """
-    test to load a csv file using pandas (as int).
-    """
-    _find(np.array(pandas.io.parsers.read_csv(CSV_FILE)).astype("int"))

+ 0 - 85
blob-simulation/python-pathfinding-master/test/grid_test.py

@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-from pathfinding.core.diagonal_movement import DiagonalMovement
-from pathfinding.core.grid import Grid
-from pathfinding.finder.a_star import AStarFinder
-import numpy as np
-
-BORDERLESS_GRID = """
-xxx
-xxx
-"""
-
-BORDER_GRID = """
-+---+
-|   |
-|   |
-+---+
-"""
-
-WALKED_GRID = """
-+---+
-|s# |
-|xe |
-+---+
-"""
-
-SIMPLE_MATRIX = [
-  [1, 1, 1],
-  [1, 0, 1],
-  [1, 1, 1]
-]
-
-SIMPLE_WALKED = """
-+---+
-|sx |
-| #x|
-|  e|
-+---+
-"""
-
-
-def test_str():
-    """
-    test printing the grid
-    """
-    grid = Grid(height=2, width=3)
-    assert grid.grid_str(border=False, empty_chr='x') == BORDERLESS_GRID[1:-1]
-    assert grid.grid_str(border=True) == BORDER_GRID[1:-1]
-    grid.nodes[0][1].walkable = False
-    start = grid.nodes[0][0]
-    end = grid.nodes[1][1]
-    path = [(0, 1)]
-    assert grid.grid_str(path, start, end) == WALKED_GRID[1:-1]
-
-
-def test_empty():
-    """
-    special test for empty values
-    """
-    matrix = ()
-    grid = Grid(matrix=matrix)
-    assert grid.grid_str() == '++\n||\n++'
-
-    matrix = np.array(matrix)
-    grid = Grid(matrix=matrix)
-    assert grid.grid_str() == '++\n||\n++'
-
-
-def test_numpy():
-    """
-    test grid from numpy array
-    """
-    matrix = np.array(SIMPLE_MATRIX)
-    grid = Grid(matrix=matrix)
-
-    start = grid.node(0, 0)
-    end = grid.node(2, 2)
-
-    finder = AStarFinder(diagonal_movement=DiagonalMovement.always)
-    path, runs = finder.find_path(start, end, grid)
-
-    assert grid.grid_str(path, start, end) == SIMPLE_WALKED[1:-1]
-
-
-if __name__ == '__main__':
-    test_str()

+ 0 - 111
blob-simulation/python-pathfinding-master/test/path_test.py

@@ -1,111 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-import json
-import pytest
-from pathfinding.finder.a_star import AStarFinder
-from pathfinding.finder.best_first import BestFirst
-from pathfinding.finder.dijkstra import DijkstraFinder
-from pathfinding.finder.bi_a_star import BiAStarFinder
-from pathfinding.finder.ida_star import IDAStarFinder
-from pathfinding.finder.breadth_first import BreadthFirstFinder
-from pathfinding.finder.finder import ExecutionRunsException
-from pathfinding.finder.finder import ExecutionTimeException
-from pathfinding.core.grid import Grid
-from pathfinding.core.diagonal_movement import DiagonalMovement
-
-
-BASE_PATH = os.path.abspath(os.path.dirname(__file__))
-
-# test scenarios from Pathfinding.JS
-scenarios = os.path.join(BASE_PATH, 'path_test_scenarios.json')
-data = json.load(open(scenarios, 'r'))
-finders = [AStarFinder, BestFirst, BiAStarFinder, DijkstraFinder,
-           IDAStarFinder, BreadthFirstFinder]
-TIME_LIMIT = 10  # give it a 10 second limit.
-
-
-def grid_from_scenario(scenario):
-    inverse = scenario['inverse'] if 'inverse' in scenario else True
-    grid = Grid(matrix=scenario['matrix'], inverse=inverse)
-    start = grid.node(scenario['startX'], scenario['startY'])
-    end = grid.node(scenario['endX'], scenario['endY'])
-    return grid, start, end
-
-
-def test_path():
-    """
-    test scenarios defined in json file
-    """
-    for scenario in data:
-        grid, start, end = grid_from_scenario(scenario)
-        for find in finders:
-            grid.cleanup()
-            finder = find(time_limit=TIME_LIMIT)
-            weighted = False
-            if 'weighted' in scenario:
-                weighted = scenario['weighted']
-            if weighted and not finder.weighted:
-                continue
-            path, runs = finder.find_path(start, end, grid)
-            print(find.__name__)
-            print(grid.grid_str(path=path, start=start, end=end,
-                                show_weight=weighted))
-            print('path: {}'.format(path))
-            assert len(path) == scenario['expectedLength']
-
-
-def test_path_diagonal():
-    # test diagonal movement
-    for scenario in data:
-        grid, start, end = grid_from_scenario(scenario)
-        for find in finders:
-            grid.cleanup()
-            finder = find(diagonal_movement=DiagonalMovement.always,
-                          time_limit=TIME_LIMIT)
-            weighted = False
-            if 'weighted' in scenario:
-                weighted = scenario['weighted']
-            print(dir(find))
-            if weighted and not finder.weighted:
-                continue
-
-            path, runs = finder.find_path(start, end, grid)
-            print(find.__name__, runs, len(path))
-            print(grid.grid_str(path=path, start=start, end=end,
-                                show_weight=weighted))
-            print('path: {}'.format(path))
-            assert len(path) == scenario['expectedDiagonalLength']
-
-
-def test_max_runs():
-    grid, start, end = grid_from_scenario(data[1])
-    for find in finders:
-        grid.cleanup()
-        finder = find(diagonal_movement=DiagonalMovement.always,
-                      time_limit=TIME_LIMIT, max_runs=3)
-        with pytest.raises(ExecutionRunsException):
-            path, runs = finder.find_path(start, end, grid)
-            print('{} finishes after {} runs without exception'.format(
-                find.__name__, finder.runs))
-        msg = '{} needed to much iterations'.format(
-            finder.__class__.__name__)
-        assert(finder.runs <= 3), msg
-
-
-def test_time():
-    grid, start, end = grid_from_scenario(data[1])
-    for find in finders:
-        grid.cleanup()
-        finder = find(diagonal_movement=DiagonalMovement.always,
-                      time_limit=-.1)
-        with pytest.raises(ExecutionTimeException):
-            path, runs = finder.find_path(start, end, grid)
-            print('{} finishes after {} runs without exception'.format(
-                find.__name__, finder.runs))
-        msg = '{} took to long'.format(finder.__class__.__name__)
-        assert(finder.runs == 1), msg
-
-
-if __name__ == '__main__':
-    test_path()
-    test_path_diagonal()

+ 0 - 84
blob-simulation/python-pathfinding-master/test/path_test_scenarios.json

@@ -1,84 +0,0 @@
-[
-    {
-        "startX": 0,
-        "startY": 0,
-        "endX": 1,
-        "endY": 1,
-        "matrix": [[0, 0],
-                  [1, 0]],
-        "expectedLength": 3,
-        "expectedDiagonalLength": 2
-    },
-    {
-        "startX": 1,
-        "startY": 1,
-        "endX": 4,
-        "endY": 4,
-        "matrix": [[0, 0, 0, 0, 0],
-                  [1, 0, 1, 1, 0],
-                  [1, 0, 1, 0, 0],
-                  [0, 1, 0, 0, 0],
-                  [1, 0, 1, 1, 0],
-                  [0, 0, 1, 0, 0]],
-        "expectedLength": 9,
-        "expectedDiagonalLength": 5
-    },
-    {
-        "startX": 0,
-        "startY": 3,
-        "endX": 3,
-        "endY": 3,
-        "matrix": [[0, 0, 0, 0, 0],
-                  [0, 0, 1, 1, 0],
-                  [0, 0, 1, 0, 0],
-                  [0, 0, 1, 0, 0],
-                  [1, 0, 1, 1, 0],
-                  [0, 0, 0, 0, 0]],
-        "expectedLength": 10,
-        "expectedDiagonalLength": 6
-    },
-    {
-        "startX": 4,
-        "startY": 4,
-        "endX": 19,
-        "endY": 19,
-        "matrix": [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
-        "expectedLength": 31,
-        "expectedDiagonalLength": 16
-    },
-    {
-        "startX": 0,
-        "startY": 0,
-        "endX": 4,
-        "endY": 0,
-        "matrix": [[1, 2, 99, 9, 1],
-                  [1, 2, 0, 0, 1],
-                  [1, 1, 0, 1, 1],
-                  [4, 3, 0, 1, 1],
-                  [1, 1, 0, 0, 1],
-                  [1, 1, 1, 1, 1]],
-        "expectedLength": 15,
-        "expectedDiagonalLength": 12,
-        "inverse": false,
-        "weighted": true
-    }
-]

+ 0 - 48
blob-simulation/python-pathfinding-master/test/util_test.py

@@ -1,48 +0,0 @@
-from pathfinding.core.util import bresenham, raytrace, smoothen_path
-from pathfinding.core.grid import Grid
-
-
-def test_bresenham():
-    """
-    test bresenham path interpolation
-    """
-    assert bresenham([0, 0], [2, 5]) == [
-        [0, 0], [0, 1],
-        [1, 2], [1, 3],
-        [2, 4], [2, 5]
-    ]
-    assert bresenham([0, 1], [0, 4]) == [
-        [0, 1], [0, 2], [0, 3], [0, 4]
-    ]
-
-
-def test_raytrace():
-    """
-    test raytrace path interpolation
-    """
-    assert raytrace([0, 0], [2, 5]) == [
-        [0, 0], [0, 1],
-        [1, 1], [1, 2], [1, 3], [1, 4],
-        [2, 4], [2, 5]
-    ]
-    assert raytrace([0, 1], [0, 4]) == [
-        [0, 1], [0, 2], [0, 3], [0, 4]
-    ]
-
-
-def test_smoothen_path():
-    matrix = [
-        [1, 1, 1, 1, 1],
-        [1, 1, 1, 1, 1],
-        [1, 1, 1, 1, 1],
-        [1, 1, 1, 1, 1],
-        [1, 1, 1, 1, 1]
-    ]
-    grid = Grid(matrix=matrix)
-    path = [
-        [0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [2, 3], [3, 3], [3, 4], [4, 4]
-    ]
-    smooth_path = [
-        [0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [2, 3], [3, 3], [4, 4]
-    ]
-    assert smoothen_path(grid, path) == smooth_path

+ 0 - 15
blob-simulation/python-pathfinding-master/tox.ini

@@ -1,15 +0,0 @@
-[tox]
-envlist = py27, py35, py36
-[testenv]
-commands = py.test
-deps =
-    pytest>=2.3.5, < 3.3
-    pandas
-    numpy
-[testenv:cov]
-deps =
-    coverage
-    {[testenv]deps}
-commands =
-    coverage run --source pathfinding -m py.test
-    coverage report

+ 83 - 40
blob-simulation/simulation/board.py

@@ -3,24 +3,27 @@ import numpy as np
 
 
 class Board:
 class Board:
 
 
-    MAX_BLOB = 255
+    MAX_BLOB = 255.0
+    MIN_BLOB = 0.0
+    MIN_FOOD_BLOB = 50
     DECREASE_BLOB = 0.1
     DECREASE_BLOB = 0.1
+    INIT_FOOD = 100
 
 
     def __init__(self, width, height):
     def __init__(self, width, height):
         self.width = width
         self.width = width
         self.height = height
         self.height = height
 
 
-        self.board_array = np.empty(shape=(width, height), dtype=object)
-        for x in range(self.width):
-            for y in range(self.height):
-                self.board_array[x, y] = Square()
+        self.dropped_blob = np.zeros(shape=(width, height), dtype=float)
+        self.foods = np.zeros(shape=(width, height), dtype=float)
+        self.touched = np.zeros(shape=(width, height), dtype=bool)
 
 
     def save(self):
     def save(self):
-        stream = ''
+        stream = str(self.width) + ' ' + str(self.height) + '\n'
         for y in range(self.height):
         for y in range(self.height):
             for x in range(self.width):
             for x in range(self.width):
-                saved_node = self.board_array[x, y].save() + " "
-                stream += str(saved_node)
+                # TODO change food savings
+                saved_node = "{:d},{:d},{} ".format(self.touched[x, y], self.foods[x, y] != 0, self.dropped_blob[x, y])
+                stream += saved_node
             stream = stream.rstrip(' ')
             stream = stream.rstrip(' ')
             stream += '\n'
             stream += '\n'
 
 
@@ -41,24 +44,56 @@ class Board:
 
 
                 x = 0
                 x = 0
                 for node in nodes:
                 for node in nodes:
-                    self.board_array[x, y].load(node)
+                    values = node.split(',')
+
+                    if len(values) != 3:
+                        print("Error with packaged values !")
+
+                    self.touched[x, y] = values[0] == '1'
+
+                    # TODO Adapt loadings
+                    if values[1] == '1':
+                        self.foods[x, y] = Board.INIT_FOOD
+
+                    self.dropped_blob[x, y] = float(values[2])
                     x += 1
                     x += 1
 
 
                 y += 1
                 y += 1
 
 
     def has_food(self, x, y):
     def has_food(self, x, y):
-        return self.inside(x, y) and self.board_array[x, y].food
+        return self.inside(x, y) and self.foods[x, y] > 0
+
+    def set_food(self, x, y):
+        if not self.foods[x, y] > 0:
+            self.foods[x, y] = Board.INIT_FOOD
+
+    def remove_food(self, x, y):
+        if self.foods[x, y] > 0:
+            self.foods[x, y] = 0
 
 
     def update_blob(self, x, y, change_value):
     def update_blob(self, x, y, change_value):
         if self.inside(x, y):
         if self.inside(x, y):
-            self.board_array[x, y].update_blob(change_value)
+            self.touched[x, y] = True
+            self.dropped_blob[x, y] = max(0, min(self.dropped_blob[x, y] + change_value, Board.MAX_BLOB))
             return True
             return True
         else:
         else:
             return False
             return False
 
 
+    def eat_food(self, x, y, change_value):
+        if self.foods[x, y] > 0:
+            if self.foods[x, y] - change_value >= 0:
+                self.foods[x, y] -= change_value
+            else:
+                change_value = self.foods[x, y]
+                self.foods[x, y] = 0
+        else:
+            change_value = 0
+
+        return change_value, self.foods[x, y] <= 0
+
     def get_blob(self, x, y):
     def get_blob(self, x, y):
         if self.inside(x, y):
         if self.inside(x, y):
-            return self.board_array[x, y].blob
+            return self.dropped_blob[x, y]
         else:
         else:
             return None
             return None
 
 
@@ -67,43 +102,51 @@ class Board:
 
 
     def is_touched(self, x, y):
     def is_touched(self, x, y):
         if self.inside(x, y):
         if self.inside(x, y):
-            return self.board_array[x, y].touched
+            return self.touched[x, y]
         else:
         else:
             return False
             return False
 
 
+    def get_cover(self, half_board=0):
+        if half_board == 1:
+            val = np.sum(self.touched[:, 0:int(self.height/2)]) * 2
+        elif half_board == 2:
+            val = np.sum(self.touched[:, int(self.height/2):self.height]) * 2
+        else:
+            val = np.sum(self.touched)
+
+        return val / self.height / self.width * 100
+
+    def get_blob_total(self):
+        total = 0
+        for x in range(self.width):
+            for y in range(self.height):
+                total += self.dropped_blob[x, y]
+
+        return total / self.height / self.width / self.MAX_BLOB * 100
+
     def next_turn(self, food_lock=True):
     def next_turn(self, food_lock=True):
         for x in range(self.width):
         for x in range(self.width):
             for y in range(self.height):
             for y in range(self.height):
-                if self.board_array[x, y].touched:
-                    if not (food_lock and self.board_array[x,y].food):
-                        self.board_array[x, y].update_blob(-Board.DECREASE_BLOB)
+                if self.touched[x, y]:
+                    if not (food_lock and self.foods[x, y] > 0 and self.dropped_blob[x, y] <= Board.MIN_FOOD_BLOB):
+                        self.update_blob(x, y, -Board.DECREASE_BLOB)
 
 
     def reset(self, x, y):
     def reset(self, x, y):
         if self.inside(x, y):
         if self.inside(x, y):
-            self.board_array[x, y] = Square()
-
-
-class Square:
-
-    def __init__(self):
-        self.food = False
-        self.touched = False
-        self.blob = 0
+            self.touched[x, y] = False
+            self.dropped_blob[x, y] = 0
+            self.foods[x, y] = 0
 
 
-    def update_blob(self, change_value):
-        self.touched = True
-        self.blob += change_value
-        self.blob = max(0, min(self.blob, Board.MAX_BLOB))
-
-    def save(self):
-        return format(self.touched, 'd') + "," + format(self.food, 'd') + "," + str(self.blob)
-
-    def load(self, node):
-        values = node.split(',')
+    def compare(self, board):
+        if board.height != self.height and board.width != self.width:
+            print("Size don't match !")
+            return None
 
 
-        if len(values) != 3:
-            print("Error with packaged values !")
+        board_comp = Board(self.width, self.height)
+        for x in range(self.width):
+            for y in range(self.height):
+                board_comp.foods[x, y] = board.foods[x, y] - self.foods[x, y]
+                board_comp.touched[x, y] = self.touched[x, y] == board.touched[x, y]
+                board_comp.dropped_blob[x, y] = board.dropped_blob[x, y] - self.dropped_blob[x, y]
 
 
-        self.touched = values[0] == '1'
-        self.food = values[1] == '1'
-        self.blob = float(values[2])
+        return board_comp

+ 13 - 0
blob-simulation/simulation/default/blob.json

@@ -0,0 +1,13 @@
+{
+    "food": [],
+    "max_scouters": 2,
+    "min_scouters": 2,
+	"drop": 1,
+	"min_harvest": 100,
+	"max_harvest": 200,
+	"eat": 1,
+	"harvest": 10,
+	"use_diagonal": true,
+	"explore_ratio": 0.2,
+	"search_local_on_food": true
+}

+ 5 - 0
blob-simulation/simulation/default/player.json

@@ -0,0 +1,5 @@
+{
+    "clean_top": true,
+	"food_size": 5,
+	"use_food_circle": true
+}

+ 44 - 28
blob-simulation/simulation/interface.py

@@ -1,5 +1,4 @@
 import pygame
 import pygame
-import random
 import time
 import time
 import datetime
 import datetime
 import os.path
 import os.path
@@ -10,6 +9,12 @@ from simulation.board import Board
 
 
 class Interface:
 class Interface:
 
 
+    FOOD_COLOR = (244, 210, 128) # (0, 150, 0)
+    TOUCHED_COLOR = (218, 196, 136) # (50, 50, 0)
+    BLOB_LOWER_COLOR = (206, 182, 86) # (255, 255, 0)
+    BLOB_HIGHER_COLOR = (162, 106, 59) # (255, 0, 0)
+    BACKGROUND = (120, 120, 120)
+
     def __init__(self, board, player, blob, scale, save_dir):
     def __init__(self, board, player, blob, scale, save_dir):
         """
         """
         :type board: Board
         :type board: Board
@@ -49,26 +54,34 @@ class Interface:
         height = self.board.height * self.scale
         height = self.board.height * self.scale
 
 
         game_surface = pygame.Surface((self.board.width, self.board.height))
         game_surface = pygame.Surface((self.board.width, self.board.height))
-        board_array = pygame.PixelArray(game_surface)
+        pixel_array = pygame.PixelArray(game_surface)
+
         for x in range(self.board.width):
         for x in range(self.board.width):
             for y in range(self.board.height):
             for y in range(self.board.height):
-                board_array[x,y] = (0,0,0)
-                if self.board.is_touched(x, y):
-                    board_array[x, y] = (50, 50, 0)
-
-                val = self.board.get_blob(x, y)
-                val = max(0, min(val, 255))
-                if val != 0:
-                    board_array[x, y] = (255, 255 - val, 0)
+                pixel_array[x, y] = Interface.BACKGROUND
 
 
                 if self.board.has_food(x, y):
                 if self.board.has_food(x, y):
-                    board_array[x, y] = (0, 150, 0)
+                    pixel_array[x, y] = Interface.FOOD_COLOR
 
 
-                if self.show_ants:
-                    for scouter in self.blob.scouters:
-                        board_array[scouter.x, scouter.y] = (255, 255, 255)
+                if self.board.is_touched(x, y):
+                    pixel_array[x, y] = Interface.TOUCHED_COLOR
 
 
-        del board_array
+                val = self.board.get_blob(x, y)
+                if val != Board.MIN_BLOB:
+                    val = (val - Board.MIN_BLOB) / (Board.MAX_BLOB - Board.MIN_BLOB)
+                    red = (Interface.BLOB_HIGHER_COLOR[0] - Interface.BLOB_LOWER_COLOR[0]) * val + \
+                          Interface.BLOB_LOWER_COLOR[0]
+                    green = (Interface.BLOB_HIGHER_COLOR[1] - Interface.BLOB_LOWER_COLOR[1]) * val + \
+                          Interface.BLOB_LOWER_COLOR[1]
+                    blue = (Interface.BLOB_HIGHER_COLOR[2] - Interface.BLOB_LOWER_COLOR[2]) * val + \
+                          Interface.BLOB_LOWER_COLOR[2]
+                    pixel_array[x, y] = (red, green, blue)
+
+        if self.show_ants:
+            for scouter in self.blob.scouters:
+                pixel_array[scouter.x, scouter.y] = (255, 255, 255)
+
+        del pixel_array
 
 
         game_window = pygame.transform.scale(game_surface, (width, height))
         game_window = pygame.transform.scale(game_surface, (width, height))
 
 
@@ -87,11 +100,11 @@ class Interface:
         f.write(self.board.save())
         f.write(self.board.save())
         f.close()
         f.close()
 
 
-        f = open(self.save_dir + ts + ".blob", 'w')
+        f = open(self.save_dir + ts + ".blob.json", 'w')
         f.write(self.blob.save())
         f.write(self.blob.save())
         f.close()
         f.close()
 
 
-        f = open(self.save_dir + ts + ".player", 'w')
+        f = open(self.save_dir + ts + ".player.json", 'w')
         f.write(self.player.save())
         f.write(self.player.save())
         f.close()
         f.close()
 
 
@@ -128,7 +141,10 @@ class Interface:
         elif event.type == MOUSEBUTTONDOWN and event.button == 1:  # Right Click
         elif event.type == MOUSEBUTTONDOWN and event.button == 1:  # Right Click
             x = int(pygame.mouse.get_pos()[0]/self.scale)
             x = int(pygame.mouse.get_pos()[0]/self.scale)
             y = int(pygame.mouse.get_pos()[1]/self.scale)
             y = int(pygame.mouse.get_pos()[1]/self.scale)
-            self.player.set_food(x, y)
+            if self.board.has_food(x, y):
+                self.player.remove_food(x, y)
+            else:
+                self.player.set_food(x, y)
 
 
         elif event.type == KEYDOWN and event.key == 99:  # C Letter
         elif event.type == KEYDOWN and event.key == 99:  # C Letter
             self.player.clean_board()
             self.player.clean_board()
@@ -137,8 +153,11 @@ class Interface:
             self.player.set_random_food(10, not self.player.clean_top)
             self.player.set_random_food(10, not self.player.clean_top)
 
 
         elif event.type == KEYDOWN and event.key == 104:  # H Letter
         elif event.type == KEYDOWN and event.key == 104:  # H Letter
-            size_percent = self.player.check_blob_size()
-            print("Current blob size percent: " + str(size_percent))
+            up_size_percent, down_size_percent = self.player.check_blob_cover()
+            print("Blob covering:")
+            print("\t{:.2f}% of the board.".format((up_size_percent + down_size_percent)/2))
+            print("\t{:.2f}% of the upper board.".format(up_size_percent))
+            print("\t{:.2f}% of the lower board.".format(down_size_percent))
 
 
         # BLOB ACTIONS
         # BLOB ACTIONS
         elif event.type == KEYDOWN and event.key == 112:  # P Letter
         elif event.type == KEYDOWN and event.key == 112:  # P Letter
@@ -148,16 +167,13 @@ class Interface:
             self.do_step = True
             self.do_step = True
 
 
         elif event.type == KEYDOWN and event.key == 107:  # K Letter
         elif event.type == KEYDOWN and event.key == 107:  # K Letter
-            if len(self.blob.scouters) > 0:
-                nbr = random.randrange(len(self.blob.scouters))
-                self.blob.knowledge['max_scouters'] -= 1
-                del self.blob.scouters[nbr]
-                print("Scouter killed ! - Total : " + str(len(self.blob.scouters)))
+            if self.blob.knowledge['min_scouters'] > 0:
+                self.blob.knowledge['min_scouters'] -= 1
+                print("New minimal scouters : " + str(self.blob.knowledge['min_scouters']) + " - Currently : " + str(len(self.blob.scouters)))
 
 
         elif event.type == KEYDOWN and event.key == 113:  # A letter
         elif event.type == KEYDOWN and event.key == 113:  # A letter
-            self.blob.knowledge['max_scouters'] += 1
-            self.blob.add_scouter()
-            print("New scouter ! - Total : " + str(len(self.blob.scouters)))
+            self.blob.knowledge['min_scouters'] += 1
+            print("New minimal scouters : " + str(self.blob.knowledge['min_scouters']) + " - Currently : " + str(len(self.blob.scouters)))
 
 
         elif event.type == KEYDOWN:
         elif event.type == KEYDOWN:
             print("Unrecognised key code : " + str(event.key))
             print("Unrecognised key code : " + str(event.key))

+ 0 - 16
blob-simulation/simulation/logic/actions.py

@@ -1,16 +0,0 @@
-
-class Actions:
-
-    UP = (0, -1)
-    LEFT = (-1, 0)
-    RIGHT = (1, 0)
-    DOWN = (0, 1)
-
-    LEFT_UP = (-1, -1)
-    LEFT_DOWN = (-1, 1)
-    RIGHT_UP = (1, -1)
-    RIGHT_DOWN = (1, 1)
-
-    ACTIONS_SIMPLE = [LEFT, RIGHT, DOWN, UP]
-    ACTIONS_DIAG = [LEFT_UP, LEFT_DOWN, RIGHT_UP, RIGHT_DOWN]
-    ACTIONS_ALL = ACTIONS_SIMPLE + ACTIONS_DIAG

+ 60 - 0
blob-simulation/simulation/logic/advanced_scouter.py

@@ -0,0 +1,60 @@
+import random
+import numpy as np
+
+from simulation.logic.sensing_scouter import SensingScouter
+
+
+class AdvancedScouter(SensingScouter):
+    """
+           Knowledge used:
+                - explore_ratio : (float, between 0 and 1) Set the ratio between exploring globally and exploring
+                locally
+                - search_local_on_food : when stepping on food, automatically search locally
+    """
+
+    def __init__(self, board, knowledge, x, y, use_diagonal=False, sight_see=3, light_compute=True):
+        SensingScouter.__init__(self, board, knowledge, x, y, use_diagonal, sight_see, light_compute)
+        self.state = 0
+
+    def choose_goal(self):
+        if self.state == 0:
+            if not (self.board.has_food(self.x, self.y) and self.knowledge['search_local_on_food']) \
+                    and self.knowledge['explore_ratio'] < random.random():
+                self.state = 1
+            return self.choose_local_goal()
+        else:
+            if self.knowledge['explore_ratio'] >= random.random():
+                self.state = 0
+            return self.choose_global_goal()
+
+    def choose_local_goal(self):
+        return SensingScouter.choose_goal(self)
+
+    def choose_global_goal(self):
+        x0, y0 = max(0, self.x - self.sight_see), max(0, self.y - self.sight_see)
+        x1, y1 = min(self.board.width, self.x + self.sight_see + 1), min(self.board.height, self.y + self.sight_see + 1)
+
+        scores = np.zeros((x1 - x0, y1 - y0), dtype=float)
+        for x in range(x1 - x0):
+            for y in range(y1 - y0):
+                local_x0, local_y0 = max(x0, x0 + x - self.sight_see), max(y0, y0 + y - self.sight_see)
+                local_x1, local_y1 = min(x1, x0 + x + self.sight_see + 1),  min(y1, y0 + y + self.sight_see + 1)
+
+                scores[x, y] = np.sum(self.board.dropped_blob[local_x0:local_x1, local_y0:local_y1])
+                total_area = (y1-y0) * (x1-x0)
+                scores[x, y] = scores[x, y] / total_area
+
+        min_indices = np.where(scores == np.min(scores))
+
+        if len(min_indices[0]) == 0:
+            return None
+        else:
+            i = np.random.randint(len(min_indices[0]))
+            return min_indices[0][i] + x0, min_indices[1][i] + y0
+
+    def move(self):
+        if self.board.has_food(self.x, self.y) and self.knowledge['search_local_on_food'] and self.state == 1:
+            self.goal = None
+            self.state = 0 # Food found, search locally
+
+        SensingScouter.move(self)

+ 3 - 4
blob-simulation/simulation/logic/dumb_scouter.py

@@ -5,19 +5,18 @@ from simulation.board import Board
 class DumbScouter:
 class DumbScouter:
     """ Dumb scouter searching food randomly and without any knowledge """
     """ Dumb scouter searching food randomly and without any knowledge """
 
 
-    def __init__(self, board, knowledge, x, y, drop_value):
+    def __init__(self, board, knowledge, x, y):
         """
         """
         :type board: Board
         :type board: Board
         :type knowledge: dict
         :type knowledge: dict
         :type x: int
         :type x: int
         :type y: int
         :type y: int
-        :type drop_value: float
         """
         """
         self.board = board
         self.board = board
         self.knowledge = knowledge
         self.knowledge = knowledge
         self.x = x
         self.x = x
         self.y = y
         self.y = y
-        self.drop_value = drop_value
+        self.drop = self.knowledge['drop']
 
 
     def move(self):
     def move(self):
         x = self.x + random.randint(-1, 1)
         x = self.x + random.randint(-1, 1)
@@ -27,4 +26,4 @@ class DumbScouter:
             self.y = y
             self.y = y
 
 
     def update(self):
     def update(self):
-        self.board.update_blob(self.x, self.y, self.drop_value)
+        self.board.update_blob(self.x, self.y, self.drop)

+ 33 - 28
blob-simulation/simulation/logic/fsm_ant.py

@@ -2,39 +2,37 @@ from simulation.board import Board
 from simulation.logic.dumb_scouter import DumbScouter
 from simulation.logic.dumb_scouter import DumbScouter
 from simulation.logic.gatherer import Gatherer
 from simulation.logic.gatherer import Gatherer
 from simulation.logic.sensing_scouter import SensingScouter
 from simulation.logic.sensing_scouter import SensingScouter
+from simulation.logic.advanced_scouter import AdvancedScouter
 
 
 
 
 class FSMAnt(DumbScouter):
 class FSMAnt(DumbScouter):
 
 
-    # Max value an ant has to store to stop being starved
-    MAX_HARVESTING = 100
-
-    # Value an ant eat to do a step
-    EAT_VALUE = 1
-
-    # Value an ant collect by stepping on food
-    HARVEST_VALUE = 10
+    """
+        Knowledge used:
+            - min_harvest: (float) Min value an ant has to store to stop being starved
+            - max_harvest: (float) Max value an ant can carry
+            - eat: (float) Value an ant eat to do a step
+            - harvest: (float) Value an ant collect by stepping on food
+            - use_diagonal: (bool) Allow ants to use diagonals to travel
+    """
 
 
     # Multiplication factor for drop value (see blob variable) when an ant is starving
     # Multiplication factor for drop value (see blob variable) when an ant is starving
     RATIO_DROP_STARVE = 2
     RATIO_DROP_STARVE = 2
 
 
-    USE_DIAGONAL = False
-
-    def __init__(self, board, knowledge, x, y, drop_value):
+    def __init__(self, board, knowledge, x, y):
         """
         """
         :type board: Board
         :type board: Board
         :type knowledge: dict
         :type knowledge: dict
         :type x: int
         :type x: int
         :type y: int
         :type y: int
-        :type drop_value: float
         """
         """
-        DumbScouter.__init__(self, board, knowledge, x, y, drop_value)
-        self.gatherer_logic = Gatherer(board, knowledge, x, y, drop_value, FSMAnt.USE_DIAGONAL)
-        self.scouting_logic = SensingScouter(board, knowledge, x, y, drop_value)
+        DumbScouter.__init__(self, board, knowledge, x, y)
+        self.gatherer_logic = Gatherer(board, knowledge, x, y, self.knowledge["use_diagonal"])
+        self.scouting_logic = AdvancedScouter(board, knowledge, x, y, self.knowledge["use_diagonal"])
 
 
-        self.harvest = FSMAnt.MAX_HARVESTING
+        self.stored = self.knowledge["min_harvest"]
         self.starving = False
         self.starving = False
-        self.init_drop = drop_value
+        self.init_drop = self.knowledge['drop']
 
 
     def move(self):
     def move(self):
         if self.starving:
         if self.starving:
@@ -52,31 +50,38 @@ class FSMAnt(DumbScouter):
         self.gatherer_logic.y = self.y
         self.gatherer_logic.y = self.y
 
 
     def init_scouting(self):
     def init_scouting(self):
+        self.scouting_logic.reset()
         self.scouting_logic.x = self.x
         self.scouting_logic.x = self.x
         self.scouting_logic.y = self.y
         self.scouting_logic.y = self.y
-        self.drop_value = self.init_drop
+        self.drop = self.init_drop
 
 
     def update(self):
     def update(self):
-        if self.harvest > 0 and self.starving:
-            self.drop_value = FSMAnt.RATIO_DROP_STARVE * self.init_drop
+        # if self.harvest > 0 and self.starving:
+        #     self.drop = FSMAnt.RATIO_DROP_STARVE * self.init_drop
 
 
+        self.drop = self.init_drop * self.stored
         DumbScouter.update(self)
         DumbScouter.update(self)
 
 
-        if len(self.knowledge['food']) != 0 and not self.starving:
-            self.harvest -= FSMAnt.EAT_VALUE
-            self.harvest = max(0, self.harvest)
+        if not self.starving:
+            self.stored -= self.knowledge["eat"]
+            self.stored = max(0, self.stored)
 
 
         if self.board.has_food(self.x, self.y):
         if self.board.has_food(self.x, self.y):
             if len(self.knowledge['food']) == 1:
             if len(self.knowledge['food']) == 1:
-                self.harvest = FSMAnt.MAX_HARVESTING
+                wanted = min(self.knowledge["min_harvest"], self.knowledge["max_harvest"] - self.stored)
             else:
             else:
-                self.harvest += FSMAnt.HARVEST_VALUE
-                self.harvest = min(self.harvest, FSMAnt.MAX_HARVESTING)
+                wanted = min(self.knowledge["harvest"], self.knowledge["max_harvest"] - self.stored)
+
+            received, finished = self.board.eat_food(self.x, self.y, wanted)
+            self.stored += received
+
+            if finished:
+                self.knowledge['food'].remove((self.x, self.y))
 
 
-        if self.harvest == 0 and not self.starving:
+        if self.stored == 0 and not self.starving:
             self.starving = True
             self.starving = True
             self.init_gathering()
             self.init_gathering()
 
 
-        if self.harvest == FSMAnt.MAX_HARVESTING and self.starving:
+        if self.stored >= self.knowledge["min_harvest"] and self.starving:
             self.starving = False
             self.starving = False
             self.init_scouting()
             self.init_scouting()

+ 76 - 28
blob-simulation/simulation/logic/gatherer.py

@@ -1,4 +1,5 @@
 import random
 import random
+import numpy as np
 
 
 from pathfinding.core.diagonal_movement import DiagonalMovement
 from pathfinding.core.diagonal_movement import DiagonalMovement
 from pathfinding.core.grid import Grid
 from pathfinding.core.grid import Grid
@@ -9,31 +10,77 @@ from simulation.logic.dumb_scouter import DumbScouter
 
 
 
 
 class Gatherer(DumbScouter):
 class Gatherer(DumbScouter):
-    def __init__(self, board, knowledge, x, y, drop_value, use_diagonal=True, light_compute=True):
-        DumbScouter.__init__(self, board, knowledge, x, y, drop_value)
+    def __init__(self, board, knowledge, x, y, use_diagonal=True, sight_see=-1, light_compute=True):
+        DumbScouter.__init__(self, board, knowledge, x, y)
 
 
         self.use_diagonal = use_diagonal
         self.use_diagonal = use_diagonal
         self.light_compute = light_compute
         self.light_compute = light_compute
+        self.sight_see = sight_see if sight_see > 0 else max(self.board.width, self.board.height)
 
 
         self.goal = None
         self.goal = None
         self.path = []
         self.path = []
 
 
-    def get_matrix(self):
-            matrix = []
-            for y in range(self.board.height):
-                matrix.append([])
-                for x in range(self.board.width):
-                    matrix[y].append(
-                        0 if self.board.board_array[x, y].blob <= 0
-                        else Board.MAX_BLOB - self.board.board_array[x, y].blob + 1)
-            return matrix
+    def get_matrix(self, x0, y0, x1, y1):
+            width = x1 - x0
+            height = y1 - y0
+            matrix = np.zeros((width, height))
+            for y in range(height):
+                for x in range(width):
+                    if self.board.get_blob(x0 + x, y0 + y) > 0:
+                        matrix[x, y] = 1 + (Board.MAX_BLOB - self.board.get_blob(x0 + x, y0 + y))
+                    else:
+                        if self.board.is_touched(x0 + x, y0 + y):
+                            matrix[x, y] = Board.MAX_BLOB * 2
+                        else:
+                            matrix[x, y] = 0
+            return np.transpose(matrix)
+
+    def compute_sight_see_goal(self, x0, y0, x1, y1):
+        if x0 <= self.goal[0] < x1 and y0 <= self.goal[1] < y1:
+            # Goal in sight_see
+            return self.goal[0] - x0, self.goal[1] - y0
+
+        delta_x = self.x - self.goal[0]
+        delta_y = self.y - self.goal[1]
+
+        t_x = None
+        if delta_x != 0:
+            x_collision = x0 if delta_x > 0 else x1 - 1
+            t_x = (x_collision - self.goal[0]) / delta_x
+
+        t_y = None
+        if delta_y != 0:
+            y_collision = y0 if delta_y >= 0 else y1 - 1
+            t_y = (y_collision - self.goal[1]) / delta_y
+
+        if t_x is None or not (0 <= t_x <= 1):
+            t = t_y
+        elif t_y is None or not (0 <= t_y <= 1):
+            t = t_x
+        else:
+            t = min(t_x, t_y)
+
+        symb_goal = (int(self.goal[0] + t * delta_x), int(self.goal[1] + t * delta_y))
+
+        found = self.board.is_touched(symb_goal[0], symb_goal[1])
+        while not found and t <= 1:
+            inc = 1 / (self.board.width + self.board.height)
+            t += inc
+            symb_goal = (int(self.goal[0] + t * delta_x), int(self.goal[1] + t * delta_y))
+            found = self.board.is_touched(symb_goal[0], symb_goal[1])
+
+        return symb_goal[0] - x0, symb_goal[1] - y0
 
 
     def best_way_to(self):
     def best_way_to(self):
+        x0, y0 = max(0, self.x - self.sight_see), max(0, self.y - self.sight_see)
+        x1, y1 = min(self.board.width, self.x + self.sight_see + 1), min(self.board.height, self.y + self.sight_see + 1)
+
+        grid = Grid(matrix=self.get_matrix(x0, y0, x1, y1))
 
 
-        grid = Grid(matrix=self.get_matrix())
+        x_goal, y_goal = self.compute_sight_see_goal(x0, y0, x1, y1)
 
 
-        start = grid.node(self.x, self.y)
-        end = grid.node(self.goal[0], self.goal[1])
+        start = grid.node(self.x - x0, self.y - y0)
+        end = grid.node(x_goal, y_goal)
 
 
         if self.use_diagonal:
         if self.use_diagonal:
             finder = AStarFinder(diagonal_movement=DiagonalMovement.always)
             finder = AStarFinder(diagonal_movement=DiagonalMovement.always)
@@ -42,20 +89,25 @@ class Gatherer(DumbScouter):
 
 
         self.path, runs = finder.find_path(start, end, grid)
         self.path, runs = finder.find_path(start, end, grid)
         self.path = self.path[1:]
         self.path = self.path[1:]
+        for i, step in enumerate(self.path):
+            self.path[i] = (step[0] + x0, step[1] + y0)
 
 
     def reached(self, goal):
     def reached(self, goal):
         return goal is not None and self.x == goal[0] and self.y == goal[1]
         return goal is not None and self.x == goal[0] and self.y == goal[1]
 
 
     def choose_goal(self):
     def choose_goal(self):
-        goals = []
-        for food in self.knowledge['food']:
-            if not self.reached(food):
-                goals.append(food)
-
-        if len(goals) == 0:
+        if len(self.knowledge['food']) == 0:
             return None
             return None
+        elif len(self.knowledge['food']) == 1:
+            if self.reached(self.knowledge['food'][0]):
+                return None
+            else:
+                return self.knowledge['food'][0]
         else:
         else:
-            return goals[random.randrange(len(goals))]
+            i = random.randrange(len(self.knowledge['food']))
+            while self.reached(self.knowledge['food'][i]):
+                i = random.randrange(len(self.knowledge['food']))
+            return self.knowledge['food'][i]
 
 
     def reset(self):
     def reset(self):
         self.goal = None
         self.goal = None
@@ -64,6 +116,7 @@ class Gatherer(DumbScouter):
         self.y = 0
         self.y = 0
 
 
     def move(self):
     def move(self):
+        # Scouter has no more goal
         if self.goal is None or self.goal not in self.knowledge['food']:
         if self.goal is None or self.goal not in self.knowledge['food']:
             self.goal = self.choose_goal()
             self.goal = self.choose_goal()
             self.path = []
             self.path = []
@@ -72,6 +125,7 @@ class Gatherer(DumbScouter):
             if self.goal is None:
             if self.goal is None:
                 return
                 return
 
 
+        # Scouter has no more path to goal
         if len(self.path) == 0 or not self.light_compute:
         if len(self.path) == 0 or not self.light_compute:
             self.best_way_to()
             self.best_way_to()
 
 
@@ -86,13 +140,7 @@ class Gatherer(DumbScouter):
         self.x = new_pos[0]
         self.x = new_pos[0]
         self.y = new_pos[1]
         self.y = new_pos[1]
 
 
+        # Scouter reached goal
         if self.reached(self.goal):
         if self.reached(self.goal):
             self.goal = None
             self.goal = None
             self.path = []
             self.path = []
-
-        if self.reached(self.goal) or (self.goal not in self.knowledge['food']):
-            val = self.choose_goal()
-            if val is None:
-                return
-            else:
-                self.goal = val

+ 86 - 29
blob-simulation/simulation/logic/main.py

@@ -9,35 +9,34 @@ from simulation.board import Board
 
 
 class Blob_Manager:
 class Blob_Manager:
 
 
-    DROP_VALUE = 25
-
-    def __init__(self, board, max_scouters):
+    def __init__(self, board, default_knowledge):
         """
         """
         :type board: Board
         :type board: Board
-        :type max_scouters: int
         """
         """
         self.board = board
         self.board = board
         self.knowledge = dict()
         self.knowledge = dict()
-        self.knowledge['food'] = []
-        self.knowledge['max_scouters'] = max_scouters
         self.scouters = []
         self.scouters = []
 
 
-        for _ in range(max_scouters):
-            self.add_scouter()
+        with open(default_knowledge, 'r') as file:
+            self.knowledge.update(json.load(file))
 
 
-    def save(self):
-        return json.dumps(self.knowledge)
+        self.knowledge['food'] = []
+        for x in range(self.board.width):
+            for y in range(self.board.height):
+                if self.board.has_food(x, y) and self.board.is_touched(x, y):
+                    self.knowledge['food'].append((x, y))
+
+        self.knowledge['max_scouters'] = self.compute_max_scouters()
+        while len(self.scouters) < self.knowledge['max_scouters']:
+            self.add_scouter()
 
 
-    def load(self, filename):
-        with open(filename, 'r') as file:
-            line = file.readline()
-            json_acceptable_string = line.replace("'", "\"")
-            k = json.loads(json_acceptable_string)
-            self.knowledge['food'] = [tuple(x) for x in k['food']]
-            self.knowledge['max_scouters'] = k['max_scouters']
+        print("Scouters: " + str(len(self.scouters)))
 
 
-            while len(self.scouters) < self.knowledge['max_scouters']:
-                self.add_scouter()
+    def save(self):
+        d = self.knowledge.copy()
+        del d["food"]
+        del d["max_scouters"]
+        return json.dumps(d, indent=4, sort_keys=True)
 
 
     def move(self):
     def move(self):
         deads = []
         deads = []
@@ -47,10 +46,26 @@ class Blob_Manager:
             if old == (scouter.x, scouter.y):
             if old == (scouter.x, scouter.y):
                 deads.append(scouter)
                 deads.append(scouter)
             else:
             else:
+                if self.board.has_food(scouter.x, scouter.y) and (scouter.x, scouter.y) not in self.knowledge['food']:
+                    self.food_discovered(scouter.x, scouter.y)
+
                 scouter.update()
                 scouter.update()
 
 
-            if self.board.has_food(scouter.x, scouter.y) and (scouter.x, scouter.y) not in self.knowledge['food']:
-                self.food_discovered(scouter.x, scouter.y)
+        new_max = self.compute_max_scouters()
+        if new_max != self.knowledge['max_scouters']:
+            print("Scouters: " + str(new_max))
+        self.knowledge['max_scouters'] = new_max
+
+        scouters_qt = len(self.scouters)
+        diff = self.knowledge['max_scouters'] - scouters_qt
+
+        if diff > 0:
+            for _ in range(diff):
+                self.add_scouter()
+
+        elif diff < 0:
+            for _ in range(-diff):
+                self.remove_scouter()
 
 
         for dead in deads:
         for dead in deads:
             self.scouters.remove(dead)
             self.scouters.remove(dead)
@@ -62,14 +77,53 @@ class Blob_Manager:
                 index = random.randrange(len(self.knowledge['food']))
                 index = random.randrange(len(self.knowledge['food']))
                 (x, y) = self.knowledge['food'][index]
                 (x, y) = self.knowledge['food'][index]
             else:
             else:
-                print("This will be nice in the future")
-                x = 0
-                y = 0
+                x, y = self.find_blob_square()
 
 
-            self.scouters.append(FSMAnt(self.board, self.knowledge, x, y, Blob_Manager.DROP_VALUE))
+            self.scouters.append(FSMAnt(self.board, self.knowledge, x, y))
         else:
         else:
             print("Max scouters already reached !")
             print("Max scouters already reached !")
 
 
+    def remove_scouter(self):
+        nbr = random.randrange(len(self.scouters))
+        del self.scouters[nbr]
+
+    def compute_max_scouters(self):
+        real_size_factor = 1 / 360
+        blob_size_factor = 3 / 4
+        cover_factor = 1 / 4
+
+        blob_qt = self.board.get_blob_total()
+
+        scouters_by_cover = int(self.board.get_cover())
+        scouters_by_size = int(blob_qt)
+
+        total_scouters = int((blob_size_factor * scouters_by_size + cover_factor * scouters_by_cover)
+                             * (real_size_factor * self.board.height * self.board.width / 100))
+
+        return max(self.knowledge['min_scouters'], total_scouters)
+
+    def find_blob_square(self):
+        availables = []
+        total_blob = 0
+        for x in range(self.board.width):
+            for y in range(self.board.height):
+                if self.board.is_touched(x, y):
+                    qt = self.board.get_blob(x, y) + 1
+                    total_blob += qt
+                    availables.append(((x, y), qt))
+
+        if len(availables) == 0:
+            return 0, 0
+
+        # Random need cast to integer
+        # Floor cast will make sure a solution is found
+        index_pond = random.randrange(int(total_blob))
+        acc = 0
+        for square, qt in availables:
+            acc += qt
+            if acc >= index_pond:
+                return square
+
     def reset(self, x, y):
     def reset(self, x, y):
         for scouter in self.scouters.copy():
         for scouter in self.scouters.copy():
             if scouter.x == x and scouter.y == y:
             if scouter.x == x and scouter.y == y:
@@ -82,9 +136,12 @@ class Blob_Manager:
 
 
     def food_discovered(self, x, y):
     def food_discovered(self, x, y):
         self.knowledge['food'].append((x, y))
         self.knowledge['food'].append((x, y))
-        self.knowledge['max_scouters'] += 1
+        # self.knowledge['max_scouters'] += 1
+
+        # for _ in range(1):
+        #     self.scouters.append(FSMAnt(self.board, self.knowledge, x, y, Blob_Manager.DROP_VALUE))
 
 
-        for _ in range(1):
-            self.scouters.append(FSMAnt(self.board, self.knowledge, x, y, Blob_Manager.DROP_VALUE))
+        # print("Food discovered in (" + str(x) + ", " + str(y) + ")")
 
 
-        print("Food discovered in (" + str(x) + ", " + str(y) + ") - Total scouters : " + str(len(self.scouters)))
+    def food_destroyed(self, x, y):
+        self.knowledge['food'].remove((x, y))

+ 106 - 19
blob-simulation/simulation/logic/sensing_scouter.py

@@ -1,28 +1,115 @@
 import random
 import random
+import numpy as np
+
+from pathfinding.core.diagonal_movement import DiagonalMovement
+from pathfinding.core.grid import Grid
+from pathfinding.finder.a_star import AStarFinder
 
 
 from simulation.board import Board
 from simulation.board import Board
 from simulation.logic.dumb_scouter import DumbScouter
 from simulation.logic.dumb_scouter import DumbScouter
-from simulation.logic.actions import Actions
 
 
 
 
 class SensingScouter(DumbScouter):
 class SensingScouter(DumbScouter):
 
 
+    def __init__(self, board, knowledge, x, y, use_diagonal=False, sight_see=-1, light_compute=True):
+        DumbScouter.__init__(self, board, knowledge, x, y)
+
+        self.use_diagonal = use_diagonal
+        self.sight_see = sight_see if sight_see > 0 else 1
+        self.light_compute = light_compute
+        self.goal = None
+        self.path = []
+
+    def get_matrix(self, x0, y0, x1, y1):
+        width = x1 - x0
+        height = y1 - y0
+        matrix = np.zeros((width, height))
+        for y in range(height):
+            for x in range(width):
+                if self.board.get_blob(x0 + x, y0 + y) > 0:
+                    matrix[x, y] = (1 + Board.MAX_BLOB - self.board.get_blob(x0 + x, y0 + y)) * 1.5
+                elif self.board.is_touched(x0 + x, y0 + y):
+                    matrix[x, y] = Board.MAX_BLOB * 2
+                else:
+                    matrix[x, y] = 1
+        return np.transpose(matrix)
+
+    def choose_goal(self):
+        x0, y0 = max(0, self.x - self.sight_see), max(0, self.y - self.sight_see)
+        x1, y1 = min(self.board.width, self.x + self.sight_see + 1), min(self.board.height, self.y + self.sight_see + 1)
+
+        mask = np.zeros((x1 - x0, y1 - y0), dtype=bool)
+        mask[self.x - x0, self.y - y0] = True
+        see = np.ma.masked_where(mask, self.board.dropped_blob[x0:x1, y0:y1])
+        min_indices = np.ma.where(see == np.min(see))
+
+        if len(min_indices[0]) == 0:
+            return None
+        else:
+            i = np.random.randint(len(min_indices[0]))
+            return min_indices[0][i] + x0, min_indices[1][i] + y0
+
+    def best_way_to(self):
+        if self.sight_see > 0:
+            x0, y0 = max(0, self.x - self.sight_see), max(0, self.y - self.sight_see)
+            x1, y1 = min(self.board.width, self.x + self.sight_see + 1), min(self.board.height,
+                                                                             self.y + self.sight_see + 1)
+        else:
+            x0, y0 = 0, 0
+            x1, y1 = self.board.width, self.board.height
+
+        grid = Grid(matrix=self.get_matrix(x0, y0, x1, y1))
+
+        start = grid.node(self.x - x0, self.y - y0)
+        end = grid.node(self.goal[0] - x0, self.goal[1] - y0)
+
+        if self.use_diagonal:
+            finder = AStarFinder(diagonal_movement=DiagonalMovement.always)
+        else:
+            finder = AStarFinder(diagonal_movement=DiagonalMovement.never)
+
+        self.path, runs = finder.find_path(start, end, grid)
+        self.path = self.path[1:]
+        for i, step in enumerate(self.path):
+            self.path[i] = (step[0] + x0, step[1] + y0)
+
+    def reached(self, goal):
+        return goal is not None and self.x == goal[0] and self.y == goal[1]
+
     def move(self):
     def move(self):
-        available_dirs = []
-        min_pheromone = Board.MAX_BLOB + 1
-
-        dirs = Actions.ACTIONS_SIMPLE
-        for new_dir in dirs:
-            if 0 <= self.x + new_dir[0] < self.board.width and 0 <= self.y + new_dir[1] < self.board.height:
-                new_pheromone = self.board.get_blob(self.x + new_dir[0], self.y + new_dir[1])
-                if new_pheromone is not None:
-                    if new_pheromone < min_pheromone:
-                        available_dirs = [new_dir]
-                        min_pheromone = new_pheromone
-                    elif new_pheromone == min_pheromone:
-                        available_dirs.append(new_dir)
-
-        if len(available_dirs) != 0:
-            direction = available_dirs[random.randrange(len(available_dirs))]
-            self.x += direction[0]
-            self.y += direction[1]
+        # Scouter has no more goal
+        if self.goal is None: #or self.board.get_blob(self.goal[0], self.goal[1]) != 0:
+            self.goal = self.choose_goal()
+            if self.goal[0] == self.x and self.goal[1] == self.y:
+                print("Shouldn't happen")
+            self.path = []
+
+            # No goal
+            if self.goal is None:
+                return
+
+        # Scouter has no more path to goal
+        if len(self.path) == 0 or not self.light_compute:
+            self.best_way_to()
+
+            # No path found, search another goal next time
+            if len(self.path) == 0:
+                self.goal = None
+                return
+
+        new_pos = self.path[0]
+        self.path = self.path[1:]
+
+        self.x = new_pos[0]
+        self.y = new_pos[1]
+
+        # Scouter reached goal
+        if self.reached(self.goal):
+            self.goal = None
+            self.path = []
+
+    def reset(self):
+        self.goal = None
+        self.path = []
+        self.x = 0
+        self.y = 0

+ 21 - 16
blob-simulation/simulation/play.py

@@ -1,7 +1,7 @@
 import pygame
 import pygame
 import argparse
 import argparse
 import time
 import time
-from os.path import exists
+from os.path import exists, join, splitext
 
 
 from pygame.locals import QUIT
 from pygame.locals import QUIT
 from simulation.interface import Interface
 from simulation.interface import Interface
@@ -16,10 +16,10 @@ def main():
                         help='Board height resolution (default = 40)')
                         help='Board height resolution (default = 40)')
     parser.add_argument('--width', type=int, default=100,
     parser.add_argument('--width', type=int, default=100,
                         help='Board width resolution (default = 100)')
                         help='Board width resolution (default = 100)')
-    parser.add_argument('--scale', type=int, default=10,
+    parser.add_argument('-s', '--scale', type=int, default=10,
                         help='Scaling from board resolution to window resolution (default = x10)')
                         help='Scaling from board resolution to window resolution (default = x10)')
     parser.add_argument('--init_from', type=str,
     parser.add_argument('--init_from', type=str,
-                        help='Initialize game from a save. Pass the filename in saved dir without extension.')
+                        help='Initialize game from a save. Pass the board filename')
     parser.add_argument('--save_dir', type=str, default="save/",
     parser.add_argument('--save_dir', type=str, default="save/",
                         help='Directory where saves are stored.')
                         help='Directory where saves are stored.')
     parser.add_argument('--computing_ratio', type=int, default=1,
     parser.add_argument('--computing_ratio', type=int, default=1,
@@ -27,23 +27,28 @@ def main():
 
 
     args = parser.parse_args()
     args = parser.parse_args()
 
 
+    default_dir = "simulation/default"
+    player_file = join(default_dir, "player.json")
+    blob_file = join(default_dir, "blob.json")
+
     if args.init_from is not None:
     if args.init_from is not None:
-        init_scouters = 0
-    else:
-        init_scouters = 3
+        board_file = join(args.save_dir, args.init_from)
+        assert exists(board_file)
+        root_name = splitext(board_file)[0]
 
 
-    board = Board(args.width, args.height)
-    blob = Blob_Manager(board, init_scouters)
-    player = Player(board, blob)
+        board = Board(args.width, args.height)
+        board.load(args.save_dir + args.init_from)
 
 
-    if args.init_from is not None:
-        assert exists(args.save_dir + args.init_from + ".board") \
-               and exists(args.save_dir + args.init_from + ".blob") \
-               and exists(args.save_dir + args.init_from + ".player")
+        if exists(root_name + ".player.json"):
+            player_file = root_name + ".player.json"
+
+        if exists(root_name + ".blob.json"):
+            blob_file = root_name + ".blob.json"
+    else:
+        board = Board(args.width, args.height)
 
 
-        board.load(args.save_dir + args.init_from + ".board")
-        blob.load(args.save_dir + args.init_from + ".blob")
-        player.load(args.save_dir + args.init_from + ".player")
+    blob = Blob_Manager(board, blob_file)
+    player = Player(board, blob, player_file)
 
 
     gui = Interface(board, player, blob, args.scale, args.save_dir)
     gui = Interface(board, player, blob, args.scale, args.save_dir)
 
 

+ 42 - 23
blob-simulation/simulation/player.py

@@ -1,35 +1,42 @@
 from math import ceil
 from math import ceil
 from random import randrange
 from random import randrange
+import json
 
 
 from simulation.board import Board
 from simulation.board import Board
 
 
 
 
 class Player:
 class Player:
 
 
-    def __init__(self, board, blob):
+    def __init__(self, board, blob, default_config):
         """
         """
         :type blob: Blob_Manager
         :type blob: Blob_Manager
         :type board: Board
         :type board: Board
         """
         """
         self.board = board
         self.board = board
         self.blob = blob
         self.blob = blob
-        self.clean_top = True
 
 
-    def save(self):
-        return format(self.clean_top, 'd')
+        with open(default_config, 'r') as file:
+            d = json.load(file)
+
+        self.clean_top = d['clean_top']
+        self.food_size = d['food_size']
+        self.use_circle = d['use_food_circle']
 
 
-    def load(self, filename):
-        with open(filename, 'r') as file:
-            self.clean_top = file.readline() == '1'
+    def save(self):
+        d = dict()
+        d['clean_top'] = self.clean_top
+        d['food_size'] = self.food_size
+        d['use_food_circle'] = self.use_circle
+        return json.dumps(d, indent=4, sort_keys=True)
 
 
     def set_random_food(self, qt, random_top=None):
     def set_random_food(self, qt, random_top=None):
         if random_top is None:  # Randomize over all the board
         if random_top is None:  # Randomize over all the board
             y_offset = 0
             y_offset = 0
             y_range = self.board.height
             y_range = self.board.height
-        if random_top:  # Randomize over the half top board
+        elif random_top:  # Randomize over the half top board
             y_offset = 0
             y_offset = 0
             y_range = ceil(self.board.height / 2)
             y_range = ceil(self.board.height / 2)
-        elif not random_top:  # Randomize over the half bottom board
+        else:  # Randomize over the half bottom board
             y_offset = int(self.board.height / 2)
             y_offset = int(self.board.height / 2)
             y_range = ceil(self.board.height / 2)
             y_range = ceil(self.board.height / 2)
 
 
@@ -41,14 +48,31 @@ class Player:
                 if self.set_food(x, y):
                 if self.set_food(x, y):
                     foods += 1
                     foods += 1
 
 
-    def set_food(self, x, y, size=1):
+    def remove_food(self, x, y):
+        food_remove = False
+        x0, y0 = int(x - self.food_size / 2), int(y - self.food_size / 2)
+        for x_size in range(self.food_size):
+            for y_size in range(self.food_size):
+                if not self.use_circle or (x_size - self.food_size / 2) ** 2 + (y_size - self.food_size / 2) ** 2 <= \
+                        (self.food_size / 2 - 0.5) ** 2:
+                    if self.board.inside(x0 + x_size, y0 + y_size) and not self.board.is_touched(x0 + x_size,
+                                                                                                 y0 + y_size):
+                        self.board.remove_food(x0 + x_size, y0 + y_size)
+                        food_remove = True
+
+        if not food_remove:
+            print("Blob already found it !")
+
+    def set_food(self, x, y):
         food_put = False
         food_put = False
-
-        for x_size in range(size):
-            for y_size in range(size):
-                if 0 <= x + x_size < self.board.width and 0 <= y + y_size < self.board.height:
-                    if not self.board.board_array[x + x_size, y + y_size].touched:
-                        self.board.board_array[x + x_size, y + y_size].food = True
+        x0, y0 = int(x - self.food_size / 2), int(y - self.food_size / 2)
+        for x_size in range(self.food_size):
+            for y_size in range(self.food_size):
+                if not self.use_circle or (x_size - self.food_size / 2) ** 2 + (y_size - self.food_size / 2) ** 2 <= \
+                        (self.food_size / 2 - 0.5) ** 2:
+                    if self.board.inside(x0 + x_size, y0 + y_size) and not self.board.is_touched(x0 + x_size,
+                                                                                                 y0 + y_size):
+                        self.board.set_food(x0 + x_size, y0 + y_size)
                         food_put = True
                         food_put = True
 
 
         if not food_put:
         if not food_put:
@@ -57,13 +81,8 @@ class Player:
 
 
         return True
         return True
 
 
-    def check_blob_size(self):
-        size = 0
-        for x in range(self.board.width):
-            for y in range(self.board.height):
-                if self.board.board_array[x, y].touched:
-                    size += 1
-        return size/(self.board.width * self.board.height) * 100
+    def check_blob_cover(self):
+        return self.board.get_cover(1), self.board.get_cover(2)
 
 
     def clean_board(self):
     def clean_board(self):
         y_range = ceil(self.board.height/2)
         y_range = ceil(self.board.height/2)