diff --git a/cmd/simu/main.go b/cmd/simu/main.go index f7d520dfa8148306eaee88f31c1b470d6c80ee3b..c29a1f66f5a6b3a3bf7c3dee0a49389e68be6697 100644 --- a/cmd/simu/main.go +++ b/cmd/simu/main.go @@ -6,7 +6,7 @@ import ( ) func main() { - s := simulation.NewSimulation(40, -1, 600*time.Second) + s := simulation.NewSimulation(1, -1, 600*time.Second) //go simulation.StartAPI(s) s.Run() } diff --git a/internal/algorithms/astar.go b/internal/algorithms/astar.go index 41e1f306dfbd9b83241179d0869468cbcbf978e3..f19a1eeda0e0c734f8f49afcfe22e0b6819ba3ce 100644 --- a/internal/algorithms/astar.go +++ b/internal/algorithms/astar.go @@ -2,19 +2,21 @@ package algorithms import ( "container/heap" + "fmt" ) /* * Utilisation de l'algorithme A* pour les déplacements * //TODO: Peut-être gérer un passage par référence et non par copie - * //TODO: Prise en compte des dimensions des agents + * */ type Node struct { - row, col, cost, heuristic int + row, col, cost, heuristic, width, height, orientation int } -func NewNode(row, col, cost, heuristic int) *Node { - return &Node{row, col, cost, heuristic} +func NewNode(row, col, cost, heuristic, width, height int) *Node { + //fmt.Println() + return &Node{row, col, cost, heuristic, width , height, 0} } func (nd *Node) Row() int { @@ -107,18 +109,27 @@ func FindPath(matrix [20][20]string, start, end Node, forbidenCell Node) []Node return nil // Aucun chemin trouvé } -func getNeighbors(matrix [20][20]string, current, end Node, forbidenCell Node) []*Node { +func getNeighbors(matrix [20][20]string, current, end Node, forbiddenCell Node) []*Node { + fmt.Println("okkk") neighbors := make([]*Node, 0) - // Déplacements possibles : haut, bas, gauche, droite + // Possible moves: up, down, left, right, rotate (clockwise) possibleMoves := [][]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}} for _, move := range possibleMoves { newRow, newCol := current.row+move[0], current.col+move[1] - // Vérifier si la nouvelle position est valide - if (forbidenCell.row != newRow || forbidenCell.col != newCol) && newRow >= 0 && newRow < len(matrix) && newCol >= 0 && newCol < len(matrix[0]) && (matrix[newRow][newCol] != "Q" && matrix[newRow][newCol] != "X") { - neighbors = append(neighbors, &Node{newRow, newCol, current.cost + 1, heuristic(newRow, newCol, end)}) + // Check if the new position is valid, considering agent dimensions and rotation + if isValidMove(matrix, current, forbiddenCell, newRow, newCol) { + neighbors = append(neighbors, &Node{ + row: newRow, + col: newCol, + cost: current.cost + 1, + heuristic: heuristic(newRow, newCol, end), + width: current.width, + height: current.height, + orientation: current.orientation, + }) } } @@ -136,3 +147,61 @@ func abs(x int) int { } return x } + +func isValidMove(matrix [20][20]string, current Node, forbiddenCell Node, newRow, newCol int) bool { + // Check if the new position is within the bounds of the matrix + if newRow < 0 || newRow >= len(matrix) || newCol < 0 || newCol >= len(matrix[0]) { + return false + } + + // Check if the new position overlaps with forbidden cells or obstacles + if forbiddenCell.row == newRow && forbiddenCell.col == newCol { + return false + } + + // Check if the agent fits in the new position, considering its dimensions and rotation + for i := 0; i < current.height; i++ { + for j := 0; j < current.width; j++ { + // Calculate the rotated coordinates based on the agent's orientation + rotatedI, rotatedJ := rotateCoordinates(i, j, current.orientation) + + // Calculate the absolute coordinates in the matrix + absRow, absCol := newRow+rotatedI, newCol+rotatedJ + + // Check if the absolute coordinates are within the bounds of the matrix + if absRow < 0 || absRow >= len(matrix) || absCol < 0 || absCol >= len(matrix[0]) { + return false + } + + // Check if the absolute coordinates overlap with forbidden cells or obstacles + if forbiddenCell.row == absRow && forbiddenCell.col == absCol { + return false + } + + // Check if the absolute coordinates overlap with obstacles in the matrix + if matrix[absRow][absCol] == "Q" || matrix[absRow][absCol] == "X" { + return false + } + } + } + + return true +} + +func rotateCoordinates(i, j, orientation int) (rotatedI, rotatedJ int) { + // Rotate the coordinates based on the agent's orientation + // You need to implement the logic for rotation based on your specific rules + // This is a simple example that assumes the agent can rotate in all directions + switch orientation { + case 0: // No rotation + rotatedI, rotatedJ = i, j + case 1: // 90-degree rotation + rotatedI, rotatedJ = j, -i + case 2: // 180-degree rotation + rotatedI, rotatedJ = -i, -j + case 3: // 270-degree rotation + rotatedI, rotatedJ = -j, i + } + + return rotatedI, rotatedJ +} diff --git a/internal/simulation/agent.go b/internal/simulation/agent.go index c0c4040ba10bb3cb073f4b350cb26b48a350ac74..4a08dd08f66998ac2ea367147ea63a5adca312f4 100644 --- a/internal/simulation/agent.go +++ b/internal/simulation/agent.go @@ -1,9 +1,19 @@ package simulation +/* + * Classe et méthodes principales de la structure Agent + * à faire : + * //TODO: gérer les orientations + * // TODO: Gérer les moments où les agents font du quasi-sur place car ils ne peuvent plus bouger + * // TODO: Il arrive encore que certains agents soient bloqués, mais c'est quand il n'y a aucun mouvement possible. + * // Il faudrait faire en sorte que les agents bougent et laisse passer les autres + * + */ + import ( //"fmt" - //"fmt" + "fmt" "log" "math/rand" alg "metrosim/internal/algorithms" @@ -23,20 +33,21 @@ type Coord [2]int type AgentID string type Agent struct { - id AgentID - vitesse time.Duration - force int - politesse bool - coordHautOccupation Coord - coordBasOccupation Coord - departure Coord - destination Coord - behavior Behavior - env *Environment - syncChan chan int - decision int - isOn string // Contenu de la case sur laquelle il se trouve - stuck bool + id AgentID + vitesse time.Duration + force int + politesse bool + position Coord // Coordonnées de référence, width et height on compte width et height à partir de cette position + departure Coord + destination Coord + behavior Behavior + env *Environment + syncChan chan int + decision int + isOn map[Coord]string // Contenu de la case sur laquelle il se trouve + stuck bool + width int + height int } type Behavior interface { @@ -45,9 +56,10 @@ type Behavior interface { Act(*Agent) } - -func NewAgent(id string, env *Environment, syncChan chan int, vitesse time.Duration, force int, politesse bool, UpCoord Coord, DownCoord Coord, behavior Behavior, departure, destination Coord) *Agent { - return &Agent{AgentID(id), vitesse, force, politesse, UpCoord, DownCoord, departure, destination, behavior, env, syncChan, Noop, env.station[UpCoord[0]][UpCoord[1]], false} +func NewAgent(id string, env *Environment, syncChan chan int, vitesse time.Duration, force int, politesse bool, behavior Behavior, departure, destination Coord, width, height int) *Agent { + isOn := make(map[Coord]string) + saveCells(&env.station, isOn, departure, width, height) + return &Agent{AgentID(id), vitesse, force, politesse, departure, departure, destination, behavior, env, syncChan, Noop, isOn, false, width, height} } func (ag *Agent) ID() AgentID { @@ -75,66 +87,151 @@ func (ag *Agent) Act(env *Environment) { } } -func IsMovementSafe(path []alg.Node, env *Environment) bool { +func IsMovementSafe(path []alg.Node, ag *Agent, env *Environment) bool { // Détermine si le movement est faisable - return len(path) > 0 && (env.station[path[0].Row()][path[0].Col()] == "B" || env.station[path[0].Row()][path[0].Col()] == "_") + if len(path) <= 0 { + return false + } + startX, startY := ag.position[1], ag.position[0] + width, height := ag.width, ag.height + + // on simule son nouvel emplacement + // et regarde s'il chevauche un autre agent + for i := path[0].Row(); i < path[0].Row()+ag.height; i++ { + for j := path[0].Col(); j < path[0].Col()+ag.width; j++ { + + if !(j >= startX && j < startX+width && i >= startY && i < startY+height) && (env.station[i][j] != "B" && env.station[i][j] != "_") { + // Si on est sur une case non atteignable, en dehors de la zone qu'occupe l'agent avant déplacement, on est bloqué + return false + } + } + } + return true } -func IsAgentBlocking(path []alg.Node, env *Environment) bool { +func IsAgentBlocking(path []alg.Node, ag *Agent, env *Environment) bool { // Détermine si un agent se trouve sur la case à visiter - return len(path) > 0 && env.station[path[0].Row()][path[0].Col()] == "A" + // Coordonnée de départ et dimensions du rectangle + if len(path) <= 0 { + return false + } + startX, startY := ag.position[1], ag.position[0] + width, height := ag.width, ag.height + + // on simule son nouvel emplacement + // et regarde s'il chevauche un autre agent + for i := path[0].Row(); i < path[0].Row()+ag.height; i++ { + for j := path[0].Col(); j < path[0].Col()+ag.width; j++ { + + if !(j >= startX && j < startX+width && i >= startY && i < startY+height) && env.station[i][j] == "A" { + // Si on est sur un agent, en dehors de la zone qu'occupe l'agent avant déplacement, on est bloqué + return true + } + } + } + + return false } func (ag *Agent) isStuck() bool { // Perception des éléments autour de l'agent pour déterminer si bloqué - s := 0 // nombre de cases indisponibles autour de l'agent - for i := 0; i < 3; i++ { - for j := 0; j < 3; j++ { - ord := (ag.coordBasOccupation[0] - 1) + i - abs := (ag.coordBasOccupation[1] - 1) + j - - if ord != ag.coordBasOccupation[0] && abs != ag.coordBasOccupation[1] { - if ord < 0 || abs < 0 || ord > 19 || abs > 19 { - s++ - } else if ag.env.station[ord][abs] == "X" || ag.env.station[ord][abs] == "Q" || ag.env.station[ord][abs] == "A" { - s++ - } + not_acc := 0 // nombre de cases indisponibles autour de l'agent + + // Coordonnée de départ et dimensions du rectangle + startX, startY := ag.position[1], ag.position[0] + width, height := ag.width, ag.height + + // Largeur et hauteur du rectangle étendu + extendedWidth := width + 2 // +2 pour les cases à gauche et à droite du rectangle + extendedHeight := height + 2 // +2 pour les cases au-dessus et en dessous du rectangle + + count := 0 + // Parcourir les cases autour du rectangle + for i := startX - 1; i < startX+extendedWidth-1; i++ { + for j := startY - 1; j < startY+extendedHeight-1; j++ { + // Éviter les cases à l'intérieur du rectangle + if i >= startX && i < startX+width && j >= startY && j < startY+height { + + continue + } else { + count++ + } + // Case inaccessible + if ag.env.station[j][i] == "X" || ag.env.station[j][i] == "Q" || ag.env.station[j][i] == "A" { + not_acc++ + } + // fmt.Printf("Border (%d, %d) = %s \n", j, i,ag.env.station[j][i]) } } // Si aucune case disponible autour de lui, il est bloqué - return s == 8 + return not_acc == count } func (ag *Agent) MoveAgent() { - // TODO: Gérer les moments où les agents font du quasi-sur place car il ne peuvent plus bouger - // TODO: Parfois, certains agents ne bougent plus, - // TODO: Il arrive encore que certains agents soient bloqués, mais c'est quand il n'y a aucun mouvement possible. - // Il faudrait faire en sorte que les agents bougent et laisse passer les autres // ============ Initialisation des noeuds de départ ====================== - start := *alg.NewNode(ag.coordBasOccupation[0], ag.coordBasOccupation[1], 0, 0) - end := *alg.NewNode(ag.destination[0], ag.destination[1], 0, 0) - + start := *alg.NewNode(ag.position[0], ag.position[1], 0, 0, ag.width, ag.height) + end := *alg.NewNode(ag.destination[0], ag.destination[1], 0, 0, ag.width, ag.height) // ================== Tentative de calcul du chemin ======================= - path := alg.FindPath(ag.env.station, start, end, *alg.NewNode(-1,-1,0,0)) - + path := alg.FindPath(ag.env.station, start, end, *alg.NewNode(-1, -1, 0, 0, 0, 0)) // ================== Etude de faisabilité ======================= - if IsAgentBlocking(path, ag.env) { + fmt.Println(path) + if IsAgentBlocking(path, ag, ag.env) { // Si un agent bloque notre déplacement, on attend un temps aléatoire, et reconstruit un chemin en évitant la position time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) path = alg.FindPath(ag.env.station, start, end, path[0]) time.Sleep(time.Second) } - - if IsMovementSafe(path, ag.env) { - ag.env.station[ag.coordBasOccupation[0]][ag.coordBasOccupation[1]] = ag.isOn - ag.isOn = ag.env.station[path[0].Row()][path[0].Col()] - ag.coordBasOccupation[0] = path[0].Row() - ag.coordBasOccupation[1] = path[0].Col() - ag.env.station[ag.coordBasOccupation[0]][ag.coordBasOccupation[1]] = "A" + if IsMovementSafe(path, ag, ag.env) { + removeAgent(&ag.env.station, ag) + //ag.env.station[ag.coordBasOccupation[0]][ag.coordBasOccupation[1]] = ag.isOn + ag.position[0] = path[0].Row() + ag.position[1] = path[0].Col() + saveCells(&ag.env.station, ag.isOn, ag.position, ag.width, ag.height) + //ag.env.station[ag.coordBasOccupation[0]][ag.coordBasOccupation[1]] = "A" + writeAgent(&ag.env.station, ag) // ============ Prise en compte de la vitesse de déplacement ====================== - time.Sleep(ag.vitesse) + time.Sleep(ag.vitesse * time.Millisecond) + //fmt.Println(path[0]) + //fmt.Println(ag.position) + } + +} + +func removeAgent(matrix *[20][20]string, agt *Agent) { + // Supprime l'agent de la matrice + for i := agt.position[0]; i < agt.position[0]+agt.height; i++ { + for j := agt.position[1]; j < agt.position[1]+agt.width; j++ { + matrix[i][j] = agt.isOn[Coord{i, j}] + removeCoord(Coord{i, j}, agt.isOn) + } + } +} + +func writeAgent(matrix *[20][20]string, agt *Agent) { + // Ecris un agent dans la matrice + for i := agt.position[0]; i < agt.position[0]+agt.height; i++ { + for j := agt.position[1]; j < agt.position[1]+agt.width; j++ { + matrix[i][j] = "A" + } } +} +func saveCells(matrix *[20][20]string, savedCells map[Coord]string, ref Coord, width, height int) { + // Enregistrement des valeurs des cellules de la matrice + for i := ref[0]; i < ref[0]+height; i++ { + for j := ref[1]; j < ref[1]+width; j++ { + savedCells[Coord{i, j}] = matrix[i][j] + } + } +} + +func removeCoord(to_remove Coord, mapping map[Coord]string) { + // Suppression d'une clé dans une map + for coord, _ := range mapping { + if coord[0] == to_remove[0] && coord[1] == to_remove[1] { + delete(mapping, coord) + } + } } diff --git a/internal/simulation/simu.go b/internal/simulation/simu.go index cfd487963669f6501d884d20380cd73b067cb27e..e67858293bb2290a61d5cf04d848f174b233ac42 100644 --- a/internal/simulation/simu.go +++ b/internal/simulation/simu.go @@ -97,7 +97,8 @@ func NewSimulation(agentCount int, maxStep int, maxDuration time.Duration) (simu syncChan := make(chan int) //ag := NewAgent(id, &simu.env, syncChan, time.Duration(time.Second), 0, true, Coord{0, 8 + i%2}, Coord{0, 8 + i%2}, &UsagerLambda{}, Coord{0, 8 + i%2}, Coord{12 - 4*(i%2), 18 - 15*(i%2)}) - ag := NewAgent(id, &simu.env, syncChan, 1, 0, true, Coord{4, 10}, Coord{4, 10}, &UsagerLambda{}, Coord{4, 10}, Coord{0, 0}) + //ag := NewAgent(id, &simu.env, syncChan, 1000, 0, true, &UsagerLambda{}, Coord{5, 8}, Coord{7, 15}, 2, 1) + ag := NewAgent(id, &simu.env, syncChan, 1000, 0, true, &UsagerLambda{}, Coord{5, 8}, Coord{0, 0}, 2, 1) // ajout de l'agent à la simulation simu.agents = append(simu.agents, *ag) diff --git a/internal/simulation/usagerLambda.go b/internal/simulation/usagerLambda.go index 1f977f18c14bf8f2708fc52e447086145261f472..843be3742b32ab72bbfaef92a5696af2d4cb08ec 100644 --- a/internal/simulation/usagerLambda.go +++ b/internal/simulation/usagerLambda.go @@ -1,6 +1,6 @@ package simulation -import( +import ( "math/rand" "time" ) @@ -11,6 +11,7 @@ func (ul *UsagerLambda) Percept(ag *Agent) { ag.stuck = ag.isStuck() if ag.stuck { return + } } @@ -31,4 +32,4 @@ func (ul *UsagerLambda) Act(ag *Agent) { time.Sleep(time.Duration(n) * time.Second) } -} \ No newline at end of file +}