diff --git a/src/AI/joblib/food.joblib b/src/AI/joblib/food.joblib deleted file mode 100644 index 07f4c3e6..00000000 Binary files a/src/AI/joblib/food.joblib and /dev/null differ diff --git a/src/AI/joblib/level1.joblib b/src/AI/joblib/level1.joblib new file mode 100644 index 00000000..05a905a6 Binary files /dev/null and b/src/AI/joblib/level1.joblib differ diff --git a/src/AI/joblib/level2.joblib b/src/AI/joblib/level2.joblib new file mode 100644 index 00000000..70e2ee26 Binary files /dev/null and b/src/AI/joblib/level2.joblib differ diff --git a/src/AI/joblib/level3.joblib b/src/AI/joblib/level3.joblib new file mode 100644 index 00000000..bd27cb70 Binary files /dev/null and b/src/AI/joblib/level3.joblib differ diff --git a/src/AI/local_deps/requirements.txt b/src/AI/local_deps/requirements.txt deleted file mode 100644 index dc5e6a54..00000000 --- a/src/AI/local_deps/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -pandas -scikit_learn -numpy -joblib -setuptools -wheel -pip diff --git a/src/AI/zappy_ai.py b/src/AI/zappy_ai.py index b0eb15fd..a7a516eb 100644 --- a/src/AI/zappy_ai.py +++ b/src/AI/zappy_ai.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from zappy_ia.Log import clearDirectory from zappy_ia.IA import IA import argparse import sys @@ -32,4 +33,5 @@ def error(self, message): teamName = args.n machineName = args.h + clearDirectory("log") bee = IA(port, machineName, teamName) diff --git a/src/AI/zappy_ia/Client.py b/src/AI/zappy_ia/Client.py index 930c2cdf..875327cf 100644 --- a/src/AI/zappy_ia/Client.py +++ b/src/AI/zappy_ia/Client.py @@ -5,108 +5,158 @@ import threading import time from typing import List +from zappy_ia.Enums import Message class Client: def __init__(self, port: int, server_ip: str = "localhost"): - self.client_socket: socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.server_ip: int = server_ip - self.isConnected: bool = False - self.port: int = port + self._client_socket: socket.socket = socket.socket( + socket.AF_INET, socket.SOCK_STREAM + ) + self._server_ip: str = server_ip + self._isConnected: bool = False + self._port: int = port - self.inTreatment: int = 0 + self._inTreatment: int = 0 - self.stopLock: threading.Lock = threading.Lock() - self.stop: bool = False + self._stopLock: threading.Lock = threading.Lock() + self._stop: bool = False - self.receivedLock: threading.Lock = threading.Lock() - self.messReceived: List[str] = [] + self._receivedLock: threading.Lock = threading.Lock() + self._messReceived: List[str] = [] - self.sendLock: threading.Lock = threading.Lock() - self.messToSend: List[str] = [] + self._broadcastLock: threading.Lock = threading.Lock() + self._broadcastReceived: List[str] = [] - self.thread: threading.Thread = threading.Thread(target=self.connect) - self.thread.start() + self._sendLock: threading.Lock = threading.Lock() + self._messToSend: List[str] = [] + + self._thread: threading.Thread = threading.Thread(target=self.connect) + self._thread.start() time.sleep(0.1) def connect(self): - self.client_socket.connect((self.server_ip, self.port)) - self.isConnected = True + self._client_socket.connect((self._server_ip, self._port)) + self._isConnected = True - self.client_socket.setblocking(False) + self._client_socket.setblocking(False) - self.stopLock.acquire() - while self.isConnected and self.stop is False: - self.stopLock.release() + self._stopLock.acquire() + while self._isConnected and self._stop is False: + self._stopLock.release() read_sockets, write_sockets, error_sockets = select.select( - [self.client_socket], [self.client_socket], [self.client_socket], 0 + [self._client_socket], [self._client_socket], [self._client_socket], 0 ) - self.read(read_sockets) - self.write(write_sockets) - self.handleError(error_sockets) - self.stopLock.acquire() - - self.client_socket.close() + self._read(read_sockets) + self._write(write_sockets) + self._handleError(error_sockets) + self._stopLock.acquire() + + self._client_socket.close() + + def _checkMessage(self, message: str): + if message.find(Message.CODE.value) != -1: + self._broadcastLock.acquire() + if message.count("\n") == 1: + self._broadcastReceived.insert(0, message[:-1]) + else: + for mess in message.split("\n"): + if mess != "": + self._broadcastReceived.insert(0, mess) + self._broadcastLock.release() + elif message: + self._receivedLock.acquire() + if len(self._messReceived) == 0 or self._messReceived[0].count("\n") > 0: + self._messReceived.insert(0, message) + else: + self._messReceived[0] += message + self._receivedLock.release() + if self._inTreatment > 0: + self._inTreatment -= 1 + else: + self._client_socket.close() + self._isConnected = False - def read(self, read_sockets): + def _read(self, read_sockets): for socket_ in read_sockets: - if socket_ == self.client_socket: + if socket_ == self._client_socket: message = socket_.recv(2048).decode() - if message: - self.receivedLock.acquire() - print("Recv: ", end="") - print(message.split("\n")[:-1]) - self.messReceived.insert(0, message) - self.receivedLock.release() - if self.inTreatment > 0: - self.inTreatment -= 1 + if message.count("\n") > 1: + if message.endswith("\n"): + endClosed = True + else: + endClosed = False + message = message.split("\n") + i = 0 + for i in range(len(message)): + if message[i] == "": + continue + if i < len(message) - 1 or endClosed: + self._checkMessage(message[i] + "\n") + else: + self._checkMessage(message[i]) else: - self.client_socket.close() - self.isConnected = False + self._checkMessage(message) + + def _addMessageToSend(self): + self._sendLock.acquire() + if len(self._messToSend) != 0: + message = self._messToSend[-1] + self._messToSend = self._messToSend[:-1] + self._sendLock.release() + if message != "\n": + self._inTreatment += 1 + self._client_socket.sendall(message.encode()) + else: + self._sendLock.release() - def write(self, write_sockets): + def _write(self, write_sockets): for socket_ in write_sockets: - if socket_ == self.client_socket and self.inTreatment < 10: - self.sendLock.acquire() - if len(self.messToSend) != 0: - message = self.messToSend[-1] - print("Send: ", end="") - print(message.split("\n")[:-1]) - self.messToSend = self.messToSend[:-1] - self.sendLock.release() - if message != "\n": - self.inTreatment += 1 - self.client_socket.sendall(message.encode()) - else: - self.sendLock.release() + if socket_ == self._client_socket and self._inTreatment < 10: + self._addMessageToSend() - def handleError(self, error_sockets): + def _handleError(self, error_sockets): for socket_ in error_sockets: - if socket_ == self.client_socket: + if socket_ == self._client_socket: raise Exception("Socket error") def input(self, message: str, arg: str = ""): if arg != "": - message += " " + arg + "\n" - self.sendLock.acquire() - self.messToSend.insert(0, message) - self.sendLock.release() + message += " " + arg + message = message.rstrip(" \n") + if not message.endswith("\n"): + message += "\n" + self._sendLock.acquire() + self._messToSend.insert(0, message) + self._sendLock.release() + + def outputBroadcast(self) -> List[str]: + res = [] + self._broadcastLock.acquire() + if len(self._broadcastReceived) != 0: + res = self._broadcastReceived + self._broadcastReceived = [] + self._broadcastLock.release() + else: + self._broadcastLock.release() + time.sleep(0.1) + return res def output(self) -> str: res = "" - self.receivedLock.acquire() - if len(self.messReceived) != 0: - message = self.messReceived[-1] - self.messReceived = self.messReceived[:-1] - self.receivedLock.release() - if message != "" or message != "\n": + self._receivedLock.acquire() + if len(self._messReceived) != 0: + message = self._messReceived[-1] + self._receivedLock.release() + if message != "" and message != "\n" and message.endswith("\n"): res = message + self._messReceived = self._messReceived[:-1] else: - self.receivedLock.release() + self._receivedLock.release() time.sleep(0.1) return res def stopClient(self): - self.stopLock.acquire() - self.stop = True - self.stopLock.release() + self._stopLock.acquire() + self._stop = True + self._stopLock.release() diff --git a/src/AI/zappy_ia/ClientManager.py b/src/AI/zappy_ia/ClientManager.py new file mode 100644 index 00000000..e68aff8c --- /dev/null +++ b/src/AI/zappy_ia/ClientManager.py @@ -0,0 +1,165 @@ +from zappy_ia.Client import Client +from zappy_ia.Log import LogGood +from typing import List, Tuple +from typing import Union +from zappy_ia.Enums import Message, Element, Command + + +class ClientManager: + def __init__( + self, port: int, machineName: str, teamName: str, id: int, log: LogGood + ): + self._teamName: str = teamName + self._log: LogGood = log + self._id: int = id + self._client: Client = Client(port, machineName) + self.connect() + + def stopClient(self): + self._client.stopClient + + def output(self) -> str: + res = self._client.output() + return res + + def connect(self): + res = self.output() + while res != "WELCOME\n": + res = self.output() + pass + self._log.info("Received: " + res) + resSetup = self.requestClient(self._teamName + "\n").split("\n") + self._log.info("Received: " + resSetup[0] + "\n") + if resSetup[0] == "ko": + self.stopClient() + raise Exception("Team name already taken") + resSetup = self.waitOutput() + self._log.info("Received: " + resSetup) + + def isIdInList(self, list_: List[int], toFindId: int) -> bool: + for id_ in list_: + if toFindId == id_: + return True + return False + + def isMyIdInList(self, list_: List[int]) -> bool: + return self.isIdInList(list_, self._id) + + def checkBroadcast(self) -> List[Tuple[int, str, List[int], int]]: + """ + This function is call to get received broadcast since last get, + check if client received broadcast from other ia, parse and return it + + Returns: + List of parsed broadcast List[[senderId, message, targets, dir]] + """ + outList: List[str] = self._client.outputBroadcast() + if len(outList) == 0: + return [] + resList: List[Tuple[int, str, List[int], int]] = [] + for res in outList: + res = res.split(",") + direc = int(res[0].split(" ")[1]) + splittedRes = res[1].split("|") + if len(splittedRes) != 4 or splittedRes[0].strip() != Message.CODE.value: + continue + toSend = list(map(int, splittedRes[3].strip().split(" "))) + if toSend[0] != 0 and self.isMyIdInList(toSend) is False: + continue + resList.append((int(splittedRes[1]), splittedRes[2], toSend, direc)) + if len(resList) > 0: + self._log.info("Received Broadcast:\n") + for broadcast in resList: + toSendStr = " ".join(map(str, broadcast[2])) + self._log.info( + " from: " + + str(broadcast[0]) + + " : " + + broadcast[1] + + " to " + + toSendStr + + " dir: " + + str(broadcast[3]) + + "\n" + ) + return resList + + def checkBroadcastWithoutNewElevation( + self, + ) -> List[Tuple[int, str, List[int], int]]: + """ + This function is call to get received broadcast since last get without new elevation broadcast, + + Returns: + List of parsed broadcast List[[senderId, message, targets, dir]] + """ + broadcasts = self.checkBroadcast() + res: List[Tuple[int, str, List[int], int]] = [] + for broadcast in broadcasts: + if broadcast[1].find(Message.L2.value[:-1]) == -1: + res.append(broadcast) + else: + self.sendBroadcast(Message.KO.value, [broadcast[0]]) + return res + + def checkBroadcastResponse(self) -> Tuple[int, str, List[int], int]: + liste = self.checkBroadcastWithoutNewElevation() + if len(liste) == 0: + return (0, "", [0], 0) + return liste[0] + + def waitOutput(self) -> str: + i = 0 + res = "" + while res == "": + res = self.output() + i += 1 + if i > 15: + self._log.debug("15 check without response") + i = 0 + self._log.info("Received: " + res) + return res + + def requestClient( + self, command: Union[Command, str], arg: Union[Element, str] = "" + ) -> str: + """ + This function send command to the server, wait the response and return it + + Parameters: + command (Union[Command, str]): command to send to the server, + can be Command or str. + + Returns: + Server response + """ + toSend: str = "" + argToSend: str = "" + if isinstance(command, Command): + toSend = command.value + else: + toSend = command + if isinstance(arg, Element): + argToSend = arg.value + else: + argToSend = arg + self._log.info("[Send: " + toSend.split("\n")[0] + " " + argToSend + "]\n") + self._client.input(toSend, argToSend) + res = self.waitOutput() + if res == "ko\n": + toSendOrd = list(map(ord, toSend)) + argToSendOrd = list(map(ord, argToSend)) + self._log.info( + "Server responded ko to : " + toSend + " " + argToSend) + return "ko\n" + return res + + def sendBroadcast(self, message: str, toSend: List[int] = []): + toSendStr: str = "|" + if len(toSend) == 0: + toSendStr += "0" + else: + toSendStr += " ".join(map(str, toSend)) + codeStr: str = Message.CODE.value + completeMessage: str = codeStr + "|" + str(self._id) + "|" + message + toSendStr + self.requestClient(Command.BROADCAST, completeMessage) diff --git a/src/AI/zappy_ia/DecisionTree.py b/src/AI/zappy_ia/DecisionTree.py new file mode 100644 index 00000000..f7f0486d --- /dev/null +++ b/src/AI/zappy_ia/DecisionTree.py @@ -0,0 +1,376 @@ +import joblib +import sys +import pandas as pd +from typing import Dict, List, Tuple +from zappy_ia.ClientManager import ClientManager +from zappy_ia.Enums import Message, Element, Command +from zappy_ia.Log import LogGood +import time + +levelParticipantsNb: List[int] = [0, 0, 1, 1, 3, 3, 5, 5] + +levelCosts: List[List[Tuple[Element, int]]] = [ + [(Element.LINEMATE, 1)], + [(Element.LINEMATE, 1), (Element.DERAUMERE, 1), (Element.SIBUR, 1)], + [(Element.LINEMATE, 2), (Element.SIBUR, 1), (Element.PHIRAS, 2)], + [ + (Element.LINEMATE, 1), + (Element.DERAUMERE, 1), + (Element.SIBUR, 2), + (Element.PHIRAS, 1), + ], + [ + (Element.LINEMATE, 1), + (Element.DERAUMERE, 2), + (Element.SIBUR, 1), + (Element.MENDIANE, 3), + ], + [ + (Element.LINEMATE, 1), + (Element.DERAUMERE, 2), + (Element.SIBUR, 3), + (Element.PHIRAS, 1), + ], + [ + (Element.LINEMATE, 2), + (Element.DERAUMERE, 2), + (Element.SIBUR, 2), + (Element.MENDIANE, 2), + (Element.PHIRAS, 2), + (Element.THYSTAME, 1), + ], +] + + +class DecisionTree: + def __init__(self, clientManager: ClientManager, log: LogGood, id: int): + self._clientManager: ClientManager = clientManager + self._level: int = 1 + self._log: LogGood = log + self._id: int = id + self.loadTree() + self._lastLook: List[List[Element]] = [] + self._outputTree: Dict = { + "Find food": self.findFood, + "Take food": self.takeFood, + "Take linemate": self.takeLinemate, + "Take deraumere": self.takeDeraumere, + "Take sibur": self.takeSibur, + "Take mendiane": self.takeMendiane, + "Take phiras": self.takePhiras, + "Take thystame": self.takeThystame, + "Elevation": self.chooseElevation, + "Fork": self.fork, + } + self._inputTree: Dict = { + "mfood": [0], + "mlinemate": [0], + "mderaumere": [0], + "msibur": [0], + "mmendiane": [0], + "mphiras": [0], + "mthystame": [0], + "lfood": [0], + "llinemate": [0], + "lderaumere": [0], + "lsibur": [0], + "lmendiane": [0], + "lphiras": [0], + "lthystame": [0], + "enemy": [0], + } + + def getCurrentLevel(self) -> int: + return self._level + + def incrementLevel(self): + self._level += 1 + self.loadTree() + + def getCurrentFood(self) -> int: + return self._inputTree["mfood"][0] + + def predict(self): + self.inventory() + self.lookForTree() + predictions = self._levelTree.predict(pd.DataFrame(self._inputTree)) + for prediction in predictions: + self._log.info("Pred:" + prediction) + self._outputTree[prediction]() + + def inventory(self): + """ + This function send the inventory command to the server and + parse response in self._inputTree which is List + """ + res = self._clientManager.requestClient(Command.INVENTORY) + if res == "ko\n": + return + try: + rescpy = res + res = res.split("[")[1].split("]")[0] + for elem in res.split(","): + parsedElem = elem.strip().split(" ") + self._inputTree["m" + parsedElem[0]][0] = int(parsedElem[1]) + except (IndexError, ValueError) as error: + print(f"ID : {self._clientManager._id} crashed in inventory when received : ", rescpy) + self._clientManager.stopClient() + + def lookForTree(self): + """ + This function get the list of the closest items on the look to + put in self._inputTree + """ + self.look() + for elem in Element: + if "l" + elem.value in self._inputTree.keys(): + case = self.findClosestElemInLastLook(elem) + self._inputTree["l" + elem.value][0] = 1 if case >= 0 else 0 + case = self.findClosestElemInLastLook(Element.PLAYER) + self._inputTree["enemy"][0] = 1 if case >= 0 else 0 + + def look(self): + """ + This function send the look command to the server and parse response in + self._lastLook which is List[List[Element]] + """ + self._lastLook.clear() + + res = self._clientManager.requestClient(Command.LOOK) + if res == "ko\n": + return + try: + rescpy = res + res = res.split("[")[1].split("]")[0] + except IndexError: + print(f"ID : {self._clientManager._id} crashed in look when received : ", rescpy) + self._clientManager.stopClient() + return + + i = 0 + for tile in res.split(","): + self._lastLook.append([]) + for elem in tile.split(" "): + if elem == "": + self._lastLook[i].append(Element.EMPTY) + else: + self._lastLook[i].append(Element(elem)) + i += 1 + + def pathFinding(self, pos: int): + """ + This function move the ia to the pos in parameters + + Parameters: + pos (int): pos which represent an index in the array of tile return + by server to command look + """ + if pos <= 0: + return + for i in range(1, 9): + self._clientManager.requestClient(Command.FORWARD) + mid = i * (i + 1) + if mid == pos: + return + if mid - i <= pos and pos < mid: + self._clientManager.requestClient(Command.LEFT) + for _ in range(mid - pos): + self._clientManager.requestClient(Command.FORWARD) + return + if mid + i >= pos and pos > mid: + self._clientManager.requestClient(Command.RIGHT) + for _ in range(pos - mid): + self._clientManager.requestClient(Command.FORWARD) + return + + def takeElement(self, element: Element, pos: int): + """ + This function move the ia to the pos of the element and pick it up + + Parameters: + element (Element): element to take + pos (int): pos (in front of the ia) of the element + """ + if pos < 0: + return + self.pathFinding(pos) + self._clientManager.requestClient(Command.TAKE_OBJECT, element) + + def takeFood(self): + self.takeElement(Element.FOOD, self.findClosestElemInLastLook(Element.FOOD)) + + def takeLinemate(self): + self.takeElement( + Element.LINEMATE, self.findClosestElemInLastLook(Element.LINEMATE) + ) + + def takeDeraumere(self): + self.takeElement( + Element.DERAUMERE, self.findClosestElemInLastLook(Element.DERAUMERE) + ) + + def takeSibur(self): + self.takeElement(Element.SIBUR, self.findClosestElemInLastLook(Element.SIBUR)) + + def takeMendiane(self): + self.takeElement( + Element.MENDIANE, self.findClosestElemInLastLook(Element.MENDIANE) + ) + + def takePhiras(self): + self.takeElement(Element.PHIRAS, self.findClosestElemInLastLook(Element.PHIRAS)) + + def takeThystame(self): + self.takeElement( + Element.THYSTAME, self.findClosestElemInLastLook(Element.THYSTAME) + ) + + def fork(self): + self.takeClosestFood() + + def findClosestElemInLastLook( + self, element: Element, checkCurrentTile: bool = True + ) -> int: + """ + This function find closest elem in last look and return it pos + + Parameters: + element (Element): elem to find pos + """ + lastLook = self._lastLook + i = 0 + if checkCurrentTile is False: + lastLook = lastLook[1:] + i = 1 + for tile in lastLook: + for elem in tile: + if elem == element: + return i + i += 1 + return -1 + + def findFood(self, distanceLimit: int = 0): + """ + This function find and take the closest food in other directions + than the current, because it will be call when ia need food + and don't see any in last look this function is call when + ia need to survive and don't care of other items + + Parameters: + distanceLimit (int): is the id of the last line where we can go take food, + after it's too far away. 0 if no limit + """ + i = 1 + pos = -1 + while pos == -1: + self._clientManager.requestClient(Command.RIGHT) + self.look() + pos = self.findClosestElemInLastLook(Element.FOOD) + if distanceLimit != 0 and i > distanceLimit: + self.findFood(distanceLimit) + elif pos != -1: + self.takeElement(Element.FOOD, pos) + i += 1 + + def chooseElevation(self): + if self._level == 1: + self.elevation() + else: + self.elevationEmitter() + + def loadTree(self): + try: + self._levelTree = joblib.load( + "src/AI/joblib/level" + str(self._level) + ".joblib" + ) + except FileNotFoundError: + print("File joblib not found", file=sys.stderr) + self._clientManager.stopClient() + sys.exit(84) + + def elevation(self): + for costTuple in levelCosts[self._level - 1]: + if costTuple[1] > self._inputTree["m" + costTuple[0].value][0]: + return + for _ in range(costTuple[1]): + self._clientManager.requestClient( + Command.SET_OBJECT, costTuple[0].value + ) + res = self._clientManager.requestClient(Command.INCANTATION) + if (res == "ko\n"): + self._clientManager.requestClient(Command.FORWARD) + return + out = self._clientManager.waitOutput() + if out != Message.KO.value + "\n": + self.incrementLevel() + else: + self._clientManager.requestClient(Command.FORWARD) + + def takeClosestFood(self): + self.look() + foodPos = self.findClosestElemInLastLook(Element.FOOD) + if foodPos == -1: + self.findFood() + else: + self.takeElement(Element.FOOD, foodPos) + self.inventory() + + def checkReceivedMessage( + self, participantsId: List[int], res: Tuple[int, str, List[int], int] + ) -> List[int]: + self._log.debug("message: " + res[1]) + if res[1] == Message.OK.value or res[1] == "ok\n": + if len(participantsId) < levelParticipantsNb[self._level]: + self._log.debug("res ok") + participantsId.append(res[0]) + self._clientManager.sendBroadcast(Message.OK.value, [res[0]]) + else: + self._log.debug("res ko") + self._clientManager.sendBroadcast(Message.KO.value, [res[0]]) + return participantsId + + def waitParticipants(self, participantsId: List[int]): + readyParticipants = 0 + self.inventory() + res: List[Tuple[int, str, List[int], int]] = [] + while ( + readyParticipants < levelParticipantsNb[self._level] + or self._inputTree["mfood"][0] < 13 + ): + self.takeClosestFood() + res = self._clientManager.checkBroadcastWithoutNewElevation() + for mess in res: + if ( + mess[1] == Message.OK.value + ): + if self._clientManager.isIdInList(participantsId, mess[0]): + self._log.debug("readyParticipants nb: " + str(readyParticipants)) + readyParticipants += 1 + else: + self._log.debug("resp ko in waitparticipant") + self._clientManager.sendBroadcast(Message.KO.value, [mess[0]]) + self.inventory() + arrivedParticipants = 0 + while arrivedParticipants < levelParticipantsNb[self._level]: + self._clientManager.sendBroadcast(Message.COME.value, participantsId) + res = self._clientManager.checkBroadcastWithoutNewElevation() + for mess in res: + if mess[1] == Message.OK.value: + arrivedParticipants += 1 + self.elevation() + + def elevationEmitter(self): + """ + This function is call by decision tree when the ia have the stones for elevation, + the ia call others to try elevation + """ + participantsId: List[int] = [] + res: List[Tuple[int, str, List[int], int]] = [] + while len(participantsId) < levelParticipantsNb[self._level]: + self._clientManager.sendBroadcast(list(Message)[self._level].value) + res = self._clientManager.checkBroadcastWithoutNewElevation() + for mess in res: + participantsId = self.checkReceivedMessage(participantsId, mess) + self.takeClosestFood() + self._clientManager.sendBroadcast(Message.OK.value, participantsId) + self.waitParticipants(participantsId) diff --git a/src/AI/zappy_ia/ElevationParticipant.py b/src/AI/zappy_ia/ElevationParticipant.py new file mode 100644 index 00000000..10c8427a --- /dev/null +++ b/src/AI/zappy_ia/ElevationParticipant.py @@ -0,0 +1,130 @@ +from zappy_ia.Log import LogGood +from typing import Dict, List, Tuple +from zappy_ia.Enums import Message +from zappy_ia.ClientManager import ClientManager +from zappy_ia.DecisionTree import DecisionTree + +cmdDirections: Dict = { + 1: ["Forward\n"], + 2: ["Forward\n", "Left\n", "Forward\n"], + 3: ["Left\n", "Forward\n"], + 4: ["Left\n", "Forward\n", "Left\n", "Forward\n"], + 5: ["Right\n", "Right\n", "Forward\n"], + 6: ["Right\n", "Forward\n", "Right\n", "Forward\n"], + 7: ["Right\n", "Forward\n"], + 8: ["Forward\n", "Right\n", "Forward\n"], +} + + +class ElevationParticipant: + def __init__( + self, clientManager: ClientManager, decisionTree: DecisionTree, log: LogGood + ): + self._emitter: int = 0 + self._log = log + self._clientManager: ClientManager = clientManager + self._decisionTree: DecisionTree = decisionTree + + def errorReturn(self) -> bool: + self._emitter = 0 + return False + + def sendAllCommand(self, messages: List[str]): + for message in messages: + self._clientManager.requestClient(message) + + def joinEmitter(self): + res = self._clientManager.checkBroadcastResponse() + while res[1] != "come": + res = self._clientManager.checkBroadcastResponse() + while res[3] != 0 and res[1] == "come": + self.sendAllCommand(cmdDirections[res[3]]) + res = self._clientManager.checkBroadcastResponse() + while res[1] != "come": + res = self._clientManager.checkBroadcastResponse() + self._clientManager.sendBroadcast(Message.OK.value, [self._emitter]) + out = self._clientManager.waitOutput() + if out == "ko\n": + return self.errorReturn() + out = self._clientManager.waitOutput() + if out != "ko\n": + self._emitter = 0 + return True + else: + return self.errorReturn() + + def checkBroadcastEmitter(self) -> List[Tuple[int, str, List[int], int]]: + broadcasts = self._clientManager.checkBroadcast() + res: List[Tuple[int, str, List[int], int]] = [] + for mess in broadcasts: + if mess[2][0] == 0: + if mess[0] == self._emitter: + continue + else: + self._clientManager.sendBroadcast(Message.KO.value, [mess[0]]) + elif mess[0] == self._emitter: + res.append(mess) + else: + self._log.debug("weird message in check broadcast emitter: " + mess[1]) + return res + + def waitNextStep(self) -> bool: + okNb = 0 + res: Tuple[int, str, List[int], int] = [0, "", [], 0] + while okNb < 2: + res = self.checkBroadcastEmitter() + for mess in res: + if mess[1] == Message.KO.value: + return False + elif mess[1] == Message.OK.value: + okNb += 1 + else: + self._log.debug("weird message in wait next step: " + mess[1]) + self._decisionTree.takeClosestFood() + return True + + def elevationParticipant(self) -> bool: + if self.waitNextStep() == False: + return self.errorReturn() + haveToCome = False + ready = False + while haveToCome is False or ready is False: + self._log.debug( + "in havetocome loop, haveToCome: " + + str(haveToCome) + + " ,ready: " + + str(ready) + ) + self._decisionTree.takeClosestFood() + if self._decisionTree.getCurrentFood() >= 13 and ready is False: + ready = True + self._clientManager.sendBroadcast(Message.OK.value, [self._emitter]) + res = self._clientManager.checkBroadcastResponse() + if res[1] == Message.COME.value: + haveToCome = True + return self.joinEmitter() + + def checkElevationParticipant(self, currentLevel: int) -> bool: + broadcasts: List[ + Tuple[int, str, List[int], int] + ] = self._clientManager.checkBroadcast() + response: List[int] = [] + if len(broadcasts) == 0: + return self.errorReturn() + for broadcast_ in broadcasts: + if ( + broadcast_[1][:-1] == "levelup" + and self._emitter == 0 + and int(broadcast_[1][-1]) == currentLevel + ): + self._emitter = broadcast_[0] + response.append(broadcast_[0]) + self._clientManager.sendBroadcast(Message.OK.value, [self._emitter]) + elif broadcast_[0] in response: + continue + else: + response.append(broadcast_[0]) + self._clientManager.sendBroadcast(Message.KO.value, [broadcast_[0]]) + if self._emitter != 0: + return self.elevationParticipant() + return self.errorReturn() diff --git a/src/AI/zappy_ia/Enums.py b/src/AI/zappy_ia/Enums.py new file mode 100644 index 00000000..ef127847 --- /dev/null +++ b/src/AI/zappy_ia/Enums.py @@ -0,0 +1,42 @@ +from enum import Enum + + +class Message(Enum): + OK = "ok" + KO = "ko" + L2 = "levelup2" + L3 = "levelup3" + L4 = "levelup4" + L5 = "levelup5" + L6 = "levelup6" + L7 = "levelup7" + L8 = "levelup8" + COME = "come" + CODE = "*$+" + + +class Element(Enum): + EMPTY = "empty" + FOOD = "food" + PLAYER = "player" + LINEMATE = "linemate" + DERAUMERE = "deraumere" + SIBUR = "sibur" + MENDIANE = "mendiane" + PHIRAS = "phiras" + THYSTAME = "thystame" + + +class Command(Enum): + FORWARD = "Forward" + RIGHT = "Right" + LEFT = "Left" + LOOK = "Look" + INVENTORY = "Inventory" + BROADCAST = "Broadcast" + CONNECT_NBR = "Connect_nbr" + FORK = "Fork" + EJECT = "Eject" + TAKE_OBJECT = "Take" + SET_OBJECT = "Set" + INCANTATION = "Incantation" diff --git a/src/AI/zappy_ia/IA.py b/src/AI/zappy_ia/IA.py index 62a06f34..e2b2ea96 100644 --- a/src/AI/zappy_ia/IA.py +++ b/src/AI/zappy_ia/IA.py @@ -1,305 +1,79 @@ -from enum import Enum +from zappy_ia.Log import LogGood +from zappy_ia.ClientManager import ClientManager +from zappy_ia.DecisionTree import DecisionTree +from zappy_ia.ElevationParticipant import ElevationParticipant +from zappy_ia.Enums import Command +import random import time -from typing import List, Tuple -from zappy_ia.Client import Client -from typing import Union -import pandas as pd import os -import joblib -import sys - - -class Element(Enum): - EMPTY = "empty" - FOOD = "food" - PLAYER = "player" - LINEMATE = "linemate" - DERAUMERE = "deraumere" - SIBUR = "sibur" - MENDIANE = "mendiane" - PHIRAS = "phiras" - THYSTAME = "thystame" - - -class Command(Enum): - FORWARD = "Forward\n" - RIGHT = "Right\n" - LEFT = "Left\n" - LOOK = "Look\n" - INVENTORY = "Inventory\n" - BROADCAST = "Broadcast text\n" - CONNECT_NBR = "Connect_nbr\n" - FORK = "Fork\n" - EJECT = "Eject\n" - TAKE_OBJECT = "Take" - SET_OBJECT = "Set" - INCANTATION = "Incantation\n" class IA: def __init__(self, port: int, machineName: str, teamName: str): - self.port: int = port - self.machineName: str = machineName - self.teamName: str = teamName + self._port: int = port + self._machineName: str = machineName + self._teamName: str = teamName self.build(7) - def build(self, neededChild: int = 0): - self.neededChild = neededChild - self.mapSize: Tuple[int, int] = [0, 0] - self.clientNb: int = 0 - self.level: int = 1 - self.lastLook: List[List[Element]] = [] - self.client: Client = Client(self.port, self.machineName) - self.inputTree: dict() = { - "level": [self.level], - "mfood": [0], - "mlinemate": [0], - "mderaumere": [0], - "msibur": [0], - "mmendiane": [0], - "mphiras": [0], - "mthystame": [0], - "lfood": [0], - "llinemate": [0], - "lderaumere": [0], - "lsibur": [0], - "lmendiane": [0], - "lphiras": [0], - "lthystame": [0], - "enemy": [0], - "broadcast": [0], - } - self.outputTree: dict() = { - "Take food": self.takeFood, - "Find food": self.findFood, - } + def build(self, neededChilds: int): + self._neededChilds = neededChilds + self.setId() + self._log = LogGood(f"log/{self._id}ia.log") + self._clientManager: ClientManager = ClientManager( + self._port, self._machineName, self._teamName, self._id, self._log + ) + self._decisionTree: DecisionTree = DecisionTree( + self._clientManager, self._log, self._id + ) + self._elevationParticipant: ElevationParticipant = ElevationParticipant( + self._clientManager, self._decisionTree, self._log + ) + self.run() - try: - self.clf = joblib.load("src/AI/joblib/food.joblib") - except FileNotFoundError: - print("File joblib not found", file=sys.stderr) - self.client.stopClient() - sys.exit(84) + def setId(self): + self._id = random.randint(10000, 99999) - while self.client.output() != "WELCOME\n": - pass - resSetup = self.requestClient(self.teamName + "\n").split("\n") - if resSetup[0] == "ko": - print("Not remaining slot") - self.client.stopClient() - sys.exit(84) - if len(resSetup[1].split(" ")) == 2: - self.clientNb = int(resSetup[0]) - mapSize = resSetup[1].split(" ") - self.mapSize = [int(mapSize[0]), int(mapSize[1])] - else: - self.clientNb = int(resSetup[0]) - mapSize = mapSize.split("\n")[0].split(" ") - self.mapSize = [int(mapSize[0]), int(mapSize[1])] - self.run() + def connectNewIA(self): + self.pid = os.fork() + if self.pid == 0: + self._clientManager.stopClient() + self.build(0) + time.sleep(0.5) + + def createEgg(self): + self._clientManager.requestClient(Command.FORK.value) + self.connectNewIA() def checkNeededChilds(self): - while self.neededChild > 0: - if int(self.requestClient(Command.CONNECT_NBR.value).split("\n")[0]) > 0: - print("newIA") + while self._neededChilds > 0: + if ( + int( + self._clientManager.requestClient(Command.CONNECT_NBR.value).split( + "\n" + )[0] + ) + > 0 + ): self.connectNewIA() - elif self.inputTree["mfood"][0] > 2: - print("newEgg") + elif self._decisionTree.getCurrentFood() > 2: self.createEgg() else: break - self.neededChild -= 1 - print("neededchilds: " + str(self.neededChild)) + self._neededChilds -= 1 def run(self): continueRun = True try: while continueRun: - self.inventory() self.checkNeededChilds() - self.lookForTree() - predictions = self.clf.predict(pd.DataFrame(self.inputTree)) - for prediction in predictions: - self.outputTree[prediction]() + if ( + self._elevationParticipant.checkElevationParticipant( + self._decisionTree.getCurrentLevel() + ) + is True + ): + self._decisionTree.incrementLevel() + self._decisionTree.predict() except KeyboardInterrupt: + self._clientManager.stopClient() return - - def waitOutput(self) -> str: - res = "" - while res == "": - res = self.client.output() - return res - - def requestClient( - self, command: Union[Command, str], arg: Union[Element, str] = "" - ) -> str: - """ - This function send command to the server, wait the response and return it - - Parameters: - command (Union[Command, str]): command to send to the server, - can be Command or str. - - Returns: - Server response - """ - toSend: str = "" - argToSend: str = "" - if isinstance(command, Command): - toSend = command.value - else: - toSend = command - if isinstance(arg, Element): - argToSend = arg.value - else: - argToSend = arg - self.client.input(toSend, argToSend) - res = self.waitOutput() - if res == "ko": - raise Exception("Server responsed ko to : " + toSend) - return res - - def inventory(self): - """ - This function send the inventory command to the server and - parse response in self.inputTree which is List - """ - res = self.requestClient(Command.INVENTORY) - print(res) - res = res.split("[")[1].split("]")[0] - - for elem in res.split(","): - parsedElem = elem.strip().split(" ") - self.inputTree["m" + parsedElem[0]][0] = int(parsedElem[1]) - - def lookForTree(self): - """ - This function get the list of the closest items on the look to - put in self.inputTree - """ - self.look() - for elem in Element: - if "l" + elem.value in self.inputTree.keys(): - self.inputTree["l" + elem.value][0] = self.findClosestElemInLastLook( - elem - ) - self.inputTree["enemy"][0] = self.findClosestElemInLastLook(Element.PLAYER) - - def look(self): - """ - This function send the look command to the server and parse response in - self.lastLook which is List[List[Element]] - """ - self.lastLook.clear() - - res = self.requestClient(Command.LOOK) - res = res.split("[")[1].split("]")[0] - - i = 0 - for tile in res.split(","): - self.lastLook.append([]) - for elem in tile.split(" "): - if elem == "": - self.lastLook[i].append(Element.EMPTY) - else: - self.lastLook[i].append(Element(elem)) - i += 1 - - def pathFinding(self, pos: int): - """ - This function move the ia to the pos in parameters - - Parameters: - pos (int): pos which represent an index in the array of tile return - by server to command look - """ - if pos <= 0: - return - for i in range(1, 9): - self.requestClient(Command.FORWARD) - mid = i * (i + 1) - if mid == pos: - return - if mid - i <= pos and pos < mid: - self.requestClient(Command.LEFT) - for x in range(mid - pos): - self.requestClient(Command.FORWARD) - return - if mid + i >= pos and pos > mid: - self.requestClient(Command.RIGHT) - for x in range(pos - mid): - self.requestClient(Command.FORWARD) - return - - def connectNewIA(self): - self.pid = os.fork() - if self.pid == 0: - self.client.stopClient() - self.build(0) - time.sleep(0.5) - - def createEgg(self): - self.requestClient(Command.FORK.value) - self.connectNewIA() - - def takeElementInLastLook(self, element: Element, pos: int): - """ - This function move the ia to the closest element and pick it up - - Parameters: - element (Element): element to take - pos (int): pos (in front of the ia) of the element - """ - if pos < 0: - return - self.pathFinding(pos) - self.requestClient(Command.TAKE_OBJECT, element) - - def takeFood(self): - self.takeElementInLastLook( - Element.FOOD, self.findClosestElemInLastLook(Element.FOOD) - ) - - def findClosestElemInLastLook( - self, element: Element, checkCurrentTile: bool = True - ) -> int: - """ - This function find closest elem in last look and return it pos - - Parameters: - element (Element): elem to find pos - """ - lastLook = self.lastLook - i = 0 - if checkCurrentTile is False: - lastLook = lastLook.pop(0) - i = 1 - for tile in lastLook: - for elem in tile: - if elem == element: - return i - i += 1 - return -1 - - def findFood(self, distanceLimit: int = 0): - """ - This function find and take the closest food in other directions - than the current, because it will be call when ia need food - and don't see any in last look this function is call when - ia need to survive and don't care of other items - - Parameters: - distanceLimit (int): is the id of the last line where we can go take food, - after it's too far away. 0 if no limit - """ - i = 1 - pos = -1 - while pos == -1: - self.requestClient("Right\n") - self.look() - pos = self.findClosestElemInLastLook(Element.FOOD) - if distanceLimit != 0 and i > distanceLimit: - self.findFood(distanceLimit) - elif pos != -1: - self.takeElementInLastLook(Element.FOOD, pos) - i += 1 diff --git a/src/AI/zappy_ia/Log.py b/src/AI/zappy_ia/Log.py new file mode 100644 index 00000000..054819b8 --- /dev/null +++ b/src/AI/zappy_ia/Log.py @@ -0,0 +1,30 @@ +import os + + +def clearDirectory(path: str): + if os.path.exists(path): + for file in os.listdir(path): + os.remove(os.path.join(path, file)) + + +class LogGood: + def __init__(self, path: str, mode: str = "x") -> None: + self._path = path + self._mode = mode + + directory = os.path.dirname(self._path) + os.makedirs(directory, exist_ok=True) + + open(self._path, self._mode).close() + + def info(self, content: str, end: str = "\n") -> None: + with open(self._path, "a") as f: + f.write(f"[INFO] {content}{end}") + + def debug(self, content: str, end: str = "\n") -> None: + with open(self._path, "a") as f: + f.write(f"[DEBUG] {content}{end}") + + def error(self, content: str, end: str = "\n") -> None: + with open(self._path, "a") as f: + f.write(f"[ERROR] {content}{end}") diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index cceae0dd..4125a6f8 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.13) set(PROJECT_NAME zappy_gui) diff --git a/src/GUI/src/CMakeLists.txt b/src/GUI/src/CMakeLists.txt index 081c3dac..0b47950d 100644 --- a/src/GUI/src/CMakeLists.txt +++ b/src/GUI/src/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.13) target_include_directories( ${PROJECT_NAME} diff --git a/src/GUI/src/Display/CMakeLists.txt b/src/GUI/src/Display/CMakeLists.txt index ce00365c..751bae5a 100644 --- a/src/GUI/src/Display/CMakeLists.txt +++ b/src/GUI/src/Display/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.13) target_include_directories( ${PROJECT_NAME} PRIVATE diff --git a/src/GUI/src/network/CMakeLists.txt b/src/GUI/src/network/CMakeLists.txt index e70ac4ae..48db9c61 100644 --- a/src/GUI/src/network/CMakeLists.txt +++ b/src/GUI/src/network/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.13) target_include_directories( ${PROJECT_NAME} diff --git a/src/SERVER/Makefile b/src/SERVER/Makefile index 788d7a20..a638ef45 100644 --- a/src/SERVER/Makefile +++ b/src/SERVER/Makefile @@ -17,7 +17,7 @@ CFLAGS = \ -Ilibs/tinylibc/includes \ -Ilibs/circularbuffer/includes \ -MMD \ - -O2 + -g3 LDFLAGS = \ -L $(LIBS_DIR)/circularbuffer/ \