Browse Source

V2 - Blob Simulation

Loïc Vanden Bemden 5 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():
-	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):
 
-	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):
-		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__":
-	main()
+    main()

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

@@ -3,9 +3,11 @@ import cv2
 import json
 import datetime
 import time
+import numpy as np
 from shutil import copyfile
 from os.path import exists
 from os import makedirs
+from math import sqrt
 
 
 def main():
@@ -30,6 +32,7 @@ def main():
 
     food_color = FoodColor(img, scale, window_name)
     board_setup = BoardLimits(img, scale, window_name)
+    food_limits = FoodLimits(img, scale, window_name)
 
     done = False
     state = 0
@@ -94,6 +97,13 @@ def main():
 
                 show_menu()
 
+            elif key == "5":
+                state = 3
+                food_limits.clear()
+                food_limits.help()
+                cv2.setMouseCallback(window_name, food_limits.on_mouse)
+
+
             elif key == "s":
                 ts = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H.%M.%S-')
                 if exists(args["config"]):
@@ -102,7 +112,8 @@ def main():
                     copyfile(args["config"], "bkp/" + ts + args["config"])
 
                 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
 
@@ -122,6 +133,12 @@ def main():
                 state = 0
                 show_menu()
 
+        elif state == 3:
+            food_limits.draw()
+            if food_limits.done:
+                state = 0
+                show_menu()
+
 
 def show_menu():
     print("\nCommands: ")
@@ -129,6 +146,7 @@ def show_menu():
     print("\tEnter '2' to setup food color")
     print("\tEnter '3' to insert image aspect ratio")
     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 'q' to quit without saving")
 
@@ -201,34 +219,105 @@ class BoardLimits:
             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.img = img.copy()
         self.scale = scale
-        self.max_qt = max_qt
         self.window_name = window_name
         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)
         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)
 
     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))
+
         if self.enough_data():
             self.confirm()
 
     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:
-            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):
         low_color = [255, 255, 255]
@@ -254,10 +343,7 @@ class FoodColor:
         return {'Low Food Color': l, 'High Food Color': h}
 
     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):
         self.colors = []
@@ -265,18 +351,19 @@ class FoodColor:
         self.done = False
 
     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)
 
     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
             print("--- Color Setup: " + str(self.compute()))
             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__":

+ 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)
 
     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 = 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:
 
-    MAX_BLOB = 255
+    MAX_BLOB = 255.0
+    MIN_BLOB = 0.0
+    MIN_FOOD_BLOB = 50
     DECREASE_BLOB = 0.1
+    INIT_FOOD = 100
 
     def __init__(self, width, height):
         self.width = width
         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):
-        stream = ''
+        stream = str(self.width) + ' ' + str(self.height) + '\n'
         for y in range(self.height):
             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 += '\n'
 
@@ -41,24 +44,56 @@ class Board:
 
                 x = 0
                 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
 
                 y += 1
 
     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):
         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
         else:
             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):
         if self.inside(x, y):
-            return self.board_array[x, y].blob
+            return self.dropped_blob[x, y]
         else:
             return None
 
@@ -67,43 +102,51 @@ class Board:
 
     def is_touched(self, x, y):
         if self.inside(x, y):
-            return self.board_array[x, y].touched
+            return self.touched[x, y]
         else:
             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):
         for x in range(self.width):
             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):
         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 random
 import time
 import datetime
 import os.path
@@ -10,6 +9,12 @@ from simulation.board import Board
 
 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):
         """
         :type board: Board
@@ -49,26 +54,34 @@ class Interface:
         height = self.board.height * self.scale
 
         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 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):
-                    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))
 
@@ -87,11 +100,11 @@ class Interface:
         f.write(self.board.save())
         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.close()
 
-        f = open(self.save_dir + ts + ".player", 'w')
+        f = open(self.save_dir + ts + ".player.json", 'w')
         f.write(self.player.save())
         f.close()
 
@@ -128,7 +141,10 @@ class Interface:
         elif event.type == MOUSEBUTTONDOWN and event.button == 1:  # Right Click
             x = int(pygame.mouse.get_pos()[0]/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
             self.player.clean_board()
@@ -137,8 +153,11 @@ class Interface:
             self.player.set_random_food(10, not self.player.clean_top)
 
         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
         elif event.type == KEYDOWN and event.key == 112:  # P Letter
@@ -148,16 +167,13 @@ class Interface:
             self.do_step = True
 
         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
-            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:
             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:
     """ 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 knowledge: dict
         :type x: int
         :type y: int
-        :type drop_value: float
         """
         self.board = board
         self.knowledge = knowledge
         self.x = x
         self.y = y
-        self.drop_value = drop_value
+        self.drop = self.knowledge['drop']
 
     def move(self):
         x = self.x + random.randint(-1, 1)
@@ -27,4 +26,4 @@ class DumbScouter:
             self.y = y
 
     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.gatherer import Gatherer
 from simulation.logic.sensing_scouter import SensingScouter
+from simulation.logic.advanced_scouter import AdvancedScouter
 
 
 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
     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 knowledge: dict
         :type x: 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.init_drop = drop_value
+        self.init_drop = self.knowledge['drop']
 
     def move(self):
         if self.starving:
@@ -52,31 +50,38 @@ class FSMAnt(DumbScouter):
         self.gatherer_logic.y = self.y
 
     def init_scouting(self):
+        self.scouting_logic.reset()
         self.scouting_logic.x = self.x
         self.scouting_logic.y = self.y
-        self.drop_value = self.init_drop
+        self.drop = self.init_drop
 
     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)
 
-        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 len(self.knowledge['food']) == 1:
-                self.harvest = FSMAnt.MAX_HARVESTING
+                wanted = min(self.knowledge["min_harvest"], self.knowledge["max_harvest"] - self.stored)
             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.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.init_scouting()

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

@@ -1,4 +1,5 @@
 import random
+import numpy as np
 
 from pathfinding.core.diagonal_movement import DiagonalMovement
 from pathfinding.core.grid import Grid
@@ -9,31 +10,77 @@ from simulation.logic.dumb_scouter import 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.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.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):
+        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:
             finder = AStarFinder(diagonal_movement=DiagonalMovement.always)
@@ -42,20 +89,25 @@ class Gatherer(DumbScouter):
 
         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 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
+        elif len(self.knowledge['food']) == 1:
+            if self.reached(self.knowledge['food'][0]):
+                return None
+            else:
+                return self.knowledge['food'][0]
         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):
         self.goal = None
@@ -64,6 +116,7 @@ class Gatherer(DumbScouter):
         self.y = 0
 
     def move(self):
+        # Scouter has no more goal
         if self.goal is None or self.goal not in self.knowledge['food']:
             self.goal = self.choose_goal()
             self.path = []
@@ -72,6 +125,7 @@ class Gatherer(DumbScouter):
             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()
 
@@ -86,13 +140,7 @@ class Gatherer(DumbScouter):
         self.x = new_pos[0]
         self.y = new_pos[1]
 
+        # Scouter reached goal
         if self.reached(self.goal):
             self.goal = None
             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:
 
-    DROP_VALUE = 25
-
-    def __init__(self, board, max_scouters):
+    def __init__(self, board, default_knowledge):
         """
         :type board: Board
-        :type max_scouters: int
         """
         self.board = board
         self.knowledge = dict()
-        self.knowledge['food'] = []
-        self.knowledge['max_scouters'] = max_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):
         deads = []
@@ -47,10 +46,26 @@ class Blob_Manager:
             if old == (scouter.x, scouter.y):
                 deads.append(scouter)
             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()
 
-            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:
             self.scouters.remove(dead)
@@ -62,14 +77,53 @@ class Blob_Manager:
                 index = random.randrange(len(self.knowledge['food']))
                 (x, y) = self.knowledge['food'][index]
             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:
             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):
         for scouter in self.scouters.copy():
             if scouter.x == x and scouter.y == y:
@@ -82,9 +136,12 @@ class Blob_Manager:
 
     def food_discovered(self, 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 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.logic.dumb_scouter import DumbScouter
-from simulation.logic.actions import Actions
 
 
 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):
-        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 argparse
 import time
-from os.path import exists
+from os.path import exists, join, splitext
 
 from pygame.locals import QUIT
 from simulation.interface import Interface
@@ -16,10 +16,10 @@ def main():
                         help='Board height resolution (default = 40)')
     parser.add_argument('--width', type=int, 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)')
     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/",
                         help='Directory where saves are stored.')
     parser.add_argument('--computing_ratio', type=int, default=1,
@@ -27,23 +27,28 @@ def main():
 
     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:
-        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)
 

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

@@ -1,35 +1,42 @@
 from math import ceil
 from random import randrange
+import json
 
 from simulation.board import Board
 
 
 class Player:
 
-    def __init__(self, board, blob):
+    def __init__(self, board, blob, default_config):
         """
         :type blob: Blob_Manager
         :type board: Board
         """
         self.board = board
         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):
         if random_top is None:  # Randomize over all the board
             y_offset = 0
             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_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_range = ceil(self.board.height / 2)
 
@@ -41,14 +48,31 @@ class Player:
                 if self.set_food(x, y):
                     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
-
-        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
 
         if not food_put:
@@ -57,13 +81,8 @@ class Player:
 
         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):
         y_range = ceil(self.board.height/2)