Browse Source

Numediart v1.1 pushed

Loïc Vanden Bemden 5 years ago
parent
commit
410e093330
54 changed files with 1262 additions and 1515 deletions
  1. 2 0
      blob-simulation/.gitignore
  2. 1 0
      blob-simulation/detection/config.json
  3. BIN
      blob-simulation/detection/data/_MG_0001.JPG
  4. BIN
      blob-simulation/detection/data/_MG_0132.JPG
  5. BIN
      blob-simulation/detection/data/_MG_0133.JPG
  6. BIN
      blob-simulation/detection/data/_MG_0207.JPG
  7. 196 0
      blob-simulation/detection/detect.py
  8. 0 128
      blob-simulation/detection/detector.py
  9. 0 1
      blob-simulation/detection/setup-BKP.json
  10. 0 1
      blob-simulation/detection/setup.json
  11. 282 259
      blob-simulation/detection/setup_detection.py
  12. 3 4
      blob-simulation/detection/utils.py
  13. 0 80
      blob-simulation/main.py
  14. 0 1
      blob-simulation/saved/2019-04-04_18.13.26.blob
  15. 0 40
      blob-simulation/saved/2019-04-04_18.13.26.board
  16. BIN
      blob-simulation/saved/2019-04-04_18.13.26.jpg
  17. 0 1
      blob-simulation/saved/2019-04-04_18.13.26.player
  18. 0 1
      blob-simulation/saved/2019-04-04_18.15.32.blob
  19. 0 40
      blob-simulation/saved/2019-04-04_18.15.32.board
  20. BIN
      blob-simulation/saved/2019-04-04_18.15.32.jpg
  21. 0 1
      blob-simulation/saved/2019-04-04_18.15.32.player
  22. 0 1
      blob-simulation/saved/2019-04-05_13.40.35.blob
  23. 0 120
      blob-simulation/saved/2019-04-05_13.40.35.board
  24. BIN
      blob-simulation/saved/2019-04-05_13.40.35.jpg
  25. 0 1
      blob-simulation/saved/2019-04-05_13.40.35.player
  26. 0 1
      blob-simulation/saved/2019-04-05_15.47.36.blob
  27. 0 80
      blob-simulation/saved/2019-04-05_15.47.36.board
  28. BIN
      blob-simulation/saved/2019-04-05_15.47.36.jpg
  29. 0 1
      blob-simulation/saved/2019-04-05_15.47.36.player
  30. 0 1
      blob-simulation/saved/2019-05-03_10.23.51.blob
  31. 0 40
      blob-simulation/saved/2019-05-03_10.23.51.board
  32. BIN
      blob-simulation/saved/2019-05-03_10.23.51.jpg
  33. 0 1
      blob-simulation/saved/2019-05-03_10.23.51.player
  34. BIN
      blob-simulation/saved/capture_2019-04-01_12.55.58.jpg
  35. BIN
      blob-simulation/saved/capture_2019-04-01_13.04.05.jpg
  36. BIN
      blob-simulation/saved/capture_2019-04-01_13.06.19.jpg
  37. BIN
      blob-simulation/saved/capture_2019-04-01_13.08.16.jpg
  38. BIN
      blob-simulation/saved/capture_2019-04-01_13.20.30.jpg
  39. BIN
      blob-simulation/saved/capture_2019-04-01_18.42.47.jpg
  40. BIN
      blob-simulation/saved/capture_2019-04-01_18.42.50.jpg
  41. 0 1
      blob-simulation/saved/test.blob
  42. 0 40
      blob-simulation/saved/test.board
  43. 0 1
      blob-simulation/saved/test.player
  44. 108 102
      blob-simulation/board.py
  45. 0 0
      blob-simulation/simulation/cross.png
  46. 162 157
      blob-simulation/interface.py
  47. 15 15
      blob-simulation/blob/actions.py
  48. 29 29
      blob-simulation/blob/dumb_scouter.py
  49. 82 82
      blob-simulation/blob/fsm_ant.py
  50. 98 97
      blob-simulation/blob/gatherer.py
  51. 90 79
      blob-simulation/blob/main.py
  52. 28 28
      blob-simulation/blob/sensing_scouter.py
  53. 85 0
      blob-simulation/simulation/play.py
  54. 81 81
      blob-simulation/player.py

+ 2 - 0
blob-simulation/.gitignore

@@ -1,2 +1,4 @@
 .idea
 __pycache__
+data/
+detection/bkp

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

@@ -0,0 +1 @@
+{"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]}

BIN
blob-simulation/detection/data/_MG_0001.JPG


BIN
blob-simulation/detection/data/_MG_0132.JPG


BIN
blob-simulation/detection/data/_MG_0133.JPG


BIN
blob-simulation/detection/data/_MG_0207.JPG


+ 196 - 0
blob-simulation/detection/detect.py

@@ -0,0 +1,196 @@
+import argparse
+import json
+from detection.utils import *
+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")
+
+	args = vars(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'])
+
+	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 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
+
+	aspect_ratio = config["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))
+
+	""" 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)
+
+	""" 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)))
+
+	""" Find food """
+	food_list, food_mask, food_img = find_food(img, 100, 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))
+
+	return img, blob_mask, blob, food_mask, food_img, food_list
+
+
+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)
+
+	with open(filename + ".player", 'w') as player_file:
+		player_file.write("0")
+
+	cv2.imwrite(filename + ".jpg", discrete_img)
+
+
+if __name__ == "__main__":
+	main()

+ 0 - 128
blob-simulation/detection/detector.py

@@ -1,128 +0,0 @@
-import argparse
-import json
-from detection.utils import *
-
-
-def main():
-	ap = argparse.ArgumentParser()
-	ap.add_argument("-i", "--input", default='data/_MG_0207.JPG', help="input image")
-	ap.add_argument("-s", "--scale", type=float, default=0.1, help="based on the image shapes, scale the results by this factor")
-	ap.add_argument("-f", "--file", type=str, default="setup.json", help="name file to load setup")
-
-	args = vars(ap.parse_args())
-
-	with open(args["file"], 'r') as file:
-		setup = json.load(file)
-
-	img = cv2.imread(args["input"])
-	height, width, _ = img.shape
-
-	global scale
-	scale = args["scale"]
-
-	limits = np.array(setup['Limits'])
-	M = cv2.getPerspectiveTransform(np.float32(limits), np.float32(
-		[[0, 0], [width, 0], [width, height], [0, height]]))
-	img = cv2.warpPerspective(img, M, (width, height))
-
-	upper_mask = cv2.rectangle(np.zeros(img.shape[0:2], np.uint8), (0, 0), (width, int(height/2)), 255)
-	lower_mask = cv2.rectangle(np.zeros(img.shape[0:2], np.uint8), (0, int(height / 2)), (width, height), 255)
-
-	show_img("Original", img)
-	blob = blob_finder(img, 70, upper_mask, lower_mask, middle=[(0, int(height / 2)), (width, int(height/2))])
-	foods = food_finder(img, setup['Low Food Color'], setup['High Food Color'])
-
-	num_blob, num_food = numerize_blob(blob, foods, 100, 40)
-	write_board(num_blob, num_food)
-
-	cv2.waitKey(0)
-
-def write_board(numerized_blob, numerized_food):
-	height, width, _ = numerized_blob.shape
-	gray = cv2.cvtColor(numerized_blob, cv2.COLOR_BGR2GRAY)
-	output = ''
-	for x in range(height):
-		for y in range(width):
-			output += format(gray[x, y] != 0, 'd') + "," + format((y, x) in numerized_food, 'd') \
-						+ "," + str(gray[x, y]) + " "
-		output = output[:-1]
-		output += "\n"
-
-	print(output)
-
-	known_food = []
-	for food in numerized_food:
-		if gray[food[1], food[0]] != 0:
-			known_food.append([food[0], food[1]])
-
-	print(known_food)
-	print(len(known_food))
-
-def show_img(name, img):
-	cv2.imshow(name, cv2.resize(img, (0,0), fx=scale, fy=scale))
-
-
-def food_finder(img, low, high):
-	foods, mask, food_img = find_food(img, 100, low, high)
-
-	for food in foods:
-		print(food)
-	print("Food discovered: " + str(len(foods)))
-
-	filled_mask = cv2.bitwise_and(img, img, mask=mask)
-
-	show_img("Foods Mask", filled_mask)
-	show_img("Foods", food_img)
-
-	return foods
-
-
-def blob_finder(img, board_ratio, upper_mask, lower_mask, limits=None, middle=None):
-	sat = saturation(img)
-	# sum = mean_image([satA, satB])
-	mask = find_blob(sat)
-	blob = cv2.bitwise_and(img, img, mask=mask)
-
-	cover = mean_percent_value(mask)
-	print("Blob covering:")
-	print("--- {:.2f}% of the image.".format(cover))
-	print("--- {:.2f}% of the board.".format(cover / board_ratio * 100))
-
-	upper_cover = mean_percent_value(cv2.bitwise_and(mask, mask, mask=upper_mask)) / mean_percent_value(upper_mask) * 100
-	lower_cover = mean_percent_value(cv2.bitwise_and(mask, mask, mask=lower_mask)) / mean_percent_value(lower_mask) * 100
-	print("--- {:.2f}% of the upper board.".format(upper_cover))
-	print("--- {:.2f}% of the lower board.".format(lower_cover))
-
-	blob_grid = blob.copy()
-
-	if limits is not None:
-		for i, limit in enumerate(limits):
-			cv2.line(blob_grid, tuple(limits[i-1]), tuple(limit), (0, 0, 255), thickness=5)
-	if middle is not None:
-		cv2.line(blob_grid, tuple(middle[0]), tuple(middle[1]), (0, 255, 0), thickness=5)
-
-	show_img("Blob Mask", mask)
-	show_img("Blob", blob_grid)
-
-	return blob
-
-
-def numerize_blob(img, foods, width, height):
-	img_height, img_width, _ = img.shape
-	numerized_blob = cv2.resize(img, (width, height), interpolation=cv2.INTER_NEAREST)
-	numerized_food = []
-
-	scaled = cv2.resize(numerized_blob, (0, 0), fx=10, fy=10, interpolation=cv2.INTER_NEAREST)
-	for food in foods:
-		x = int((food[0] + food[2]/2) / img_width * width)
-		y = int((food[1] + food[3]/2) / img_height * height)
-		numerized_food.append((x,y))
-		cv2.drawMarker(scaled, (x *10 + 5, y * 10 + 5), (0, 0, 255), thickness=1)
-
-	cv2.imshow("Test", scaled)
-
-	return numerized_blob, numerized_food
-
-
-if __name__ == "__main__":
-	main()

+ 0 - 1
blob-simulation/detection/setup-BKP.json

@@ -1 +0,0 @@
-{"Limits": [[44, 628], [5596, 572], [5584, 3256], [56, 2592]], "Middle": [[20, 1560], [5604, 1912]], "Adjusted": [[49, 1559], [5590, 1911]], "Low Food Color": [70, 109, 124], "High Food Color": [122, 154, 167]}

+ 0 - 1
blob-simulation/detection/setup.json

@@ -1 +0,0 @@
-{"Limits": [[268, 272], [5464, 260], [5456, 2864], [316, 2992]], "Middle": [[332, 1740], [4144, 2452]], "Adjusted": [[293, 1740], [5457, 2456]], "Low Food Color": [144, 178, 201], "High Food Color": [188, 218, 232]}

+ 282 - 259
blob-simulation/detection/setup_detection.py

@@ -1,260 +1,283 @@
-import argparse
-import cv2
-import json
-
-
-def main():
-
-    ap = argparse.ArgumentParser()
-    ap.add_argument("-i", "--input", default='data/_MG_0207.JPG', help="input image")
-    ap.add_argument("-s", "--scale", type=float, default=0.25, help="based on the image shapes, scale the results by this factor")
-    ap.add_argument("-f", "--file", type=str, default="setup.json", help="name file to save setup")
-
-    args = vars(ap.parse_args())
-
-    img = cv2.imread(args["input"])
-    height, width, _ = img.shape
-    scale = args["scale"]
-
-    window_name = "Setup"
-    cv2.namedWindow(window_name)
-    show_menu()
-
-    food_color = FoodColor(img, scale, window_name)
-    board_setup = BoardLimits(img, scale, window_name)
-
-    done = False
-    state = 0
-    while not done:
-        key = cv2.waitKey(10)
-
-        if state == 0:
-            if key == ord("q"):
-                done = True
-
-            elif key == ord("1"):
-                state = 1
-                board_setup.clear()
-                board_setup.help()
-                cv2.setMouseCallback(window_name, board_setup.on_mouse)
-
-            elif key == ord("2"):
-                state = 2
-                food_color.clear()
-                food_color.help()
-                cv2.setMouseCallback(window_name, food_color.on_mouse)
-
-            elif key == ord("s"):
-                with open(args["file"], "w") as file:
-                    json.dump({**board_setup.toJSON(), **food_color.toJSON()}, file)
-                done = True
-
-        if state == 1:
-            board_setup.draw()
-            if board_setup.done:
-                state = 0
-                show_menu()
-
-        elif state == 2:
-            food_color.draw()
-            if food_color.done:
-                state = 0
-                show_menu()
-
-        else:
-            cv2.imshow(window_name, cv2.resize(img, (0, 0), fx=scale, fy=scale))
-            cv2.setMouseCallback(window_name, null_callback)
-
-
-def show_menu():
-    print("\nCommands : ")
-    print("\tPress '1' to setup board limits")
-    print("\tPress '2' to setup food color")
-    print("\tPress 's' to save & quit")
-    print("\tPress 'q' to quit without saving")
-
-
-def null_callback(event, x, y, flags, param):
-    return
-
-
-class BoardLimits:
-
-    def __init__(self, img, scale, window_name):
-        self.limits = []
-        self.max_limits = 4
-        self.middle = []
-        self.max_middle = 2
-        self.adjusted_middle = []
-        self.orig = img
-        self.img = img.copy()
-        self.scale = scale
-        self.window_name = window_name
-        self.done = False
-        self.limits_drawn = False
-        self.middle_drawn = False
-
-    def add_limit(self, x, y):
-        x_img = int(x / self.scale)
-        y_img = int(y / self.scale)
-        self.limits.append((x_img, y_img))
-        cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
-
-    def add_middle(self, x, y):
-        x_img = int(x / self.scale)
-        y_img = int(y / self.scale)
-        self.middle.append((x_img, y_img))
-        cv2.drawMarker(self.img, (x_img, y_img), (0, 255, 0), 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
-
-        if len(self.middle) == self.max_middle and not self.middle_drawn:
-            cv2.line(self.img, self.middle[0], self.middle[1], (0, 255, 0), thickness=3)
-
-            self.adjusted_middle.append(self.project_point(self.limits[0], self.limits[-1], self.middle[0]))
-            self.adjusted_middle.append(self.project_point(self.limits[1], self.limits[2], self.middle[1]))
-            cv2.drawMarker(self.img, self.adjusted_middle[0], (255, 0, 0), thickness=5)
-            cv2.drawMarker(self.img, self.adjusted_middle[1], (255, 0, 0), thickness=5)
-            cv2.line(self.img, self.adjusted_middle[0], self.adjusted_middle[1], (255, 0, 0), thickness=3)
-
-            self.middle_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):
-        return len(self.limits) == self.max_limits and len(self.middle) == self.max_middle
-
-    def compute(self):
-        return self.limits, self.middle
-
-    def toJSON(self):
-        return {'Limits': self.limits, 'Middle': self.middle, 'Adjusted': self.adjusted_middle}
-
-    def project_point(self, v1, v2, p):
-        e1 = (v2[0] - v1[0], v2[1] - v1[1])
-        e2 = (p[0] - v1[0], p[1] - v1[1])
-        dp = e1[0] * e2[0] + e1[1] * e2[1]
-        dist = e1[0]**2 + e1[1]**2
-        px = int(v1[0] + (dp * e1[0] / dist))
-        py = int(v1[1] + (dp * e1[1] / dist))
-        return px, py
-
-    def help(self):
-        print("--- Board Setup: Click on the {} corners of the board and "
-              "then on each side of the half board separation".format(self.max_limits))
-        print("--- Board Setup: Please start from left corner and do it in the right order ")
-
-    def clear(self):
-        self.limits = []
-        self.limits_drawn = False
-        self.middle = []
-        self.middle_drawn = False
-        self.adjusted_middle = []
-        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)
-            else:
-                self.add_middle(x, y)
-
-    def confirm(self):
-        print("--- Board 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("--- Board Setup: " + str(self.compute()))
-            self.done = True
-        else:
-            self.clear()
-            self.help()
-
-
-class FoodColor:
-
-    def __init__(self, img, scale, window_name, max_qt=0):
-        self.colors = []
-        self.orig = img
-        self.img = img.copy()
-        self.scale = scale
-        self.max_qt = max_qt
-        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])
-        cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
-
-    def draw(self):
-        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
-        else:
-            return len(self.colors) == self.max_qt
-
-    def compute(self):
-        low_color = [255, 255, 255]
-        high_color = [0, 0, 0]
-
-        if len(self.colors) == 0:
-            return tuple(high_color), tuple(low_color)
-
-        for color in self.colors:
-            for i, c in enumerate(color):
-                if c < low_color[i]:
-                    low_color[i] = c
-
-                if c > high_color[i]:
-                    high_color[i] = c
-
-        return tuple(low_color), tuple(high_color)
-
-    def toJSON(self):
-        l, h = self.compute()
-        l = tuple([int(x) for x in l])
-        h = tuple([int(x) for x in h])
-        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))
-
-    def clear(self):
-        self.colors = []
-        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() :
-            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)
-        if key == 13:  # Enter
-            print("--- Color Setup: " + str(self.compute()))
-            self.done = True
-        else:
-            self.clear()
-            self.help()
-
-
-if __name__ == "__main__":
+import argparse
+import cv2
+import json
+import datetime
+import time
+from shutil import copyfile
+from os.path import exists
+from os import makedirs
+
+
+def main():
+
+    ap = argparse.ArgumentParser()
+    ap.add_argument("-i", "--input", required=True, help="input image")
+    ap.add_argument("-s", "--scale", type=float, default=0.25, help="scales input image by this factor (default: x0.25)")
+    ap.add_argument("-c", "--config", type=str, default="config.json",
+                    help="name file to save config (default: config.json)")
+
+    args = vars(ap.parse_args())
+
+    img = cv2.imread(args["input"])
+    height, width, _ = img.shape
+    scale = args["scale"]
+
+    window_name = "Setup"
+    cv2.namedWindow(window_name)
+    cv2.imshow(window_name, cv2.resize(img, (0, 0), fx=scale, fy=scale))
+    cv2.setMouseCallback(window_name, null_callback)
+    show_menu()
+
+    food_color = FoodColor(img, scale, window_name)
+    board_setup = BoardLimits(img, scale, window_name)
+
+    done = False
+    state = 0
+    setup_vars = {'Aspect Ratio': 1.0, 'Discrete Height': 100, 'Discrete Width': 100}
+    while not done:
+        cv2.waitKey(10)
+
+        if state == 0:
+            cv2.imshow(window_name, cv2.resize(img, (0, 0), fx=scale, fy=scale))
+            cv2.setMouseCallback(window_name, null_callback)
+
+            key = input("Enter command: ")
+
+            if key == "q":
+                done = True
+
+            elif key == "1":
+                state = 1
+                board_setup.clear()
+                board_setup.help()
+                cv2.setMouseCallback(window_name, board_setup.on_mouse)
+
+            elif key == "2":
+                state = 2
+                food_color.clear()
+                food_color.help()
+                cv2.setMouseCallback(window_name, food_color.on_mouse)
+
+            elif key == "3":
+                setup_vars['Aspect Ratio'] = -1
+                while setup_vars['Aspect Ratio'] <= 0:
+                    try:
+                        setup_vars['Aspect Ratio'] = float(input("Insert image height by width ratio here: "))
+                    except ValueError:
+                        setup_vars['Aspect Ratio'] = -1
+
+                    if setup_vars['Aspect Ratio'] <= 0:
+                        print("Insert only a floating number with dot as separator.")
+
+                show_menu()
+
+            elif key == "4":
+                setup_vars['Discrete Height'] = -1
+                while setup_vars['Discrete Height'] <= 0:
+                    try:
+                        setup_vars['Discrete Height'] = int(input("Insert height discrete resolution: "))
+                    except ValueError:
+                        setup_vars['Discrete Height'] = -1
+
+                    if setup_vars['Discrete Height'] <= 0:
+                        print("Insert only round numbers.")
+
+                setup_vars['Discrete Width'] = -1
+                while setup_vars['Discrete Width'] <= 0:
+                    try:
+                        setup_vars['Discrete Width'] = int(input("Insert width discrete resolution: "))
+                    except ValueError:
+                        setup_vars['Discrete Width'] = -1
+
+                    if setup_vars['Discrete Width'] <= 0:
+                        print("Insert only round numbers.")
+
+                show_menu()
+
+            elif key == "s":
+                ts = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H.%M.%S-')
+                if exists(args["config"]):
+                    if not exists("bkp/"):
+                        makedirs("bkp")
+                    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)
+
+                done = True
+
+            else:
+                print("Error: Unrecognised Command.")
+                show_menu()
+
+        elif state == 1:
+            board_setup.draw()
+            if board_setup.done:
+                state = 0
+                show_menu()
+
+        elif state == 2:
+            food_color.draw()
+            if food_color.done:
+                state = 0
+                show_menu()
+
+
+def show_menu():
+    print("\nCommands: ")
+    print("\tEnter '1' to setup board limits")
+    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 's' to save & quit")
+    print("\tEnter 'q' to quit without saving")
+
+
+def null_callback(event, x, y, flags, param):
+    return
+
+
+class BoardLimits:
+
+    def __init__(self, img, scale, window_name):
+        self.limits = []
+        self.max_limits = 4
+        self.orig = img
+        self.img = img.copy()
+        self.scale = scale
+        self.window_name = window_name
+        self.done = False
+        self.limits_drawn = False
+
+    def add_limit(self, x, y):
+        x_img = int(x / self.scale)
+        y_img = int(y / self.scale)
+        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):
+        return len(self.limits) == self.max_limits
+
+    def compute(self):
+        return self.limits
+
+    def toJSON(self):
+        return {'Limits': self.limits}
+
+    def help(self):
+        print("--- Board Setup: Click on the {} corners of the board".format(self.max_limits))
+        print("--- Board Setup: Please start from left corner and do it in the right order")
+
+    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("--- Board 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("--- Board Setup: " + str(self.compute()))
+            self.done = True
+        else:
+            self.clear()
+            self.help()
+
+
+class FoodColor:
+
+    def __init__(self, img, scale, window_name, max_qt=0):
+        self.colors = []
+        self.orig = img
+        self.img = img.copy()
+        self.scale = scale
+        self.max_qt = max_qt
+        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])
+        cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
+
+    def draw(self):
+        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
+        else:
+            return len(self.colors) == self.max_qt
+
+    def compute(self):
+        low_color = [255, 255, 255]
+        high_color = [0, 0, 0]
+
+        if len(self.colors) == 0:
+            return tuple(high_color), tuple(low_color)
+
+        for color in self.colors:
+            for i, c in enumerate(color):
+                if c < low_color[i]:
+                    low_color[i] = c
+
+                if c > high_color[i]:
+                    high_color[i] = c
+
+        return tuple(low_color), tuple(high_color)
+
+    def toJSON(self):
+        l, h = self.compute()
+        l = tuple([int(x) for x in l])
+        h = tuple([int(x) for x in h])
+        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))
+
+    def clear(self):
+        self.colors = []
+        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() :
+            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)
+        if key == 13:  # Enter
+            print("--- Color Setup: " + str(self.compute()))
+            self.done = True
+        else:
+            self.clear()
+            self.help()
+
+
+if __name__ == "__main__":
     main()

+ 3 - 4
blob-simulation/detection/utils.py

@@ -17,8 +17,9 @@ def mean_image(images):
     return mean.astype(np.uint8)
 
 
-def mean_percent_value(img):
-    return np.sum(img, dtype=np.int64) / img.size / 255 * 100
+# If percentage should be linked to a smaller region than whole image, fill img_ratio with factor value
+def mean_percent_value(img, img_ratio=1.0):
+    return np.sum(img, dtype=np.int64) / img_ratio / img.size / 255 * 100
 
 
 def find_food(img, min_food_size, lower_color_boundary, upper_color_boundary, kernel=None):
@@ -66,8 +67,6 @@ def find_blob(sat_img, max_blob=1, area_ratio=0.8, kernel=None):
     cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel)
     cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_OPEN, kernel)
 
-    # Below code will show smoother blob zone but will include holes at well...
-
     contours, hierarchy = cv2.findContours(cleaned, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
 
     blobs = []

+ 0 - 80
blob-simulation/main.py

@@ -1,80 +0,0 @@
-import pygame
-import argparse
-import time
-from os.path import exists
-
-from pygame.locals import QUIT
-from interface import Interface
-from board import Board
-from player import Player
-from blob.main import Blob_Manager
-
-
-def main():
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--height', type=int, default=120,
-                        help='Board height resolution (default = 120)')
-    parser.add_argument('--width', type=int, default=300,
-                        help='Board width resolution (default = 300)')
-    parser.add_argument('--scale', type=int, default=4,
-                        help='Scaling from board resolution to window resolution (default = x10)')
-    parser.add_argument('--init_from', type=str, default='2019-04-05_13.40.35',
-                        help='Initialize game from a save. Pass the filename in saved dir without extension.')
-    parser.add_argument('--save_dir', type=str, default="saved/",
-                        help='Directory where saves are stored.')
-
-    args = parser.parse_args()
-
-    if args.init_from is not None:
-        init_scouters = 0
-    else:
-        init_scouters = 3
-
-    board = Board(args.width, args.height)
-    blob = Blob_Manager(board, init_scouters)
-    player = Player(board, blob)
-
-    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")
-
-        board.load(open(args.save_dir + args.init_from + ".board", 'r'))
-        blob.load(open(args.save_dir + args.init_from + ".blob", 'r'))
-        player.load(open(args.save_dir + args.init_from + ".player", 'r'))
-
-    gui = Interface(board, player, blob, args.scale, args.save_dir)
-
-    init_counter = 100
-    timer = time.time()
-    chrono = 0
-    counter = init_counter
-
-    ended = False
-    while not ended:
-        if gui.play or gui.do_step:
-            blob.move()
-            board.next_turn()
-            gui.do_step = False
-
-        gui.draw()
-        pygame.time.wait(10)
-
-        for event in pygame.event.get():
-            if event.type == QUIT:
-                ended = True
-            else:
-                gui.event_listener(event)
-
-        chrono += time.time() - timer
-        counter -= 1
-        timer = time.time()
-
-        if counter == 0:
-            print("Loop mean time :", chrono, "seconds over", init_counter, "iterations.")
-            counter = init_counter
-            chrono = 0
-
-
-if __name__ == "__main__":
-    main()

+ 0 - 1
blob-simulation/saved/2019-04-04_18.13.26.blob

@@ -1 +0,0 @@
-{"food": [], "max_scouters": 6}

File diff suppressed because it is too large
+ 0 - 40
blob-simulation/saved/2019-04-04_18.13.26.board


BIN
blob-simulation/saved/2019-04-04_18.13.26.jpg


+ 0 - 1
blob-simulation/saved/2019-04-04_18.13.26.player

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

+ 0 - 1
blob-simulation/saved/2019-04-04_18.15.32.blob

@@ -1 +0,0 @@
-{"food": [], "max_scouters": 6}

File diff suppressed because it is too large
+ 0 - 40
blob-simulation/saved/2019-04-04_18.15.32.board


BIN
blob-simulation/saved/2019-04-04_18.15.32.jpg


+ 0 - 1
blob-simulation/saved/2019-04-04_18.15.32.player

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

File diff suppressed because it is too large
+ 0 - 1
blob-simulation/saved/2019-04-05_13.40.35.blob


File diff suppressed because it is too large
+ 0 - 120
blob-simulation/saved/2019-04-05_13.40.35.board


BIN
blob-simulation/saved/2019-04-05_13.40.35.jpg


+ 0 - 1
blob-simulation/saved/2019-04-05_13.40.35.player

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

File diff suppressed because it is too large
+ 0 - 1
blob-simulation/saved/2019-04-05_15.47.36.blob


File diff suppressed because it is too large
+ 0 - 80
blob-simulation/saved/2019-04-05_15.47.36.board


BIN
blob-simulation/saved/2019-04-05_15.47.36.jpg


+ 0 - 1
blob-simulation/saved/2019-04-05_15.47.36.player

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

File diff suppressed because it is too large
+ 0 - 1
blob-simulation/saved/2019-05-03_10.23.51.blob


File diff suppressed because it is too large
+ 0 - 40
blob-simulation/saved/2019-05-03_10.23.51.board


BIN
blob-simulation/saved/2019-05-03_10.23.51.jpg


+ 0 - 1
blob-simulation/saved/2019-05-03_10.23.51.player

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

BIN
blob-simulation/saved/capture_2019-04-01_12.55.58.jpg


BIN
blob-simulation/saved/capture_2019-04-01_13.04.05.jpg


BIN
blob-simulation/saved/capture_2019-04-01_13.06.19.jpg


BIN
blob-simulation/saved/capture_2019-04-01_13.08.16.jpg


BIN
blob-simulation/saved/capture_2019-04-01_13.20.30.jpg


BIN
blob-simulation/saved/capture_2019-04-01_18.42.47.jpg


BIN
blob-simulation/saved/capture_2019-04-01_18.42.50.jpg


+ 0 - 1
blob-simulation/saved/test.blob

@@ -1 +0,0 @@
-{"food": [[74, 39], [38, 38], [35, 37], [78, 36], [82, 36], [70, 35], [86, 35], [87, 28], [76, 27], [37, 23], [44, 5]], "max_scouters": 11}

File diff suppressed because it is too large
+ 0 - 40
blob-simulation/saved/test.board


+ 0 - 1
blob-simulation/saved/test.player

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

+ 108 - 102
blob-simulation/board.py

@@ -1,103 +1,109 @@
-import numpy as np
-
-
-class Board:
-
-    MAX_BLOB = 255
-    DECREASE_BLOB = 0.1
-
-    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()
-
-    def save(self):
-        stream = ''
-        for y in range(self.height):
-            for x in range(self.width):
-                saved_node = self.board_array[x, y].save() + " "
-                stream += str(saved_node)
-            stream = stream.rstrip(' ')
-            stream += '\n'
-
-        return stream.rstrip('\n')
-
-    def load(self, file):
-        y = 0
-        for line in file:
-            nodes = line.split(' ')
-            if len(nodes) != self.width:
-                print("Error with initialized height !" + str(len(nodes)))
-
-            x = 0
-            for node in nodes:
-                self.board_array[x, y].load(node)
-                x += 1
-
-            y += 1
-
-    def has_food(self, x, y):
-        return self.inside(x, y) and self.board_array[x, y].food
-
-    def update_blob(self, x, y, change_value):
-        if self.inside(x, y):
-            self.board_array[x, y].update_blob(change_value)
-            return True
-        else:
-            return False
-
-    def get_blob(self, x, y):
-        if self.inside(x, y):
-            return self.board_array[x, y].blob
-        else:
-            return None
-
-    def inside(self, x, y):
-        return 0 <= x < self.width and 0 <= y < self.height
-
-    def is_touched(self, x, y):
-        if self.inside(x, y):
-            return self.board_array[x, y].touched
-        else:
-            return False
-
-    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)
-
-    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
-
-    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(',')
-
-        if len(values) != 3:
-            print("Error with packaged values !")
-
-        self.touched = values[0] == '1'
-        self.food = values[1] == '1'
+import numpy as np
+
+
+class Board:
+
+    MAX_BLOB = 255
+    DECREASE_BLOB = 0.1
+
+    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()
+
+    def save(self):
+        stream = ''
+        for y in range(self.height):
+            for x in range(self.width):
+                saved_node = self.board_array[x, y].save() + " "
+                stream += str(saved_node)
+            stream = stream.rstrip(' ')
+            stream += '\n'
+
+        return stream.rstrip('\n')
+
+    def load(self, filename):
+        with open(filename, 'r') as file:
+            dim = file.readline()
+            dims = dim.split(' ')
+            if dims[0] != self.width and dims[1] != self.height:
+                self.__init__(int(dims[0]), int(dims[1]))
+
+            y = 0
+            for line in file:
+                nodes = line.split(' ')
+                if len(nodes) != self.width:
+                    print("Error with given height !" + str(len(nodes)))
+
+                x = 0
+                for node in nodes:
+                    self.board_array[x, y].load(node)
+                    x += 1
+
+                y += 1
+
+    def has_food(self, x, y):
+        return self.inside(x, y) and self.board_array[x, y].food
+
+    def update_blob(self, x, y, change_value):
+        if self.inside(x, y):
+            self.board_array[x, y].update_blob(change_value)
+            return True
+        else:
+            return False
+
+    def get_blob(self, x, y):
+        if self.inside(x, y):
+            return self.board_array[x, y].blob
+        else:
+            return None
+
+    def inside(self, x, y):
+        return 0 <= x < self.width and 0 <= y < self.height
+
+    def is_touched(self, x, y):
+        if self.inside(x, y):
+            return self.board_array[x, y].touched
+        else:
+            return False
+
+    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)
+
+    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
+
+    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(',')
+
+        if len(values) != 3:
+            print("Error with packaged values !")
+
+        self.touched = values[0] == '1'
+        self.food = values[1] == '1'
         self.blob = float(values[2])

blob-simulation/cross.png → blob-simulation/simulation/cross.png


+ 162 - 157
blob-simulation/interface.py

@@ -1,158 +1,163 @@
-import pygame
-import random
-import time
-import datetime
-
-from pygame.locals import *
-from board import Board
-from blob.main import Blob_Manager
-from player import Player
-
-
-class Interface:
-
-    def __init__(self, board, player, blob, scale, save_dir):
-        """
-        :type board: Board
-        :type player: Player
-        :type blob: Blob_Manager
-        :type scale: float
-        :type save_dir: str
-        """
-        pygame.init()
-        # pygame.key.set_repeat(5, 50)
-
-        self.board = board
-        self.player = player
-        self.blob = blob
-        self.scale = scale
-
-        self.save_dir = save_dir
-
-        self.debug_mode = False
-        self.play = False
-        self.do_step = False
-        self.show_ants = True
-
-        width = self.board.width * scale
-        height = self.board.height * scale
-        self.window = pygame.display.set_mode((width, height))
-
-        discovered_food = pygame.image.load("cross.png").convert()
-        discovered_food.set_colorkey((255, 255, 255))
-        self.discovered_food = pygame.transform.scale(discovered_food, (scale, scale))
-
-    def draw(self):
-
-        width = self.board.width * self.scale
-        height = self.board.height * self.scale
-
-        game_surface = pygame.Surface((self.board.width, self.board.height))
-        board_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)
-
-                if self.board.has_food(x, y):
-                    board_array[x, y] = (0, 150, 0)
-
-                if self.show_ants:
-                    for scouter in self.blob.scouters:
-                        board_array[scouter.x, scouter.y] = (255, 255, 255)
-
-        del board_array
-
-        game_window = pygame.transform.scale(game_surface, (width, height))
-
-        pygame.draw.line(game_window, (125, 125, 125), (0, height / 2), (width, height / 2))
-
-        self.window.blit(game_window, (0, 0))
-        for food in self.blob.knowledge['food']:
-            self.window.blit(self.discovered_food, (food[0] * self.scale, food[1] * self.scale))
-        pygame.display.flip()
-
-    def event_listener(self, event):
-
-        # ADMIN ACTIONS
-        if event.type == KEYDOWN and event.key == 100:  # D Letter
-            self.debug_mode = not self.debug_mode
-            print("Debug Mode: " + str(self.debug_mode))
-
-        elif event.type == KEYDOWN and event.key == K_UP:
-            Board.DECREASE_BLOB += 0.1
-            print("Pheromone evaporation : " + str(Board.DECREASE_BLOB))
-
-        elif event.type == KEYDOWN and event.key == K_DOWN:
-            Board.DECREASE_BLOB -= 0.1
-            print("Pheromone evaporation : " + str(Board.DECREASE_BLOB))
-
-        elif event.type == KEYDOWN and event.key == K_SPACE:
-            self.show_ants = not self.show_ants
-
-        elif event.type == KEYDOWN and event.key == 115:  # S Letter
-            ts = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H.%M.%S')
-
-            print("Data saved at " + ts)
-            f = open(self.save_dir + ts + ".board", 'w')
-            f.write(self.board.save())
-            f.close()
-
-            f = open(self.save_dir + ts + ".blob", 'w')
-            f.write(self.blob.save())
-            f.close()
-
-            f = open(self.save_dir + ts + ".player", 'w')
-            f.write(self.player.save())
-            f.close()
-
-            pygame.image.save(self.window, self.save_dir + ts + ".jpg")
-
-        # DEBUG ACTIONS
-        elif self.debug_mode and 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.board.update_blob(x, y, 10)
-
-        # PLAYER ACTIONS
-        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)
-
-        elif event.type == KEYDOWN and event.key == 110:  # N Letter
-            self.player.clean_board()
-
-        elif event.type == KEYDOWN and event.key == 114:  # R Letter
-            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))
-
-        # BLOB ACTIONS
-        elif event.type == KEYDOWN and event.key == 112:  # P Letter
-            self.play = not self.play
-
-        elif event.type == KEYDOWN and event.key == K_RETURN:
-            self.do_step = True
-
-        elif event.type == KEYDOWN and event.key == 107:  # K Letter
-            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)))
-
-        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)))
-
-        elif event.type == KEYDOWN:
+import pygame
+import random
+import time
+import datetime
+import os.path
+
+from pygame.locals import *
+from simulation.board import Board
+
+
+class Interface:
+
+    def __init__(self, board, player, blob, scale, save_dir):
+        """
+        :type board: Board
+        :type player: Player
+        :type blob: Blob_Manager
+        :type scale: float
+        :type save_dir: str
+        """
+        pygame.init()
+        # pygame.key.set_repeat(5, 50)
+
+        self.board = board
+        self.player = player
+        self.blob = blob
+        self.scale = scale
+
+        self.save_dir = save_dir
+
+        self.debug_mode = False
+        self.play = False
+        self.do_step = False
+        self.show_ants = True
+
+        width = self.board.width * scale
+        height = self.board.height * scale
+        self.window = pygame.display.set_mode((width, height))
+
+        cross_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cross.png")
+
+        discovered_food = pygame.image.load(cross_file).convert()
+        discovered_food.set_colorkey((255, 255, 255))
+        self.discovered_food = pygame.transform.scale(discovered_food, (scale, scale))
+
+    def draw(self):
+
+        width = self.board.width * self.scale
+        height = self.board.height * self.scale
+
+        game_surface = pygame.Surface((self.board.width, self.board.height))
+        board_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)
+
+                if self.board.has_food(x, y):
+                    board_array[x, y] = (0, 150, 0)
+
+                if self.show_ants:
+                    for scouter in self.blob.scouters:
+                        board_array[scouter.x, scouter.y] = (255, 255, 255)
+
+        del board_array
+
+        game_window = pygame.transform.scale(game_surface, (width, height))
+
+        pygame.draw.line(game_window, (125, 125, 125), (0, height / 2), (width, height / 2))
+
+        self.window.blit(game_window, (0, 0))
+        for food in self.blob.knowledge['food']:
+            self.window.blit(self.discovered_food, (food[0] * self.scale, food[1] * self.scale))
+        pygame.display.flip()
+
+    def save(self):
+        ts = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H.%M.%S')
+
+        print("Data saved at " + ts)
+        f = open(self.save_dir + ts + ".board", 'w')
+        f.write(self.board.save())
+        f.close()
+
+        f = open(self.save_dir + ts + ".blob", 'w')
+        f.write(self.blob.save())
+        f.close()
+
+        f = open(self.save_dir + ts + ".player", 'w')
+        f.write(self.player.save())
+        f.close()
+
+        pygame.image.save(self.window, self.save_dir + ts + ".jpg")
+
+    def event_listener(self, event):
+
+        # ADMIN ACTIONS
+        if event.type == KEYDOWN and event.key == 100:  # D Letter
+            self.debug_mode = not self.debug_mode
+            print("Debug Mode: " + str(self.debug_mode))
+
+        elif event.type == KEYDOWN and event.key == K_UP:
+            Board.DECREASE_BLOB += 0.1
+            print("Pheromone evaporation : " + str(Board.DECREASE_BLOB))
+
+        elif event.type == KEYDOWN and event.key == K_DOWN:
+            Board.DECREASE_BLOB -= 0.1
+            print("Pheromone evaporation : " + str(Board.DECREASE_BLOB))
+
+        elif event.type == KEYDOWN and event.key == K_SPACE:
+            self.show_ants = not self.show_ants
+
+        elif event.type == KEYDOWN and event.key == 115:  # S Letter
+            self.save()
+
+        # DEBUG ACTIONS
+        elif self.debug_mode and 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.board.update_blob(x, y, 10)
+
+        # PLAYER ACTIONS
+        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)
+
+        elif event.type == KEYDOWN and event.key == 99:  # C Letter
+            self.player.clean_board()
+
+        elif event.type == KEYDOWN and event.key == 114:  # R Letter
+            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))
+
+        # BLOB ACTIONS
+        elif event.type == KEYDOWN and event.key == 112:  # P Letter
+            self.play = not self.play
+
+        elif event.type == KEYDOWN and event.key == K_RETURN:
+            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)))
+
+        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)))
+
+        elif event.type == KEYDOWN:
             print("Unrecognised key code : " + str(event.key))

+ 15 - 15
blob-simulation/blob/actions.py

@@ -1,16 +1,16 @@
-
-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]
+
+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

+ 29 - 29
blob-simulation/blob/dumb_scouter.py

@@ -1,30 +1,30 @@
-import random
-
-from board import Board
-
-class DumbScouter:
-    """ Dumb scouter searching food randomly and without any knowledge """
-
-    def __init__(self, board, knowledge, x, y, drop_value):
-        """
-        :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
-
-    def move(self):
-        x = self.x + random.randint(-1, 1)
-        y = self.y + random.randint(-1, 1)
-        if self.board.inside(x, y):
-            self.x = x
-            self.y = y
-
-    def update(self):
+import random
+
+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):
+        """
+        :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
+
+    def move(self):
+        x = self.x + random.randint(-1, 1)
+        y = self.y + random.randint(-1, 1)
+        if self.board.inside(x, y):
+            self.x = x
+            self.y = y
+
+    def update(self):
         self.board.update_blob(self.x, self.y, self.drop_value)

+ 82 - 82
blob-simulation/blob/fsm_ant.py

@@ -1,82 +1,82 @@
-from board import Board
-from blob.dumb_scouter import DumbScouter
-from blob.gatherer import Gatherer
-from blob.sensing_scouter import SensingScouter
-
-
-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
-
-    # 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):
-        """
-        :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)
-
-        self.harvest = FSMAnt.MAX_HARVESTING
-        self.starving = False
-        self.init_drop = drop_value
-
-    def move(self):
-        if self.starving:
-            self.gatherer_logic.move()
-            self.x = self.gatherer_logic.x
-            self.y = self.gatherer_logic.y
-        else:
-            self.scouting_logic.move()
-            self.x = self.scouting_logic.x
-            self.y = self.scouting_logic.y
-
-    def init_gathering(self):
-        self.gatherer_logic.reset()
-        self.gatherer_logic.x = self.x
-        self.gatherer_logic.y = self.y
-
-    def init_scouting(self):
-        self.scouting_logic.x = self.x
-        self.scouting_logic.y = self.y
-        self.drop_value = self.init_drop
-
-    def update(self):
-        if self.harvest > 0 and self.starving:
-            self.drop_value = FSMAnt.RATIO_DROP_STARVE * self.init_drop
-
-        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 self.board.has_food(self.x, self.y):
-            if len(self.knowledge['food']) == 1:
-                self.harvest = FSMAnt.MAX_HARVESTING
-            else:
-                self.harvest += FSMAnt.HARVEST_VALUE
-                self.harvest = min(self.harvest, FSMAnt.MAX_HARVESTING)
-
-        if self.harvest == 0 and not self.starving:
-            self.starving = True
-            self.init_gathering()
-
-        if self.harvest == FSMAnt.MAX_HARVESTING and self.starving:
-            self.starving = False
-            self.init_scouting()
+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
+
+
+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
+
+    # 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):
+        """
+        :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)
+
+        self.harvest = FSMAnt.MAX_HARVESTING
+        self.starving = False
+        self.init_drop = drop_value
+
+    def move(self):
+        if self.starving:
+            self.gatherer_logic.move()
+            self.x = self.gatherer_logic.x
+            self.y = self.gatherer_logic.y
+        else:
+            self.scouting_logic.move()
+            self.x = self.scouting_logic.x
+            self.y = self.scouting_logic.y
+
+    def init_gathering(self):
+        self.gatherer_logic.reset()
+        self.gatherer_logic.x = self.x
+        self.gatherer_logic.y = self.y
+
+    def init_scouting(self):
+        self.scouting_logic.x = self.x
+        self.scouting_logic.y = self.y
+        self.drop_value = self.init_drop
+
+    def update(self):
+        if self.harvest > 0 and self.starving:
+            self.drop_value = FSMAnt.RATIO_DROP_STARVE * self.init_drop
+
+        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 self.board.has_food(self.x, self.y):
+            if len(self.knowledge['food']) == 1:
+                self.harvest = FSMAnt.MAX_HARVESTING
+            else:
+                self.harvest += FSMAnt.HARVEST_VALUE
+                self.harvest = min(self.harvest, FSMAnt.MAX_HARVESTING)
+
+        if self.harvest == 0 and not self.starving:
+            self.starving = True
+            self.init_gathering()
+
+        if self.harvest == FSMAnt.MAX_HARVESTING and self.starving:
+            self.starving = False
+            self.init_scouting()

+ 98 - 97
blob-simulation/blob/gatherer.py

@@ -1,97 +1,98 @@
-import random
-
-from pathfinding.core.diagonal_movement import DiagonalMovement
-from pathfinding.core.grid import Grid
-from pathfinding.finder.a_star import AStarFinder
-
-from board import Board
-from blob.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)
-
-        self.use_diagonal = use_diagonal
-        self.light_compute = light_compute
-
-        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 best_way_to(self):
-
-        grid = Grid(matrix=self.get_matrix())
-
-        start = grid.node(self.x, self.y)
-        end = grid.node(self.goal[0], self.goal[1])
-
-        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)
-
-    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:
-            return None
-        else:
-            return goals[random.randrange(len(goals))]
-
-    def reset(self):
-        self.goal = None
-        self.path = []
-        self.x = 0
-        self.y = 0
-
-    def move(self):
-        if self.goal is None or self.goal not in self.knowledge['food']:
-            self.goal = self.choose_goal()
-            self.path = []
-
-            # No goal
-            if self.goal is None:
-                return
-
-        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]
-
-        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
+import random
+
+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
+
+
+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)
+
+        self.use_diagonal = use_diagonal
+        self.light_compute = light_compute
+
+        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 best_way_to(self):
+
+        grid = Grid(matrix=self.get_matrix())
+
+        start = grid.node(self.x, self.y)
+        end = grid.node(self.goal[0], self.goal[1])
+
+        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:]
+
+    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:
+            return None
+        else:
+            return goals[random.randrange(len(goals))]
+
+    def reset(self):
+        self.goal = None
+        self.path = []
+        self.x = 0
+        self.y = 0
+
+    def move(self):
+        if self.goal is None or self.goal not in self.knowledge['food']:
+            self.goal = self.choose_goal()
+            self.path = []
+
+            # No goal
+            if self.goal is None:
+                return
+
+        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]
+
+        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

+ 90 - 79
blob-simulation/blob/main.py

@@ -1,79 +1,90 @@
-import random
-import json
-
-# from ant import Ant
-# from gatherer import Gatherer
-from blob.fsm_ant import FSMAnt
-from board import Board
-
-
-class Blob_Manager:
-
-    DROP_VALUE = 25
-
-    def __init__(self, board, max_scouters):
-        """
-        :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()
-
-    def save(self):
-        return json.dumps(self.knowledge)
-
-    def load(self, 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']
-
-        while len(self.scouters) < self.knowledge['max_scouters']:
-            self.add_scouter()
-
-    def move(self):
-        for scouter in self.scouters:
-            scouter.move()
-            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)
-
-    def add_scouter(self):
-        if len(self.scouters) < self.knowledge['max_scouters']:
-            if len(self.knowledge['food']) != 0:
-                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
-
-            self.scouters.append(FSMAnt(self.board, self.knowledge, x, y, Blob_Manager.DROP_VALUE))
-        else:
-            print("Max scouters already reached !")
-
-    def reset(self, x, y):
-        for scouter in self.scouters.copy():
-            if scouter.x == x and scouter.y == y:
-                self.scouters.remove(scouter)
-
-        for food in self.knowledge['food'].copy():
-            if food == (x, y):
-                self.knowledge['food'].remove(food)
-                self.knowledge['max_scouters'] -= 1
-
-    def food_discovered(self, x, y):
-        self.knowledge['food'].append((x, y))
-        self.knowledge['max_scouters'] += 1
-
-        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) + ") - Total scouters : " + str(len(self.scouters)))
+import random
+import json
+
+# from ant import Ant
+# from gatherer import Gatherer
+from simulation.logic.fsm_ant import FSMAnt
+from simulation.board import Board
+
+
+class Blob_Manager:
+
+    DROP_VALUE = 25
+
+    def __init__(self, board, max_scouters):
+        """
+        :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()
+
+    def save(self):
+        return json.dumps(self.knowledge)
+
+    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']
+
+            while len(self.scouters) < self.knowledge['max_scouters']:
+                self.add_scouter()
+
+    def move(self):
+        deads = []
+        for scouter in self.scouters:
+            old = (scouter.x, scouter.y)
+            scouter.move()
+            if old == (scouter.x, scouter.y):
+                deads.append(scouter)
+            else:
+                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)
+
+        for dead in deads:
+            self.scouters.remove(dead)
+            self.add_scouter()
+
+    def add_scouter(self):
+        if len(self.scouters) < self.knowledge['max_scouters']:
+            if len(self.knowledge['food']) != 0:
+                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
+
+            self.scouters.append(FSMAnt(self.board, self.knowledge, x, y, Blob_Manager.DROP_VALUE))
+        else:
+            print("Max scouters already reached !")
+
+    def reset(self, x, y):
+        for scouter in self.scouters.copy():
+            if scouter.x == x and scouter.y == y:
+                self.scouters.remove(scouter)
+
+        for food in self.knowledge['food'].copy():
+            if food == (x, y):
+                self.knowledge['food'].remove(food)
+                self.knowledge['max_scouters'] -= 1
+
+    def food_discovered(self, x, y):
+        self.knowledge['food'].append((x, y))
+        self.knowledge['max_scouters'] += 1
+
+        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) + ") - Total scouters : " + str(len(self.scouters)))

+ 28 - 28
blob-simulation/blob/sensing_scouter.py

@@ -1,28 +1,28 @@
-import random
-
-from board import Board
-from blob.dumb_scouter import DumbScouter
-from blob.actions import Actions
-
-
-class SensingScouter(DumbScouter):
-
-    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]
+import random
+
+from simulation.board import Board
+from simulation.logic.dumb_scouter import DumbScouter
+from simulation.logic.actions import Actions
+
+
+class SensingScouter(DumbScouter):
+
+    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]

+ 85 - 0
blob-simulation/simulation/play.py

@@ -0,0 +1,85 @@
+import pygame
+import argparse
+import time
+from os.path import exists
+
+from pygame.locals import QUIT
+from simulation.interface import Interface
+from simulation.board import Board
+from simulation.player import Player
+from simulation.logic.main import Blob_Manager
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--height', type=int, default=40,
+                        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,
+                        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.')
+    parser.add_argument('--save_dir', type=str, default="save/",
+                        help='Directory where saves are stored.')
+    parser.add_argument('--computing_ratio', type=int, default=1,
+                        help='how many times computing loop is done before drawing GUI')
+
+    args = parser.parse_args()
+
+    if args.init_from is not None:
+        init_scouters = 0
+    else:
+        init_scouters = 3
+
+    board = Board(args.width, args.height)
+    blob = Blob_Manager(board, init_scouters)
+    player = Player(board, blob)
+
+    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")
+
+        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")
+
+    gui = Interface(board, player, blob, args.scale, args.save_dir)
+
+    init_counter = 100
+    timer = time.time()
+    counter = init_counter
+
+    init_computing_ratio = args.computing_ratio
+    ended = False
+    while not ended:
+        computing_ratio = init_computing_ratio
+        while computing_ratio > 0:
+            if gui.play or gui.do_step:
+                blob.move()
+                board.next_turn()
+                gui.do_step = False
+
+            for event in pygame.event.get():
+                if event.type == QUIT:
+                    ended = True
+                else:
+                    gui.event_listener(event)
+
+            computing_ratio -= 1
+
+        gui.draw()
+
+        pygame.time.wait(10)
+
+        counter -= 1
+        if counter == 0:
+            timing = time.time() - timer
+            print("Loop mean time : {:.3f}s per iteration".format(timing / init_counter))
+            counter = init_counter
+            timer = time.time()
+
+
+if __name__ == "__main__":
+    main()

+ 81 - 81
blob-simulation/player.py

@@ -1,81 +1,81 @@
-from math import ceil
-from random import randrange
-
-from blob.main import Blob_Manager
-from board import Board
-
-
-class Player:
-
-    def __init__(self, board, blob):
-        """
-        :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')
-
-    def load(self, file):
-        self.clean_top = file.readline() == '1'
-
-    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
-            y_offset = 0
-            y_range = ceil(self.board.height / 2)
-        elif not random_top:  # Randomize over the half bottom board
-            y_offset = int(self.board.height / 2)
-            y_range = ceil(self.board.height / 2)
-
-        foods = 0
-        while foods < qt:
-            x = randrange(self.board.width)
-            y = y_offset + randrange(y_range)
-            if not self.board.has_food(x, y):
-                if self.set_food(x, y):
-                    foods += 1
-
-    def set_food(self, x, y, size=1):
-        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
-                        food_put = True
-
-        if not food_put:
-            print("There is blob there !")
-            return False
-
-        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 clean_board(self):
-        y_range = ceil(self.board.height/2)
-
-        if self.clean_top:
-            y_offset = 0
-        else:
-            y_offset = int(self.board.height/2)
-
-        for x in range(self.board.width):
-            for y in range(y_range):
-                self.board.reset(x, y_offset + y)
-                self.blob.reset(x, y_offset + y)
-
-        self.clean_top = not self.clean_top
+from math import ceil
+from random import randrange
+
+from simulation.board import Board
+
+
+class Player:
+
+    def __init__(self, board, blob):
+        """
+        :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')
+
+    def load(self, filename):
+        with open(filename, 'r') as file:
+            self.clean_top = file.readline() == '1'
+
+    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
+            y_offset = 0
+            y_range = ceil(self.board.height / 2)
+        elif not random_top:  # Randomize over the half bottom board
+            y_offset = int(self.board.height / 2)
+            y_range = ceil(self.board.height / 2)
+
+        foods = 0
+        while foods < qt:
+            x = randrange(self.board.width)
+            y = y_offset + randrange(y_range)
+            if not self.board.has_food(x, y):
+                if self.set_food(x, y):
+                    foods += 1
+
+    def set_food(self, x, y, size=1):
+        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
+                        food_put = True
+
+        if not food_put:
+            print("There is blob there !")
+            return False
+
+        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 clean_board(self):
+        y_range = ceil(self.board.height/2)
+
+        if self.clean_top:
+            y_offset = 0
+        else:
+            y_offset = int(self.board.height/2)
+
+        for x in range(self.board.width):
+            for y in range(y_range):
+                self.board.reset(x, y_offset + y)
+                self.blob.reset(x, y_offset + y)
+
+        self.clean_top = not self.clean_top