瀏覽代碼

V2.1 Numediart

Loïc Vanden Bemden 6 年之前
父節點
當前提交
ab4115abbf
共有 44 個文件被更改,包括 1649 次插入683 次删除
  1. 24 23
      blob-simulation/compare.py
  2. 二進制
      blob-simulation/data/output-examples/example-detect-details.jpg
  3. 161 0
      blob-simulation/data/output-examples/example-detect.board
  4. 二進制
      blob-simulation/data/output-examples/example-detect.jpg
  5. 5 0
      blob-simulation/data/output-examples/example-detect.player.json
  6. 二進制
      blob-simulation/data/output-examples/refined/example-detect-details.jpg
  7. 161 0
      blob-simulation/data/output-examples/refined/example-detect.board
  8. 二進制
      blob-simulation/data/output-examples/refined/example-detect.jpg
  9. 5 0
      blob-simulation/data/output-examples/refined/example-detect.player.json
  10. 32 0
      blob-simulation/data/output-examples/simulation/100_loops/100_loops.blob.json
  11. 161 0
      blob-simulation/data/output-examples/simulation/100_loops/100_loops.board
  12. 二進制
      blob-simulation/data/output-examples/simulation/100_loops/100_loops.jpg
  13. 5 0
      blob-simulation/data/output-examples/simulation/100_loops/100_loops.player.json
  14. 10 0
      blob-simulation/data/output-examples/simulation/100_loops/100_loops.results.json
  15. 32 0
      blob-simulation/data/output-examples/simulation/10_loops/10_loops.blob.json
  16. 161 0
      blob-simulation/data/output-examples/simulation/10_loops/10_loops.board
  17. 二進制
      blob-simulation/data/output-examples/simulation/10_loops/10_loops.jpg
  18. 5 0
      blob-simulation/data/output-examples/simulation/10_loops/10_loops.player.json
  19. 10 0
      blob-simulation/data/output-examples/simulation/10_loops/10_loops.results.json
  20. 12 0
      blob-simulation/data/refine-example.json
  21. 48 0
      blob-simulation/detect.py
  22. 20 8
      blob-simulation/detection/config.json
  23. 8 60
      blob-simulation/detection/detect.py
  24. 159 0
      blob-simulation/detection/detection_setup.py
  25. 80 0
      blob-simulation/detection/food_colors.py
  26. 24 0
      blob-simulation/detection/food_limits.py
  27. 67 0
      blob-simulation/detection/limits_maker.py
  28. 94 0
      blob-simulation/detection/refine.py
  29. 0 370
      blob-simulation/detection/setup.py
  30. 4 5
      blob-simulation/detection/utils.py
  31. 154 0
      blob-simulation/play.py
  32. 18 0
      blob-simulation/setup.py
  33. 8 15
      blob-simulation/simulation/board.py
  34. 30 11
      blob-simulation/simulation/default/blob.json
  35. 8 0
      blob-simulation/simulation/default/interface.json
  36. 69 32
      blob-simulation/simulation/interface.py
  37. 11 10
      blob-simulation/simulation/logic/advanced_scouter.py
  38. 9 13
      blob-simulation/simulation/logic/main.py
  39. 2 1
      blob-simulation/simulation/logic/dumb_scouter.py
  40. 20 19
      blob-simulation/simulation/logic/fsm_ant.py
  41. 14 14
      blob-simulation/simulation/logic/gatherer.py
  42. 4 5
      blob-simulation/simulation/logic/sensing_scouter.py
  43. 0 90
      blob-simulation/simulation/play.py
  44. 14 7
      blob-simulation/simulation/player.py

+ 24 - 23
blob-simulation/compare.py

@@ -1,21 +1,22 @@
 import argparse
 from simulation import board
 import pygame
-from pygame.locals import QUIT
+from pygame.locals import QUIT, KEYDOWN, K_ESCAPE
 
 
 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")
+    ap.add_argument("-s", "--scale", type=float, default=10,
+                    help="Scales board resolution by this factor (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 = board.Board(0, 0)
     board_1.load(args.first)
 
-    board_2 = board.Board(0,0)
+    board_2 = board.Board(0, 0)
     board_2.load(args.second)
 
     board_comp = board_1.compare(board_2)
@@ -23,24 +24,21 @@ def main():
     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)
+            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
+                val = - val  # int(-val/2) + 125
                 pixel_array[x, y] = (val/4, val/4, val)
             elif val > 0:
-                val = val # int(val/2) + 125
+                val = val  # int(val/2) + 125
                 pixel_array[x, y] = (val, val/4, val/4)
             else:
                 if not board_comp.is_touched(x, y):
@@ -50,24 +48,27 @@ def main():
                         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)
+                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)
+        pygame.image.save(game_window, args.output)
+
+    else:
+        pygame.init()
+        window = pygame.display.set_mode((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 or event.type == KEYDOWN and event.key == K_ESCAPE:
+                    ended = True
 
 
 if __name__ == "__main__":

二進制
blob-simulation/data/output-examples/example-detect-details.jpg


文件差異過大導致無法顯示
+ 161 - 0
blob-simulation/data/output-examples/example-detect.board


二進制
blob-simulation/data/output-examples/example-detect.jpg


+ 5 - 0
blob-simulation/data/output-examples/example-detect.player.json

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

二進制
blob-simulation/data/output-examples/refined/example-detect-details.jpg


文件差異過大導致無法顯示
+ 161 - 0
blob-simulation/data/output-examples/refined/example-detect.board


二進制
blob-simulation/data/output-examples/refined/example-detect.jpg


+ 5 - 0
blob-simulation/data/output-examples/refined/example-detect.player.json

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

+ 32 - 0
blob-simulation/data/output-examples/simulation/100_loops/100_loops.blob.json

@@ -0,0 +1,32 @@
+{
+    "Computing": {
+        "Blob Size Factor": 0.25,
+        "Covering Factor": 0.75,
+        "Global Factor": 2.78,
+        "Known Foods Factor": 0.05
+    },
+    "Gathering": {
+        "Diagonal Moves": true,
+        "Light Compute": true,
+        "Sightline": -1
+    },
+    "Global Decrease": 0.1,
+    "Harvesting": {
+        "Collect": 10,
+        "Eat": 5,
+        "Max": 30,
+        "Min": 30
+    },
+    "Remaining Blob on Food": 50,
+    "Scouters": {
+        "Drop by eat": 25,
+        "Min": 2
+    },
+    "Scouting": {
+        "Diagonal Moves": true,
+        "Global Explore Probability": 0.02,
+        "Light Compute": true,
+        "Search Locally on Food": true,
+        "Sightline": 3
+    }
+}

文件差異過大導致無法顯示
+ 161 - 0
blob-simulation/data/output-examples/simulation/100_loops/100_loops.board


二進制
blob-simulation/data/output-examples/simulation/100_loops/100_loops.jpg


+ 5 - 0
blob-simulation/data/output-examples/simulation/100_loops/100_loops.player.json

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

+ 10 - 0
blob-simulation/data/output-examples/simulation/100_loops/100_loops.results.json

@@ -0,0 +1,10 @@
+{
+    "Covering": {
+        "Bottom": 34.74375,
+        "Top": 9.35,
+        "Total": 22.046875
+    },
+    "From": "save/example-detect",
+    "Loops": 100,
+    "To": "save/2019-06-07_16.26.50"
+}

+ 32 - 0
blob-simulation/data/output-examples/simulation/10_loops/10_loops.blob.json

@@ -0,0 +1,32 @@
+{
+    "Computing": {
+        "Blob Size Factor": 0.25,
+        "Covering Factor": 0.75,
+        "Global Factor": 2.78,
+        "Known Foods Factor": 0.05
+    },
+    "Gathering": {
+        "Diagonal Moves": true,
+        "Light Compute": true,
+        "Sightline": -1
+    },
+    "Global Decrease": 0.1,
+    "Harvesting": {
+        "Collect": 10,
+        "Eat": 5,
+        "Max": 30,
+        "Min": 30
+    },
+    "Remaining Blob on Food": 50,
+    "Scouters": {
+        "Drop by eat": 25,
+        "Min": 2
+    },
+    "Scouting": {
+        "Diagonal Moves": true,
+        "Global Explore Probability": 0.02,
+        "Light Compute": true,
+        "Search Locally on Food": true,
+        "Sightline": 3
+    }
+}

文件差異過大導致無法顯示
+ 161 - 0
blob-simulation/data/output-examples/simulation/10_loops/10_loops.board


二進制
blob-simulation/data/output-examples/simulation/10_loops/10_loops.jpg


+ 5 - 0
blob-simulation/data/output-examples/simulation/10_loops/10_loops.player.json

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

+ 10 - 0
blob-simulation/data/output-examples/simulation/10_loops/10_loops.results.json

@@ -0,0 +1,10 @@
+{
+    "Covering": {
+        "Bottom": 33.6875,
+        "Top": 6.809375000000001,
+        "Total": 20.2484375
+    },
+    "From": "save/example-detect",
+    "Loops": 10,
+    "To": "save/10_loops"
+}

+ 12 - 0
blob-simulation/data/refine-example.json

@@ -0,0 +1,12 @@
+{
+	"Width": 100,
+	"Height": 40,
+	"Foods": [
+		[10, 10],
+		[18,28],
+		[23, 8],
+		[44,23],
+		[96,22]
+	],
+	"Clean Top": false
+}

+ 48 - 0
blob-simulation/detect.py

@@ -0,0 +1,48 @@
+import argparse
+import json
+from detection.detection import detect, discretize, print_results
+from detection.refine import simulate, save
+from os.path import splitext, basename, join
+
+
+def main():
+    ap = argparse.ArgumentParser()
+    ap.add_argument("-i", "--input", required=True, help="Uses this input as image for detection")
+    ap.add_argument("-s", "--scale", type=float, default=0.10, help="Scales images by this factor (default: x0.1)")
+    ap.add_argument("-c", "--config", type=str, default="detection/config.json",
+                    help="Loads config from this file (default: detection/config.json)")
+    ap.add_argument("--save", type=str, default="save/",
+                    help="Pass the directory where saves are stored. (default: save/)")
+    ap.add_argument("--hide", action='store_true', default=False, help="Hide images if parameter is set")
+    ap.add_argument("--refine", type=str, help="Pass a json file to refine model")
+
+    args = ap.parse_args()
+    with open(args.config, 'r') as file:
+        config = json.load(file)
+
+    if args.refine is not None:
+        with open(args.refine, 'r') as file:
+            refine = json.load(file)
+    else:
+        refine = None
+
+    orig, blob_mask, blob, food_mask, food_img = detect(args.input, config)
+    dsc_img, dsc_blob, dsc_food_list = discretize(blob, food_mask, config['Discrete Width'], config['Discrete Height'])
+
+    if args.save is not None:
+        filename = splitext(basename(args.input))[0] + "-detect"
+        file_path = join(args.save, filename)
+
+        board, player, img = simulate(dsc_img, dsc_blob, dsc_food_list, config, refine)
+        save(file_path, board, player, img)
+
+        # Prepare file_path for details if any to save
+        file_path += "-details"
+    else:
+        file_path = None
+
+    print_results(orig, blob_mask, blob, food_mask, food_img, dsc_img, args.scale, file_path, args.hide)
+
+
+if __name__ == "__main__":
+    main()

+ 20 - 8
blob-simulation/detection/config.json

@@ -1,22 +1,34 @@
 {
     "Aspect Ratio": 0.4,
-    "Discrete Height": 240,
-    "Discrete Width": 600,
+    "Discrete Height": 160,
+    "Discrete Width": 400,
     "High Food Color": [
         214,
         230,
         237
     ],
     "Limits": [
-		[136, 268],
-		[5484, 208],
-		[5452, 3296], 
-		[136, 3308]
-	],
+        [
+            136,
+            268
+        ],
+        [
+            5484,
+            208
+        ],
+        [
+            5452,
+            3296
+        ],
+        [
+            136,
+            3308
+        ]
+    ],
     "Low Food Color": [
         150,
         185,
         198
     ],
-	"Min Food Size": 60
+    "Min Food Size": 60
 }

+ 8 - 60
blob-simulation/detection/detect.py

@@ -1,33 +1,4 @@
-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.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 = ap.parse_args()
-    with open(args.config, 'r') as file:
-        config = json.load(file)
-
-    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)
-
-    if not args.hide:
-        print_results(orig, blob_mask, blob, food_mask, food_img, discrete_img, args.scale)
 
 
 def detect(input_file, config):
@@ -77,7 +48,7 @@ def detect(input_file, config):
     return img, blob_mask, blob, food_mask, food_img
 
 
-def print_results(orig, blob_mask, blob, food_mask, food, discrete, scale=1.0):
+def print_results(orig, blob_mask, blob, food_mask, food, discrete, scale=1.0, filename=None, hide=False):
     padding = 35
     nbr_width = 2
     nbr_height = 3
@@ -128,9 +99,13 @@ def print_results(orig, blob_mask, blob, food_mask, food, discrete, scale=1.0):
                 (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)
+    if filename is not None:
+        cv2.imwrite(filename + ".jpg", aggregate)
+
+    if not hide:
+        cv2.imshow("Results", aggregate)
+        print("\nPress any key...")
+        cv2.waitKey(0)
 
 
 def discretize(blob_img, food_mask, width, height):
@@ -165,30 +140,3 @@ def discretize(blob_img, food_mask, width, height):
                            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 + ".board", 'w') as board_file:
-        board_file.write(board_str)
-
-    cv2.imwrite(filename + ".jpg", discrete_img)
-
-
-if __name__ == "__main__":
-    main()

+ 159 - 0
blob-simulation/detection/detection_setup.py

@@ -0,0 +1,159 @@
+import cv2
+import json
+import datetime
+import time
+from shutil import copyfile
+from os import path, makedirs
+
+from detection.limits_maker import LimitsMaker
+from detection.food_limits import FoodLimits
+from detection.food_colors import FoodColors
+
+
+def setup(setup_img, config_filename, scale=1.0, bkp_path="detection/bkp/"):
+    img = cv2.imread(setup_img)
+    height, width, _ = img.shape
+
+    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)
+
+    food_color = FoodColors(img, scale, window_name)
+    board_setup = LimitsMaker(img, scale, window_name, "Board Setup")
+    food_limits = FoodLimits(img, scale, window_name)
+    setup_vars = {'Aspect Ratio': 1.0, 'Discrete Height': 100, 'Discrete Width': 100}
+
+    if path.exists(config_filename):
+        with open(config_filename, "r") as file:
+            config = json.load(file)
+        setup_vars['Aspect Ratio'] = config['Aspect Ratio']
+        setup_vars['Discrete Height'] = config['Discrete Height']
+        setup_vars['Discrete Width'] = config['Discrete Width']
+        board_setup.limits = [tuple(x) for x in config['Limits']]
+        food_color.colors.append(tuple(config["High Food Color"]))
+        food_color.colors.append(tuple(config["Low Food Color"]))
+        food_color.show_selected()
+        food_limits.min_dist = config["Min Food Size"]
+
+    def show_menu():
+        print("\nCommands: ")
+        print("\tEnter '1' to setup board limits")
+        low, high = food_color.compute()
+        print("\tEnter '2' to setup food color. (Current from {} to {})".format(low, high))
+        print("\tEnter '3' to insert image aspect ratio. (Current: {})".format(setup_vars['Aspect Ratio']))
+        print("\tEnter '4' to insert discrete image width and height. (Current: {}x{})"
+              .format(setup_vars['Discrete Width'], setup_vars['Discrete Height']))
+        print("\tEnter '5' to setup food limits. (Current min food size: {})".format(food_limits.min_dist))
+        print("\tEnter 's' to save & quit")
+        print("\tEnter 'q' to quit without saving")
+
+    show_menu()
+    done = False
+    state = 0
+    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 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.")
+
+                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.")
+
+
+                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 path.exists(config_filename):
+                    if not path.exists(bkp_path):
+                        makedirs(bkp_path)
+                    copyfile(config_filename, bkp_path + ts + path.basename(config_filename))
+
+                with open(config_filename, "w") as file:
+                    json.dump({**setup_vars, **board_setup.toJSON(), **food_color.toJSON(), **food_limits.toJSON()},
+                              file, indent=4, sort_keys=True)
+
+                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()
+
+        elif state == 3:
+            food_limits.draw()
+            if food_limits.done:
+                state = 0
+                show_menu()
+
+
+def null_callback(event, x, y, flags, param):
+    return
+

+ 80 - 0
blob-simulation/detection/food_colors.py

@@ -0,0 +1,80 @@
+import cv2
+import numpy as np
+
+
+class FoodColors:
+
+    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]
+        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):
+        print("--- Color Setup: Click several times on foods to setup food color and then press enter.")
+
+    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:
+            self.add(x, y)
+
+    def confirm(self):
+        key = cv2.waitKey(10) & 0xFF
+        if key == 13:  # Enter
+            print("--- Color Setup: " + str(self.compute()))
+            self.done = True
+        elif len(self.colors) > 0 and key == 8:  # Backspace
+            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)))

+ 24 - 0
blob-simulation/detection/food_limits.py

@@ -0,0 +1,24 @@
+import cv2
+from detection.limits_maker import LimitsMaker
+from math import sqrt
+
+
+class FoodLimits(LimitsMaker):
+
+    def __init__(self, img, scale, window_name):
+        LimitsMaker.__init__(self, img, scale, window_name, "Food Setup")
+        self.min_dist = 5
+
+    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("--- " + self.name + ": Click on the {} corners of the tiniest food".format(self.max_limits))

+ 67 - 0
blob-simulation/detection/limits_maker.py

@@ -0,0 +1,67 @@
+import cv2
+
+
+class LimitsMaker:
+
+    def __init__(self, img, scale, window_name, 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
+        self.name = name
+
+    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("--- " + self.name + ": Click on the {} corners.".format(self.max_limits))
+        print("--- " + self.name + ": Please start from left corner and do it in the right order")
+
+    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 clear(self):
+        self.limits = []
+        self.limits_drawn = False
+        self.img = self.orig.copy()
+        self.done = False
+
+    def confirm(self):
+        print("--- " + self.name + ": Press enter if you're ok with data or any other key if you want to restart "
+                                   "setup...")
+        key = cv2.waitKey(0) & 0xFF
+        if key == 13:  # Enter
+            print("--- " + self.name + ": " + str(self.compute()))
+            self.done = True
+        else:
+            self.clear()
+            self.help()

+ 94 - 0
blob-simulation/detection/refine.py

@@ -0,0 +1,94 @@
+import cv2
+from simulation.board import Board
+from simulation.player import Player
+
+
+def simulate(discrete_img, discrete_blob, discrete_food_list, config, refine=None):
+
+    height, width = discrete_blob.shape
+
+    board = Board(width, height)
+    player = Player(board, None, "simulation/default/player.json")
+    player.food_size = compute_discrete_food_size(config, player.use_circle)
+    player.clean_top = refine['Clean Top'] if refine is not None else True
+
+    for (x, y) in discrete_food_list:
+        if board.is_touched(x, y):
+            # TODO Set up value
+            board.set_food(x, y, value=Board.INIT_FOOD/2)
+        else:
+            board.set_food(x, y)
+
+    for x in range(width):
+        for y in range(height):
+            if discrete_blob[y, x] != 0:
+                board.update_blob(x, y, discrete_blob[y, x])
+
+    if refine is not None:
+        adapt_food(board, player, config, refine)
+
+    return board, player, discrete_img
+
+
+def adapt_food(board, player, config, refine):
+    square_size = round(1 / refine["Width"] * config["Discrete Width"])
+
+    adding_food = 0
+
+    for food_origin in refine["Foods"]:
+        discrete_food = (int(food_origin[0] / refine["Width"] * config["Discrete Width"]),
+                         int(food_origin[1] / refine["Height"] * config["Discrete Height"]))
+
+        food_found = False
+        for i in range(square_size):
+            for j in range(square_size):
+                if board.has_food(discrete_food[0] + i, discrete_food[1] + j):
+                    food_found = True
+
+        if not food_found:
+            # TODO Set up value
+            player.set_food(round(discrete_food[0] + square_size/2), round(discrete_food[1] + square_size/2),
+                            force=True, value=Board.INIT_FOOD/4)
+            adding_food += 1
+
+    print("Foods added: {}".format(adding_food))
+
+
+def save(filename, board, player, img):
+    with open(filename + ".board", 'w') as file:
+        file.write(board.save())
+
+    with open(filename + ".player.json", 'w') as file:
+        file.write(player.save())
+
+    cv2.imwrite(filename + ".jpg", img)
+
+
+def compute_discrete_food_size(config, use_circle=False):
+
+    food_size = config["Min Food Size"]
+    limits = config["Limits"]
+    x_min = limits[0][0]
+    x_max = limits[0][0]
+    y_min = limits[0][1]
+    y_max = limits[0][1]
+
+    for limit in limits:
+        x_min = min(x_min, limit[0])
+        x_max = max(x_max, limit[0])
+
+        y_min = min(y_min, limit[1])
+        y_max = max(y_max, limit[1])
+
+    img_width = x_max - x_min
+    img_height = y_max - y_min
+
+    if use_circle:
+        ratio = 1.5  # ~sqrt(2)
+    else:
+        ratio = 1
+
+    discrete_food_size = round((food_size / img_height * config["Discrete Height"]
+                                + food_size / img_width * config["Discrete Width"]) * ratio / 2)
+
+    return discrete_food_size

+ 0 - 370
blob-simulation/detection/setup.py

@@ -1,370 +0,0 @@
-import argparse
-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():
-
-    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)
-    food_limits = FoodLimits(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 == "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"]):
-                    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(), **food_limits.toJSON()},
-                              file, indent=4, sort_keys=True)
-
-                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()
-
-        elif state == 3:
-            food_limits.draw()
-            if food_limits.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 '5' to setup food limits")
-    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 FoodLimits:
-
-    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
-        self.min_dist = 5
-
-    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):
-        # 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:
-            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]
-        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):
-        print("--- Color Setup: Click several times on foods to setup food color and then press enter.")
-
-    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:
-            self.add(x, y)
-
-    def confirm(self):
-        key = cv2.waitKey(10)
-        if key == 13:  # Enter
-            print("--- Color Setup: " + str(self.compute()))
-            self.done = True
-        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__":
-    main()

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

@@ -5,11 +5,11 @@ import imutils
 
 def saturation(img):
     hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
-    return hsv[:,:,1]
+    return hsv[:, :, 1]
 
 
 def mean_image(images):
-    mean = 0
+    mean = np.zeros(images[0].shape)
 
     for image in images:
         mean += image/len(images)
@@ -71,9 +71,9 @@ def find_blob(sat_img, max_blob=1, area_ratio=0.8, kernel=None):
 
     blobs = []
 
-    while len(contours) != 0 and len(blobs) < max_blob :
+    while len(contours) != 0 and len(blobs) < max_blob:
         c = max(contours, key=cv2.contourArea)
-        if len(blobs) != 0 and cv2.contourArea(blobs[0]) * area_ratio >= cv2.contourArea(c) :
+        if len(blobs) != 0 and cv2.contourArea(blobs[0]) * area_ratio >= cv2.contourArea(c):
             break
         blobs.append(c)
         contours.remove(c)
@@ -84,4 +84,3 @@ def find_blob(sat_img, max_blob=1, area_ratio=0.8, kernel=None):
     kept_cleaned = cv2.bitwise_and(cleaned, cleaned, mask=mask)
 
     return kept_cleaned
-

+ 154 - 0
blob-simulation/play.py

@@ -0,0 +1,154 @@
+import pygame
+import argparse
+import time
+from os.path import exists, join, splitext
+import os
+import json
+
+from pygame.locals import QUIT, KEYDOWN, K_ESCAPE
+from simulation.interface import Interface
+from simulation.board import Board
+from simulation.player import Player
+from simulation.logic.blob_manager import BlobManager
+
+HIDE_GUI = 0
+WINDOW_GUI = 1
+BORDERLESS_GUI = 2
+FULLSCREEN_GUI = 3
+
+SCREEN_RESOLUTION = (1920, 1080)
+DEFAULT_DIR = "simulation/default"
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--height', type=int, default=40,
+                        help='New game board height resolution (default: 40)')
+    parser.add_argument('--width', type=int, default=100,
+                        help='New game board width resolution (default: 100)')
+    parser.add_argument('-i', '--input', type=str,
+                        help='Initialize game from a save. Overwrite height and width parameters. '
+                             'Pass the board filename as input (.board extension)')
+    parser.add_argument('-s', '--scale', type=int, default=10,
+                        help='Scales board resolution by this factor (default: x10)')
+    parser.add_argument('--save', type=str, default="save/",
+                        help="Pass the directory where saves are stored. (default: save/)")
+
+    parser.add_argument('--computing_ratio', type=int, default=1,
+                        help='how many times computing loop is done before drawing GUI')
+    parser.add_argument('--auto_loops', type=int, default=-1,
+                        help='set number of loops needed before saving and closing automatically')
+    parser.add_argument('--display', type=int, default=WINDOW_GUI,
+                        help="Set to '{}' to display as centered window, "
+                             "'{}' to display as centered window with no border, "
+                             "'{}' to display as fullscreen, "
+                             "'{}' to hide display (only if auto_loops is set correctly)"
+                        .format(WINDOW_GUI, BORDERLESS_GUI, FULLSCREEN_GUI, HIDE_GUI))
+    parser.add_argument('--init_foods', type=int, default=0,
+                        help='Starts the game by initializing a certain quantity of foods in one of the half-board')
+
+    args = parser.parse_args()
+
+    player_file = join(DEFAULT_DIR, "player.json")
+    blob_file = join(DEFAULT_DIR, "blob.json")
+    gui_file = join(DEFAULT_DIR, "interface.json")
+
+    results = dict()
+
+    if args.input is not None:
+        assert exists(args.input)
+        root_name = splitext(args.input)[0]
+
+        results['From'] = root_name
+
+        board = Board(args.width, args.height)
+        board.load(args.input)
+
+        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)
+
+    blob = BlobManager(board, blob_file)
+    player = Player(board, blob, player_file)
+
+    if args.init_foods > 0:
+        results['Init_foods'] = player.set_random_food(args.init_foods, not player.clean_top)
+
+    mode = 0
+    window_x = int((SCREEN_RESOLUTION[0] - board.width * args.scale) / 2)
+    window_y = int((SCREEN_RESOLUTION[1] - board.height * args.scale) / 2)
+    os.environ['SDL_VIDEO_WINDOW_POS'] = str(window_x) + "," + str(window_y)
+
+    if args.display == BORDERLESS_GUI:
+        mode = mode | pygame.NOFRAME
+    elif args.display == FULLSCREEN_GUI:
+        mode = mode | pygame.FULLSCREEN
+    elif args.display == HIDE_GUI:
+        if args.auto_loops <= 0:
+            args.display = WINDOW_GUI
+
+    gui = Interface(board, player, blob, args.scale, args.save, mode, args.display == HIDE_GUI, gui_file)
+    if args.auto_loops > 0:
+        results['Loops'] = args.auto_loops
+        gui.play = True
+
+    init_counter = 100
+    timer = time.time()
+    counter = init_counter
+
+    init_computing_ratio = args.computing_ratio
+    ended = False
+    while not ended and args.auto_loops != 0:
+        computing_ratio = init_computing_ratio
+        while computing_ratio > 0 and args.auto_loops != 0:
+            if gui.play or gui.do_step:
+                blob.move()
+                gui.do_step = False
+                if args.auto_loops > 0:
+                    args.auto_loops -= 1
+
+            for event in pygame.event.get():
+                if event.type == QUIT or event.type == KEYDOWN and event.key == K_ESCAPE:
+                    ended = True
+                else:
+                    used = gui.event_listener(event)
+
+                    # Quit automatic mode if user had interactions
+                    if used and args.auto_loops != -1:
+                        args.auto_loops = -1
+                        print("User interaction detected ! \n\t --- Automatic mode stopped.")
+
+            computing_ratio -= 1
+
+        if args.display != HIDE_GUI:
+            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 args.auto_loops == 0:
+        name = gui.save()
+
+        up_size_percent, down_size_percent = player.check_blob_cover()
+        results['Covering'] = dict()
+        results['Covering']['Total'] = (up_size_percent + down_size_percent)/2
+        results['Covering']['Top'] = up_size_percent
+        results['Covering']['Bottom'] = down_size_percent
+        results['To'] = args.save + name
+
+        with open(args.save + name + ".results.json", 'w') as file:
+            json.dump(results, file, indent=4, sort_keys=True)
+
+
+if __name__ == "__main__":
+    main()

+ 18 - 0
blob-simulation/setup.py

@@ -0,0 +1,18 @@
+import argparse
+from detection.detection_setup import setup
+
+
+def main():
+
+    ap = argparse.ArgumentParser()
+    ap.add_argument("-i", "--input", required=True, help="Uses this input as image for setup")
+    ap.add_argument("-s", "--scale", type=float, default=0.25, help="Scales image by this factor (default: x0.25)")
+    ap.add_argument("-c", "--config", type=str, default="detection/config.json",
+                    help="Saves config under this filename (default: detection/config.json)")
+
+    args = vars(ap.parse_args())
+    setup(args['input'], args['config'], args['scale'])
+
+
+if __name__ == "__main__":
+    main()

+ 8 - 15
blob-simulation/simulation/board.py

@@ -5,8 +5,6 @@ class Board:
 
     MAX_BLOB = 255.0
     MIN_BLOB = 0.0
-    MIN_FOOD_BLOB = 50
-    DECREASE_BLOB = 0.1
     INIT_FOOD = 100
 
     def __init__(self, width, height):
@@ -21,8 +19,7 @@ class Board:
         stream = str(self.width) + ' ' + str(self.height) + '\n'
         for y in range(self.height):
             for x in range(self.width):
-                # TODO change food savings
-                saved_node = "{:d},{:d},{} ".format(self.touched[x, y], self.foods[x, y] != 0, self.dropped_blob[x, y])
+                saved_node = "{:d},{},{} ".format(self.touched[x, y], self.foods[x, y], self.dropped_blob[x, y])
                 stream += saved_node
             stream = stream.rstrip(' ')
             stream += '\n'
@@ -50,11 +47,7 @@ class Board:
                         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.foods[x, y] = float(values[1])
                     self.dropped_blob[x, y] = float(values[2])
                     x += 1
 
@@ -63,9 +56,9 @@ class Board:
     def has_food(self, x, y):
         return self.inside(x, y) and self.foods[x, y] > 0
 
-    def set_food(self, x, y):
+    def set_food(self, x, y, value=INIT_FOOD):
         if not self.foods[x, y] > 0:
-            self.foods[x, y] = Board.INIT_FOOD
+            self.foods[x, y] = value
 
     def remove_food(self, x, y):
         if self.foods[x, y] > 0:
@@ -74,7 +67,7 @@ class Board:
     def update_blob(self, x, y, change_value):
         if self.inside(x, y):
             self.touched[x, y] = True
-            self.dropped_blob[x, y] = max(0, min(self.dropped_blob[x, y] + change_value, Board.MAX_BLOB))
+            self.dropped_blob[x, y] = max(Board.MIN_BLOB, min(self.dropped_blob[x, y] + change_value, Board.MAX_BLOB))
             return True
         else:
             return False
@@ -124,12 +117,12 @@ class Board:
 
         return total / self.height / self.width / self.MAX_BLOB * 100
 
-    def next_turn(self, food_lock=True):
+    def manage_blob(self, value, min_food_value=MIN_BLOB):
         for x in range(self.width):
             for y in range(self.height):
                 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)
+                    if not (self.foods[x, y] > 0 and self.dropped_blob[x, y] <= min_food_value):
+                        self.update_blob(x, y, -value)
 
     def reset(self, x, y):
         if self.inside(x, y):

+ 30 - 11
blob-simulation/simulation/default/blob.json

@@ -1,13 +1,32 @@
 {
-    "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
+    "Computing": {
+        "Blob Size Factor": 0.25,
+        "Covering Factor": 0.75,
+        "Global Factor": 2.78,
+        "Known Foods Factor": 0.05
+    },
+    "Gathering": {
+        "Diagonal Moves": true,
+        "Light Compute": true,
+        "Sightline": -1
+    },
+    "Global Decrease": 0.1,
+    "Harvesting": {
+        "Collect": 10,
+        "Eat": 5,
+        "Max": 30,
+        "Min": 30
+    },
+    "Remaining Blob on Food": 50,
+    "Scouters": {
+        "Drop by eat": 25,
+        "Min": 2
+    },
+    "Scouting": {
+        "Diagonal Moves": true,
+        "Global Explore Probability": 0.02,
+        "Light Compute": true,
+        "Search Locally on Food": true,
+        "Sightline": 3
+    }
 }

+ 8 - 0
blob-simulation/simulation/default/interface.json

@@ -0,0 +1,8 @@
+{
+	"FOOD_COLOR": [244, 210, 128],
+	"TOUCHED_COLOR": [218, 196, 136],
+	"BLOB_LOWER_COLOR": [206, 182, 86],
+	"BLOB_HIGHER_COLOR": [162, 106, 59],
+	"BACKGROUND": [120, 120, 120],
+	"BOARD_SEPARATOR": [0, 0, 0]
+}

+ 69 - 32
blob-simulation/simulation/interface.py

@@ -2,6 +2,7 @@ import pygame
 import time
 import datetime
 import os.path
+import json
 
 from pygame.locals import *
 from simulation.board import Board
@@ -9,13 +10,14 @@ 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)
+    FOOD_COLOR = (0, 150, 0)
+    TOUCHED_COLOR = (50, 50, 0)
+    BLOB_LOWER_COLOR = (255, 255, 0)
+    BLOB_HIGHER_COLOR = (255, 0, 0)
+    BACKGROUND = (0, 0, 0)
+    BOARD_SEPARATOR = (120, 120, 120)
 
-    def __init__(self, board, player, blob, scale, save_dir):
+    def __init__(self, board, player, blob, scale, save_dir, mode, hidden=False, colors_file=None):
         """
         :type board: Board
         :type player: Player
@@ -23,9 +25,21 @@ class Interface:
         :type scale: float
         :type save_dir: str
         """
+
         pygame.init()
         # pygame.key.set_repeat(5, 50)
 
+        if colors_file is not None:
+            with open(colors_file, 'r') as file:
+                colors = json.load(file)
+
+            Interface.FOOD_COLOR = tuple(colors['FOOD_COLOR'])
+            Interface.TOUCHED_COLOR = tuple(colors['TOUCHED_COLOR'])
+            Interface.BLOB_LOWER_COLOR = tuple(colors['BLOB_LOWER_COLOR'])
+            Interface.BLOB_HIGHER_COLOR = tuple(colors['BLOB_HIGHER_COLOR'])
+            Interface.BACKGROUND = tuple(colors['BACKGROUND'])
+            Interface.BOARD_SEPARATOR = tuple(colors['BOARD_SEPARATOR'])
+
         self.board = board
         self.player = player
         self.blob = blob
@@ -40,11 +54,18 @@ class Interface:
 
         width = self.board.width * scale
         height = self.board.height * scale
-        self.window = pygame.display.set_mode((width, height))
+
+        self.hidden = hidden
+        if not self.hidden:
+            self.window = pygame.display.set_mode((width, height), mode)
+
+        self.window_surface = pygame.Surface((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 = pygame.image.load(cross_file)
+        if not self.hidden:
+            discovered_food = discovered_food.convert()
         discovered_food.set_colorkey((255, 255, 255))
         self.discovered_food = pygame.transform.scale(discovered_food, (scale, scale))
 
@@ -85,30 +106,38 @@ class Interface:
 
         game_window = pygame.transform.scale(game_surface, (width, height))
 
-        pygame.draw.line(game_window, (125, 125, 125), (0, height / 2), (width, height / 2))
+        pygame.draw.line(game_window, Interface.BOARD_SEPARATOR, (0, height / 2), (width, height / 2))
 
-        self.window.blit(game_window, (0, 0))
+        self.window_surface.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()
+            self.window_surface.blit(self.discovered_food, (food[0] * self.scale, food[1] * self.scale))
 
-    def save(self):
-        ts = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H.%M.%S')
+        if not self.hidden:
+            self.window.blit(self.window_surface, (0, 0))
+            pygame.display.flip()
 
-        print("Data saved at " + ts)
-        f = open(self.save_dir + ts + ".board", 'w')
+    def save(self, name=None):
+        if name is None:
+            name = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H.%M.%S')
+
+        print("Data saved at " + name)
+        f = open(self.save_dir + name + ".board", 'w')
         f.write(self.board.save())
         f.close()
 
-        f = open(self.save_dir + ts + ".blob.json", 'w')
+        f = open(self.save_dir + name + ".blob.json", 'w')
         f.write(self.blob.save())
         f.close()
 
-        f = open(self.save_dir + ts + ".player.json", 'w')
+        f = open(self.save_dir + name + ".player.json", 'w')
         f.write(self.player.save())
         f.close()
 
-        pygame.image.save(self.window, self.save_dir + ts + ".jpg")
+        if self.hidden:
+            self.draw()
+        pygame.image.save(self.window_surface, self.save_dir + name + ".jpg")
+
+        return name
 
     def event_listener(self, event):
 
@@ -118,12 +147,12 @@ class Interface:
             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))
+            self.blob.knowledge["Global Decrease"] += 0.1
+            print("Pheromone evaporation : " + str(self.blob.knowledge["Global Decrease"]))
 
         elif event.type == KEYDOWN and event.key == K_DOWN:
-            Board.DECREASE_BLOB -= 0.1
-            print("Pheromone evaporation : " + str(Board.DECREASE_BLOB))
+            self.blob.knowledge["Global Decrease"] -= 0.1
+            print("Pheromone evaporation : " + str(self.blob.knowledge["Global Decrease"]))
 
         elif event.type == KEYDOWN and event.key == K_SPACE:
             self.show_ants = not self.show_ants
@@ -133,9 +162,9 @@ class Interface:
 
         # 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)
+            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
@@ -167,13 +196,21 @@ class Interface:
             self.do_step = True
 
         elif event.type == KEYDOWN and event.key == 107:  # K Letter
-            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)))
+            if self.blob.knowledge["Scouters"]["Min"] > 0:
+                self.blob.knowledge["Scouters"]["Min"] -= 1
+                print("New minimal scouters : " + str(self.blob.knowledge["Scouters"]["Min"]) + " - Currently : "
+                      + str(len(self.blob.scouters)))
 
         elif event.type == KEYDOWN and event.key == 113:  # A letter
-            self.blob.knowledge['min_scouters'] += 1
-            print("New minimal scouters : " + str(self.blob.knowledge['min_scouters']) + " - Currently : " + str(len(self.blob.scouters)))
+            self.blob.knowledge["Scouters"]["Min"] += 1
+            print("New minimal scouters : " + str(self.blob.knowledge["Scouters"]["Min"]) + " - Currently : "
+                  + str(len(self.blob.scouters)))
 
         elif event.type == KEYDOWN:
-            print("Unrecognised key code : " + str(event.key))
+            print("Unrecognised key code : " + str(event.key))
+            return False
+
+        else:
+            return False
+
+        return True

+ 11 - 10
blob-simulation/simulation/logic/advanced_scouter.py

@@ -7,23 +7,23 @@ 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
+                - ["Scouting"]["Global Explore Probability"] : (float, between 0 and 1) Set the ratio between exploring
+                    globally and exploring locally
+                - ["Scouting"]["Search Locally 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)
+    def __init__(self, board, knowledge, x, y, use_diagonal=False, sightline=3, light_compute=True):
+        SensingScouter.__init__(self, board, knowledge, x, y, use_diagonal, sightline, 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():
+            if not (self.board.has_food(self.x, self.y) and self.knowledge["Scouting"]["Search Locally on Food"]) \
+                    and self.knowledge["Scouting"]["Global Explore Probability"] < random.random():
                 self.state = 1
             return self.choose_local_goal()
         else:
-            if self.knowledge['explore_ratio'] >= random.random():
+            if self.knowledge["Scouting"]["Global Explore Probability"] >= random.random():
                 self.state = 0
             return self.choose_global_goal()
 
@@ -53,8 +53,9 @@ class AdvancedScouter(SensingScouter):
             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:
+        if self.board.has_food(self.x, self.y) and self.knowledge["Scouting"]["Search Locally on Food"] \
+                and self.state == 1:
             self.goal = None
-            self.state = 0 # Food found, search locally
+            self.state = 0  # Food found, search locally
 
         SensingScouter.move(self)

+ 9 - 13
blob-simulation/simulation/logic/main.py

@@ -7,7 +7,7 @@ from simulation.logic.fsm_ant import FSMAnt
 from simulation.board import Board
 
 
-class Blob_Manager:
+class BlobManager:
 
     def __init__(self, board, default_knowledge):
         """
@@ -71,6 +71,8 @@ class Blob_Manager:
             self.scouters.remove(dead)
             self.add_scouter()
 
+        self.board.manage_blob(self.knowledge["Global Decrease"], self.knowledge["Remaining Blob on Food"])
+
     def add_scouter(self):
         if len(self.scouters) < self.knowledge['max_scouters']:
             if len(self.knowledge['food']) != 0:
@@ -88,19 +90,13 @@ class Blob_Manager:
         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 = self.knowledge["Computing"]["Blob Size Factor"] * self.board.get_blob_total() \
+                         + self.knowledge["Computing"]["Covering Factor"] * self.board.get_cover() \
+                         + self.knowledge["Computing"]["Known Foods Factor"] * len(self.knowledge['food'])
 
-        total_scouters = int((blob_size_factor * scouters_by_size + cover_factor * scouters_by_cover)
-                             * (real_size_factor * self.board.height * self.board.width / 100))
+        total_scouters *= (self.knowledge["Computing"]["Global Factor"] * (self.board.height * self.board.width / 100000))
 
-        return max(self.knowledge['min_scouters'], total_scouters)
+        return max(self.knowledge["Scouters"]["Min"], int(total_scouters))
 
     def find_blob_square(self):
         availables = []
@@ -144,4 +140,4 @@ class Blob_Manager:
         # print("Food discovered in (" + str(x) + ", " + str(y) + ")")
 
     def food_destroyed(self, x, y):
-        self.knowledge['food'].remove((x, y))
+        self.knowledge['food'].remove((x, y))

+ 2 - 1
blob-simulation/simulation/logic/dumb_scouter.py

@@ -2,6 +2,7 @@ import random
 
 from simulation.board import Board
 
+
 class DumbScouter:
     """ Dumb scouter searching food randomly and without any knowledge """
 
@@ -16,7 +17,7 @@ class DumbScouter:
         self.knowledge = knowledge
         self.x = x
         self.y = y
-        self.drop = self.knowledge['drop']
+        self.drop = self.knowledge["Scouters"]["Drop by eat"]
 
     def move(self):
         x = self.x + random.randint(-1, 1)

+ 20 - 19
blob-simulation/simulation/logic/fsm_ant.py

@@ -9,11 +9,11 @@ class FSMAnt(DumbScouter):
 
     """
         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
+            - ["Harvesting"]["Min"]: (float) Min value an ant has to store to stop being starved
+            - ["Harvesting"]["Max"]: (float) Max value an ant can carry
+            - ["Harvesting"]["Eat"]: (float) Value an ant eat to do a step
+            - ["Harvesting"]["Collect"]: (float) Value an ant collect by stepping on food
+            - ["Gathering"]/["Scouting"]["Diagonal Moves"]: (bool) Allow ants to use diagonals to travel
     """
 
     # Multiplication factor for drop value (see blob variable) when an ant is starving
@@ -27,12 +27,15 @@ class FSMAnt(DumbScouter):
         :type y: int
         """
         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.stored = self.knowledge["min_harvest"]
+        self.gatherer_logic = Gatherer(board, knowledge, x, y, self.knowledge["Gathering"]["Diagonal Moves"],
+                                       self.knowledge["Gathering"]["Sightline"],
+                                       self.knowledge["Gathering"]["Light Compute"])
+        self.scouting_logic = AdvancedScouter(board, knowledge, x, y, self.knowledge["Scouting"]["Diagonal Moves"],
+                                              self.knowledge["Scouting"]["Sightline"],
+                                              self.knowledge["Scouting"]["Light Compute"])
+
+        self.stored = self.knowledge["Harvesting"]["Min"]
         self.starving = False
-        self.init_drop = self.knowledge['drop']
 
     def move(self):
         if self.starving:
@@ -53,24 +56,22 @@ class FSMAnt(DumbScouter):
         self.scouting_logic.reset()
         self.scouting_logic.x = self.x
         self.scouting_logic.y = self.y
-        self.drop = self.init_drop
 
     def update(self):
-        # if self.harvest > 0 and self.starving:
-        #     self.drop = FSMAnt.RATIO_DROP_STARVE * self.init_drop
-
-        self.drop = self.init_drop * self.stored
+        eat_ratio = self.knowledge["Harvesting"]["Eat"] * (Board.MAX_BLOB - self.board.get_blob(self.x, self.y)) \
+                    / Board.MAX_BLOB
+        self.drop = self.knowledge["Scouters"]["Drop by eat"] * eat_ratio
         DumbScouter.update(self)
 
         if not self.starving:
-            self.stored -= self.knowledge["eat"]
+            self.stored -= eat_ratio
             self.stored = max(0, self.stored)
 
         if self.board.has_food(self.x, self.y):
             if len(self.knowledge['food']) == 1:
-                wanted = min(self.knowledge["min_harvest"], self.knowledge["max_harvest"] - self.stored)
+                wanted = min(self.knowledge["Harvesting"]["Min"], self.knowledge["Harvesting"]["Max"] - self.stored)
             else:
-                wanted = min(self.knowledge["harvest"], self.knowledge["max_harvest"] - self.stored)
+                wanted = min(self.knowledge["Harvesting"]["Collect"], self.knowledge["Harvesting"]["Max"] - self.stored)
 
             received, finished = self.board.eat_food(self.x, self.y, wanted)
             self.stored += received
@@ -82,6 +83,6 @@ class FSMAnt(DumbScouter):
             self.starving = True
             self.init_gathering()
 
-        if self.stored >= self.knowledge["min_harvest"] and self.starving:
+        if self.stored >= self.knowledge["Harvesting"]["Min"] and self.starving:
             self.starving = False
             self.init_scouting()

+ 14 - 14
blob-simulation/simulation/logic/gatherer.py

@@ -10,30 +10,30 @@ from simulation.logic.dumb_scouter import DumbScouter
 
 
 class Gatherer(DumbScouter):
-    def __init__(self, board, knowledge, x, y, use_diagonal=True, sight_see=-1, light_compute=True):
+    def __init__(self, board, knowledge, x, y, use_diagonal=True, sightline=-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.sight_see = sightline if sightline > 0 else max(self.board.width, self.board.height)
 
         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))
+        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:
-                        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)
+                        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:

+ 4 - 5
blob-simulation/simulation/logic/sensing_scouter.py

@@ -1,4 +1,3 @@
-import random
 import numpy as np
 
 from pathfinding.core.diagonal_movement import DiagonalMovement
@@ -11,11 +10,11 @@ from simulation.logic.dumb_scouter import DumbScouter
 
 class SensingScouter(DumbScouter):
 
-    def __init__(self, board, knowledge, x, y, use_diagonal=False, sight_see=-1, light_compute=True):
+    def __init__(self, board, knowledge, x, y, use_diagonal=False, sightline=-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.sight_see = sightline if sightline > 0 else 1
         self.light_compute = light_compute
         self.goal = None
         self.path = []
@@ -78,7 +77,7 @@ class SensingScouter(DumbScouter):
 
     def move(self):
         # Scouter has no more goal
-        if self.goal is None: #or self.board.get_blob(self.goal[0], self.goal[1]) != 0:
+        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")
@@ -112,4 +111,4 @@ class SensingScouter(DumbScouter):
         self.goal = None
         self.path = []
         self.x = 0
-        self.y = 0
+        self.y = 0

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

@@ -1,90 +0,0 @@
-import pygame
-import argparse
-import time
-from os.path import exists, join, splitext
-
-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('-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 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,
-                        help='how many times computing loop is done before drawing GUI')
-
-    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:
-        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)
-        board.load(args.save_dir + args.init_from)
-
-        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)
-
-    blob = Blob_Manager(board, blob_file)
-    player = Player(board, blob, player_file)
-
-    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()

+ 14 - 7
blob-simulation/simulation/player.py

@@ -41,12 +41,16 @@ class Player:
             y_range = ceil(self.board.height / 2)
 
         foods = 0
+        foods_list = []
         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
+                    foods_list.append((x, y))
+
+        return foods_list
 
     def remove_food(self, x, y):
         food_remove = False
@@ -61,22 +65,25 @@ class Player:
                         food_remove = True
 
         if not food_remove:
-            print("Blob already found it !")
+            # print("Blob already found it !")
+            return False
+
+        return True
 
-    def set_food(self, x, y):
+    def set_food(self, x, y, force=False, value=Board.INIT_FOOD):
         food_put = 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.set_food(x0 + x_size, y0 + y_size)
-                        food_put = True
+                    if self.board.inside(x0 + x_size, y0 + y_size):
+                        if force or not self.board.is_touched(x0 + x_size, y0 + y_size):
+                            self.board.set_food(x0 + x_size, y0 + y_size, value)
+                            food_put = True
 
         if not food_put:
-            print("There is blob there !")
+            # print("There is blob there !")
             return False
 
         return True