From c6dd0ddf0a763e0baecc7563c2b977edb26125f4 Mon Sep 17 00:00:00 2001 From: Marius-P1 Date: Sun, 23 Jun 2024 07:37:42 +0200 Subject: [PATCH 01/10] chore: Add bonus and api-ai folders --- bonus/api-ai/logs/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 bonus/api-ai/logs/.gitkeep diff --git a/bonus/api-ai/logs/.gitkeep b/bonus/api-ai/logs/.gitkeep new file mode 100644 index 00000000..e69de29b From 36a554c7eeae955428383f2192eabcfb5723d1cb Mon Sep 17 00:00:00 2001 From: Marius-P1 Date: Sun, 23 Jun 2024 07:38:55 +0200 Subject: [PATCH 02/10] chore: Update gitignore with logs folder from bonus --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9b6aa790..f05ae94c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ logs/*.log doc/doxygen/html doc/doxygen/latex website-doc/ + +# Bonus +bonus/api-ai/logs/*.log From b4980f74b6a78928c2ec2370a2cc68013fee2576 Mon Sep 17 00:00:00 2001 From: Marius-P1 Date: Sun, 23 Jun 2024 07:39:15 +0200 Subject: [PATCH 03/10] chore: Add Makefile for AI bonus --- bonus/api-ai/Makefile | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 bonus/api-ai/Makefile diff --git a/bonus/api-ai/Makefile b/bonus/api-ai/Makefile new file mode 100644 index 00000000..3492c611 --- /dev/null +++ b/bonus/api-ai/Makefile @@ -0,0 +1,23 @@ +## +## EPITECH PROJECT, 2024 +## AI Zappy +## File description: +## AI Makefile +## + +NAME = zappy_ai_bonus + +all: $(NAME) + +$(NAME): + cp src/main.py $(NAME) + chmod 775 $(NAME) + +clean: + rm -rf __pycache__ + +fclean: clean + rm -f $(NAME) + rm -f $(TEST_NAME) + +re: fclean all From 8c6e948c0baf69207c0e8c5a81b5f9fbd972370a Mon Sep 17 00:00:00 2001 From: Marius-P1 Date: Sun, 23 Jun 2024 07:39:20 +0200 Subject: [PATCH 04/10] chore: Add fastapi to requirements.txt --- bonus/api-ai/requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 bonus/api-ai/requirements.txt diff --git a/bonus/api-ai/requirements.txt b/bonus/api-ai/requirements.txt new file mode 100644 index 00000000..170703df --- /dev/null +++ b/bonus/api-ai/requirements.txt @@ -0,0 +1 @@ +fastapi \ No newline at end of file From 764f6657b8473c040abf176e7f0a1fa4397d46f4 Mon Sep 17 00:00:00 2001 From: Marius-P1 Date: Sun, 23 Jun 2024 07:40:31 +0200 Subject: [PATCH 05/10] feat: Add initial AI project to accelerate the bonus developpement --- bonus/api-ai/src/Enum/Action.py | 27 +++ bonus/api-ai/src/Enum/Item.py | 21 ++ bonus/api-ai/src/Enum/Mode.py | 21 ++ bonus/api-ai/src/Enum/Role.py | 13 ++ bonus/api-ai/src/Errors/ArgsException.py | 27 +++ bonus/api-ai/src/Errors/IError.py | 56 +++++ bonus/api-ai/src/Network/APIException.py | 45 ++++ bonus/api-ai/src/Player/Inventory.py | 258 +++++++++++++++++++++ bonus/api-ai/src/Player/PlayerException.py | 47 ++++ bonus/api-ai/src/Utils/Message.py | 184 +++++++++++++++ bonus/api-ai/src/Utils/Utils.py | 90 +++++++ 11 files changed, 789 insertions(+) create mode 100644 bonus/api-ai/src/Enum/Action.py create mode 100644 bonus/api-ai/src/Enum/Item.py create mode 100644 bonus/api-ai/src/Enum/Mode.py create mode 100644 bonus/api-ai/src/Enum/Role.py create mode 100644 bonus/api-ai/src/Errors/ArgsException.py create mode 100644 bonus/api-ai/src/Errors/IError.py create mode 100644 bonus/api-ai/src/Network/APIException.py create mode 100644 bonus/api-ai/src/Player/Inventory.py create mode 100644 bonus/api-ai/src/Player/PlayerException.py create mode 100644 bonus/api-ai/src/Utils/Message.py create mode 100644 bonus/api-ai/src/Utils/Utils.py diff --git a/bonus/api-ai/src/Enum/Action.py b/bonus/api-ai/src/Enum/Action.py new file mode 100644 index 00000000..68973cbf --- /dev/null +++ b/bonus/api-ai/src/Enum/Action.py @@ -0,0 +1,27 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## Action +## + +from enum import Enum + +class Action(Enum): + """ + Action class + A class to list the actions the player can do + """ + FORWARD = "Forward" + RIGHT = "Right" + LEFT = "Left" + LOOK = "Look" + INVENTORY = "Inventory" + BROADCAST = "Broadcast" + CONNECT_NBR = "Connect_nbr" + FORK = "Fork" + EJECT = "Eject" + TAKE = "Take" + SET = "Set" + INCANTATION = "Incantation" + NONE = "None" diff --git a/bonus/api-ai/src/Enum/Item.py b/bonus/api-ai/src/Enum/Item.py new file mode 100644 index 00000000..c5b263d3 --- /dev/null +++ b/bonus/api-ai/src/Enum/Item.py @@ -0,0 +1,21 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## Item +## + +from enum import Enum + +class Item(Enum): + """ + Item class + A class to list the items in the game + """ + FOOD = "food" + LINEMATE = "linemate" + DERAUMERE = "deraumere" + SIBUR = "sibur" + MENDIANE = "mendiane" + PHIRAS = "phiras" + THYSTAME = "thystame" diff --git a/bonus/api-ai/src/Enum/Mode.py b/bonus/api-ai/src/Enum/Mode.py new file mode 100644 index 00000000..4aae8dd0 --- /dev/null +++ b/bonus/api-ai/src/Enum/Mode.py @@ -0,0 +1,21 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## Mode +## + +from enum import Enum + +class Mode(Enum): + FOOD = 0 + STONES = 1 + FORKING = 2 + BROADCASTING = 3 + HANDLINGRESPONSE = 4 + WAITING = 5 + ELEVATING = 6 + REGROUP = 7 + DROPPING = 8 + NONE = 9 + DYING = 10 diff --git a/bonus/api-ai/src/Enum/Role.py b/bonus/api-ai/src/Enum/Role.py new file mode 100644 index 00000000..a4cf3169 --- /dev/null +++ b/bonus/api-ai/src/Enum/Role.py @@ -0,0 +1,13 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## Role +## + +from enum import Enum + +class Role(Enum): + UNDEFINED = 0 + LEADER = 1 + SLAVE = 2 diff --git a/bonus/api-ai/src/Errors/ArgsException.py b/bonus/api-ai/src/Errors/ArgsException.py new file mode 100644 index 00000000..87f8ec3d --- /dev/null +++ b/bonus/api-ai/src/Errors/ArgsException.py @@ -0,0 +1,27 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## ArgsException +## + +from Errors.IError import IError + +class ArgsException(IError): + """ + ArgsException class + + A class to handle exceptions that can occur in the Args + The ArgsException class inherits from the IError class + + Attributes : + message : str + the message of the exception + """ + + + def __init__(self, message): + """ + Constructor of the ArgsException class + """ + super().__init__("ArgsException: " + message) diff --git a/bonus/api-ai/src/Errors/IError.py b/bonus/api-ai/src/Errors/IError.py new file mode 100644 index 00000000..61a92061 --- /dev/null +++ b/bonus/api-ai/src/Errors/IError.py @@ -0,0 +1,56 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## IError +## + +from abc import abstractmethod + +@abstractmethod +class IError(Exception): + """ + IError class + + A class to handle errors that can occur in the project + + Attributes : + message : str + the message of the error + + ---------- + + Methods : + __str__() + return the message of the error + __repr__() + return the message of the error + """ + + + def __init__(self, message): + """ + Constructor of the IError class + + Assign the message of the error + + Parameters : + message : str + the message of the error + """ + self.message = message + super().__init__(self.message) + + + def __str__(self): + """ + Return the message of the error + """ + return self.message + + + def __repr__(self): + """ + Return the message of the error + """ + return self.message diff --git a/bonus/api-ai/src/Network/APIException.py b/bonus/api-ai/src/Network/APIException.py new file mode 100644 index 00000000..4904987b --- /dev/null +++ b/bonus/api-ai/src/Network/APIException.py @@ -0,0 +1,45 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## APIException +## + +from Errors.IError import IError + +class APIException(IError): + """ + APIException class + + A class to handle exceptions that can occur in the API + The APIException class inherits from the IError class + + Attributes : + message : str + the message of the exception + fileName : str + the file name of logs + + ---------- + + Methods : + __init__(message : str, fileName : str = "") + Constructor of the APIException class + getFileName() + Get the file name of logs + """ + + + def __init__(self, message, fileName=""): + """ + Constructor of the APIException class + """ + self.fileName = fileName + super().__init__("APIException: " + message) + + + def getFileName(self): + """ + Get the file name + """ + return self.fileName diff --git a/bonus/api-ai/src/Player/Inventory.py b/bonus/api-ai/src/Player/Inventory.py new file mode 100644 index 00000000..4a3a6fd6 --- /dev/null +++ b/bonus/api-ai/src/Player/Inventory.py @@ -0,0 +1,258 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## Inventory +## + +class Inventory: + """ + Inventory class + A class to handle the inventory of the player + + Attributes : + food : int + the number of food + linemate : int + the number of linemate + deraumere : int + the number of deraumere + sibur : int + the number of sibur + mendiane : int + the number of mendiane + phiras : int + the number of phiras + thystame : int + the number of thystame + player : int + the number of players + + ---------- + + Methods : + __init__() + Constructor of the Inventory class + __str__() + Print the inventory + updateInventory(data) + Update the inventory with the data from the inventory command + updateCaseContent(data) + Update the case content with the data from the vision command + addAnObject(ressource) + Add an object to the inventory + removeAnObject(ressource) + Remove an object from the inventory + """ + + + def __init__(self, food=10, linemate=0, deraumere=0, sibur=0, mendiane=0, phiras=0, thystame=0, player=0): + """ + Constructor of the Inventory class + """ + self.food = food + self.linemate = linemate + self.deraumere = deraumere + self.sibur = sibur + self.mendiane = mendiane + self.phiras = phiras + self.thystame = thystame + self.player = player + + + def __str__(self): + """ + Print the inventory + """ + return f"food {self.food}, linemate {self.linemate}, deraumere {self.deraumere}, sibur {self.sibur}, mendiane {self.mendiane}, phiras {self.phiras}, thystame {self.thystame}, player {self.player}" + + + def toStr(self): + """ + Return the inventory as a string + + Returns : + str + the inventory as a string + """ + return f"[food {self.food}, linemate {self.linemate}, deraumere {self.deraumere}, sibur {self.sibur}, mendiane {self.mendiane}, phiras {self.phiras}, thystame {self.thystame}, player {self.player}]" + + + def __eq__(self, inventory): + """ + Compare two inventories + + Parameters : + inventory : Inventory + the inventory to compare with + + Returns : + bool + True if the inventories are the same, False otherwise + """ + if self.food == inventory.food and self.linemate == inventory.linemate and self.deraumere == inventory.deraumere and self.sibur == inventory.sibur and self.mendiane == inventory.mendiane and self.phiras == inventory.phiras and self.thystame == inventory.thystame and self.player == inventory.player: + return True + return False + + + def __add__(self, inventory): + """ + Add two inventories + + Parameters : + inventory : Inventory + the inventory to add + + Returns : + Inventory + the self inventory with the inventory added + """ + return Inventory(self.food + inventory.food, self.linemate + inventory.linemate, self.deraumere + inventory.deraumere, self.sibur + inventory.sibur, self.mendiane + inventory.mendiane, self.phiras + inventory.phiras, self.thystame + inventory.thystame, self.player + inventory.player) + + + def hasMoreStones(self, inventory : "Inventory"): + """ + Check if the self inventory has more stones than the inventory + + Parameters : + inventory : Inventory + the inventory to compare with + + Returns : + bool + True if the self inventory has more stones, False otherwise + """ + if self.linemate > inventory.linemate and self.deraumere > inventory.deraumere and self.sibur > inventory.sibur and self.mendiane > inventory.mendiane and self.phiras > inventory.phiras and self.thystame > inventory.thystame: + return True + return False + + + def updateInventory(self, data : str): + """ + Update the inventory with the data from the inventory command + + Parameters : + data : str + the data from the inventory command + """ + data = data[1:-1] + data = data.split(", ") + for elem in data: + elem = elem.split(" ") + if (elem.count("") > 0): + elem.remove("") + if elem[0] == "food": + self.food = int(elem[1]) + elif elem[0] == "linemate": + self.linemate = int(elem[1]) + elif elem[0] == "deraumere": + self.deraumere = int(elem[1]) + elif elem[0] == "sibur": + self.sibur = int(elem[1]) + elif elem[0] == "mendiane": + self.mendiane = int(elem[1]) + elif elem[0] == "phiras": + self.phiras = int(elem[1]) + elif elem[0] == "thystame": + self.thystame = int(elem[1]) + elif elem[0] == "player": + self.player = int(elem[1]) + + + def updateCaseContent(self, data : list): + """ + Update the case content with the data from the vision command + + Parameters : + data : list + the data from the vision command + """ + for elem in data: + if elem == "food": + self.food += 1 + elif elem == "linemate": + self.linemate += 1 + elif elem == "deraumere": + self.deraumere += 1 + elif elem == "sibur": + self.sibur += 1 + elif elem == "mendiane": + self.mendiane += 1 + elif elem == "phiras": + self.phiras += 1 + elif elem == "thystame": + self.thystame += 1 + elif elem == "player": + self.player += 1 + + + def addAnObject(self, ressource : str): + """ + Add an object to the inventory + + Parameters : + ressource : str + the ressource to add + """ + if ressource == "food": + self.food += 1 + elif ressource == "linemate": + self.linemate += 1 + elif ressource == "deraumere": + self.deraumere += 1 + elif ressource == "sibur": + self.sibur += 1 + elif ressource == "mendiane": + self.mendiane += 1 + elif ressource == "phiras": + self.phiras += 1 + elif ressource == "thystame": + self.thystame += 1 + + + def removeAnObject(self, ressource : str): + """ + Remove an object from the inventory + + Parameters : + ressource : str + the ressource to remove + """ + if ressource == "food" and self.food > 0: + self.food -= 1 + elif ressource == "linemate" and self.linemate > 0: + self.linemate -= 1 + elif ressource == "deraumere" and self.deraumere > 0: + self.deraumere -= 1 + elif ressource == "sibur" and self.sibur > 0: + self.sibur -= 1 + elif ressource == "mendiane" and self.mendiane > 0: + self.mendiane -= 1 + elif ressource == "phiras" and self.phiras > 0: + self.phiras -= 1 + elif ressource == "thystame" and self.thystame > 0: + self.thystame -= 1 + + + def countStones(self): + """ + Count the number of different stones in a case + + Returns : + int + the number of different stones in a case + """ + count = 0 + if self.linemate > 0: + count += 1 + if self.deraumere > 0: + count += 1 + if self.sibur > 0: + count += 1 + if self.mendiane > 0: + count += 1 + if self.phiras > 0: + count += 1 + if self.thystame > 0: + count += 1 + return count diff --git a/bonus/api-ai/src/Player/PlayerException.py b/bonus/api-ai/src/Player/PlayerException.py new file mode 100644 index 00000000..0784e4f7 --- /dev/null +++ b/bonus/api-ai/src/Player/PlayerException.py @@ -0,0 +1,47 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## PlayerException +## + +from Errors.IError import IError + +class PlayerException(IError): + """ + PlayerException class + + A class to handle exceptions that can occur in the Player + The PlayerException class inherits from the IError class + + Attributes : + message : str + the message of the exception + """ + + + def __init__(self, message): + """ + Constructor of the PlayerException class + """ + super().__init__("PlayerException: " + message) + + +class PlayerDeathException(PlayerException): + """ + PlayerDeathException class + + A class to handle the death of the player + The PlayerDeathException class inherits from the PlayerException class + + Attributes : + message : str + the message of the exception + """ + + + def __init__(self, message): + """ + Constructor of the PlayerDeathException class + """ + super().__init__("PlayerDeathException: " + message) diff --git a/bonus/api-ai/src/Utils/Message.py b/bonus/api-ai/src/Utils/Message.py new file mode 100644 index 00000000..1e0fff35 --- /dev/null +++ b/bonus/api-ai/src/Utils/Message.py @@ -0,0 +1,184 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## Message +## + +import uuid +import json +import time + +class Message: + """ + Message class + A class to handle messages when AI is broadcasting + This class is used to create, encrypt and decrypt messages + + Attributes : + messageUuid : str + the uuid of the message + messageTimestamp : int + the timestamp of the message + message : str + the message + senderUuid : str + the uuid of the sender + senderCreationTime : int + the creation time of the sender + key : int + the key to encrypt and decrypt the message + + ---------- + + Methods : + __init__(key : str) + Constructor of the Message class + createMessage(message : str, senderUuid : str, senderCreationTime : int) + Create a message + createMessageFromJson(jsonStr : str) + Create a message from a json string + createMessageFromEncryptedJson(jsonStr : str) + Create a message from an encrypted json string + __str__() + Return the message as a json string + __repr__() + Return the message as a json string + __eq__(other) + Compare two messages + __ne__(other) + Compare two messages + encrypt() + Encrypt the message + decrypt(cipher) + Decrypt the message + """ + + + def __init__(self, key : str): + """ + Constructor of the Message class + + Parameters : + key : str + the key to encrypt and decrypt the message + (the key is the team name of the AI) + """ + self.messageUuid = str(uuid.uuid4()) + self.messageTimestamp = 0 + self.message = "" + self.senderUuid = "" + self.senderCreationTime = 0 + self.key = 0 + for char in key: + self.key += ord(char) + + + def createMessage(self, message : str, senderUuid : str, senderCreationTime : int): + """ + Create a message and assign the message, the sender uuid and the sender creation time + + Parameters : + message : str + the message + senderUuid : str + the uuid of the sender + senderCreationTime : int + the creation time of the sender + """ + self.message = message + self.senderUuid = senderUuid + self.senderCreationTime = senderCreationTime + self.messageTimestamp = int(time.time_ns()) + + + def createMessageFromJson(self, jsonStr : str): + """ + Create a message from a json string + And assign the message, the sender uuid, the sender creation time, the message uuid and the message timestamp + + Parameters : + jsonStr : str + the json string + """ + try: + jsonStr = json.loads(jsonStr) + self.message = jsonStr["message"] + self.senderUuid = jsonStr["senderUuid"] + self.senderCreationTime = jsonStr["senderCreationTime"] + self.messageUuid = jsonStr["messageUuid"] + self.messageTimestamp = jsonStr["messageTimestamp"] + except: + return False + return True + + + def createMessageFromEncryptedJson(self, jsonStr : str): + """ + Create a message from an encrypted json string, decrypt it + And assign the message, the sender uuid, the sender creation time, the message uuid and the message timestamp + + Parameters : + jsonStr : str + the json string + """ + jsonStr = self.decrypt(jsonStr) + return self.createMessageFromJson(jsonStr) + + + def __str__(self): + """ + Return the message as a json string + """ + jsonStr = {"message": self.message, "senderUuid": self.senderUuid, "senderCreationTime": self.senderCreationTime, "messageUuid": self.messageUuid, "messageTimestamp": self.messageTimestamp} + return json.dumps(jsonStr) + + + def __repr__(self): + """ + Return the message as a json string + """ + return self.__str__() + + + def __eq__(self, other): + """ + Compare two messages + Check if the message, the sender uuid, the sender creation time, the message uuid and the message timestamp are the same + """ + return self.message == other.message and self.senderUuid == other.senderUuid and self.senderCreationTime == other.senderCreationTime and self.messageUuid == other.messageUuid and self.messageTimestamp == other.messageTimestamp + + + def __ne__(self, other): + """ + Compare two messages + Check if the message, the sender uuid, the sender creation time, the message uuid and the message timestamp are different + """ + return not self.__eq__(other) + + + def encrypt(self): + """ + Encrypt the message using the key + The message is encrypted using the XOR operator + """ + encryptedMessage = "" + initialMessage = self.__str__() + for i in range(len(initialMessage)): + encryptedMessage += chr(ord(initialMessage[i]) ^ self.key) + return encryptedMessage + + + def decrypt(self, cipher): + """ + Decrypt the message using the key + The message is decrypted using the XOR operator + + Parameters : + cipher : str + the encrypted message + """ + decryptedMessage = "" + for i in range(len(cipher)): + decryptedMessage += chr(ord(cipher[i]) ^ self.key) + return decryptedMessage diff --git a/bonus/api-ai/src/Utils/Utils.py b/bonus/api-ai/src/Utils/Utils.py new file mode 100644 index 00000000..fece80f7 --- /dev/null +++ b/bonus/api-ai/src/Utils/Utils.py @@ -0,0 +1,90 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## Utils +## + +def stringifyData(data : str): + """ + Transform data to a string to print it + + Parameters : + data : str + the data to transform + """ + string = "\"" + for c in data: + if c == '\n': + string += "\\n" + elif c == '\t': + string += "\\t" + else: + string += c + string += "\"" + return string + + +def mapRangeOpti(n): + """ + Function to get map's index view but optimized + Parameters : + n : int + Vision length + Returns : + List of indexes + """ + yield 1 + yield 0 + for i in range(2, n): + yield i + + +def getXmovement(middle, max, width, target): + """ + Get the horizontal movements to do to reach the target tile + + Parameters : + middle : int + index of the middle tile + max : int + index of the last tile on the row + width : int + width of the current row + + Returns : + int : the number of movements to do + """ + if middle == target: + return 0 + return target - middle + + +def getMovesTowardTile(index): + """ + Return the XY movements to do to reach the tile at index X + + Parameters : + index : int + Index of the tile to reach + + Returns : + tuple : (int, int) movements to do + """ + maxRowNum = 3 + crowWidth = 3 + fwdRow = 1 + middleTileIndex = 2 + + if index == 0: + return (0, 0) + if index <= maxRowNum: + return (getXmovement(middleTileIndex, maxRowNum, crowWidth, index), 1) + for i in range(7): + fwdRow += 1 + crowWidth += 2 + middleTileIndex += fwdRow*2 + maxRowNum += crowWidth + if index <= maxRowNum: + return (getXmovement(middleTileIndex, maxRowNum, crowWidth, index), fwdRow) + return -1 From 75f53db4b9790dd7a0c86e9332279f6b0bf3c573 Mon Sep 17 00:00:00 2001 From: Marius-P1 Date: Sun, 23 Jun 2024 07:41:47 +0200 Subject: [PATCH 06/10] feat: Add the AI API with the basics routes --- bonus/api-ai/src/main.py | 328 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 bonus/api-ai/src/main.py diff --git a/bonus/api-ai/src/main.py b/bonus/api-ai/src/main.py new file mode 100644 index 00000000..8d121e6d --- /dev/null +++ b/bonus/api-ai/src/main.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 +## +## EPITECH PROJECT, 2024 +## Zappy AI +## File description: +## main +## + +import sys +import uuid +from typing import List +from typing import Union +from fastapi import FastAPI, HTTPException + +import AI + +# Port min +PORT_MIN = 0 +# Port max +PORT_MAX = 65535 +# Localhost +LOCALHOST = "127.0.0.1" + +app = FastAPI() + +aiList : List[AI.AI] = [] + + +@app.get("/") +def welcome_root(): + return {"content": "Welcome to the Zappy AI"} + + +@app.get("/create") +def create_ai(host: str = LOCALHOST, port: int = -1, team_name: str = "team1"): + if port < PORT_MIN or port > PORT_MAX: + raise HTTPException(status_code=400, detail="Invalid port") + aiToken = uuid.uuid4() + try: + ai = AI.AI(host, port, team_name) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + aiList.append((ai, aiToken)) + return {"token": f"{aiToken}"} + + +@app.get("/leave") +def leave_ai(token: str): + for ai, aiToken in aiList: + if str(aiToken) == token: + ai.close() + aiList.remove((ai, aiToken)) + return {"content": "AI left successfully"} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/list") +def list_ai(): + return {"content": [f"({str(aiToken)}, {ai})" for ai, aiToken in aiList]} + + +@app.get("/state") +def is_alive(token: str): + for ai, aiToken in aiList: + if str(aiToken) == token: + return {"state": ai.state()} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/actions") +def get_actions(token: str = None): + if token is None: + return {"actions": ["forward", "right", "left", "look", "inventory", "broadcast", "connect_nbr", "fork", "eject", "take", "set"]} + for ai, aiToken in aiList: + if str(aiToken) == token: + return {"currentActions": ai.actions()} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/inventory") +def do_action(token: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.action("inventory") + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/forward") +def do_action(token: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.action("forward") + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/right") +def do_action(token: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.action("right") + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/left") +def do_action(token: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.action("left") + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/look") +def do_action(token: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.action("look") + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + +@app.get("/action/connect_nbr") +def do_action(token: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.action("connect_nbr") + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/fork") +def do_action(token: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.action("fork") + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/eject") +def do_action(token: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.action("eject") + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/take") +def do_action(token: str = None, item: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + if item is None: + raise HTTPException(status_code=400, detail="Item not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.take(item) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/set") +def do_action(token: str = None, item: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + if item is None: + raise HTTPException(status_code=400, detail="Item not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.set(item) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/broadcast") +def do_action(token: str = None, message: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + if message is None: + raise HTTPException(status_code=400, detail="Message not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.broadcast(message) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/action/incantation") +def do_action(token: str = None): + if token is None: + raise HTTPException(status_code=400, detail="Token not provided") + for ai, aiToken in aiList: + if str(aiToken) == token: + try: + isFull, actions = ai.incantation() + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + if isFull: + return {"error": "Too many actions in queue", "actions in queue": actions} + return {"content": "Action added to queue", "actions in queue": actions} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/info/ejected") +def get_ejected(token: str): + for ai, aiToken in aiList: + if str(aiToken) == token: + return {"ejected": ai.ejected()} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/info/inventory") +def get_inventory(token: str): + for ai, aiToken in aiList: + if str(aiToken) == token: + return {"inventory": ai.getInventory()} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/info/level") +def get_level(token: str): + for ai, aiToken in aiList: + if str(aiTokeservern) == token: + return {"level": ai.getLevel()} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/info/teamname") +def get_teamname(token: str): + for ai, aiToken in aiList: + if str(aiToken) == token: + return {"teamname": ai.getTeamName()} + raise HTTPException(status_code=404, detail="AI not found") + + +@app.get("/info/messages") +def get_messages(tokeservern: str): + for ai, aiToken in aiList: + if str(aiToken) == token: + return {"messages": ai.getMessagesReceived()} + raise HTTPException(status_code=404, detail="AI not found") + +@app.get("/info/vision") +def get_vision(token: str): + for ai, aiToken in aiList: + if str(aiToken) == token: + return {"vision": ai.getVision()} + raise HTTPException(status_code=404, detail="AI not found") + +@app.get("/info/response") +def get_response(token: str): + for ai, aiToken in aiList: + if str(aiToken) == token: + return {"response": ai.getLastReceivedData()} + raise HTTPException(status_code=404, detail="AI not found") From 1e71445b7be21e016744a9103d6f89ab4c363310 Mon Sep 17 00:00:00 2001 From: Marius-P1 Date: Sun, 23 Jun 2024 07:43:02 +0200 Subject: [PATCH 07/10] feat: Update API, Player and AI class to adapt to the bonus --- bonus/api-ai/src/AI.py | 324 +++++++++ bonus/api-ai/src/Network/API.py | 186 +++++ bonus/api-ai/src/Player/Player.py | 1089 +++++++++++++++++++++++++++++ 3 files changed, 1599 insertions(+) create mode 100644 bonus/api-ai/src/AI.py create mode 100644 bonus/api-ai/src/Network/API.py create mode 100644 bonus/api-ai/src/Player/Player.py diff --git a/bonus/api-ai/src/AI.py b/bonus/api-ai/src/AI.py new file mode 100644 index 00000000..66eb093e --- /dev/null +++ b/bonus/api-ai/src/AI.py @@ -0,0 +1,324 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## AI +## + +import os +import sys +import time +import uuid +import threading + +from Enum.Role import Role +from Network.API import API +from Utils.Message import Message +from Player.Player import Player, Action, Mode + + + +class AI: + """ + AI class + A class to handle the AI of the Zappy project + + Attributes : + api : API + the API to communicate with the server + player : Player + the player + teamName : str + the name of the team + + ---------- + + Methods : + __init__(host : str, port : int, teamName : str) + Constructor of the AI class + run() + Run the AI + """ + + + def __init__(self, host, port, teamName, isAuto=False): + """ + Constructor of the AI class + Assign the API, the player and the team name + + Parameters : + host : str + the host of the server + port : int + the port of the server + teamName : str + the name of the team + """ + self.api = API(host, port) + self.player = Player(teamName) + self.teamName = teamName + self.threads = [] + self.creationTime = time.time_ns() + self.myuuid = str(uuid.uuid4()) + self.isRunning = True + self.buffer = "" + self.lastReceivedData = "" + fileName = createLogs(self.myuuid) + self.api.connect() + self.api.initConnection(self.teamName, fileName) + + thread = threading.Thread(target=self.serverCommunicationInThread) + thread.start() + self.threads.append(thread) + + + def __str__(self) -> str: + """ + Return the string representation of the AI + """ + return f"AI: [teamName: {self.teamName}, AI creationTime: {self.creationTime}, AI uuid: {self.myuuid}]" + + + def serverCommunicationInThread(self): + """ + Handle the communication with the server in a thread + """ + try: + while self.isRunning: + responses = self.api.receiveData(0.1) + if responses is not None: + responses = self.buffer + responses + responses = responses.split("\n") + self.buffer = "" + if responses[-1] != "": + self.buffer = responses[-1] + responses.pop() + for response in responses: + if response == '': + continue + self.player.handleResponse(response, self.creationTime, self.teamName, self.myuuid, self.creationTime) + self.lastReceivedData = response + for _ in range(0, len(self.player.callbacks)): + self.player.currentAction = self.player.actions[0] + self.player.currentCommand = self.player.commands[0] + self.player.currentCallback = self.player.callbacks[0] + self.api.sendData(self.player.currentCommand) + while self.player.currentAction != Action.NONE: + responses = self.buffer + self.api.receiveData() + responses = responses.split("\n") + self.buffer = "" + if responses[-1] != "": + self.buffer = responses[-1] + responses.pop() + for response in responses: + if response == '': + continue + self.player.handleResponse(response, self.creationTime, self.teamName, self.myuuid, self.creationTime) + self.lastReceivedData = response + self.player.actions.pop(0) + self.player.commands.pop(0) + self.player.callbacks.pop(0) + except Exception as e: + print(e, flush=True, file=sys.stderr) + self.isRunning = False + + + def state(self): + """ + Get the state of the AI + """ + if self.isRunning: + return "alive" + return "dead" + + + def actions(self): + """ + Get the actions of the AI + """ + return self.player.actions + + + def action(self, action): + """ + Add an action to the AI + """ + if len(self.player.actions) > 9: + return (True, self.player.actions) + if action == "forward": + self.player.moveForward() + elif action == "right": + self.player.turnRight() + elif action == "left": + self.player.turnLeft() + elif action == "look": + self.player.look() + elif action == "inventory": + self.player.cmdInventory() + elif action == "connectf05ffef4-2853-4d3e-a0ba-c5bbb0090446_nbr": + self.player.connectNbr() + elif action == "fork": + self.player.fork() + elif action == "eject": + self.player.eject() + else: + raise Exception("Unknown action") + return (False, self.player.actions) + + + def take(self, item): + """ + Add an action to the AI + """ + if len(self.player.actions) > 9: + return (True, self.player.actions) + self.player.take(item) + return (False, self.player.actions) + + + def set(self, item): + """ + Add an action to the AI + """ + if len(self.player.actions) > 9: + return (True, self.player.actions) + self.player.set(item) + return (False, self.player.actions) + + + def broadcast(self, message): + """ + Add an action to the AI + """ + if len(self.player.actions) > 9: + return (True, self.player.actions) + self.player.broadcast(message) + return (False, self.player.actions) + + def incantation(self): + """ + Add an action to the AI + """ + return self.player.incantation() + + def ejected(self): + """ + Add an action to the AI + """ + return self.player.ejectionReceived + + + def getInventory(self): + """ + Get the inventory of the AI + """ + return self.player.inventory + + + def getLevel(self): + """ + Get the level of the AI + """ + return self.player.level + + + def getTeamName(self): + """ + Get the team name of the AI + """ + return self.teamName + + + def getMessagesReceived(self): + """ + Get the messages of the AI + """ + return self.player.broadcastReceived + + + def getVision(self): + """ + Get the vision of the AI + """ + return self.player.vision + + + def getLastReceivedData(self): + """ + Get the last received data of the AI + """ + return self.lastReceivedData + + + def run(self): + """ + Run the AI (the main loop) + Connect to the server, initialize the connection and start the main loop + + The main loop is an infinite loop that will select an action for the player + and send it to the server and after that, it will receive the response from the server + and handle it + """ + self.player.broadcast("IsLeader?", self.teamName, self.myuuid, self.creationTime) + + while self.player.isLeader == Role.UNDEFINED and self.isRunning: + if len(self.player.callbacks) == 0: + self.player.cmdInventory() + self.player.look() + if self.threads[0].is_alive() == False: + break + if self.player.inventory.food <= 8: + for msg in self.player.broadcastReceived: + if msg[1].message == "Yes": + print("I'm a slave", flush=True, file=sys.stderr) + self.player.isLeader = Role.SLAVE + self.player.broadcastReceived.remove(msg) + break + if self.player.isLeader == Role.UNDEFINED: + print("I'm a leader", flush=True, file=sys.stderr) + self.player.isLeader = Role.LEADER + + if self.player.isLeader == Role.LEADER: + self.player.completeTeam(self.api.host, self.api.port, self.teamName) + + while self.isRunning: + if len(self.player.actions) == 0: + if self.player.currentMode != Mode.NONE: + print("Choose action", flush=True) + self.player.chooseAction(self.teamName, self.myuuid, self.creationTime, self.api.host, self.api.port) + if self.threads[0].is_alive() == False: + break + self.api.close() + + + def close(self): + """ + Close the AI + """ + self.isRunning = False + for thread in self.threads: + thread.join() + self.api.close() + + +def forkAI(host, port, teamName): + """ + Fork the AI + """ + pid = os.fork() + if pid == 0: + ai = AI(host, port, teamName) + ai.run() + sys.exit(0) + return pid + + +def createLogs(myuuid): + """ + Create the logs + """ + fileName = f"{myuuid}.log" + if not os.path.exists("logs"): + os.makedirs("logs") + sys.stderr = open("logs/stderr_" + fileName, "w") + return fileName diff --git a/bonus/api-ai/src/Network/API.py b/bonus/api-ai/src/Network/API.py new file mode 100644 index 00000000..caf6ca7c --- /dev/null +++ b/bonus/api-ai/src/Network/API.py @@ -0,0 +1,186 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## API +## + +import sys +import socket +import select + +from Utils.Utils import stringifyData +from Network.APIException import APIException + +LIMIT_TRANSFER = 10240 + +class API: + """ + API class + A class to communicate with the server + + Attributes : + host : str + the host of the server + port : int + the port of the server + inputs : list + the list of inputs + outputs : list + the list of outputs + sock : socket + the socket to communicate with the server + + ---------- + + Methods : + sendData(data : str, timeout : int = None) + send data to the server + receiveData(timeout : int = None) + receive data from the server + connect(team_name : str) + connect to the server + close() + close the connection + """ + + + def __init__(self, host : str, port : int): + """ + Constructor of the API class + + Assign the host and the port of the server + Create the socket to communicate with the server + Connect to the server and add the socket to the inputs and outputs lists + + Parameters : + host : str + the host of the server + port : int + the port of the server + """ + self.host : str = host + self.port : int = port + self.inputs : list = [] + self.outputs : list = [] + self.sock : socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + + def connect(self): + """ + Connect to the server + Add the socket to the inputs and outputs lists + """ + try: + self.sock.connect((self.host, self.port)) + except Exception as e: + raise APIException("connection to the server failed") + self.inputs.append(self.sock) + self.outputs.append(self.sock) + + + def sendData(self, data : str, timeout : int = None): + """ + Send data to the server + + Parameters : + data : str + the data to send + timeout : int + the timeout to wait for the server to be ready to receive data + (default is None which means no timeout) + """ + if -1 in self.outputs: + return + _, write, _ = select.select([], self.outputs, [], timeout) + + if data[-1] != '\n': + data += '\n' + for s in write: + if s == self.sock: + s.send(data.encode()) + print("sent : ", stringifyData(data), flush=True, file=sys.stderr) + + + def receiveData(self, timeout : float = None): + """ + Receive data from the server + + Parameters : + timeout : float + the timeout to wait for the server to send data + (default is None which means no timeout) + """ + if -1 in self.inputs: + return None + readable, _, _ = select.select(self.inputs, [], [], timeout) + for s in readable: + if s == self.sock: + data = s.recv(LIMIT_TRANSFER) + if not data: + print("Server disconnected", flush=True, file=sys.stderr) + sys.exit(0) + else: + print("received :", stringifyData(data.decode()), flush=True, file=sys.stderr) + return data.decode() + return None + + + def initConnection(self, teamName : str, fileName : str = ""): + """ + Function to do the first exchange with the server + + Send the team name to the server + Receive the client number and the map size from the server + Print the client number and the map size + + Parameters : + team_name : str + the name of the team + fileName : str + the file name of logs + + Returns : + client_num : int + the client number + x : int + the x size of the map + y : int + the y size of the map + """ + welcome = self.receiveData() + if welcome != "WELCOME\n": + raise APIException("invalid welcome message", fileName) + + self.sendData(f"{teamName}\n") + received = self.receiveData() + if received == "ko\n": + raise APIException("invalid team name", fileName) + if received.count('\n') == 2: + clientNum, data = received.split('\n', 1) + data = data.split(' ') + else: + clientNum = received.replace('\n', '') + data = self.receiveData() + data = data[0:data.find('\n')].split(' ') + + if len(data) != 2: + raise APIException("invalid map size", fileName) + try: + clientNum = int(clientNum) + x = int(data[0]) + y = int(data[1]) + except Exception as e: + raise APIException("invalid map size", fileName) + + print("Connected to server", flush=True, file=sys.stderr) + print(f"Client number: {clientNum}", flush=True, file=sys.stderr) + print(f"Map size: x = {x}, y = {y}", flush=True, file=sys.stderr) + return clientNum, x, y + + + def close(self): + """ + Close the connection with the server + """ + self.sock.close() diff --git a/bonus/api-ai/src/Player/Player.py b/bonus/api-ai/src/Player/Player.py new file mode 100644 index 00000000..1356a0ad --- /dev/null +++ b/bonus/api-ai/src/Player/Player.py @@ -0,0 +1,1089 @@ +## +## EPITECH PROJECT, 2024 +## Zappy +## File description: +## Player +## + +import sys +import random + +from Enum.Mode import Mode +from Enum.Role import Role +from Enum.Action import Action +from Utils.Message import Message +from Player.Inventory import Inventory +from Player.PlayerException import PlayerDeathException + +class Player: + """ + Player class + A class to handle the player + + Attributes : + inventory : Inventory + the inventory of the player + level : int + the level of the player + actions : list + the actions of the player + currentAction : Action + the current action of the player + commands : list + the commands of the player + currentCommand : str + the current command of the player + callbacks : list + the callbacks of the player + currentCallback : function + the current callback of the player + vision : list + the vision of the player + broadcastReceived : list + the broadcast received by the player + ejectionReceived : list + the ejection received by the player + isLeader : Role + if the player is the leader/undefined/slave + unusedSlots : int + the unused slots + currentlyElevating : bool + if the player is currently elevating + currentMode : Mode + the current mode of the player + currentFood : int + the current food of the player + nbSlaves : int + the number of slaves that are alive + waitingResponse : bool + if the player is waiting for a response + regroupDirection : int + the direction of the regroup + arrived : bool + if the player arrived to the regroup + isTimed : bool + if the player is timed + nbSlavesHere : int + the number of slaves here + messageHistory : list + the history of the messages + teamName : str + the name of the team + enemyBroadcast : list + the enemy broadcast + ---------- + + Methods : + __init__() + Constructor of the Player class + __str__() + Print the player + moveForward(callback = None) + Move the player forward + turnRight(callback = None) + Turn the player right + turnLeft(callback = None) + Turn the player left + look(callback = None) + Look around the player + cmdInventory(callback = None) + Get the inventory of the player + broadcast(message : str = "Hello", callback = None) + Broadcast a message + connectNbr(callback = None) + Connect to the number of players + fork(callback = None) + Fork the player + eject(callback = None) + Eject the player + take(resource : str = "food", callback = None) + Take a resource + set(resource : str = "food", callback = None) + Set a resource + incantation(callback = None) + Start the incantation + none() + Do nothing + updateVision(vision : str) + Update the vision of the player + updateInventory(inventory : str) + Update the inventory of the player + updateBroadcastReceived(message : str) + Update the broadcast received by the player + updateEjectionReceived(message : str) + Update the ejection received by the player + updateLevel(level : int) + Update the level of the player + handleElevation(response : str) + Handle the elevation + hasSomethingHappened(response : str) + Check if something happened + handleResponse(response : str) + Handle the response + connectMissingPlayers() + Connect the missing players + completeTeam() + Complete the team + updateModeSlave() + Update the mode of the player when he is a slave + updateModeLeader() + Update the mode of the player when he is a leader + updateMode() + Update the mode of the player + lookingForFood() + Look for food + lookingForStones() + Look for stones + askSlavesForInventory() + Ask the slaves for their inventory + checkIfEnoughFood(response : str) + Check if the slave has enough food + handleResponseBroadcast() + Handle the response of the broadcast + slavesReponses() + Handle the leader order as a slave + waitingEveryone() + Wait for everyone to finish the regroup + waitingDrop() + Wait for everyone to finish droping the stones + dropping() + Drop the stones + regroupAction() + Regroup the players + chooseAction() + Choose the action of the player + """ + + + def __init__(self, teamName : str): + """ + Constructor of the Player class + """ + self.inventory = Inventory() + self.level = 1 + self.actions = [] + self.currentAction = Action.NONE + self.commands = [] + self.currentCommand = "" + self.callbacks = [] + self.currentCallback = None + self.vision = [] + self.broadcastReceived = [] + self.ejectionReceived = [] + self.isLeader = Role.UNDEFINED + self.unusedSlots = 0 + self.currentlyElevating = False + self.currentMode = Mode.FOOD + self.currentFood = 0 + self.nbSlaves = 0 + self.waitingResponse = False + self.regroupDirection = 0 + self.arrived = False + self.isTimed = False + self.nbSlavesHere = 0 + self.messageHistory = [] + self.teamName = teamName + self.enemyBroadcast = [] + self.alliesUuid = [] + + + def __str__(self): + """ + Print the player + """ + return f"Level: {self.level}, Inventory: [{self.inventory}], Current action: {self.currentAction}, Current command: {self.currentCommand}, Vision: {self.vision}, Broadcast received: {self.broadcastReceived}, Ejection received: {self.ejectionReceived}" + + + def moveForward(self, callback = None): + """ + Set the current action to forward + + Parameters : + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.FORWARD) + self.commands.append("Forward") + self.callbacks.append(callback) + + + def turnRight(self, callback = None): + """ + Set the current action to right + + Parameters : + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.RIGHT) + self.commands.append("Right") + self.callbacks.append(callback) + + + def turnLeft(self, callback = None): + """ + Set the current action tl moderation bot designed for mo left + + Parameters : + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.LEFT) + self.commands.append("Left") + self.callbacks.append(callback) + + + def look(self, callback = None): + """ + Set the current action to look + + Parameters : + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.LOOK) + self.commands.append("Look") + self.callbacks.append(callback) + + + def cmdInventory(self, callback = None): + """ + Set the current action to inventory + + Parameters : + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.INVENTORY) + self.commands.append("Inventory") + self.callbacks.append(callback) + + + def broadcast(self, message : str = "Hello", callback = None): + """ + Set the current action to broadcast + + Parameters : + message : str + the message to broadcast + callback : function + the callback to call after the action + (default is None) + teamName : str + the name of the team + myuuid : str + the uuid of the player + creationTime : int + the creation time of the message + """ + self.actions.append(Action.BROADCAST) + self.commands.append(f"Broadcast \"{message}\"") + self.callbacks.append(callback) + + + def broadcastEnemyMessage(self, callback = None): + """ + Set the current action to broadcast an enemy message + + Parameters : + callback : function + the callback to call after the action + (default is None) + """ + message = (1, "English or Spanish?") + if len(self.enemyBroadcast) > 0: + message = random.choice(self.enemyBroadcast) + self.actions.append(Action.BROADCAST) + self.commands.append(f"Broadcast \"{message[1]}\"") + self.callbacks.append(callback) + + + def connectNbr(self, callback = None): + """ + Set the current action to connect_nbr + + Parameters : + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.CONNECT_NBR) + self.commands.append("Connect_nbr") + self.callbacks.append(callback) + + + def fork(self, callback = None): + """ + Set the current action to fork + + Parameters : + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.FORK) + self.commands.append("Fork") + self.callbacks.append(callback) + + + def eject(self, callback = None): + """ + Set the current action to eject + + Parameters : + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.EJECT) + self.commands.append("Eject") + self.callbacks.append(callback) + + + def take(self, resource : str = "food", callback = None): + """ + Set the current action to take + + Parameters : + resource : str + the resource to take + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.TAKE) + self.commands.append(f"Take {resource}") + self.callbacks.append(callback) + + + def set(self, resource : str = "food", callback = None): + """ + Set the current action to set + + Parameters : + resource : str + the resource to set + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.SET) + self.commands.append(f"Set {resource}") + self.callbacks.append(self.inventory.removeAnObject(resource)) + + + def incantation(self, callback = None): + """ + Set the current action to incantation + + Parameters : + callback : function + the callback to call after the action + (default is None) + """ + self.actions.append(Action.INCANTATION) + self.commands.append("Incantation") + self.callbacks.append(callback) + + + def none(self): + """ + Set the current action to none + """ + self.actions.append(Action.NONE) + self.commands.append("") + self.callbacks.append(None) + + + def updateVision(self, vision : str): + """ + Update the vision of the player with the data from the look command + + Parameters : + vision : str + the vision from the server + """ + vision = vision[1:-1] + vision = vision.split(',') + self.vision = [] + for case in vision: + inventory = Inventory(0, 0, 0, 0, 0, 0, 0, 0) + inventory.updateCaseContent(case.split(" ")) + self.vision.append(inventory) + if self.currentCallback is not None: + self.currentCallback() + return + + + def updateInventory(self, inventory : str): + """ + Update the inventory of the player with the data from the inventory command + + Parameters : + inventory : str + the inventory from the server + """ + self.inventory.updateInventory(inventory) + + + def updateBroadcastReceived(self, message : str, aiTimestamp : int): + """ + Update the broadcast received by the player + + Parameters : + message : str + the message from the server + aiTimestamp : int + the timestamp of the AI + """ + message = message[8:] + direction = int(message.split(", ")[0]) + if message.find('\"') != -1: + message = message[message.find('\"') + 1: message.rfind('\"')] + else: + message = message.split(", ")[1] + msg = Message(self.teamName) + if msg.createMessageFromEncryptedJson(message): + print("Received message: ", msg.message, flush=True, file=sys.stderr) + if msg in self.messageHistory or msg.messageTimestamp < aiTimestamp: + print("Already received this message", flush=True, file=sys.stderr) + return + self.broadcastReceived.append((direction, msg)) + self.messageHistory.append(msg) + if self.isLeader == Role.LEADER: + if msg.senderUuid not in self.alliesUuid: + self.alliesUuid.append(msg.senderUuid) + else: + print("Received enemy message: ", message, flush=True, file=sys.stderr) + self.enemyBroadcast.append((direction, message)) + + + def updateEjectionReceived(self, message : str): + """ + Update the ejection received by the player + + Parameters : + message : str + the message from the server + """ + message = message[7:] + direction = int(message) + self.ejectionReceived.append(direction) + + + def updateLevel(self, level : int): + """ + Update the level of the player + + Parameters : + level : int + the level of the player + """ + self.level = level + + + def handleElevation(self, response : str, teamName : str, myuuid : str, creationTime : int): + """ + Handle the response of the elevation command + + Parameters : + response : str + the response from the server + teamName : str + the name of the team + myuuid : str + the uuid of the player + creationTime : int + the creation time of the message + """ + if response == "Elevation underway": + self.currentlyElevating = True + if self.isLeader == Role.SLAVE: + self.currentMode = Mode.NONE + return True + elif response.startswith("Current level:"): + self.updateLevel(int(response.split(" ")[2])) + self.currentlyElevating = False + return False + elif response == "ko": + print("Elevation failed", flush=True, file=sys.stderr) + if self.isLeader == Role.LEADER: + self.currentMode = Mode.FOOD + self.broadcast("Food", teamName, myuuid, creationTime) + self.currentlyElevating = False + return False + + + def hasSomethingHappened(self, response : str, aiTimestamp : int): + """ + Check if something happened to the player + Look if the player is dead, if he received a message or if he was ejected + + Parameters : + response : str + the response from the server + aiTimestamp : int + the timestamp of the AI + """ + if response == "dead": + raise PlayerDeathException("Player is dead") + elif response.startswith("message"): + self.updateBroadcastReceived(response, aiTimestamp) + return True + elif response.startswith("eject:"): + self.updateEjectionReceived(response) + return True + return False + + + def handleResponse(self, response : str, aiTimestamp : int, teamName : str, myuuid : str, creationTime : int): + """ + Handle the response from the server + + Parameters : + response : str + the response from the server + aiTimestamp : int + the timestamp of the AI + teamName : str + the name of the team + myuuid : str + the uuid of the player + creationTime : int + the creation time of the message + """ + if self.hasSomethingHappened(response, aiTimestamp) or self.currentMode == Mode.DYING: + return + if response == "ko" and self.currentAction != Action.INCANTATION: + self.currentAction = Action.NONE + self.currentCommand = "" + return + if response == "ok": + if self.currentCallback is not None: + self.currentCallback() + elif self.currentAction == Action.LOOK: + self.updateVision(response) + elif self.currentAction == Action.INVENTORY: + self.updateInventory(response) + elif self.currentAction == Action.CONNECT_NBR: + self.unusedSlots = int(response) + if self.currentCallback is not None: + self.currentCallback() + elif self.currentAction == Action.INCANTATION: + if self.handleElevation(response, teamName, myuuid, creationTime): + return + self.currentAction = Action.NONE + self.currentCommand = "" + self.callback = None + if self.currentMode == Mode.REGROUP and self.isLeader == Role.SLAVE: + if response == "ok": + self.broadcastReceived = [] + + + def connectMissingPlayers(self, host, port, teamName): + """ + Connect the missing players + """ + print("Connecting missing players", flush=True, file=sys.stderr) + for _ in range(0, min(self.unusedSlots, 5)): + from AI import forkAI + forkAI(host, port, teamName) + + + def completeTeam(self, host, port, teamName): + """ + Complete the team + """ + self.connectNbr(self.connectMissingPlayers(host, port, teamName)) + + + def updateModeSlave(self): + """ + Update the mode of the player when he is a slave + """ + if self.inventory.food < 35: + self.currentMode = Mode.FOOD + elif self.inventory.food >= 45: + self.currentMode = Mode.STONES + + + def updateModeLeader(self): + """ + Update the mode of the player when he is a leader + """ + if self.inventory.food < 35: + self.currentMode = Mode.FOOD + elif self.inventory.food >= 45 or self.currentMode != Mode.FOOD: + print(self.currentFood, self.inventory.food) + if self.currentFood != self.inventory.food and self.waitingResponse == True: + if self.isTimed == True: + print("Handling response") + self.currentMode = Mode.HANDLINGRESPONSE + self.isTimed = False + else: + self.isTimed = True + elif self.currentFood != self.inventory.food and self.waitingResponse == False: + print("Broadcasting") + self.currentMode = Mode.BROADCASTING + self.waitingResponse = True + elif self.nbSlaves < 5 and self.waitingResponse == False: + self.currentMode = Mode.FORKING + else: + self.currentMode = Mode.WAITING + self.currentFood = self.inventory.food + + + def updateMode(self): + """ + Update the mode of the player + """ + if self.currentMode == Mode.REGROUP or self.currentMode == Mode.DROPPING or self.currentMode == Mode.ELEVATING or self.currentMode == Mode.NONE: + return + if self.isLeader == Role.LEADER: + self.updateModeLeader() + else: + self.updateModeSlave() + + + def lookingForFood(self): + """ + Look for food + The player will look for the nearest food in his vision. + When he finds food, he will go to the case + where there is food and take it. + """ + index = -1 + order = [0, 2, 1, 3] + for i in order: + if len(self.vision) > i and self.vision[i].food > 0: + index = i + break + if index == -1: + randAction = random.choice([self.moveForward, self.turnLeft, self.turnRight]) + randAction() + self.moveForward() + self.cmdInventory() + return + if index == 1: + self.moveForward() + self.turnLeft() + self.moveForward() + elif index == 2: + self.moveForward() + elif index == 3: + self.moveForward() + self.turnRight() + self.moveForward() + for i in range(0, self.vision[index].food): + if len(self.actions) < 9: + self.take("food") + else: + break + self.cmdInventory() + + + def lookingForStones(self): + """ + Look for stones + The player will look for the case with the most stones in his vision. + When he finds stones, he will go to the case + where there are stones and take them. + """ + index = -1 + count = 0 + order = [0, 2, 1, 3] + for i in order: + if len(self.vision) > i and self.vision[i].countStones() > count: + index = i + count = self.vision[i].countStones() + if index == -1: + self.moveForward() + self.moveForward() + self.cmdInventory() + return + if index == 1: + self.moveForward() + self.turnLeft() + self.moveForward() + elif index == 2: + self.moveForward() + elif index == 3: + self.moveForward() + self.turnRight() + self.moveForward() + if self.vision[index].linemate > 0: + self.take("linemate") + if self.vision[index].deraumere > 0: + self.take("deraumere") + if self.vision[index].sibur > 0: + self.take("sibur") + if self.vision[index].mendiane > 0: + self.take("mendiane") + if self.vision[index].phiras > 0: + self.take("phiras") + if self.vision[index].thystame > 0: + self.take("thystame") + self.cmdInventory() + + + def askSlavesForInventory(self, teamName : str, myuuid : str, creationTime : int): + """ + Ask the slaves for their inventory + The leader will ask the slaves for their inventory + + Parameters : + teamName : str + the name of the team + myuuid : str + the uuid of the player + creationTime : int + the creation time of the message + """ + self.broadcast("Inventory", teamName, myuuid, creationTime) + self.nbSlaves = 0 + + + def checkIfEnoughFood(self, response : str): + """ + Check if the slave has enough food to survive the regroup + + Parameters : + response : str + the response from the slave + """ + inv = Inventory(0, 0, 0, 0, 0, 0, 0, 0) + inv.updateInventory(response) + if inv.food < 35: + return False + return True + + + def isMessageInventory(self, message : str): + """ + Check if the message is an inventory message + + Parameters : + message : str + the message + """ + if message.startswith("[") and message.endswith("]") and message.count(",") == 7: + return True + return False + + + def countSlavesThatHaveSendInventory(self, messages : list): + """ + Count the number of slaves that have send their inventory + + Parameters : + messages : list + the messages received by the player + """ + nbSlaves = 0 + sendersUuid = [] + for msg in messages: + if self.isMessageInventory(msg[1].message) and msg[1].senderUuid not in sendersUuid: + nbSlaves += 1 + sendersUuid.append(msg[1].senderUuid) + return nbSlaves + + + def handleResponseBroadcast(self): + """ + Handle the response of the broadcast + """ + print(self.broadcastReceived, flush=True) + self.nbSlaves = len(self.broadcastReceived) + print("nb slaves: ", self.nbSlaves, flush=True) + globalInv = Inventory(0, 0, 0, 0, 0, 0, 0, 0) + minInv = Inventory(0, 8, 9, 10, 5, 6, 1, 0) + if self.nbSlaves >= 5: + for response in self.broadcastReceived: + if self.checkIfEnoughFood(response[1].message) == False: + self.waitingResponse = False + self.broadcastReceived = [] + return + inv = Inventory(0, 0, 0, 0, 0, 0, 0, 0) + inv.updateInventory(response[1].message) + globalInv = globalInv + inv + if globalInv.hasMoreStones(minInv): + self.currentMode = Mode.REGROUP + else: + print("Not enough stones", flush=True, file=sys.stdout) + print("Not enough stones", flush=True, file=sys.stderr) + self.waitingResponse = False + self.broadcastReceived = [] + + + def slavesReponses(self, teamName : str, myuuid : str, creationTime : int): + """ + Handle the leader order as a slave + + Parameters : + teamName : str + the name of the team + myuuid : str + the uuid of the player + creationTime : int + the creation time of the message + """ + for broadcast in self.broadcastReceived: + if broadcast[1].message == "Inventory": + response = self.inventory.toStr() + self.broadcast(response, teamName, myuuid, creationTime) + if broadcast[1].message == "Regroup": + self.currentMode = Mode.REGROUP + self.regroupDirection = broadcast[0] + return + + + def countSlavesThatArrived(self, messages : list): + """ + Count the number of slaves that arrived to the regroup + + Parameters : + messages : list + the messages received by the player + """ + nbSlavesHere = 0 + sendersUuid = [] + for msg in messages: + if msg[1].message == "I'm here" and msg[1].senderUuid not in sendersUuid: + nbSlavesHere += 1 + sendersUuid.append(msg[1].senderUuid) + return nbSlavesHere + + + def waitingEveryone(self, teamName : str, myuuid : str, creationTime : int): + """ + Wait for everyone to finish the regroup + + Parameters : + teamName : str + the name of the team + myuuid : str + the uuid of the player + creationTime : int + the creation time of the message + """ + nbSlavesHere = self.countSlavesThatArrived(self.broadcastReceived) + print("nb slaves here: ", nbSlavesHere, flush=True) + if nbSlavesHere >= 5: + self.broadcast("Drop", teamName, myuuid, creationTime) + self.currentMode = Mode.DROPPING + self.broadcastReceived = [] + else: + self.broadcast("Regroup", teamName, myuuid, creationTime) + + + def countSlavesThatFinishedDroping(self, messages : list): + """ + Count the number of slaves that finished droping the stones + + Parameters : + messages : list + the messages received by the player + """ + nbSlavesHere = 0 + sendersUuid = [] + for msg in messages: + if msg[1].message == "Finished dropping" and msg[1].senderUuid not in sendersUuid: + nbSlavesHere += 1 + sendersUuid.append(msg[1].senderUuid) + return nbSlavesHere + + + def waitingDrop(self): + """ + Wait for everyone to finish droping the stones + """ + nbSlavesHere = self.countSlavesThatFinishedDroping(self.broadcastReceived) + minStoneCase = Inventory(0, 8, 9, 10, 5, 6, 1, 0) + currentCase = self.vision[0] + if currentCase.hasMoreStones(minStoneCase): + self.currentMode = Mode.ELEVATING + self.broadcastReceived = [] + self.nbSlavesHere = nbSlavesHere + print("nb slaves who finished droping: ", nbSlavesHere, flush=True) + if nbSlavesHere >= 5: + self.currentMode = Mode.ELEVATING + self.broadcastReceived = [] + else: + self.look() + + + def dropping(self, teamName : str, myuuid : str, creationTime : int): + """ + Drop the stones + As a leader, you will wait for the slaves to drop the stones + As a slave, you will drop the stones until you have none left + + Parameters : + teamName : str + the name of the team + myuuid : str + the uuid of the player + creationTime : int + the creation time of the message + """ + if self.isLeader == Role.LEADER: + self.waitingDrop() + else: + print("Dropping", flush=True, file=sys.stderr) + if self.inventory.linemate > 0: + self.set("linemate") + if self.inventory.deraumere > 0: + self.set("deraumere") + if self.inventory.sibur > 0: + self.set("sibur") + if self.inventory.mendiane > 0: + self.set("mendiane") + if self.inventory.phiras > 0: + self.set("phiras") + if self.inventory.thystame > 0: + self.set("thystame") + if self.inventory.linemate == 0 and self.inventory.deraumere == 0 and self.inventory.sibur == 0 and self.inventory.mendiane == 0 and self.inventory.phiras == 0 and self.inventory.thystame == 0: + self.broadcast("Finished dropping", teamName, myuuid, creationTime) + self.currentMode = Mode.NONE + return + + + def regroupAction(self, teamName : str, myuuid : str, creationTime : int): + """ + Regroup the players + As a leader, you will wait for the slaves to regroup + As a slave, you will regroup with the leader + + Parameters : + teamName : str + the name of the team + myuuid : str + the uuid of the player + creationTime : int + the creation time of the message + """ + if self.isLeader == Role.LEADER: + self.waitingEveryone(teamName, myuuid, creationTime) + else: + isThereARegroup = False + + if len(self.broadcastReceived) != 0: + print(self.broadcastReceived, flush=True, file=sys.stderr) + for broadcast in self.broadcastReceived: + if broadcast[1].message == "Drop": + print("DROP MODE", flush=True, file=sys.stderr) + self.currentMode = Mode.DROPPING + self.broadcastReceived = [] + return + if broadcast[1].message == "Regroup": + isThereARegroup = True + self.regroupDirection = broadcast[0] + + self.broadcastReceived = [] + if isThereARegroup == False: + return + if self.regroupDirection == 0 and self.arrived == False: + self.broadcast("I'm here", teamName, myuuid, creationTime) + self.arrived = True + if self.regroupDirection == 3: + self.turnLeft() + if self.regroupDirection == 7: + self.turnRight() + if self.regroupDirection == 1: + self.moveForward() + if self.regroupDirection == 5: + self.turnRight() + self.turnRight() + if self.regroupDirection == 2 or self.regroupDirection == 8: + self.moveForward() + if self.regroupDirection == 4 or self.regroupDirection == 6: + self.turnRight() + self.turnRight() + + + def chooseAction(self, teamName : str, myuuid : str, creationTime : int, host : str, port : int): + """ + Choose the action of the player + The action is chosen depending on the mode of the player + The mode is updated before choosing the action + + Parameters : + teamName : str + the name of the team + myuuid : str + the uuid of the player + creationTime : int + the creation time of the message + """ + if self.currentMode == Mode.DYING: + return + if self.inventory.food <= 1 and self.isLeader == Role.LEADER: + self.broadcast(random.choice(self.alliesUuid), teamName, myuuid, creationTime) + self.currentMode = Mode.DYING + if self.isLeader == Role.LEADER: + for msg in self.broadcastReceived: + if msg[1].message == "IsLeader?": + self.broadcast("Yes", teamName, myuuid, creationTime) + self.broadcastReceived.remove(msg) + if self.isLeader == Role.SLAVE: + for msg in self.broadcastReceived: + if msg[1].message == "Food": + self.currentMode = Mode.FOOD + self.arrived = False + self.broadcastReceived.remove(msg) + if msg[1].message == myuuid: + self.isLeader = Role.LEADER + self.currentMode = Mode.FOOD + self.arrived = False + self.broadcastReceived.remove(msg) + self.updateMode() + if self.currentMode == Mode.REGROUP: + self.regroupAction(teamName, myuuid, creationTime) + return + if self.currentMode == Mode.DROPPING: + self.dropping(teamName, myuuid, creationTime) + return + if self.isLeader == Role.SLAVE: + if len(self.broadcastReceived) > 0: + self.slavesReponses(teamName, myuuid, creationTime) + self.broadcastReceived = [] + if self.currentMode == Mode.FOOD: + self.look(self.lookingForFood) + elif self.currentMode == Mode.STONES: + self.look(self.lookingForStones) + return + elif self.currentMode == Mode.FORKING: + print("Forking") + from AI import forkAI + self.fork(forkAI(host, port, teamName)) + self.nbSlaves += 1 + self.cmdInventory() + return + elif self.currentMode == Mode.BROADCASTING: + print("in broadcast") + self.askSlavesForInventory(teamName, myuuid, creationTime) + self.cmdInventory() + return + elif self.currentMode == Mode.HANDLINGRESPONSE: + self.handleResponseBroadcast() + self.cmdInventory() + return + elif self.currentMode == Mode.WAITING: + self.cmdInventory() + rand = random.randint(0, 5) + if rand == 0: + self.broadcastEnemyMessage() + else: + self.look() + return + elif self.currentMode == Mode.ELEVATING: + self.incantation() + return + elif self.currentMode == Mode.NONE: + return + return From 4e287ce8a3c42590115ff41b82b50f1ee15e6473 Mon Sep 17 00:00:00 2001 From: Marius-P1 Date: Sun, 23 Jun 2024 07:44:01 +0200 Subject: [PATCH 08/10] feat: Add README to document the AI-API bonus --- bonus/api-ai/README.md | 352 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 bonus/api-ai/README.md diff --git a/bonus/api-ai/README.md b/bonus/api-ai/README.md new file mode 100644 index 00000000..87d520fb --- /dev/null +++ b/bonus/api-ai/README.md @@ -0,0 +1,352 @@ +# Zappy API - Bonus Feature + +## Overview + +Welcome to the Zappy API documentation. This API serves as an additional feature to facilitate the interaction with the Zappy game’s AI. Built with FastAPI, this API enables seamless integration and management of AI functionalities, making it easier for developers and players to utilize AI agents within the game. + +## Features + +The Zappy API provides the following features: + +1. **AI Agent Management**: Create, update, and delete AI agents. + +2. **AI Agent Interaction**: Send commands to AI agents and receive responses. + +3. **AI Agent Monitoring**: Monitor the status of AI agents and their interactions. + +## Installation + +To install the Zappy API, follow these steps: + +1. Clone the repository: + +```bash +git clone https://github.com/FppEpitech/Zappy +``` + +2. Navigate to the `bonus/api-ai` directory: + +```bash +cd Zappy/bonus/api-ai +``` + +3. Install the prerequisites: + +Ubuntu: +```bash +sudo apt-get install python3 python3-pip virtualenv +``` + +Fedora: +```bash +sudo dnf install python3 python3-pip virtualenv +``` + +4. Create and activate a virtual environment: + +```bash +virtualenv venv +source venv/bin/activate +``` + +5. Install the dependencies: + +```bash +pip install -r requirements.txt +``` + +6. Start the FastAPI server: + +```bash +fastapi run ./src/main.py +``` + +## Usage + +To use the Zappy API, follow these steps: + +1. Open a web browser and navigate to `http://localhost:8000/docs`. + +2. Use the interactive API documentation to explore the available endpoints and send requests to the Zappy API. + +## API Endpoints + +The Zappy API provides the following endpoints: + +### Global Endpoints + +#### Welcome Route + +- **Method**: `GET` +- **URL**: `/` +- **Description**: Welcome route for the Zappy API. +- **Response**: JSON object containing a welcome message. + + +#### Create AI Agent + +- **Method**: `GET` +- **URL**: `/create` +- **Description**: Create a new AI agent. +- **Response**: JSON object containing the token of the new AI agent. + + +#### List AI Agents + +- **Method**: `GET` +- **URL**: `/list` +- **Description**: List all AI agents. +- **Response**: JSON object containing a list of AI agents. + + +#### State AI Agent + +- **Method**: `GET` +- **URL**: `/state` +- **Description**: Get the state of an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the state of the AI agent. + + +#### Leave AI Agent + +- **Method**: `GET` +- **URL**: `/leave` +- **Description**: Leave the game for an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the result of the operation. + + +### Actions Endpoints + +#### Actions list + +- **Method**: `GET` +- **URL**: `/actions` +- **Description**: List all actions available for an AI agent. +- **Response**: JSON object containing a list of actions. + + +#### Current action of an AI agent + +- **Method**: `GET` +- **URL**: `/actions` +- **Description**: Get the current action of an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the current action of the AI agent. + + +#### Action : Forward + +- **Method**: `GET` +- **URL**: `/action/forward` +- **Description**: Move an AI agent forward. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Right + +- **Method**: `GET` +- **URL**: `/action/right` +- **Description**: Turn an AI agent to the right. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Left + +- **Method**: `GET` +- **URL**: `/action/left` +- **Description**: Turn an AI agent to the left. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Look + +- **Method**: `GET` +- **URL**: `/action/look` +- **Description**: Look around an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Inventory + +- **Method**: `GET` +- **URL**: `/action/inventory` +- **Description**: Ask for the inventory of an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Broadcast + +- **Method**: `GET` +- **URL**: `/action/broadcast` +- **Description**: Broadcast a message from an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. + - `message`: The message to broadcast. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Connect Nbr + +- **Method**: `GET` +- **URL**: `/action/connect_nbr` +- **Description**: Ask for the number of free slots on the server. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Fork + +- **Method**: `GET` +- **URL**: `/action/fork` +- **Description**: Fork an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Eject + +- **Method**: `GET` +- **URL**: `/action/eject` +- **Description**: Eject an AI agent from a tile. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Take + +- **Method**: `GET` +- **URL**: `/action/take` +- **Description**: Take an object from a tile. +- **Query Parameters**: + - `token`: The token of the AI agent. + - `object`: The object to take. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Set + +- **Method**: `GET` +- **URL**`: `/action/set` +- **Description**: Set an object on a tile. +- **Query Parameters**: + - `token`: The token of the AI agent. + - `object`: The object to set. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +#### Action : Incantation + +- **Method**: `GET` +- **URL**: `/action/incantation` +- **Description**: Start an incantation on a tile. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the status of the operation and a list of the actions in queue. + + +### Information Endpoints + + +#### Get AI Vision + +- **Method**: `GET` +- **URL**: `/info/vision` +- **Description**: Get the vision of an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the vision of the AI agent. + + +#### Get AI Inventory + +- **Method**: `GET` +- **URL**: `/info/inventory` +- **Description**: Get the inventory of an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the inventory of the AI agent. + + +#### Get AI Level + +- **Method**: `GET` +- **URL**: `/info/level` +- **Description**: Get the level of an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the level of the AI agent. + + +#### Get AI Team + +- **Method**: `GET` +- **URL**: `/info/teamname` +- **Description**: Get the team name of an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the team name of the AI agent. + + +#### Get AI Ejection Status + +- **Method**: `GET` +- **URL**: `/info/ejected` +- **Description**: Get the ejection status of an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the ejection status of the AI agent. + + +#### Get AI received message + +- **Method**: `GET` +- **URL**: `/info/messages` +- **Description**: Get the last message received by an AI agent. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the last message received by the AI agent. + + +#### Get Server last response + +- **Method**: `GET` +- **URL**: `/info/response` +- **Description**: Get the last response from the server. +- **Query Parameters**: + - `token`: The token of the AI agent. +- **Response**: JSON object containing the last response from the server. + + +## Contributing + +To contribute to the Zappy API, follow these steps: + +1. Fork the repository. + +2. Create a new branch. + +3. Make your changes. + +4. Commit your changes. + +5. Push your branch. + +6. Create a pull request. From e1e070cb543682391a04d506a0df6e92057336ab Mon Sep 17 00:00:00 2001 From: Marius-P1 Date: Sun, 23 Jun 2024 07:53:32 +0200 Subject: [PATCH 09/10] feat: Add export of API endpoints for Insomnia --- bonus/api-ai/API_Endpoints(Insomnia).json | 1 + bonus/api-ai/README.md | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 bonus/api-ai/API_Endpoints(Insomnia).json diff --git a/bonus/api-ai/API_Endpoints(Insomnia).json b/bonus/api-ai/API_Endpoints(Insomnia).json new file mode 100644 index 00000000..db4728fe --- /dev/null +++ b/bonus/api-ai/API_Endpoints(Insomnia).json @@ -0,0 +1 @@ +{"_type":"export","__export_format":4,"__export_date":"2024-06-23T04:57:54.315Z","__export_source":"insomnia.desktop.app:v9.2.0","resources":[{"_id":"req_d503b0779a0140d7bc003694dfc2f861","parentId":"wrk_229471fe0a5645998ad896f3c65d1f47","modified":1719112635073,"created":1719112623605,"url":"http://127.0.0.1:8000","name":"Welcome root","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719112623605,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"wrk_229471fe0a5645998ad896f3c65d1f47","parentId":null,"modified":1719112573387,"created":1719112573387,"name":"Zappy AI","description":"","scope":"collection","_type":"workspace"},{"_id":"req_3c9ee1dee604417c9d64e2f3a8282e1e","parentId":"wrk_229471fe0a5645998ad896f3c65d1f47","modified":1719114662385,"created":1719112579990,"url":"http://127.0.0.1:8000/create","name":"Create AI","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_28010f33d9c0485fb311656ced32f9e0","name":"host","value":"127.0.0.1","description":""},{"id":"pair_abfe39bae70744868a6d62d4e1e2353d","name":"port","value":"4242","description":""},{"id":"pair_f4b7ae2073344a5183107273a6189628","name":"team_name","value":"Jean1","description":""},{"id":"pair_c5cdd46e853942bfb94256adcf378d1c","name":"automated","value":"True","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719112579990,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_04912fe4e21c490984794395bdd2da53","parentId":"wrk_229471fe0a5645998ad896f3c65d1f47","modified":1719117733430,"created":1719112766937,"url":"http://127.0.0.1:8000/leave","name":"Leave AI","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_9b2c1570d4044f51b1abbf8adf99c6e4","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719112579890,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_e4919cc186284db398c8abf4608d9e8c","parentId":"wrk_229471fe0a5645998ad896f3c65d1f47","modified":1719113661079,"created":1719113630942,"url":"http://127.0.0.1:8000/list","name":"List AIs","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719112579790,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_aac1fbdc82834b89beb4e23fcc40e36e","parentId":"wrk_229471fe0a5645998ad896f3c65d1f47","modified":1719117726180,"created":1719113993624,"url":"http://127.0.0.1:8000/state","name":"State","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_53f5cd963edb441f80bd95d7aeed2707","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719112579752.5,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_b2a5f477b2aa476799d585f4a4e4fd73","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719116339125,"created":1719116298734,"url":"http://127.0.0.1:8000/actions","name":"Actions List","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719116298734,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_095f08fca86943518208f9338746070f","parentId":"wrk_229471fe0a5645998ad896f3c65d1f47","modified":1719117167674,"created":1719114138477,"name":"Actions","description":"","environment":{},"environmentPropertyOrder":{},"metaSortKey":-1719112579733.75,"_type":"request_group"},{"_id":"req_5b7c0ef0da094b90a769c2fa1820e746","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117240498,"created":1719116312236,"url":"http://127.0.0.1:8000/actions","name":"Current Player Actions","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_45da79c602f440cfb21bec70837f559a","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719115222588.5,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_ea8023443f5d44d2a42f17a83a11cb0b","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117839569,"created":1719114018814,"url":"http://127.0.0.1:8000/action/inventory","name":"Inventory","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_1f631b7e758c4e02b71af9816943f32d","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719114146443,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_a29ee3e38c914efe9191468091b3a431","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117855611,"created":1719114026194,"url":"http://127.0.0.1:8000/action/look","name":"Look","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_aa260e800b204b99a0e20a0daf223773","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719114146343,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_d8ebf7a235ba47358d034e943ee41e64","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117859612,"created":1719114036183,"url":"http://127.0.0.1:8000/action/forward","name":"Forward","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_f761365049e34151ac3b504c6399398b","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719114146243,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_92fe457676d2481bbb456b11a11795c6","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117863434,"created":1719114051885,"url":"http://127.0.0.1:8000/action/right","name":"Right","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_6c23c6beec084e0da9fb45ad891ae35c","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719114146143,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f143ab0f63d943cd9b2f89a645e736f8","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117867514,"created":1719114059588,"url":"http://127.0.0.1:8000/action/left","name":"Left","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_d8951ff9b1184ff4b77c14c52d51a4ff","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719114146043,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_fdee7487b3ce423bb8239a4813f5582b","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117871985,"created":1719114065622,"url":"http://127.0.0.1:8000/action/take","name":"Take","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_d7bc7686b8fd4c86a7fca48317c2b652","name":"token","value":"{{ _.token }}","description":""},{"id":"pair_242cc074c7764ed39e67c9aa63fef59a","name":"item","value":"food","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719114145943,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_8d6e234c7adb47a899aedecde54ecb00","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117876460,"created":1719114081490,"url":"http://127.0.0.1:8000/action/set","name":"Set","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_4ff01344713e4513ad271013f9d2df07","name":"token","value":"{{ _.token }}","description":""},{"id":"pair_962ca310184b499daa62840347933d70","name":"item","value":"food","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719114145843,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0982f138ecd24f16a178649352e2d8cf","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117880156,"created":1719114086756,"url":"http://127.0.0.1:8000/action/broadcast","name":"Broadcast","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_64cb41ad7b854c429394d91a91762d77","name":"token","value":"{{ _.token }}","description":""},{"id":"pair_865945f9ad864dc68db34fd05e116595","name":"message","value":"Hello the world","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719114145743,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_a8b9c3b2baba4d9ab78f085200f0721b","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117886286,"created":1719114096173,"url":"http://127.0.0.1:8000/action/eject","name":"Eject","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_549f7e602e804864abe531a5eeac8061","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719114145643,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_9d49a01d035a49319701760adf689481","parentId":"fld_095f08fca86943518208f9338746070f","modified":1719117890732,"created":1719114104164,"url":"http://127.0.0.1:8000/action/fork","name":"Fork","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_51c6061e2f104425b9786fd9d0cb0667","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719114145543,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_66a40756890e414ca280b76889b5d3ae","parentId":"fld_89e6c13ff53a4f31a621376076f21daf","modified":1719118158996,"created":1719118054970,"url":"http://127.0.0.1:8000/info/vision","name":"Get Vision","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_db9851618e9b40b292cd530cca88d8a0","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719118054970,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_89e6c13ff53a4f31a621376076f21daf","parentId":"wrk_229471fe0a5645998ad896f3c65d1f47","modified":1719114295638,"created":1719114262735,"name":"Information","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1719112579715,"_type":"request_group"},{"_id":"req_2c6a96d37c874ba99b4e6d080920c087","parentId":"fld_89e6c13ff53a4f31a621376076f21daf","modified":1719117696655,"created":1719114309871,"url":"http://127.0.0.1:8000/info/messages","name":"Get received broadcast","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_d22e2ae2f4ac4dfd84356519c533becf","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719117602204,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_ed5ad20abd664b1da3099312d3c93fdd","parentId":"fld_89e6c13ff53a4f31a621376076f21daf","modified":1719117688841,"created":1719117552096,"url":"http://127.0.0.1:8000/info/inventory","name":"Get Inventory content","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_e4e6ba7b97d040f68f935aab29d21ed2","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719117602154,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_3ac44e55cbfc423e9d017e5d7f27bee6","parentId":"fld_89e6c13ff53a4f31a621376076f21daf","modified":1719117619816,"created":1719114331178,"url":"http://127.0.0.1:8000/info/ejected","name":"I have been ejected ?","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_dcd2449e62664d04bcb309bd04fb1caa","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719117602129,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_23fd5fadf99e4ca19564ebfbae5b52b9","parentId":"fld_89e6c13ff53a4f31a621376076f21daf","modified":1719117657861,"created":1719117574287,"url":"http://127.0.0.1:8000/info/level","name":"Get Level","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_c39047b1c462429cb8b62481837973c6","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719117602116.5,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_5c575052b8084d25ba1cf2db6334a165","parentId":"fld_89e6c13ff53a4f31a621376076f21daf","modified":1719117640346,"created":1719117602104,"url":"http://127.0.0.1:8000/info/teamname","name":"Get TeamName","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_11d40c12f46d4258977f465690ce4fbd","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719117602104,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_1c4f6ee7eb494fa09842c20fe8e164d6","parentId":"fld_89e6c13ff53a4f31a621376076f21daf","modified":1719118151124,"created":1719118094384,"url":"http://127.0.0.1:8000/server/response","name":"Get last server response","description":"","method":"GET","body":{},"preRequestScript":"","parameters":[{"id":"pair_4732669caba54ef3bb012c09b4f79556","name":"token","value":"{{ _.token }}","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/9.2.0"}],"authentication":{},"metaSortKey":-1719117602004,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_a0abb9fb96509d81dda0162af287b31e3f1df040","parentId":"wrk_229471fe0a5645998ad896f3c65d1f47","modified":1719118464766,"created":1719112573389,"name":"Base Environment","data":{"token":"ab2e339d-f3eb-4ac1-b849-59039a415aef"},"dataPropertyOrder":{"&":["token"]},"color":null,"isPrivate":false,"metaSortKey":1719112573389,"_type":"environment"},{"_id":"jar_a0abb9fb96509d81dda0162af287b31e3f1df040","parentId":"wrk_229471fe0a5645998ad896f3c65d1f47","modified":1719112573389,"created":1719112573389,"name":"Default Jar","cookies":[],"_type":"cookie_jar"}]} \ No newline at end of file diff --git a/bonus/api-ai/README.md b/bonus/api-ai/README.md index 87d520fb..b9a110bf 100644 --- a/bonus/api-ai/README.md +++ b/bonus/api-ai/README.md @@ -69,6 +69,10 @@ To use the Zappy API, follow these steps: 2. Use the interactive API documentation to explore the available endpoints and send requests to the Zappy API. +You can also use the API endpoints directly via Insomnia, Postman, or any other API client. + +*You will find an export of these endpoints (for Insomnia) : [here](./API_Endpoints(Insomnia).json)* + ## API Endpoints The Zappy API provides the following endpoints: From 215a3562bec5ec758a36e8dde062f8e847abe19b Mon Sep 17 00:00:00 2001 From: Marius-P1 Date: Sun, 23 Jun 2024 11:53:46 +0200 Subject: [PATCH 10/10] fix: Remove typo in get_messages function --- bonus/api-ai/src/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bonus/api-ai/src/main.py b/bonus/api-ai/src/main.py index 8d121e6d..e23f1727 100644 --- a/bonus/api-ai/src/main.py +++ b/bonus/api-ai/src/main.py @@ -293,7 +293,7 @@ def get_inventory(token: str): @app.get("/info/level") def get_level(token: str): for ai, aiToken in aiList: - if str(aiTokeservern) == token: + if str(aiToken) == token: return {"level": ai.getLevel()} raise HTTPException(status_code=404, detail="AI not found") @@ -307,7 +307,7 @@ def get_teamname(token: str): @app.get("/info/messages") -def get_messages(tokeservern: str): +def get_messages(token: str): for ai, aiToken in aiList: if str(aiToken) == token: return {"messages": ai.getMessagesReceived()}