From 1bd165caf326435272cbf196bc88befc1d4cc1a6 Mon Sep 17 00:00:00 2001 From: StutenEXE <alexandre.bidaux78@gmail.com> Date: Mon, 30 Dec 2024 12:13:50 +0100 Subject: [PATCH 1/7] Started hornets --- backend/server/websocketserver.go | 10 +- backend/simulation/agent/agent.go | 145 +++++++++++++++--- backend/simulation/agent/beeAgent.go | 116 +++----------- backend/simulation/agent/exagent.go | 8 +- backend/simulation/agent/hornetAgent.go | 122 +++++++++++++-- .../agent/vision/equilateralTriangleVision.go | 46 ++++++ .../agent/vision/explorerBeeVision.go | 38 +---- .../simulation/agent/vision/hornetVision.go | 13 ++ backend/simulation/environment/environment.go | 17 +- backend/simulation/environment/position.go | 48 +++--- backend/simulation/simulation.go | 25 ++- backend/utils/configreader.go | 8 + 12 files changed, 393 insertions(+), 203 deletions(-) create mode 100644 backend/simulation/agent/vision/equilateralTriangleVision.go create mode 100644 backend/simulation/agent/vision/hornetVision.go diff --git a/backend/server/websocketserver.go b/backend/server/websocketserver.go index f828a65..7ba89b1 100644 --- a/backend/server/websocketserver.go +++ b/backend/server/websocketserver.go @@ -26,8 +26,9 @@ func (server *WebSocketServer) newSimulation(conn *websocket.Conn) { server.simulation.Stop() } nAgts := utils.GetNumberBees() - nObjs := utils.GetNumberBees() - server.simulation = simulation.NewSimulation(nAgts, nObjs, conn) + nObjs := utils.GetNumberFlowers() + nHorn := utils.GetNumberHornets() + server.simulation = simulation.NewSimulation(nAgts, nObjs, nHorn, conn) } func (server *WebSocketServer) launchSimulation( @@ -35,8 +36,9 @@ func (server *WebSocketServer) launchSimulation( ) { if server.simulation == nil { nAgts := utils.GetNumberBees() - nObjs := utils.GetNumberBees() - server.simulation = simulation.NewSimulation(nAgts, nObjs, conn) + nObjs := utils.GetNumberFlowers() + nHorn := utils.GetNumberHornets() + server.simulation = simulation.NewSimulation(nAgts, nObjs, nHorn, conn) } if server.simulation.IsRunning() { diff --git a/backend/simulation/agent/agent.go b/backend/simulation/agent/agent.go index 339ae4e..0d864ac 100644 --- a/backend/simulation/agent/agent.go +++ b/backend/simulation/agent/agent.go @@ -8,6 +8,23 @@ import ( envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" ) +type objectiveType string + +const ( + None objectiveType = "none" + Position objectiveType = "position" + Flower objectiveType = "flower" + Hive objectiveType = "hive" + Bee objectiveType = "bee" +) + +// BeeAgent structure to be marshalled in json +type objective struct { + TargetedElem interface{} `json:"targetedElem"` + Position *envpkg.Position `json:"position"` + Type objectiveType `json:"type"` +} + // Abstract class used with a template pattern // - iagt: An interface representing the agent's actions // - id: An identifier for the agent @@ -23,6 +40,8 @@ type Agent struct { syncChan chan bool speed int lastPos *envpkg.Position + alive bool + objective objective } // Agent is launched as a microservice @@ -31,8 +50,8 @@ func (agt *Agent) Start() { go func() { for { run := <-agt.syncChan - if !run { - agt.syncChan <- run + if !run || !agt.alive { + agt.syncChan <- !run || !agt.alive break } agt.iagt.Percept() @@ -67,51 +86,68 @@ func (agt Agent) Orientation() envpkg.Orientation { return agt.orientation } +// Additionally we check the agent is on the position func (agt *Agent) goNorth() bool { - success := agt.pos.GoNorth(agt.env.GetMap()) + success := agt.pos.GoNorth(agt.env.GetMap(), agt) agt.orientation = envpkg.North + if agt.env.GetAt(agt.pos.X, agt.pos.Y) == nil { + agt.env.GetMap()[agt.pos.X][agt.pos.Y] = agt + } return success } +func (agt *Agent) forceAgentInPlace(env *envpkg.Environment) { + if env.GetAt(agt.pos.X, agt.pos.Y) == nil { + env.GetMap()[agt.pos.X][agt.pos.Y] = agt + } +} + func (agt *Agent) goSouth() bool { - success := agt.pos.GoSouth(agt.env.GetMap()) + success := agt.pos.GoSouth(agt.env.GetMap(), agt) agt.orientation = envpkg.South + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goEast() bool { - success := agt.pos.GoEast(agt.env.GetMap()) + success := agt.pos.GoEast(agt.env.GetMap(), agt) agt.orientation = envpkg.East + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goWest() bool { - success := agt.pos.GoWest(agt.env.GetMap()) + success := agt.pos.GoWest(agt.env.GetMap(), agt) agt.orientation = envpkg.West + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goNorthEast() bool { - success := agt.pos.GoNorthEast(agt.env.GetMap()) + success := agt.pos.GoNorthEast(agt.env.GetMap(), agt) agt.orientation = envpkg.NorthEast + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goSouthEast() bool { - success := agt.pos.GoSouthEast(agt.env.GetMap()) + success := agt.pos.GoSouthEast(agt.env.GetMap(), agt) agt.orientation = envpkg.SouthEast + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goNorthWest() bool { - success := agt.pos.GoNorthWest(agt.env.GetMap()) + success := agt.pos.GoNorthWest(agt.env.GetMap(), agt) agt.orientation = envpkg.NorthWest + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goSouthWest() bool { - success := agt.pos.GoSouthWest(agt.env.GetMap()) + success := agt.pos.GoSouthWest(agt.env.GetMap(), agt) agt.orientation = envpkg.SouthWest + agt.forceAgentInPlace(agt.env) return success } @@ -122,7 +158,7 @@ func (agt *Agent) gotoNextStepTowards(pos *envpkg.Position) { if len(chain) > 0 && chain[0].Equal(agt.pos) { chain = chain[1:] } - fmt.Printf("\n[%s] Going to [%d %d] : [%d %d] -> ", agt.id, pos.X, pos.Y, agt.pos.X, agt.pos.Y) + //fmt.Printf("\n[%s] Going to [%d %d] : [%d %d] -> ", agt.id, pos.X, pos.Y, agt.pos.X, agt.pos.Y) for i := 0; i < agt.speed && i < len(chain); i++ { pos := chain[i] if agt.pos.X < pos.X { @@ -151,6 +187,72 @@ func (agt *Agent) gotoNextStepTowards(pos *envpkg.Position) { } } +func (agt *Agent) wander() { + if agt.pos == nil { + return + } + var closestBorder *envpkg.Position = nil + minBorderDistance := agt.pos.DistanceFrom(&envpkg.Position{X: 0, Y: 0}) + isCloseToBorder := false + // If we are close to the border, we go in the opposite direction + // We put -1 in the list to test the flat border cases + for _, x := range []int{-1, 0, agt.env.GetMapDimension() - 1} { + for _, y := range []int{-1, 0, agt.env.GetMapDimension() - 1} { + isCorner := true + if x == -1 && y == -1 { + continue + } + // Flat north or south border + if x == -1 { + x = agt.pos.X + isCorner = false + } + if y == -1 { + // Flat west or east border + y = agt.pos.Y + isCorner = false + } + // Corner case + distance := agt.pos.DistanceFrom(&envpkg.Position{X: x, Y: y}) + isCloseToBorder = distance < float64(agt.speed) + // We allow some leeway to border cases to avoid getting stuck + if (isCloseToBorder && distance < minBorderDistance) || (isCloseToBorder && isCorner && distance <= minBorderDistance) { + closestBorder = &envpkg.Position{X: x, Y: y} + minBorderDistance = distance + } + } + } + if closestBorder != nil { + // If we are too close to the border, we go to the opposite side + keepAwayFromBorderPos := agt.pos.Copy() + if closestBorder.X == 0 { + keepAwayFromBorderPos.GoEast(nil, nil) + } else if closestBorder.X == agt.env.GetMapDimension()-1 { + keepAwayFromBorderPos.GoWest(nil, nil) + } + if closestBorder.Y == 0 { + keepAwayFromBorderPos.GoSouth(nil, nil) + } else if closestBorder.Y == agt.env.GetMapDimension()-1 { + keepAwayFromBorderPos.GoNorth(nil, nil) + } + agt.objective.Position = keepAwayFromBorderPos.Copy() + agt.objective.Type = Position + fmt.Printf("[%s] Too close to border (%d %d), going to (%d %d)\n", agt.id, closestBorder.X, closestBorder.Y, agt.objective.Position.X, agt.objective.Position.Y) + } else { + // While we don't have an objective, we wander + for agt.objective.Type == None { + newObjective := agt.getNextWanderingPosition() + elemAtObjective := agt.env.GetAt(newObjective.X, newObjective.Y) + if elemAtObjective != nil { + continue + } + fmt.Printf("[%s] Wandering towards %v\n", agt.id, *newObjective) + agt.objective.Type = Position + agt.objective.Position = newObjective.Copy() + } + } +} + func (agt *Agent) getNextWanderingPosition() *envpkg.Position { surroundings := agt.pos.GetNeighbours(agt.speed) // We remove the positions that are occupied @@ -225,21 +327,21 @@ func (agt *Agent) getNextWanderingPosition() *envpkg.Position { for i := 0; i < agt.speed; i++ { switch nextWanderingOrientation { case envpkg.North: - newObjective.GoNorth(nil) + newObjective.GoNorth(nil, nil) case envpkg.South: - newObjective.GoSouth(nil) + newObjective.GoSouth(nil, nil) case envpkg.East: - newObjective.GoEast(nil) + newObjective.GoEast(nil, nil) case envpkg.West: - newObjective.GoWest(nil) + newObjective.GoWest(nil, nil) case envpkg.NorthEast: - newObjective.GoNorthEast(nil) + newObjective.GoNorthEast(nil, nil) case envpkg.NorthWest: - newObjective.GoNorthWest(nil) + newObjective.GoNorthWest(nil, nil) case envpkg.SouthEast: - newObjective.GoSouthEast(nil) + newObjective.GoSouthEast(nil, nil) case envpkg.SouthWest: - newObjective.GoSouthWest(nil) + newObjective.GoSouthWest(nil, nil) } } // Find the closest available position in surroundings @@ -259,3 +361,8 @@ func (agt *Agent) getNextWanderingPosition() *envpkg.Position { return newObjective } + +func (agt *Agent) die() { + agt.alive = false + agt.pos = nil +} diff --git a/backend/simulation/agent/beeAgent.go b/backend/simulation/agent/beeAgent.go index bb0207d..06ac55a 100644 --- a/backend/simulation/agent/beeAgent.go +++ b/backend/simulation/agent/beeAgent.go @@ -30,28 +30,11 @@ type BeeAgent struct { job job seenElems []*vision.SeenElem availablePositions []envpkg.Position - objective objective -} - -type objectiveType string - -const ( - None objectiveType = "none" - Position objectiveType = "position" - Flower objectiveType = "flower" - Hive objectiveType = "hive" -) - -// BeeAgent structure to be marshalled in json -type objective struct { - TargetedElem envpkg.IObject - Position *envpkg.Position `json:"position"` - Type objectiveType `json:"type"` } type BeeAgentJson struct { ID string `json:"id"` - Position envpkg.Position `json:"position"` + Position *envpkg.Position `json:"position"` Orientation envpkg.Orientation `json:"orientation"` SeenElems []*vision.SeenElem `json:"seenElems"` MaxNectar int `json:"maxNectar"` @@ -69,16 +52,17 @@ func NewBeeAgent(id string, env *envpkg.Environment, syncChan chan bool, speed i syncChan: syncChan, visionFunc: nil, speed: speed, + alive: true, + objective: objective{Position: nil, Type: None}, } beeAgent.hive = hive beeAgent.birthDate = dob beeAgent.maxNectar = maxnectar beeAgent.job = job beeAgent.nectar = 0 - beeAgent.objective = objective{Position: nil, Type: None} beeAgent.availablePositions = []envpkg.Position{} beeAgent.seenElems = []*vision.SeenElem{} - beeAgent.pos = hive.Position().Copy() + //beeAgent.pos = hive.Position().Copy() return beeAgent } @@ -103,11 +87,13 @@ func (agt *BeeAgent) Act() { } } +func (agt *BeeAgent) Kill() { + agt.env.RemoveAgent(agt) + agt.die() +} + func (agt *BeeAgent) hasFlowerObjective() bool { - if agt.objective.Type == Flower { - return true - } - return false + return agt.objective.Type == Flower } func (agt *BeeAgent) foragerPerception() { @@ -165,66 +151,7 @@ func (agt *BeeAgent) foragerDeliberation() { } // If has no objective, wander if agt.objective.Type == None { - var closestBorder *envpkg.Position = nil - minBorderDistance := agt.pos.DistanceFrom(&envpkg.Position{X: 0, Y: 0}) - isCloseToBorder := false - // If we are close to the border, we go in the opposite direction - // We put -1 in the list to test the flat border cases - for _, x := range []int{-1, 0, agt.env.GetMapDimension() - 1} { - for _, y := range []int{-1, 0, agt.env.GetMapDimension() - 1} { - isCorner := true - if x == -1 && y == -1 { - continue - } - // Flat north or south border - if x == -1 { - x = agt.pos.X - isCorner = false - } - if y == -1 { - // Flat west or east border - y = agt.pos.Y - isCorner = false - } - // Corner case - distance := agt.pos.DistanceFrom(&envpkg.Position{X: x, Y: y}) - isCloseToBorder = distance < float64(agt.speed) - // We allow some leeway to border cases to avoid getting stuck - if (isCloseToBorder && distance < minBorderDistance) || (isCloseToBorder && isCorner && distance <= minBorderDistance) { - closestBorder = &envpkg.Position{X: x, Y: y} - minBorderDistance = distance - } - } - } - if closestBorder != nil { - // If we are too close to the border, we go to the opposite side - keepAwayFromBorderPos := agt.pos.Copy() - if closestBorder.X == 0 { - keepAwayFromBorderPos.GoEast(nil) - } else if closestBorder.X == agt.env.GetMapDimension()-1 { - keepAwayFromBorderPos.GoWest(nil) - } - if closestBorder.Y == 0 { - keepAwayFromBorderPos.GoSouth(nil) - } else if closestBorder.Y == agt.env.GetMapDimension()-1 { - keepAwayFromBorderPos.GoNorth(nil) - } - agt.objective.Position = keepAwayFromBorderPos.Copy() - agt.objective.Type = Position - fmt.Printf("[%s] Too close to border (%d %d), going to (%d %d)\n", agt.id, closestBorder.X, closestBorder.Y, agt.objective.Position.X, agt.objective.Position.Y) - } else { - // While we don't have an objective, we wander - for agt.objective.Type == None { - newObjective := agt.getNextWanderingPosition() - elemAtObjective := agt.env.GetAt(newObjective.X, newObjective.Y) - if elemAtObjective != nil { - continue - } - fmt.Printf("[%s] Wandering towards %v\n", agt.id, *newObjective) - agt.objective.Type = Position - agt.objective.Position = newObjective.Copy() - } - } + agt.wander() } } @@ -237,6 +164,9 @@ func (agt *BeeAgent) foragerAction() { agt.objective.Type = None } else { agt.gotoNextStepTowards(objf.Position.Copy()) + if agt.pos.Equal(objf.Position) { + agt.objective.Type = None + } } case Flower: if flower, ok := objf.TargetedElem.(*obj.Flower); ok { @@ -244,7 +174,7 @@ func (agt *BeeAgent) foragerAction() { agt.nectar += flower.RetreiveNectar(agt.maxNectar - agt.nectar) agt.objective.Type = None } else { - fmt.Printf("[%s] Going to flower %v\n", agt.id, objf.TargetedElem.ID()) + fmt.Printf("[%s] Going to flower %v\n", agt.id, objf.TargetedElem.(envpkg.IObject).ID()) agt.gotoNextStepTowards(objf.Position.Copy()) } } @@ -256,7 +186,7 @@ func (agt *BeeAgent) foragerAction() { agt.objective.Type = None } } else { - fmt.Printf("[%s] Going to hive %v\n", agt.id, objf.TargetedElem.ID()) + fmt.Printf("[%s] Going to hive %v\n", agt.id, objf.TargetedElem.(envpkg.IObject).ID()) agt.gotoNextStepTowards(objf.Position.Copy()) } } @@ -275,25 +205,21 @@ func (agt *BeeAgent) workerAction() { agt.pos = agt.hive.Position().Copy() if xFactor == 0 { - agt.pos.GoWest(agt.env.GetMap()) - agt.orientation = envpkg.West + agt.goWest() } else { - agt.pos.GoEast(agt.env.GetMap()) - agt.orientation = envpkg.East + agt.goEast() } if yFactor == 0 { - agt.pos.GoNorth(agt.env.GetMap()) - agt.orientation = envpkg.North + agt.goNorth() } else { - agt.pos.GoSouth(agt.env.GetMap()) - agt.orientation = envpkg.South + agt.goSouth() } } } func (agt *BeeAgent) ToJsonObj() interface{} { return BeeAgentJson{ID: string(agt.id), - Position: *agt.pos, + Position: agt.pos, Orientation: agt.orientation, SeenElems: agt.seenElems, MaxNectar: agt.maxNectar, diff --git a/backend/simulation/agent/exagent.go b/backend/simulation/agent/exagent.go index 83f955b..b7d04bd 100644 --- a/backend/simulation/agent/exagent.go +++ b/backend/simulation/agent/exagent.go @@ -96,13 +96,13 @@ func (agt *ExAgent) Act() { agt.value += agt.toAdd switch agt.movement { case envpkg.North: - agt.pos.GoNorth(agt.env.GetMap()) + agt.goNorth() case envpkg.East: - agt.pos.GoEast(agt.env.GetMap()) + agt.goEast() case envpkg.South: - agt.pos.GoSouth(agt.env.GetMap()) + agt.goSouth() case envpkg.West: - agt.pos.GoWest(agt.env.GetMap()) + agt.goWest() } agt.orientation = agt.movement } diff --git a/backend/simulation/agent/hornetAgent.go b/backend/simulation/agent/hornetAgent.go index 73f6c27..a51dd6e 100644 --- a/backend/simulation/agent/hornetAgent.go +++ b/backend/simulation/agent/hornetAgent.go @@ -1,8 +1,11 @@ package agent import ( + "fmt" + "math/rand" "time" + "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/agent/vision" envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" ) @@ -11,43 +14,128 @@ import ( type HornetAgent struct { Agent birthDate time.Time - maxNectar int - job int + seenElems []*vision.SeenElem } type HornetAgentJson struct { - ID string `json:"id"` + ID string `json:"id"` + Position *envpkg.Position `json:"position"` + Orientation envpkg.Orientation `json:"orientation"` + Objective objective `json:"objective"` + SeenElems []*vision.SeenElem `json:"seenElems"` } func NewHornetAgent(id string, env *envpkg.Environment, syncChan chan bool, s int) *HornetAgent { hAgent := &HornetAgent{} hAgent.Agent = Agent{ - iagt: hAgent, - id: envpkg.AgentID(id), - env: env, - syncChan: syncChan, - speed: s, + iagt: hAgent, + id: envpkg.AgentID(id), + env: env, + syncChan: syncChan, + speed: s, + visionFunc: vision.HornetVision, + alive: true, + objective: objective{Position: nil, Type: None}, } - + hAgent.birthDate = time.Now() return hAgent } -func (agt *HornetAgent) Start() { -} - -func (agt *HornetAgent) Stop() { - -} - func (agt *HornetAgent) Percept() { + if agt.pos == nil { + chance := rand.Intn(2) + if chance == 0 { + // either spawns at the top or the bottom or the left or the right + chance = rand.Intn(4) + x := 0 + y := 0 + switch chance { + case 0: + x = 0 + y = rand.Intn(agt.env.GetMapDimension()) + agt.orientation = envpkg.East + case 1: + x = agt.env.GetMapDimension() - 1 + y = rand.Intn(agt.env.GetMapDimension()) + agt.orientation = envpkg.West + case 2: + x = rand.Intn(agt.env.GetMapDimension()) + y = 0 + agt.orientation = envpkg.South + case 3: + x = rand.Intn(agt.env.GetMapDimension()) + y = agt.env.GetMapDimension() - 1 + agt.orientation = envpkg.North + } + agt.pos = envpkg.NewPosition(x, y, agt.env.GetMapDimension(), agt.env.GetMapDimension()) + agt.env.AddAgent(agt) + fmt.Printf("[%s] Hornet spawned at %d %d\n", agt.id, x, y) + agt.objective.Type = None + } + } + if agt.pos != nil { + agt.seenElems = agt.see() + } } +// The hornet agent always targets the closest bee func (agt *HornetAgent) Deliberate() { + distanceToTarget := float64(agt.env.GetMapDimension()) + if agt.objective.Type == Bee { + if bee, ok := agt.objective.TargetedElem.(BeeAgent); ok { + distanceToTarget = bee.Position().DistanceFrom(agt.Position()) + } + } + for _, seen := range agt.seenElems { + if seen.Elem != nil { + switch seen.Elem.(type) { + case *BeeAgent: + bee := seen.Elem.(*BeeAgent) + distance := bee.Position().DistanceFrom(agt.Position()) + if distance < distanceToTarget { + agt.objective.Type = Bee + agt.objective.TargetedElem = bee + agt.objective.Position = bee.Position().Copy() + distanceToTarget = distance + } + case BeeAgent: + fmt.Println("=================================Hornet sees a bee wtf") + } + } + } + if agt.objective.Type == None { + agt.wander() + } } func (agt *HornetAgent) Act() { + if agt.objective.Type == None { + return + } + switch agt.objective.Type { + case Position: + agt.gotoNextStepTowards(agt.objective.Position.Copy()) + if agt.Position().Equal(agt.objective.Position) { + agt.objective.Type = None + } + case Bee: + fmt.Println("=====================Hornet attacking bee !") + bee := agt.objective.TargetedElem.(*BeeAgent) + agt.gotoNextStepTowards(bee.Position().Copy()) + if agt.Position().Near(bee.pos.Copy(), 1) { + bee.Kill() + agt.objective.Type = None + fmt.Println("=====================Hornet killed bee !!!") + } + } } func (agt *HornetAgent) ToJsonObj() interface{} { - return BeeAgentJson{ID: string(agt.id)} + return HornetAgentJson{ + ID: string(agt.id), + Position: agt.pos, + Orientation: agt.orientation, + Objective: agt.objective, + SeenElems: agt.seenElems, + } } diff --git a/backend/simulation/agent/vision/equilateralTriangleVision.go b/backend/simulation/agent/vision/equilateralTriangleVision.go new file mode 100644 index 0000000..f24f61e --- /dev/null +++ b/backend/simulation/agent/vision/equilateralTriangleVision.go @@ -0,0 +1,46 @@ +package vision + +import ( + "math" + "sort" + + envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" + "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" +) + +// We decide that the returned list is sorted by proximity to the agent +func EquilateralTriangleVision(agt envpkg.IAgent, env *envpkg.Environment, distance float64) []*SeenElem { + // Side of the triangle - formula for an equilateral triangle + sideSize := (2*(distance))/math.Sqrt(3) + 1 + // Vision triangle coordinates + topCornerX, topCornerY, leftCornerX, leftCornerY, rightCornerX, rightCornerY := getTriangleCoordinates(*agt.Position(), distance, sideSize, agt.Orientation()) + // Getting the bounding box of the triangle + minX := utils.Min(topCornerX, utils.Min(leftCornerX, rightCornerX)) + maxX := utils.Max(topCornerX, utils.Max(leftCornerX, rightCornerX)) + minY := utils.Min(topCornerY, utils.Min(leftCornerY, rightCornerY)) + maxY := utils.Max(topCornerY, utils.Max(leftCornerY, rightCornerY)) + + // Contains all the elements seen by the agent + seenElems := make([]*SeenElem, 0) + + addElemToList := func(x, y int) { + if env.IsValidPosition(x, y) { + if pointIsInTriangle(float64(x), float64(y), topCornerX, topCornerY, leftCornerX, leftCornerY, rightCornerX, rightCornerY) { + seenElems = append(seenElems, NewSeenElem(&envpkg.Position{X: x, Y: y}, env.GetAt(x, y))) + } + } + } + + for x := utils.Round(minX) + 1; x <= utils.Round(maxX)+1; x++ { + for y := utils.Round(minY); y <= utils.Round(maxY)+1; y++ { + addElemToList(x, y) + } + } + + // Sorting the list by proximity to the agent + sort.Slice(seenElems, func(i, j int) bool { + return agt.Position().DistanceFrom(seenElems[i].Pos) < agt.Position().DistanceFrom(seenElems[j].Pos) + }) + + return seenElems +} diff --git a/backend/simulation/agent/vision/explorerBeeVision.go b/backend/simulation/agent/vision/explorerBeeVision.go index d9e381a..27b91a4 100644 --- a/backend/simulation/agent/vision/explorerBeeVision.go +++ b/backend/simulation/agent/vision/explorerBeeVision.go @@ -1,9 +1,6 @@ package vision import ( - "math" - "sort" - envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" ) @@ -11,39 +8,6 @@ import ( // For now we decide that the vision of a bee is an equilateral triangle // We decide that the returned list is sorted by proximity to the agent func ExplorerBeeVision(agt envpkg.IAgent, env *envpkg.Environment) []*SeenElem { - // Height of the triangle distance := utils.GetBeeAgentVisionRange() - // Side of the triangle - formula for an equilateral triangle - sideSize := (2*(distance))/math.Sqrt(3) + 1 - // Vision triangle coordinates - topCornerX, topCornerY, leftCornerX, leftCornerY, rightCornerX, rightCornerY := getTriangleCoordinates(*agt.Position(), distance, sideSize, agt.Orientation()) - // Getting the bounding box of the triangle - minX := utils.Min(topCornerX, utils.Min(leftCornerX, rightCornerX)) - maxX := utils.Max(topCornerX, utils.Max(leftCornerX, rightCornerX)) - minY := utils.Min(topCornerY, utils.Min(leftCornerY, rightCornerY)) - maxY := utils.Max(topCornerY, utils.Max(leftCornerY, rightCornerY)) - - // Contains all the elements seen by the agent - seenElems := make([]*SeenElem, 0) - - addElemToList := func(x, y int) { - if env.IsValidPosition(x, y) { - if pointIsInTriangle(float64(x), float64(y), topCornerX, topCornerY, leftCornerX, leftCornerY, rightCornerX, rightCornerY) { - seenElems = append(seenElems, NewSeenElem(&envpkg.Position{X: x, Y: y}, env.GetAt(x, y))) - } - } - } - - for x := utils.Round(minX) + 1; x <= utils.Round(maxX)+1; x++ { - for y := utils.Round(minY); y <= utils.Round(maxY)+1; y++ { - addElemToList(x, y) - } - } - - // Sorting the list by proximity to the agent - sort.Slice(seenElems, func(i, j int) bool { - return agt.Position().DistanceFrom(seenElems[i].Pos) < agt.Position().DistanceFrom(seenElems[j].Pos) - }) - - return seenElems + return EquilateralTriangleVision(agt, env, distance) } diff --git a/backend/simulation/agent/vision/hornetVision.go b/backend/simulation/agent/vision/hornetVision.go new file mode 100644 index 0000000..865dc62 --- /dev/null +++ b/backend/simulation/agent/vision/hornetVision.go @@ -0,0 +1,13 @@ +package vision + +import ( + envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" + "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" +) + +// For now we decide that the vision of a hornet is an equilateral triangle +// We decide that the returned list is sorted by proximity to the agent +func HornetVision(agt envpkg.IAgent, env *envpkg.Environment) []*SeenElem { + distance := utils.GetHornetAgentVisionRange() + return EquilateralTriangleVision(agt, env, distance) +} diff --git a/backend/simulation/environment/environment.go b/backend/simulation/environment/environment.go index 0dbe031..6830ee8 100644 --- a/backend/simulation/environment/environment.go +++ b/backend/simulation/environment/environment.go @@ -51,7 +51,7 @@ func (env *Environment) IsValidPosition(x int, y int) bool { func (env *Environment) AddAgent(agt IAgent) bool { pos := agt.Position() - if env.GetAt(pos.X, pos.Y) == nil { + if pos != nil && env.GetAt(pos.X, pos.Y) == nil { //return false env.grid[pos.X][pos.Y] = agt } @@ -59,6 +59,16 @@ func (env *Environment) AddAgent(agt IAgent) bool { return true } +func (env *Environment) RemoveAgent(agt IAgent) { + for i, a := range env.agts { + if a.ID() == agt.ID() { + env.agts = append(env.agts[:i], env.agts[i+1:]...) + env.grid[a.Position().X][a.Position().Y] = nil + break + } + } +} + func (env *Environment) AddObject(obj IObject) bool { pos := obj.Position() if env.GetAt(pos.X, pos.Y) != nil { @@ -139,7 +149,10 @@ func (env *Environment) PathFinding(start *Position, end *Position, numberMoves } path := []*Position{} - currentNode := openList[len(openList)-1] + var currentNode *node = nil + if len(openList) > 0 { + currentNode = openList[len(openList)-1] + } for currentNode != nil { path = append([]*Position{currentNode.position.Copy()}, path...) currentNode = currentNode.parent diff --git a/backend/simulation/environment/position.go b/backend/simulation/environment/position.go index 7fdd438..983bc6e 100644 --- a/backend/simulation/environment/position.go +++ b/backend/simulation/environment/position.go @@ -1,6 +1,7 @@ package environment import ( + "fmt" "math" "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" @@ -19,7 +20,7 @@ func NewPosition(x int, y int, maxX int, maxY int) *Position { } // Movement can be simulated with by passing a nil grid -func (p *Position) move(grid [][]interface{}, newX int, newY int) bool { +func (p *Position) move(grid [][]interface{}, newX int, newY int, elem interface{}) bool { // Out of bounds if newX < 0 || newY < 0 || newX >= p.maxX || newY >= p.maxY { return false @@ -30,7 +31,12 @@ func (p *Position) move(grid [][]interface{}, newX int, newY int) bool { } // Not a simulation if grid != nil { - grid[p.X][p.Y], grid[newX][newY] = nil, grid[p.X][p.Y] + if grid[p.X][p.Y] == elem { + fmt.Println("Grid is not nil : ", grid[p.X][p.Y]) + grid[p.X][p.Y], grid[newX][newY] = nil, grid[p.X][p.Y] + } else { + grid[newX][newY] = elem + } } p.X = newX p.Y = newY @@ -38,43 +44,43 @@ func (p *Position) move(grid [][]interface{}, newX int, newY int) bool { } // Movement can be simulated with by passing a nil grid -func (p *Position) GoNorth(grid [][]interface{}) bool { - return p.move(grid, p.X, p.Y-1) +func (p *Position) GoNorth(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X, p.Y-1, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoSouth(grid [][]interface{}) bool { - return p.move(grid, p.X, p.Y+1) +func (p *Position) GoSouth(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X, p.Y+1, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoWest(grid [][]interface{}) bool { - return p.move(grid, p.X-1, p.Y) +func (p *Position) GoWest(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X-1, p.Y, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoEast(grid [][]interface{}) bool { - return p.move(grid, p.X+1, p.Y) +func (p *Position) GoEast(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X+1, p.Y, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoNorthEast(grid [][]interface{}) bool { - return p.move(grid, p.X+1, p.Y-1) +func (p *Position) GoNorthEast(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X+1, p.Y-1, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoNorthWest(grid [][]interface{}) bool { - return p.move(grid, p.X-1, p.Y-1) +func (p *Position) GoNorthWest(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X-1, p.Y-1, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoSouthEast(grid [][]interface{}) bool { - return p.move(grid, p.X+1, p.Y+1) +func (p *Position) GoSouthEast(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X+1, p.Y+1, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoSouthWest(grid [][]interface{}) bool { - return p.move(grid, p.X-1, p.Y+1) +func (p *Position) GoSouthWest(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X-1, p.Y+1, elem) } func (p Position) DistanceFrom(p2 *Position) float64 { @@ -86,9 +92,9 @@ func (p Position) Near(p2 *Position, distance int) bool { } // Returns the mirrored position of the point according to this point -func (p Position) GetSymmetricOfPoint(origin Position) *Position { - symX := 2*origin.X - p.X - symY := 2*origin.Y - p.Y +func (p Position) GetSymmetricOfPoint(p2 Position) *Position { + symX := 2*p.X - p2.X + symY := 2*p.Y - p2.Y return &Position{X: symX, Y: symY, maxX: p.maxX, maxY: p.maxY} } diff --git a/backend/simulation/simulation.go b/backend/simulation/simulation.go index 5e9af8f..64fe28e 100644 --- a/backend/simulation/simulation.go +++ b/backend/simulation/simulation.go @@ -30,7 +30,7 @@ type SimulationJson struct { Environment interface{} `json:"environment"` } -func NewSimulation(nbees int, nflowers int, maWs *websocket.Conn) *Simulation { +func NewSimulation(nbees int, nflowers int, nhornets int, maWs *websocket.Conn) *Simulation { simu := &Simulation{} simu.ws = maWs env := envpkg.NewEnvironment([]envpkg.IAgent{}, []envpkg.IObject{}) @@ -116,6 +116,17 @@ func NewSimulation(nbees int, nflowers int, maWs *websocket.Conn) *Simulation { simu.env.AddAgent(agt) } + for i := 0; i < nhornets; i++ { + // Creating a hornet + id := fmt.Sprintf("Hornet #%d", i) + syncChan := make(chan bool) + agt := agt.NewHornetAgent(id, simu.env, syncChan, rand.Intn(2)+1) + // ajout de l'agent à la simulation + simu.agts = append(simu.agts, agt) + // ajout de l'agent à l'environnement + //simu.env.AddAgent(agt) // Pas encore car pas spawn + } + simu.sendState() return simu } @@ -147,11 +158,14 @@ func (simu *Simulation) Run(maWs *websocket.Conn) { // Boucle de simulation for simu.IsRunning() { - for _, agt := range simu.agts { + for i, agt := range simu.agts { c := agt.GetSyncChan() simu.env.Lock() c <- true - <-c + // If dead + if !<-c { + simu.agts = append(simu.agts[:i], simu.agts[i+1:]...) + } simu.env.Unlock() time.Sleep(time.Second / 100) // 100 Tour / Sec } @@ -196,7 +210,7 @@ func (simu *Simulation) print() { startTime := time.Now() for simu.IsRunning() { fmt.Printf("\rRunning simulation for %vms... - ", time.Since(startTime).Milliseconds()) - time.Sleep(time.Second / 60) // 60 fps + time.Sleep(time.Second / 30) // 60 fps } } @@ -212,5 +226,8 @@ func (simu *Simulation) ToJsonObj() SimulationJson { objects = append(objects, obj.ToJsonObj()) } + simu.env.Lock() + defer simu.env.Unlock() + return SimulationJson{Agents: agents, Objects: objects, Environment: simu.env.ToJsonObj()} } diff --git a/backend/utils/configreader.go b/backend/utils/configreader.go index a93beab..239dba5 100644 --- a/backend/utils/configreader.go +++ b/backend/utils/configreader.go @@ -57,6 +57,10 @@ func GetNumberBees() int { return getIntAttributeFromConfigFile("NumberBees") } +func GetNumberHornets() int { + return getIntAttributeFromConfigFile("NumberHornets") +} + func GetMaxNectar() int { return getIntAttributeFromConfigFile("MaxNectar") } @@ -81,6 +85,10 @@ func GetBeeAgentVisionRange() float64 { return getFloat64AttributeFromConfigFile("BeeAgentVisionRange") } +func GetHornetAgentVisionRange() float64 { + return getFloat64AttributeFromConfigFile("HornetAgentVisionRange") +} + func GetExName() string { return getStringAttributeFromConfigFile("ExName") } -- GitLab From a07cf9d7685363007995a42d406032c233def220 Mon Sep 17 00:00:00 2001 From: StutenEXE <alexandre.bidaux78@gmail.com> Date: Mon, 30 Dec 2024 12:13:50 +0100 Subject: [PATCH 2/7] Started hornets --- backend/server/websocketserver.go | 10 +- backend/simulation/agent/agent.go | 145 +++++++++++++++--- backend/simulation/agent/beeAgent.go | 116 +++----------- backend/simulation/agent/exagent.go | 8 +- backend/simulation/agent/hornetAgent.go | 122 +++++++++++++-- .../agent/vision/equilateralTriangleVision.go | 46 ++++++ .../agent/vision/explorerBeeVision.go | 38 +---- .../simulation/agent/vision/hornetVision.go | 13 ++ backend/simulation/environment/environment.go | 17 +- backend/simulation/environment/position.go | 48 +++--- backend/simulation/simulation.go | 25 ++- backend/utils/configreader.go | 8 + config.yaml | 6 +- frontend-ws-test/index.html | 25 ++- 14 files changed, 420 insertions(+), 207 deletions(-) create mode 100644 backend/simulation/agent/vision/equilateralTriangleVision.go create mode 100644 backend/simulation/agent/vision/hornetVision.go diff --git a/backend/server/websocketserver.go b/backend/server/websocketserver.go index f828a65..7ba89b1 100644 --- a/backend/server/websocketserver.go +++ b/backend/server/websocketserver.go @@ -26,8 +26,9 @@ func (server *WebSocketServer) newSimulation(conn *websocket.Conn) { server.simulation.Stop() } nAgts := utils.GetNumberBees() - nObjs := utils.GetNumberBees() - server.simulation = simulation.NewSimulation(nAgts, nObjs, conn) + nObjs := utils.GetNumberFlowers() + nHorn := utils.GetNumberHornets() + server.simulation = simulation.NewSimulation(nAgts, nObjs, nHorn, conn) } func (server *WebSocketServer) launchSimulation( @@ -35,8 +36,9 @@ func (server *WebSocketServer) launchSimulation( ) { if server.simulation == nil { nAgts := utils.GetNumberBees() - nObjs := utils.GetNumberBees() - server.simulation = simulation.NewSimulation(nAgts, nObjs, conn) + nObjs := utils.GetNumberFlowers() + nHorn := utils.GetNumberHornets() + server.simulation = simulation.NewSimulation(nAgts, nObjs, nHorn, conn) } if server.simulation.IsRunning() { diff --git a/backend/simulation/agent/agent.go b/backend/simulation/agent/agent.go index 339ae4e..0d864ac 100644 --- a/backend/simulation/agent/agent.go +++ b/backend/simulation/agent/agent.go @@ -8,6 +8,23 @@ import ( envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" ) +type objectiveType string + +const ( + None objectiveType = "none" + Position objectiveType = "position" + Flower objectiveType = "flower" + Hive objectiveType = "hive" + Bee objectiveType = "bee" +) + +// BeeAgent structure to be marshalled in json +type objective struct { + TargetedElem interface{} `json:"targetedElem"` + Position *envpkg.Position `json:"position"` + Type objectiveType `json:"type"` +} + // Abstract class used with a template pattern // - iagt: An interface representing the agent's actions // - id: An identifier for the agent @@ -23,6 +40,8 @@ type Agent struct { syncChan chan bool speed int lastPos *envpkg.Position + alive bool + objective objective } // Agent is launched as a microservice @@ -31,8 +50,8 @@ func (agt *Agent) Start() { go func() { for { run := <-agt.syncChan - if !run { - agt.syncChan <- run + if !run || !agt.alive { + agt.syncChan <- !run || !agt.alive break } agt.iagt.Percept() @@ -67,51 +86,68 @@ func (agt Agent) Orientation() envpkg.Orientation { return agt.orientation } +// Additionally we check the agent is on the position func (agt *Agent) goNorth() bool { - success := agt.pos.GoNorth(agt.env.GetMap()) + success := agt.pos.GoNorth(agt.env.GetMap(), agt) agt.orientation = envpkg.North + if agt.env.GetAt(agt.pos.X, agt.pos.Y) == nil { + agt.env.GetMap()[agt.pos.X][agt.pos.Y] = agt + } return success } +func (agt *Agent) forceAgentInPlace(env *envpkg.Environment) { + if env.GetAt(agt.pos.X, agt.pos.Y) == nil { + env.GetMap()[agt.pos.X][agt.pos.Y] = agt + } +} + func (agt *Agent) goSouth() bool { - success := agt.pos.GoSouth(agt.env.GetMap()) + success := agt.pos.GoSouth(agt.env.GetMap(), agt) agt.orientation = envpkg.South + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goEast() bool { - success := agt.pos.GoEast(agt.env.GetMap()) + success := agt.pos.GoEast(agt.env.GetMap(), agt) agt.orientation = envpkg.East + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goWest() bool { - success := agt.pos.GoWest(agt.env.GetMap()) + success := agt.pos.GoWest(agt.env.GetMap(), agt) agt.orientation = envpkg.West + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goNorthEast() bool { - success := agt.pos.GoNorthEast(agt.env.GetMap()) + success := agt.pos.GoNorthEast(agt.env.GetMap(), agt) agt.orientation = envpkg.NorthEast + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goSouthEast() bool { - success := agt.pos.GoSouthEast(agt.env.GetMap()) + success := agt.pos.GoSouthEast(agt.env.GetMap(), agt) agt.orientation = envpkg.SouthEast + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goNorthWest() bool { - success := agt.pos.GoNorthWest(agt.env.GetMap()) + success := agt.pos.GoNorthWest(agt.env.GetMap(), agt) agt.orientation = envpkg.NorthWest + agt.forceAgentInPlace(agt.env) return success } func (agt *Agent) goSouthWest() bool { - success := agt.pos.GoSouthWest(agt.env.GetMap()) + success := agt.pos.GoSouthWest(agt.env.GetMap(), agt) agt.orientation = envpkg.SouthWest + agt.forceAgentInPlace(agt.env) return success } @@ -122,7 +158,7 @@ func (agt *Agent) gotoNextStepTowards(pos *envpkg.Position) { if len(chain) > 0 && chain[0].Equal(agt.pos) { chain = chain[1:] } - fmt.Printf("\n[%s] Going to [%d %d] : [%d %d] -> ", agt.id, pos.X, pos.Y, agt.pos.X, agt.pos.Y) + //fmt.Printf("\n[%s] Going to [%d %d] : [%d %d] -> ", agt.id, pos.X, pos.Y, agt.pos.X, agt.pos.Y) for i := 0; i < agt.speed && i < len(chain); i++ { pos := chain[i] if agt.pos.X < pos.X { @@ -151,6 +187,72 @@ func (agt *Agent) gotoNextStepTowards(pos *envpkg.Position) { } } +func (agt *Agent) wander() { + if agt.pos == nil { + return + } + var closestBorder *envpkg.Position = nil + minBorderDistance := agt.pos.DistanceFrom(&envpkg.Position{X: 0, Y: 0}) + isCloseToBorder := false + // If we are close to the border, we go in the opposite direction + // We put -1 in the list to test the flat border cases + for _, x := range []int{-1, 0, agt.env.GetMapDimension() - 1} { + for _, y := range []int{-1, 0, agt.env.GetMapDimension() - 1} { + isCorner := true + if x == -1 && y == -1 { + continue + } + // Flat north or south border + if x == -1 { + x = agt.pos.X + isCorner = false + } + if y == -1 { + // Flat west or east border + y = agt.pos.Y + isCorner = false + } + // Corner case + distance := agt.pos.DistanceFrom(&envpkg.Position{X: x, Y: y}) + isCloseToBorder = distance < float64(agt.speed) + // We allow some leeway to border cases to avoid getting stuck + if (isCloseToBorder && distance < minBorderDistance) || (isCloseToBorder && isCorner && distance <= minBorderDistance) { + closestBorder = &envpkg.Position{X: x, Y: y} + minBorderDistance = distance + } + } + } + if closestBorder != nil { + // If we are too close to the border, we go to the opposite side + keepAwayFromBorderPos := agt.pos.Copy() + if closestBorder.X == 0 { + keepAwayFromBorderPos.GoEast(nil, nil) + } else if closestBorder.X == agt.env.GetMapDimension()-1 { + keepAwayFromBorderPos.GoWest(nil, nil) + } + if closestBorder.Y == 0 { + keepAwayFromBorderPos.GoSouth(nil, nil) + } else if closestBorder.Y == agt.env.GetMapDimension()-1 { + keepAwayFromBorderPos.GoNorth(nil, nil) + } + agt.objective.Position = keepAwayFromBorderPos.Copy() + agt.objective.Type = Position + fmt.Printf("[%s] Too close to border (%d %d), going to (%d %d)\n", agt.id, closestBorder.X, closestBorder.Y, agt.objective.Position.X, agt.objective.Position.Y) + } else { + // While we don't have an objective, we wander + for agt.objective.Type == None { + newObjective := agt.getNextWanderingPosition() + elemAtObjective := agt.env.GetAt(newObjective.X, newObjective.Y) + if elemAtObjective != nil { + continue + } + fmt.Printf("[%s] Wandering towards %v\n", agt.id, *newObjective) + agt.objective.Type = Position + agt.objective.Position = newObjective.Copy() + } + } +} + func (agt *Agent) getNextWanderingPosition() *envpkg.Position { surroundings := agt.pos.GetNeighbours(agt.speed) // We remove the positions that are occupied @@ -225,21 +327,21 @@ func (agt *Agent) getNextWanderingPosition() *envpkg.Position { for i := 0; i < agt.speed; i++ { switch nextWanderingOrientation { case envpkg.North: - newObjective.GoNorth(nil) + newObjective.GoNorth(nil, nil) case envpkg.South: - newObjective.GoSouth(nil) + newObjective.GoSouth(nil, nil) case envpkg.East: - newObjective.GoEast(nil) + newObjective.GoEast(nil, nil) case envpkg.West: - newObjective.GoWest(nil) + newObjective.GoWest(nil, nil) case envpkg.NorthEast: - newObjective.GoNorthEast(nil) + newObjective.GoNorthEast(nil, nil) case envpkg.NorthWest: - newObjective.GoNorthWest(nil) + newObjective.GoNorthWest(nil, nil) case envpkg.SouthEast: - newObjective.GoSouthEast(nil) + newObjective.GoSouthEast(nil, nil) case envpkg.SouthWest: - newObjective.GoSouthWest(nil) + newObjective.GoSouthWest(nil, nil) } } // Find the closest available position in surroundings @@ -259,3 +361,8 @@ func (agt *Agent) getNextWanderingPosition() *envpkg.Position { return newObjective } + +func (agt *Agent) die() { + agt.alive = false + agt.pos = nil +} diff --git a/backend/simulation/agent/beeAgent.go b/backend/simulation/agent/beeAgent.go index bb0207d..06ac55a 100644 --- a/backend/simulation/agent/beeAgent.go +++ b/backend/simulation/agent/beeAgent.go @@ -30,28 +30,11 @@ type BeeAgent struct { job job seenElems []*vision.SeenElem availablePositions []envpkg.Position - objective objective -} - -type objectiveType string - -const ( - None objectiveType = "none" - Position objectiveType = "position" - Flower objectiveType = "flower" - Hive objectiveType = "hive" -) - -// BeeAgent structure to be marshalled in json -type objective struct { - TargetedElem envpkg.IObject - Position *envpkg.Position `json:"position"` - Type objectiveType `json:"type"` } type BeeAgentJson struct { ID string `json:"id"` - Position envpkg.Position `json:"position"` + Position *envpkg.Position `json:"position"` Orientation envpkg.Orientation `json:"orientation"` SeenElems []*vision.SeenElem `json:"seenElems"` MaxNectar int `json:"maxNectar"` @@ -69,16 +52,17 @@ func NewBeeAgent(id string, env *envpkg.Environment, syncChan chan bool, speed i syncChan: syncChan, visionFunc: nil, speed: speed, + alive: true, + objective: objective{Position: nil, Type: None}, } beeAgent.hive = hive beeAgent.birthDate = dob beeAgent.maxNectar = maxnectar beeAgent.job = job beeAgent.nectar = 0 - beeAgent.objective = objective{Position: nil, Type: None} beeAgent.availablePositions = []envpkg.Position{} beeAgent.seenElems = []*vision.SeenElem{} - beeAgent.pos = hive.Position().Copy() + //beeAgent.pos = hive.Position().Copy() return beeAgent } @@ -103,11 +87,13 @@ func (agt *BeeAgent) Act() { } } +func (agt *BeeAgent) Kill() { + agt.env.RemoveAgent(agt) + agt.die() +} + func (agt *BeeAgent) hasFlowerObjective() bool { - if agt.objective.Type == Flower { - return true - } - return false + return agt.objective.Type == Flower } func (agt *BeeAgent) foragerPerception() { @@ -165,66 +151,7 @@ func (agt *BeeAgent) foragerDeliberation() { } // If has no objective, wander if agt.objective.Type == None { - var closestBorder *envpkg.Position = nil - minBorderDistance := agt.pos.DistanceFrom(&envpkg.Position{X: 0, Y: 0}) - isCloseToBorder := false - // If we are close to the border, we go in the opposite direction - // We put -1 in the list to test the flat border cases - for _, x := range []int{-1, 0, agt.env.GetMapDimension() - 1} { - for _, y := range []int{-1, 0, agt.env.GetMapDimension() - 1} { - isCorner := true - if x == -1 && y == -1 { - continue - } - // Flat north or south border - if x == -1 { - x = agt.pos.X - isCorner = false - } - if y == -1 { - // Flat west or east border - y = agt.pos.Y - isCorner = false - } - // Corner case - distance := agt.pos.DistanceFrom(&envpkg.Position{X: x, Y: y}) - isCloseToBorder = distance < float64(agt.speed) - // We allow some leeway to border cases to avoid getting stuck - if (isCloseToBorder && distance < minBorderDistance) || (isCloseToBorder && isCorner && distance <= minBorderDistance) { - closestBorder = &envpkg.Position{X: x, Y: y} - minBorderDistance = distance - } - } - } - if closestBorder != nil { - // If we are too close to the border, we go to the opposite side - keepAwayFromBorderPos := agt.pos.Copy() - if closestBorder.X == 0 { - keepAwayFromBorderPos.GoEast(nil) - } else if closestBorder.X == agt.env.GetMapDimension()-1 { - keepAwayFromBorderPos.GoWest(nil) - } - if closestBorder.Y == 0 { - keepAwayFromBorderPos.GoSouth(nil) - } else if closestBorder.Y == agt.env.GetMapDimension()-1 { - keepAwayFromBorderPos.GoNorth(nil) - } - agt.objective.Position = keepAwayFromBorderPos.Copy() - agt.objective.Type = Position - fmt.Printf("[%s] Too close to border (%d %d), going to (%d %d)\n", agt.id, closestBorder.X, closestBorder.Y, agt.objective.Position.X, agt.objective.Position.Y) - } else { - // While we don't have an objective, we wander - for agt.objective.Type == None { - newObjective := agt.getNextWanderingPosition() - elemAtObjective := agt.env.GetAt(newObjective.X, newObjective.Y) - if elemAtObjective != nil { - continue - } - fmt.Printf("[%s] Wandering towards %v\n", agt.id, *newObjective) - agt.objective.Type = Position - agt.objective.Position = newObjective.Copy() - } - } + agt.wander() } } @@ -237,6 +164,9 @@ func (agt *BeeAgent) foragerAction() { agt.objective.Type = None } else { agt.gotoNextStepTowards(objf.Position.Copy()) + if agt.pos.Equal(objf.Position) { + agt.objective.Type = None + } } case Flower: if flower, ok := objf.TargetedElem.(*obj.Flower); ok { @@ -244,7 +174,7 @@ func (agt *BeeAgent) foragerAction() { agt.nectar += flower.RetreiveNectar(agt.maxNectar - agt.nectar) agt.objective.Type = None } else { - fmt.Printf("[%s] Going to flower %v\n", agt.id, objf.TargetedElem.ID()) + fmt.Printf("[%s] Going to flower %v\n", agt.id, objf.TargetedElem.(envpkg.IObject).ID()) agt.gotoNextStepTowards(objf.Position.Copy()) } } @@ -256,7 +186,7 @@ func (agt *BeeAgent) foragerAction() { agt.objective.Type = None } } else { - fmt.Printf("[%s] Going to hive %v\n", agt.id, objf.TargetedElem.ID()) + fmt.Printf("[%s] Going to hive %v\n", agt.id, objf.TargetedElem.(envpkg.IObject).ID()) agt.gotoNextStepTowards(objf.Position.Copy()) } } @@ -275,25 +205,21 @@ func (agt *BeeAgent) workerAction() { agt.pos = agt.hive.Position().Copy() if xFactor == 0 { - agt.pos.GoWest(agt.env.GetMap()) - agt.orientation = envpkg.West + agt.goWest() } else { - agt.pos.GoEast(agt.env.GetMap()) - agt.orientation = envpkg.East + agt.goEast() } if yFactor == 0 { - agt.pos.GoNorth(agt.env.GetMap()) - agt.orientation = envpkg.North + agt.goNorth() } else { - agt.pos.GoSouth(agt.env.GetMap()) - agt.orientation = envpkg.South + agt.goSouth() } } } func (agt *BeeAgent) ToJsonObj() interface{} { return BeeAgentJson{ID: string(agt.id), - Position: *agt.pos, + Position: agt.pos, Orientation: agt.orientation, SeenElems: agt.seenElems, MaxNectar: agt.maxNectar, diff --git a/backend/simulation/agent/exagent.go b/backend/simulation/agent/exagent.go index 83f955b..b7d04bd 100644 --- a/backend/simulation/agent/exagent.go +++ b/backend/simulation/agent/exagent.go @@ -96,13 +96,13 @@ func (agt *ExAgent) Act() { agt.value += agt.toAdd switch agt.movement { case envpkg.North: - agt.pos.GoNorth(agt.env.GetMap()) + agt.goNorth() case envpkg.East: - agt.pos.GoEast(agt.env.GetMap()) + agt.goEast() case envpkg.South: - agt.pos.GoSouth(agt.env.GetMap()) + agt.goSouth() case envpkg.West: - agt.pos.GoWest(agt.env.GetMap()) + agt.goWest() } agt.orientation = agt.movement } diff --git a/backend/simulation/agent/hornetAgent.go b/backend/simulation/agent/hornetAgent.go index 73f6c27..a51dd6e 100644 --- a/backend/simulation/agent/hornetAgent.go +++ b/backend/simulation/agent/hornetAgent.go @@ -1,8 +1,11 @@ package agent import ( + "fmt" + "math/rand" "time" + "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/agent/vision" envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" ) @@ -11,43 +14,128 @@ import ( type HornetAgent struct { Agent birthDate time.Time - maxNectar int - job int + seenElems []*vision.SeenElem } type HornetAgentJson struct { - ID string `json:"id"` + ID string `json:"id"` + Position *envpkg.Position `json:"position"` + Orientation envpkg.Orientation `json:"orientation"` + Objective objective `json:"objective"` + SeenElems []*vision.SeenElem `json:"seenElems"` } func NewHornetAgent(id string, env *envpkg.Environment, syncChan chan bool, s int) *HornetAgent { hAgent := &HornetAgent{} hAgent.Agent = Agent{ - iagt: hAgent, - id: envpkg.AgentID(id), - env: env, - syncChan: syncChan, - speed: s, + iagt: hAgent, + id: envpkg.AgentID(id), + env: env, + syncChan: syncChan, + speed: s, + visionFunc: vision.HornetVision, + alive: true, + objective: objective{Position: nil, Type: None}, } - + hAgent.birthDate = time.Now() return hAgent } -func (agt *HornetAgent) Start() { -} - -func (agt *HornetAgent) Stop() { - -} - func (agt *HornetAgent) Percept() { + if agt.pos == nil { + chance := rand.Intn(2) + if chance == 0 { + // either spawns at the top or the bottom or the left or the right + chance = rand.Intn(4) + x := 0 + y := 0 + switch chance { + case 0: + x = 0 + y = rand.Intn(agt.env.GetMapDimension()) + agt.orientation = envpkg.East + case 1: + x = agt.env.GetMapDimension() - 1 + y = rand.Intn(agt.env.GetMapDimension()) + agt.orientation = envpkg.West + case 2: + x = rand.Intn(agt.env.GetMapDimension()) + y = 0 + agt.orientation = envpkg.South + case 3: + x = rand.Intn(agt.env.GetMapDimension()) + y = agt.env.GetMapDimension() - 1 + agt.orientation = envpkg.North + } + agt.pos = envpkg.NewPosition(x, y, agt.env.GetMapDimension(), agt.env.GetMapDimension()) + agt.env.AddAgent(agt) + fmt.Printf("[%s] Hornet spawned at %d %d\n", agt.id, x, y) + agt.objective.Type = None + } + } + if agt.pos != nil { + agt.seenElems = agt.see() + } } +// The hornet agent always targets the closest bee func (agt *HornetAgent) Deliberate() { + distanceToTarget := float64(agt.env.GetMapDimension()) + if agt.objective.Type == Bee { + if bee, ok := agt.objective.TargetedElem.(BeeAgent); ok { + distanceToTarget = bee.Position().DistanceFrom(agt.Position()) + } + } + for _, seen := range agt.seenElems { + if seen.Elem != nil { + switch seen.Elem.(type) { + case *BeeAgent: + bee := seen.Elem.(*BeeAgent) + distance := bee.Position().DistanceFrom(agt.Position()) + if distance < distanceToTarget { + agt.objective.Type = Bee + agt.objective.TargetedElem = bee + agt.objective.Position = bee.Position().Copy() + distanceToTarget = distance + } + case BeeAgent: + fmt.Println("=================================Hornet sees a bee wtf") + } + } + } + if agt.objective.Type == None { + agt.wander() + } } func (agt *HornetAgent) Act() { + if agt.objective.Type == None { + return + } + switch agt.objective.Type { + case Position: + agt.gotoNextStepTowards(agt.objective.Position.Copy()) + if agt.Position().Equal(agt.objective.Position) { + agt.objective.Type = None + } + case Bee: + fmt.Println("=====================Hornet attacking bee !") + bee := agt.objective.TargetedElem.(*BeeAgent) + agt.gotoNextStepTowards(bee.Position().Copy()) + if agt.Position().Near(bee.pos.Copy(), 1) { + bee.Kill() + agt.objective.Type = None + fmt.Println("=====================Hornet killed bee !!!") + } + } } func (agt *HornetAgent) ToJsonObj() interface{} { - return BeeAgentJson{ID: string(agt.id)} + return HornetAgentJson{ + ID: string(agt.id), + Position: agt.pos, + Orientation: agt.orientation, + Objective: agt.objective, + SeenElems: agt.seenElems, + } } diff --git a/backend/simulation/agent/vision/equilateralTriangleVision.go b/backend/simulation/agent/vision/equilateralTriangleVision.go new file mode 100644 index 0000000..f24f61e --- /dev/null +++ b/backend/simulation/agent/vision/equilateralTriangleVision.go @@ -0,0 +1,46 @@ +package vision + +import ( + "math" + "sort" + + envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" + "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" +) + +// We decide that the returned list is sorted by proximity to the agent +func EquilateralTriangleVision(agt envpkg.IAgent, env *envpkg.Environment, distance float64) []*SeenElem { + // Side of the triangle - formula for an equilateral triangle + sideSize := (2*(distance))/math.Sqrt(3) + 1 + // Vision triangle coordinates + topCornerX, topCornerY, leftCornerX, leftCornerY, rightCornerX, rightCornerY := getTriangleCoordinates(*agt.Position(), distance, sideSize, agt.Orientation()) + // Getting the bounding box of the triangle + minX := utils.Min(topCornerX, utils.Min(leftCornerX, rightCornerX)) + maxX := utils.Max(topCornerX, utils.Max(leftCornerX, rightCornerX)) + minY := utils.Min(topCornerY, utils.Min(leftCornerY, rightCornerY)) + maxY := utils.Max(topCornerY, utils.Max(leftCornerY, rightCornerY)) + + // Contains all the elements seen by the agent + seenElems := make([]*SeenElem, 0) + + addElemToList := func(x, y int) { + if env.IsValidPosition(x, y) { + if pointIsInTriangle(float64(x), float64(y), topCornerX, topCornerY, leftCornerX, leftCornerY, rightCornerX, rightCornerY) { + seenElems = append(seenElems, NewSeenElem(&envpkg.Position{X: x, Y: y}, env.GetAt(x, y))) + } + } + } + + for x := utils.Round(minX) + 1; x <= utils.Round(maxX)+1; x++ { + for y := utils.Round(minY); y <= utils.Round(maxY)+1; y++ { + addElemToList(x, y) + } + } + + // Sorting the list by proximity to the agent + sort.Slice(seenElems, func(i, j int) bool { + return agt.Position().DistanceFrom(seenElems[i].Pos) < agt.Position().DistanceFrom(seenElems[j].Pos) + }) + + return seenElems +} diff --git a/backend/simulation/agent/vision/explorerBeeVision.go b/backend/simulation/agent/vision/explorerBeeVision.go index d9e381a..27b91a4 100644 --- a/backend/simulation/agent/vision/explorerBeeVision.go +++ b/backend/simulation/agent/vision/explorerBeeVision.go @@ -1,9 +1,6 @@ package vision import ( - "math" - "sort" - envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" ) @@ -11,39 +8,6 @@ import ( // For now we decide that the vision of a bee is an equilateral triangle // We decide that the returned list is sorted by proximity to the agent func ExplorerBeeVision(agt envpkg.IAgent, env *envpkg.Environment) []*SeenElem { - // Height of the triangle distance := utils.GetBeeAgentVisionRange() - // Side of the triangle - formula for an equilateral triangle - sideSize := (2*(distance))/math.Sqrt(3) + 1 - // Vision triangle coordinates - topCornerX, topCornerY, leftCornerX, leftCornerY, rightCornerX, rightCornerY := getTriangleCoordinates(*agt.Position(), distance, sideSize, agt.Orientation()) - // Getting the bounding box of the triangle - minX := utils.Min(topCornerX, utils.Min(leftCornerX, rightCornerX)) - maxX := utils.Max(topCornerX, utils.Max(leftCornerX, rightCornerX)) - minY := utils.Min(topCornerY, utils.Min(leftCornerY, rightCornerY)) - maxY := utils.Max(topCornerY, utils.Max(leftCornerY, rightCornerY)) - - // Contains all the elements seen by the agent - seenElems := make([]*SeenElem, 0) - - addElemToList := func(x, y int) { - if env.IsValidPosition(x, y) { - if pointIsInTriangle(float64(x), float64(y), topCornerX, topCornerY, leftCornerX, leftCornerY, rightCornerX, rightCornerY) { - seenElems = append(seenElems, NewSeenElem(&envpkg.Position{X: x, Y: y}, env.GetAt(x, y))) - } - } - } - - for x := utils.Round(minX) + 1; x <= utils.Round(maxX)+1; x++ { - for y := utils.Round(minY); y <= utils.Round(maxY)+1; y++ { - addElemToList(x, y) - } - } - - // Sorting the list by proximity to the agent - sort.Slice(seenElems, func(i, j int) bool { - return agt.Position().DistanceFrom(seenElems[i].Pos) < agt.Position().DistanceFrom(seenElems[j].Pos) - }) - - return seenElems + return EquilateralTriangleVision(agt, env, distance) } diff --git a/backend/simulation/agent/vision/hornetVision.go b/backend/simulation/agent/vision/hornetVision.go new file mode 100644 index 0000000..865dc62 --- /dev/null +++ b/backend/simulation/agent/vision/hornetVision.go @@ -0,0 +1,13 @@ +package vision + +import ( + envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" + "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" +) + +// For now we decide that the vision of a hornet is an equilateral triangle +// We decide that the returned list is sorted by proximity to the agent +func HornetVision(agt envpkg.IAgent, env *envpkg.Environment) []*SeenElem { + distance := utils.GetHornetAgentVisionRange() + return EquilateralTriangleVision(agt, env, distance) +} diff --git a/backend/simulation/environment/environment.go b/backend/simulation/environment/environment.go index 0dbe031..6830ee8 100644 --- a/backend/simulation/environment/environment.go +++ b/backend/simulation/environment/environment.go @@ -51,7 +51,7 @@ func (env *Environment) IsValidPosition(x int, y int) bool { func (env *Environment) AddAgent(agt IAgent) bool { pos := agt.Position() - if env.GetAt(pos.X, pos.Y) == nil { + if pos != nil && env.GetAt(pos.X, pos.Y) == nil { //return false env.grid[pos.X][pos.Y] = agt } @@ -59,6 +59,16 @@ func (env *Environment) AddAgent(agt IAgent) bool { return true } +func (env *Environment) RemoveAgent(agt IAgent) { + for i, a := range env.agts { + if a.ID() == agt.ID() { + env.agts = append(env.agts[:i], env.agts[i+1:]...) + env.grid[a.Position().X][a.Position().Y] = nil + break + } + } +} + func (env *Environment) AddObject(obj IObject) bool { pos := obj.Position() if env.GetAt(pos.X, pos.Y) != nil { @@ -139,7 +149,10 @@ func (env *Environment) PathFinding(start *Position, end *Position, numberMoves } path := []*Position{} - currentNode := openList[len(openList)-1] + var currentNode *node = nil + if len(openList) > 0 { + currentNode = openList[len(openList)-1] + } for currentNode != nil { path = append([]*Position{currentNode.position.Copy()}, path...) currentNode = currentNode.parent diff --git a/backend/simulation/environment/position.go b/backend/simulation/environment/position.go index 7fdd438..983bc6e 100644 --- a/backend/simulation/environment/position.go +++ b/backend/simulation/environment/position.go @@ -1,6 +1,7 @@ package environment import ( + "fmt" "math" "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" @@ -19,7 +20,7 @@ func NewPosition(x int, y int, maxX int, maxY int) *Position { } // Movement can be simulated with by passing a nil grid -func (p *Position) move(grid [][]interface{}, newX int, newY int) bool { +func (p *Position) move(grid [][]interface{}, newX int, newY int, elem interface{}) bool { // Out of bounds if newX < 0 || newY < 0 || newX >= p.maxX || newY >= p.maxY { return false @@ -30,7 +31,12 @@ func (p *Position) move(grid [][]interface{}, newX int, newY int) bool { } // Not a simulation if grid != nil { - grid[p.X][p.Y], grid[newX][newY] = nil, grid[p.X][p.Y] + if grid[p.X][p.Y] == elem { + fmt.Println("Grid is not nil : ", grid[p.X][p.Y]) + grid[p.X][p.Y], grid[newX][newY] = nil, grid[p.X][p.Y] + } else { + grid[newX][newY] = elem + } } p.X = newX p.Y = newY @@ -38,43 +44,43 @@ func (p *Position) move(grid [][]interface{}, newX int, newY int) bool { } // Movement can be simulated with by passing a nil grid -func (p *Position) GoNorth(grid [][]interface{}) bool { - return p.move(grid, p.X, p.Y-1) +func (p *Position) GoNorth(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X, p.Y-1, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoSouth(grid [][]interface{}) bool { - return p.move(grid, p.X, p.Y+1) +func (p *Position) GoSouth(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X, p.Y+1, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoWest(grid [][]interface{}) bool { - return p.move(grid, p.X-1, p.Y) +func (p *Position) GoWest(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X-1, p.Y, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoEast(grid [][]interface{}) bool { - return p.move(grid, p.X+1, p.Y) +func (p *Position) GoEast(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X+1, p.Y, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoNorthEast(grid [][]interface{}) bool { - return p.move(grid, p.X+1, p.Y-1) +func (p *Position) GoNorthEast(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X+1, p.Y-1, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoNorthWest(grid [][]interface{}) bool { - return p.move(grid, p.X-1, p.Y-1) +func (p *Position) GoNorthWest(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X-1, p.Y-1, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoSouthEast(grid [][]interface{}) bool { - return p.move(grid, p.X+1, p.Y+1) +func (p *Position) GoSouthEast(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X+1, p.Y+1, elem) } // Movement can be simulated with by passing a nil grid -func (p *Position) GoSouthWest(grid [][]interface{}) bool { - return p.move(grid, p.X-1, p.Y+1) +func (p *Position) GoSouthWest(grid [][]interface{}, elem interface{}) bool { + return p.move(grid, p.X-1, p.Y+1, elem) } func (p Position) DistanceFrom(p2 *Position) float64 { @@ -86,9 +92,9 @@ func (p Position) Near(p2 *Position, distance int) bool { } // Returns the mirrored position of the point according to this point -func (p Position) GetSymmetricOfPoint(origin Position) *Position { - symX := 2*origin.X - p.X - symY := 2*origin.Y - p.Y +func (p Position) GetSymmetricOfPoint(p2 Position) *Position { + symX := 2*p.X - p2.X + symY := 2*p.Y - p2.Y return &Position{X: symX, Y: symY, maxX: p.maxX, maxY: p.maxY} } diff --git a/backend/simulation/simulation.go b/backend/simulation/simulation.go index 5e9af8f..64fe28e 100644 --- a/backend/simulation/simulation.go +++ b/backend/simulation/simulation.go @@ -30,7 +30,7 @@ type SimulationJson struct { Environment interface{} `json:"environment"` } -func NewSimulation(nbees int, nflowers int, maWs *websocket.Conn) *Simulation { +func NewSimulation(nbees int, nflowers int, nhornets int, maWs *websocket.Conn) *Simulation { simu := &Simulation{} simu.ws = maWs env := envpkg.NewEnvironment([]envpkg.IAgent{}, []envpkg.IObject{}) @@ -116,6 +116,17 @@ func NewSimulation(nbees int, nflowers int, maWs *websocket.Conn) *Simulation { simu.env.AddAgent(agt) } + for i := 0; i < nhornets; i++ { + // Creating a hornet + id := fmt.Sprintf("Hornet #%d", i) + syncChan := make(chan bool) + agt := agt.NewHornetAgent(id, simu.env, syncChan, rand.Intn(2)+1) + // ajout de l'agent à la simulation + simu.agts = append(simu.agts, agt) + // ajout de l'agent à l'environnement + //simu.env.AddAgent(agt) // Pas encore car pas spawn + } + simu.sendState() return simu } @@ -147,11 +158,14 @@ func (simu *Simulation) Run(maWs *websocket.Conn) { // Boucle de simulation for simu.IsRunning() { - for _, agt := range simu.agts { + for i, agt := range simu.agts { c := agt.GetSyncChan() simu.env.Lock() c <- true - <-c + // If dead + if !<-c { + simu.agts = append(simu.agts[:i], simu.agts[i+1:]...) + } simu.env.Unlock() time.Sleep(time.Second / 100) // 100 Tour / Sec } @@ -196,7 +210,7 @@ func (simu *Simulation) print() { startTime := time.Now() for simu.IsRunning() { fmt.Printf("\rRunning simulation for %vms... - ", time.Since(startTime).Milliseconds()) - time.Sleep(time.Second / 60) // 60 fps + time.Sleep(time.Second / 30) // 60 fps } } @@ -212,5 +226,8 @@ func (simu *Simulation) ToJsonObj() SimulationJson { objects = append(objects, obj.ToJsonObj()) } + simu.env.Lock() + defer simu.env.Unlock() + return SimulationJson{Agents: agents, Objects: objects, Environment: simu.env.ToJsonObj()} } diff --git a/backend/utils/configreader.go b/backend/utils/configreader.go index a93beab..239dba5 100644 --- a/backend/utils/configreader.go +++ b/backend/utils/configreader.go @@ -57,6 +57,10 @@ func GetNumberBees() int { return getIntAttributeFromConfigFile("NumberBees") } +func GetNumberHornets() int { + return getIntAttributeFromConfigFile("NumberHornets") +} + func GetMaxNectar() int { return getIntAttributeFromConfigFile("MaxNectar") } @@ -81,6 +85,10 @@ func GetBeeAgentVisionRange() float64 { return getFloat64AttributeFromConfigFile("BeeAgentVisionRange") } +func GetHornetAgentVisionRange() float64 { + return getFloat64AttributeFromConfigFile("HornetAgentVisionRange") +} + func GetExName() string { return getStringAttributeFromConfigFile("ExName") } diff --git a/config.yaml b/config.yaml index f1d295f..f6a07c5 100644 --- a/config.yaml +++ b/config.yaml @@ -6,6 +6,10 @@ NumberBees: 20 BeeAgentVisionRange: 4.0 MaxNectar: 70 # in mg -> https://wildflowermeadows.com/2024/01/how-far-do-honeybees-fly/ +# Hornets +NumberHornets: 2 +HornetAgentVisionRange: 4.0 + # Flowers -NumberFlowers: 10 +NumberFlowers: 20 NumberFlowerPatches: 3 diff --git a/frontend-ws-test/index.html b/frontend-ws-test/index.html index cfec1ae..9909fc2 100644 --- a/frontend-ws-test/index.html +++ b/frontend-ws-test/index.html @@ -118,6 +118,9 @@ } function drawAgent(ctx, agent, cellSize) { + if (!agent.position) { + return; + } text = agent.id.replace('ExAgent ', 'ExA'); let agtX = agent.position.x * cellSize; let agtY = agent.position.y * cellSize; @@ -129,9 +132,20 @@ ctx.fillStyle = 'yellow'; } // drawing yellow square + orientationColor = 'red'; + if (agent.id.includes('Bee')) { + ctx.fillStyle = 'yellow'; + text = agent.id.replace('Bee ', 'B'); + orientationColor = 'red'; + } + if (agent.id.includes('Hornet')) { + ctx.fillStyle = 'red'; + text = agent.id.replace('Hornet ', 'Ho'); + orientationColor = 'white'; + } ctx.fillRect(agtX, agtY, cellSize, cellSize); // drawing movement - ctx.strokeStyle = 'red'; + ctx.strokeStyle = orientationColor; if (agent.orientation === "N") { canvas_arrow(ctx, agtX + cellSize / 2, agtY + cellSize, agtX + cellSize / 2, agtY); } else if (agent.orientation === "E") { @@ -158,13 +172,16 @@ function drawObject(ctx, object, cellSize) { let text = object.id; + let textColor = 'black'; if (object.id.includes('Flower')) { - ctx.fillStyle = 'red'; + ctx.fillStyle = 'blue'; + textColor = 'white'; text = object.id.replace('Flower ', 'F'); } else if (object.id.includes('Hive')) { ctx.fillStyle = 'orange'; text = object.id.replace('Hive ', 'H'); + textColor = 'black'; } else { ctx.fillStyle = 'green'; @@ -173,7 +190,7 @@ objY = object.position.y * cellSize; ctx.fillRect(objX, objY, cellSize, cellSize); font = findRightFont(ctx, 10, text, cellSize, cellSize); - ctx.fillStyle = 'black'; + ctx.fillStyle = textColor; ctx.font = font + 'px Arial'; ctx.fillText(text, objX, objY + cellSize / 2); } @@ -188,6 +205,8 @@ } const data = JSON.parse(event.data); + console.log(data.agents); + console.log(data.environment); const table = document.createElement('table'); const headerRow = document.createElement('tr'); -- GitLab From 21deb252a22d2d000ce88595f4c221caf54e0cc4 Mon Sep 17 00:00:00 2001 From: StutenEXE <alexandre.bidaux78@gmail.com> Date: Tue, 31 Dec 2024 11:24:11 +0100 Subject: [PATCH 3/7] Basic Hornet behavior and added function to agent interface --- backend/simulation/agent/agent.go | 33 ++++++++++++++--- backend/simulation/agent/beeAgent.go | 25 +++++++++---- backend/simulation/agent/exagent.go | 4 +++ backend/simulation/agent/hornetAgent.go | 36 ++++++++++--------- .../agent/vision/equilateralTriangleVision.go | 5 ++- backend/simulation/environment/environment.go | 24 ++++++++++--- backend/simulation/environment/iagent.go | 9 +++++ backend/simulation/environment/position.go | 2 -- backend/simulation/simulation.go | 6 ++-- config.yaml | 8 ++--- 10 files changed, 111 insertions(+), 41 deletions(-) diff --git a/backend/simulation/agent/agent.go b/backend/simulation/agent/agent.go index 0d864ac..9feb8e3 100644 --- a/backend/simulation/agent/agent.go +++ b/backend/simulation/agent/agent.go @@ -51,13 +51,16 @@ func (agt *Agent) Start() { for { run := <-agt.syncChan if !run || !agt.alive { - agt.syncChan <- !run || !agt.alive + agt.syncChan <- false break } + fmt.Printf("[%s] === Percieving === [%s]\n", agt.ID(), agt.ID()) agt.iagt.Percept() + fmt.Printf("[%s] === Deliberating === [%s]\n", agt.ID(), agt.ID()) agt.iagt.Deliberate() + fmt.Printf("[%s] === Acting === [%s]\n", agt.ID(), agt.ID()) agt.iagt.Act() - agt.syncChan <- run + agt.syncChan <- agt.alive } fmt.Printf("[%s] Stopping Agent\n", agt.ID()) }() @@ -71,6 +74,18 @@ func (agt Agent) GetSyncChan() chan bool { return agt.syncChan } +func (agt *Agent) Percept() { + agt.iagt.Percept() +} + +func (agt *Agent) Deliberate() { + agt.iagt.Deliberate() +} + +func (agt *Agent) Act() { + agt.iagt.Act() +} + func (agt Agent) Position() *envpkg.Position { if agt.pos == nil { return nil @@ -345,7 +360,7 @@ func (agt *Agent) getNextWanderingPosition() *envpkg.Position { } } // Find the closest available position in surroundings - closestPosition := newObjective.Copy() + closestPosition := agt.pos.Copy() minDistance := agt.pos.DistanceFrom(newObjective) for _, pos := range surroundings { distance := pos.DistanceFrom(newObjective) @@ -362,7 +377,17 @@ func (agt *Agent) getNextWanderingPosition() *envpkg.Position { return newObjective } -func (agt *Agent) die() { +func (agt *Agent) Kill() { + agt.env.RemoveAgent(agt) agt.alive = false agt.pos = nil + +} + +func (agt Agent) Type() envpkg.AgentType { + return agt.iagt.Type() +} + +func (agt *Agent) ToJsonObj() interface{} { + return agt.iagt.ToJsonObj() } diff --git a/backend/simulation/agent/beeAgent.go b/backend/simulation/agent/beeAgent.go index 06ac55a..1154495 100644 --- a/backend/simulation/agent/beeAgent.go +++ b/backend/simulation/agent/beeAgent.go @@ -2,6 +2,7 @@ package agent import ( "fmt" + "reflect" "time" "math/rand" @@ -87,11 +88,6 @@ func (agt *BeeAgent) Act() { } } -func (agt *BeeAgent) Kill() { - agt.env.RemoveAgent(agt) - agt.die() -} - func (agt *BeeAgent) hasFlowerObjective() bool { return agt.objective.Type == Flower } @@ -122,6 +118,7 @@ func (agt *BeeAgent) foragerDeliberation() { if seen.Elem != nil { switch elem := (seen.Elem).(type) { case *HornetAgent: + fmt.Printf("[%s] *****************Close to hornet %v\n", agt.id, reflect.TypeOf(seen.Elem)) closestHornet = elem case *obj.Flower: if !hasAlreadySeenCloserFlower { @@ -135,8 +132,8 @@ func (agt *BeeAgent) foragerDeliberation() { agt.objective.Type = Flower hasAlreadySeenCloserFlower = true } - default: - fmt.Printf("[%s] Unknown element seen : %v\n", agt.id, elem) + //default: + //fmt.Printf("[%s] Unknown element seen : %v\n", agt.id, elem) } } if closestHornet != nil { @@ -148,6 +145,16 @@ func (agt *BeeAgent) foragerDeliberation() { fmt.Printf("[%s] Hornet seen, fleeing in opposite direction\n", agt.id) agt.objective.Position = agt.pos.GetSymmetricOfPoint(*closestHornet.pos.Copy()) agt.objective.Type = Position + if agt.objective.Position.X < 0 { + agt.objective.Position.X = 0 + } else if agt.objective.Position.X >= agt.env.GetMapDimension() { + agt.objective.Position.X = agt.env.GetMapDimension() - 1 + } + if agt.objective.Position.Y < 0 { + agt.objective.Position.Y = 0 + } else if agt.objective.Position.Y >= agt.env.GetMapDimension() { + agt.objective.Position.Y = agt.env.GetMapDimension() - 1 + } } // If has no objective, wander if agt.objective.Type == None { @@ -217,6 +224,10 @@ func (agt *BeeAgent) workerAction() { } } +func (agt BeeAgent) Type() envpkg.AgentType { + return envpkg.Bee +} + func (agt *BeeAgent) ToJsonObj() interface{} { return BeeAgentJson{ID: string(agt.id), Position: agt.pos, diff --git a/backend/simulation/agent/exagent.go b/backend/simulation/agent/exagent.go index b7d04bd..7c896da 100644 --- a/backend/simulation/agent/exagent.go +++ b/backend/simulation/agent/exagent.go @@ -107,6 +107,10 @@ func (agt *ExAgent) Act() { agt.orientation = agt.movement } +func (agt *ExAgent) Type() envpkg.AgentType { + return envpkg.ExAgent +} + func (agt ExAgent) ToJsonObj() interface{} { return ExAgentJson{ID: string(agt.id), Value: agt.value, diff --git a/backend/simulation/agent/hornetAgent.go b/backend/simulation/agent/hornetAgent.go index a51dd6e..8bc574f 100644 --- a/backend/simulation/agent/hornetAgent.go +++ b/backend/simulation/agent/hornetAgent.go @@ -82,24 +82,24 @@ func (agt *HornetAgent) Percept() { func (agt *HornetAgent) Deliberate() { distanceToTarget := float64(agt.env.GetMapDimension()) if agt.objective.Type == Bee { - if bee, ok := agt.objective.TargetedElem.(BeeAgent); ok { + if bee, ok := agt.objective.TargetedElem.(*BeeAgent); ok { distanceToTarget = bee.Position().DistanceFrom(agt.Position()) } } for _, seen := range agt.seenElems { - if seen.Elem != nil { - switch seen.Elem.(type) { - case *BeeAgent: - bee := seen.Elem.(*BeeAgent) - distance := bee.Position().DistanceFrom(agt.Position()) - if distance < distanceToTarget { - agt.objective.Type = Bee - agt.objective.TargetedElem = bee - agt.objective.Position = bee.Position().Copy() - distanceToTarget = distance + if seen.Elem != nil && seen.Elem != agt { + switch elem := seen.Elem.(type) { + case *Agent: + if elem.Type() == envpkg.Bee { + fmt.Printf("[%s] Found a close bee (%s) \n", agt.id, elem.ID()) + distance := elem.Position().DistanceFrom(agt.Position()) + if distance < distanceToTarget { + agt.objective.Type = Bee + agt.objective.TargetedElem = elem + agt.objective.Position = elem.Position().Copy() + distanceToTarget = distance + } } - case BeeAgent: - fmt.Println("=================================Hornet sees a bee wtf") } } } @@ -119,17 +119,21 @@ func (agt *HornetAgent) Act() { agt.objective.Type = None } case Bee: - fmt.Println("=====================Hornet attacking bee !") - bee := agt.objective.TargetedElem.(*BeeAgent) + bee := agt.objective.TargetedElem.(*Agent) + fmt.Printf("[%s] Hornet attacking %s !\n", agt.id, bee.ID()) agt.gotoNextStepTowards(bee.Position().Copy()) if agt.Position().Near(bee.pos.Copy(), 1) { bee.Kill() agt.objective.Type = None - fmt.Println("=====================Hornet killed bee !!!") + fmt.Printf("[%s] Hornet killed %s !!!\n", agt.id, bee.ID()) } } } +func (agt HornetAgent) Type() envpkg.AgentType { + return envpkg.Hornet +} + func (agt *HornetAgent) ToJsonObj() interface{} { return HornetAgentJson{ ID: string(agt.id), diff --git a/backend/simulation/agent/vision/equilateralTriangleVision.go b/backend/simulation/agent/vision/equilateralTriangleVision.go index f24f61e..2f500df 100644 --- a/backend/simulation/agent/vision/equilateralTriangleVision.go +++ b/backend/simulation/agent/vision/equilateralTriangleVision.go @@ -5,6 +5,7 @@ import ( "sort" envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" + "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" ) @@ -26,7 +27,9 @@ func EquilateralTriangleVision(agt envpkg.IAgent, env *envpkg.Environment, dista addElemToList := func(x, y int) { if env.IsValidPosition(x, y) { if pointIsInTriangle(float64(x), float64(y), topCornerX, topCornerY, leftCornerX, leftCornerY, rightCornerX, rightCornerY) { - seenElems = append(seenElems, NewSeenElem(&envpkg.Position{X: x, Y: y}, env.GetAt(x, y))) + elem := env.GetAt(x, y) + seenElems = append(seenElems, NewSeenElem(&envpkg.Position{X: x, Y: y}, elem)) + // fmt.Printf("Agent %s sees element %v at position (%d, %d) (%s)\n", agt.ID(), elem, x, y, reflect.TypeOf(elem)) } } } diff --git a/backend/simulation/environment/environment.go b/backend/simulation/environment/environment.go index 6830ee8..207609a 100644 --- a/backend/simulation/environment/environment.go +++ b/backend/simulation/environment/environment.go @@ -34,7 +34,18 @@ func (env *Environment) GetAt(x int, y int) interface{} { if x < 0 || y < 0 || x >= mapDimension || y >= mapDimension { return nil } - return env.grid[x][y] + elem := env.grid[x][y] + if elem == nil { + return nil + } + switch obj := elem.(type) { + case IAgent: + return obj + case IObject: + return obj + default: + return obj + } } func (env *Environment) GetMap() [][]interface{} { @@ -49,13 +60,14 @@ func (env *Environment) IsValidPosition(x int, y int) bool { return x >= 0 && y >= 0 && x < mapDimension && y < mapDimension } +// Returns true is added on grid, false if not func (env *Environment) AddAgent(agt IAgent) bool { pos := agt.Position() + env.agts = append(env.agts, agt) if pos != nil && env.GetAt(pos.X, pos.Y) == nil { - //return false env.grid[pos.X][pos.Y] = agt + return false } - env.agts = append(env.agts, agt) return true } @@ -107,9 +119,10 @@ func (env *Environment) PathFinding(start *Position, end *Position, numberMoves startNode := &node{position: start.Copy(), cost: 0, heuristic: start.ManhattanDistance(end)} openList = append(openList, startNode) - // We allow pathfinding to last 50 iterations + // We allow pathfinding to last 3 times the number of moves cpt := 0 - for len(openList) > 0 && cpt < numberMoves*2 { + for len(openList) > 0 && cpt < numberMoves*3 { + //fmt.Printf("%d ", cpt) currentNode := openList[0] currentIndex := 0 for index, node := range openList { @@ -147,6 +160,7 @@ func (env *Environment) PathFinding(start *Position, end *Position, numberMoves } cpt++ } + //fmt.Println("") path := []*Position{} var currentNode *node = nil diff --git a/backend/simulation/environment/iagent.go b/backend/simulation/environment/iagent.go index 432a60e..8c41bf4 100644 --- a/backend/simulation/environment/iagent.go +++ b/backend/simulation/environment/iagent.go @@ -15,6 +15,14 @@ const ( SouthWest Orientation = "SW" ) +type AgentType string + +const ( + ExAgent AgentType = "ExAgent" + Bee AgentType = "Bee" + Hornet AgentType = "Hornet" +) + // IAgent is an interface representing the agent's actions // limits type IAgent interface { @@ -27,4 +35,5 @@ type IAgent interface { Percept() Deliberate() Act() + Type() AgentType } diff --git a/backend/simulation/environment/position.go b/backend/simulation/environment/position.go index 983bc6e..ed4dde2 100644 --- a/backend/simulation/environment/position.go +++ b/backend/simulation/environment/position.go @@ -1,7 +1,6 @@ package environment import ( - "fmt" "math" "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" @@ -32,7 +31,6 @@ func (p *Position) move(grid [][]interface{}, newX int, newY int, elem interface // Not a simulation if grid != nil { if grid[p.X][p.Y] == elem { - fmt.Println("Grid is not nil : ", grid[p.X][p.Y]) grid[p.X][p.Y], grid[newX][newY] = nil, grid[p.X][p.Y] } else { grid[newX][newY] = elem diff --git a/backend/simulation/simulation.go b/backend/simulation/simulation.go index 64fe28e..ceca97c 100644 --- a/backend/simulation/simulation.go +++ b/backend/simulation/simulation.go @@ -162,8 +162,10 @@ func (simu *Simulation) Run(maWs *websocket.Conn) { c := agt.GetSyncChan() simu.env.Lock() c <- true - // If dead - if !<-c { + isAlive := <-c + // If dead, remove agent from simulation + if !isAlive { + fmt.Printf("{{SIMULATION}} - [%s] is dead\n", agt.ID()) simu.agts = append(simu.agts[:i], simu.agts[i+1:]...) } simu.env.Unlock() diff --git a/config.yaml b/config.yaml index f6a07c5..4fa1ffe 100644 --- a/config.yaml +++ b/config.yaml @@ -1,15 +1,15 @@ # Environment -MapDimension: 40 +MapDimension: 25 # Bees -NumberBees: 20 +NumberBees: 10 BeeAgentVisionRange: 4.0 MaxNectar: 70 # in mg -> https://wildflowermeadows.com/2024/01/how-far-do-honeybees-fly/ # Hornets -NumberHornets: 2 +NumberHornets: 1 HornetAgentVisionRange: 4.0 # Flowers -NumberFlowers: 20 +NumberFlowers: 15 NumberFlowerPatches: 3 -- GitLab From 33aa4a0f44771673f92e0d56e5e98f320a5ebb76 Mon Sep 17 00:00:00 2001 From: StutenEXE <alexandre.bidaux78@gmail.com> Date: Tue, 31 Dec 2024 11:24:11 +0100 Subject: [PATCH 4/7] Basic Hornet behavior and added function to agent interface --- backend/simulation/agent/agent.go | 33 ++++++++++++++--- backend/simulation/agent/beeAgent.go | 25 +++++++++---- backend/simulation/agent/exagent.go | 4 +++ backend/simulation/agent/hornetAgent.go | 36 ++++++++++--------- .../agent/vision/equilateralTriangleVision.go | 5 ++- backend/simulation/agent/vision/visionFunc.go | 2 +- backend/simulation/environment/environment.go | 24 ++++++++++--- backend/simulation/environment/iagent.go | 9 +++++ backend/simulation/environment/position.go | 2 -- backend/simulation/simulation.go | 6 ++-- config.yaml | 8 ++--- 11 files changed, 112 insertions(+), 42 deletions(-) diff --git a/backend/simulation/agent/agent.go b/backend/simulation/agent/agent.go index 0d864ac..9feb8e3 100644 --- a/backend/simulation/agent/agent.go +++ b/backend/simulation/agent/agent.go @@ -51,13 +51,16 @@ func (agt *Agent) Start() { for { run := <-agt.syncChan if !run || !agt.alive { - agt.syncChan <- !run || !agt.alive + agt.syncChan <- false break } + fmt.Printf("[%s] === Percieving === [%s]\n", agt.ID(), agt.ID()) agt.iagt.Percept() + fmt.Printf("[%s] === Deliberating === [%s]\n", agt.ID(), agt.ID()) agt.iagt.Deliberate() + fmt.Printf("[%s] === Acting === [%s]\n", agt.ID(), agt.ID()) agt.iagt.Act() - agt.syncChan <- run + agt.syncChan <- agt.alive } fmt.Printf("[%s] Stopping Agent\n", agt.ID()) }() @@ -71,6 +74,18 @@ func (agt Agent) GetSyncChan() chan bool { return agt.syncChan } +func (agt *Agent) Percept() { + agt.iagt.Percept() +} + +func (agt *Agent) Deliberate() { + agt.iagt.Deliberate() +} + +func (agt *Agent) Act() { + agt.iagt.Act() +} + func (agt Agent) Position() *envpkg.Position { if agt.pos == nil { return nil @@ -345,7 +360,7 @@ func (agt *Agent) getNextWanderingPosition() *envpkg.Position { } } // Find the closest available position in surroundings - closestPosition := newObjective.Copy() + closestPosition := agt.pos.Copy() minDistance := agt.pos.DistanceFrom(newObjective) for _, pos := range surroundings { distance := pos.DistanceFrom(newObjective) @@ -362,7 +377,17 @@ func (agt *Agent) getNextWanderingPosition() *envpkg.Position { return newObjective } -func (agt *Agent) die() { +func (agt *Agent) Kill() { + agt.env.RemoveAgent(agt) agt.alive = false agt.pos = nil + +} + +func (agt Agent) Type() envpkg.AgentType { + return agt.iagt.Type() +} + +func (agt *Agent) ToJsonObj() interface{} { + return agt.iagt.ToJsonObj() } diff --git a/backend/simulation/agent/beeAgent.go b/backend/simulation/agent/beeAgent.go index 06ac55a..1154495 100644 --- a/backend/simulation/agent/beeAgent.go +++ b/backend/simulation/agent/beeAgent.go @@ -2,6 +2,7 @@ package agent import ( "fmt" + "reflect" "time" "math/rand" @@ -87,11 +88,6 @@ func (agt *BeeAgent) Act() { } } -func (agt *BeeAgent) Kill() { - agt.env.RemoveAgent(agt) - agt.die() -} - func (agt *BeeAgent) hasFlowerObjective() bool { return agt.objective.Type == Flower } @@ -122,6 +118,7 @@ func (agt *BeeAgent) foragerDeliberation() { if seen.Elem != nil { switch elem := (seen.Elem).(type) { case *HornetAgent: + fmt.Printf("[%s] *****************Close to hornet %v\n", agt.id, reflect.TypeOf(seen.Elem)) closestHornet = elem case *obj.Flower: if !hasAlreadySeenCloserFlower { @@ -135,8 +132,8 @@ func (agt *BeeAgent) foragerDeliberation() { agt.objective.Type = Flower hasAlreadySeenCloserFlower = true } - default: - fmt.Printf("[%s] Unknown element seen : %v\n", agt.id, elem) + //default: + //fmt.Printf("[%s] Unknown element seen : %v\n", agt.id, elem) } } if closestHornet != nil { @@ -148,6 +145,16 @@ func (agt *BeeAgent) foragerDeliberation() { fmt.Printf("[%s] Hornet seen, fleeing in opposite direction\n", agt.id) agt.objective.Position = agt.pos.GetSymmetricOfPoint(*closestHornet.pos.Copy()) agt.objective.Type = Position + if agt.objective.Position.X < 0 { + agt.objective.Position.X = 0 + } else if agt.objective.Position.X >= agt.env.GetMapDimension() { + agt.objective.Position.X = agt.env.GetMapDimension() - 1 + } + if agt.objective.Position.Y < 0 { + agt.objective.Position.Y = 0 + } else if agt.objective.Position.Y >= agt.env.GetMapDimension() { + agt.objective.Position.Y = agt.env.GetMapDimension() - 1 + } } // If has no objective, wander if agt.objective.Type == None { @@ -217,6 +224,10 @@ func (agt *BeeAgent) workerAction() { } } +func (agt BeeAgent) Type() envpkg.AgentType { + return envpkg.Bee +} + func (agt *BeeAgent) ToJsonObj() interface{} { return BeeAgentJson{ID: string(agt.id), Position: agt.pos, diff --git a/backend/simulation/agent/exagent.go b/backend/simulation/agent/exagent.go index b7d04bd..7c896da 100644 --- a/backend/simulation/agent/exagent.go +++ b/backend/simulation/agent/exagent.go @@ -107,6 +107,10 @@ func (agt *ExAgent) Act() { agt.orientation = agt.movement } +func (agt *ExAgent) Type() envpkg.AgentType { + return envpkg.ExAgent +} + func (agt ExAgent) ToJsonObj() interface{} { return ExAgentJson{ID: string(agt.id), Value: agt.value, diff --git a/backend/simulation/agent/hornetAgent.go b/backend/simulation/agent/hornetAgent.go index a51dd6e..8bc574f 100644 --- a/backend/simulation/agent/hornetAgent.go +++ b/backend/simulation/agent/hornetAgent.go @@ -82,24 +82,24 @@ func (agt *HornetAgent) Percept() { func (agt *HornetAgent) Deliberate() { distanceToTarget := float64(agt.env.GetMapDimension()) if agt.objective.Type == Bee { - if bee, ok := agt.objective.TargetedElem.(BeeAgent); ok { + if bee, ok := agt.objective.TargetedElem.(*BeeAgent); ok { distanceToTarget = bee.Position().DistanceFrom(agt.Position()) } } for _, seen := range agt.seenElems { - if seen.Elem != nil { - switch seen.Elem.(type) { - case *BeeAgent: - bee := seen.Elem.(*BeeAgent) - distance := bee.Position().DistanceFrom(agt.Position()) - if distance < distanceToTarget { - agt.objective.Type = Bee - agt.objective.TargetedElem = bee - agt.objective.Position = bee.Position().Copy() - distanceToTarget = distance + if seen.Elem != nil && seen.Elem != agt { + switch elem := seen.Elem.(type) { + case *Agent: + if elem.Type() == envpkg.Bee { + fmt.Printf("[%s] Found a close bee (%s) \n", agt.id, elem.ID()) + distance := elem.Position().DistanceFrom(agt.Position()) + if distance < distanceToTarget { + agt.objective.Type = Bee + agt.objective.TargetedElem = elem + agt.objective.Position = elem.Position().Copy() + distanceToTarget = distance + } } - case BeeAgent: - fmt.Println("=================================Hornet sees a bee wtf") } } } @@ -119,17 +119,21 @@ func (agt *HornetAgent) Act() { agt.objective.Type = None } case Bee: - fmt.Println("=====================Hornet attacking bee !") - bee := agt.objective.TargetedElem.(*BeeAgent) + bee := agt.objective.TargetedElem.(*Agent) + fmt.Printf("[%s] Hornet attacking %s !\n", agt.id, bee.ID()) agt.gotoNextStepTowards(bee.Position().Copy()) if agt.Position().Near(bee.pos.Copy(), 1) { bee.Kill() agt.objective.Type = None - fmt.Println("=====================Hornet killed bee !!!") + fmt.Printf("[%s] Hornet killed %s !!!\n", agt.id, bee.ID()) } } } +func (agt HornetAgent) Type() envpkg.AgentType { + return envpkg.Hornet +} + func (agt *HornetAgent) ToJsonObj() interface{} { return HornetAgentJson{ ID: string(agt.id), diff --git a/backend/simulation/agent/vision/equilateralTriangleVision.go b/backend/simulation/agent/vision/equilateralTriangleVision.go index f24f61e..2f500df 100644 --- a/backend/simulation/agent/vision/equilateralTriangleVision.go +++ b/backend/simulation/agent/vision/equilateralTriangleVision.go @@ -5,6 +5,7 @@ import ( "sort" envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" + "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" ) @@ -26,7 +27,9 @@ func EquilateralTriangleVision(agt envpkg.IAgent, env *envpkg.Environment, dista addElemToList := func(x, y int) { if env.IsValidPosition(x, y) { if pointIsInTriangle(float64(x), float64(y), topCornerX, topCornerY, leftCornerX, leftCornerY, rightCornerX, rightCornerY) { - seenElems = append(seenElems, NewSeenElem(&envpkg.Position{X: x, Y: y}, env.GetAt(x, y))) + elem := env.GetAt(x, y) + seenElems = append(seenElems, NewSeenElem(&envpkg.Position{X: x, Y: y}, elem)) + // fmt.Printf("Agent %s sees element %v at position (%d, %d) (%s)\n", agt.ID(), elem, x, y, reflect.TypeOf(elem)) } } } diff --git a/backend/simulation/agent/vision/visionFunc.go b/backend/simulation/agent/vision/visionFunc.go index 32e5fe9..0056637 100644 --- a/backend/simulation/agent/vision/visionFunc.go +++ b/backend/simulation/agent/vision/visionFunc.go @@ -31,7 +31,7 @@ func getTriangleCoordinates(startPt envpkg.Position, height float64, oppositeBas // of topCorner on C1 // Combining the two circles gives us the leftCorner and rightCorner points // First circle : C1 = (x - a)² + (y - b)² = height² - // Second circle : C2 = (x - p)² + (y - q)² = oppositeBaseSize² + // Second circle : C2 = (x - p)² + (y - q)² = (oppositeBaseSize/2)² // We get the tangent of the center of C2 (point on C1) // The points of intersection between C2 and the tangent of C1 are the leftCorner and rightCorner // This allows for maintining precision over the height of the triangle and the opposite base size diff --git a/backend/simulation/environment/environment.go b/backend/simulation/environment/environment.go index 6830ee8..207609a 100644 --- a/backend/simulation/environment/environment.go +++ b/backend/simulation/environment/environment.go @@ -34,7 +34,18 @@ func (env *Environment) GetAt(x int, y int) interface{} { if x < 0 || y < 0 || x >= mapDimension || y >= mapDimension { return nil } - return env.grid[x][y] + elem := env.grid[x][y] + if elem == nil { + return nil + } + switch obj := elem.(type) { + case IAgent: + return obj + case IObject: + return obj + default: + return obj + } } func (env *Environment) GetMap() [][]interface{} { @@ -49,13 +60,14 @@ func (env *Environment) IsValidPosition(x int, y int) bool { return x >= 0 && y >= 0 && x < mapDimension && y < mapDimension } +// Returns true is added on grid, false if not func (env *Environment) AddAgent(agt IAgent) bool { pos := agt.Position() + env.agts = append(env.agts, agt) if pos != nil && env.GetAt(pos.X, pos.Y) == nil { - //return false env.grid[pos.X][pos.Y] = agt + return false } - env.agts = append(env.agts, agt) return true } @@ -107,9 +119,10 @@ func (env *Environment) PathFinding(start *Position, end *Position, numberMoves startNode := &node{position: start.Copy(), cost: 0, heuristic: start.ManhattanDistance(end)} openList = append(openList, startNode) - // We allow pathfinding to last 50 iterations + // We allow pathfinding to last 3 times the number of moves cpt := 0 - for len(openList) > 0 && cpt < numberMoves*2 { + for len(openList) > 0 && cpt < numberMoves*3 { + //fmt.Printf("%d ", cpt) currentNode := openList[0] currentIndex := 0 for index, node := range openList { @@ -147,6 +160,7 @@ func (env *Environment) PathFinding(start *Position, end *Position, numberMoves } cpt++ } + //fmt.Println("") path := []*Position{} var currentNode *node = nil diff --git a/backend/simulation/environment/iagent.go b/backend/simulation/environment/iagent.go index 432a60e..8c41bf4 100644 --- a/backend/simulation/environment/iagent.go +++ b/backend/simulation/environment/iagent.go @@ -15,6 +15,14 @@ const ( SouthWest Orientation = "SW" ) +type AgentType string + +const ( + ExAgent AgentType = "ExAgent" + Bee AgentType = "Bee" + Hornet AgentType = "Hornet" +) + // IAgent is an interface representing the agent's actions // limits type IAgent interface { @@ -27,4 +35,5 @@ type IAgent interface { Percept() Deliberate() Act() + Type() AgentType } diff --git a/backend/simulation/environment/position.go b/backend/simulation/environment/position.go index 983bc6e..ed4dde2 100644 --- a/backend/simulation/environment/position.go +++ b/backend/simulation/environment/position.go @@ -1,7 +1,6 @@ package environment import ( - "fmt" "math" "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/utils" @@ -32,7 +31,6 @@ func (p *Position) move(grid [][]interface{}, newX int, newY int, elem interface // Not a simulation if grid != nil { if grid[p.X][p.Y] == elem { - fmt.Println("Grid is not nil : ", grid[p.X][p.Y]) grid[p.X][p.Y], grid[newX][newY] = nil, grid[p.X][p.Y] } else { grid[newX][newY] = elem diff --git a/backend/simulation/simulation.go b/backend/simulation/simulation.go index 64fe28e..ceca97c 100644 --- a/backend/simulation/simulation.go +++ b/backend/simulation/simulation.go @@ -162,8 +162,10 @@ func (simu *Simulation) Run(maWs *websocket.Conn) { c := agt.GetSyncChan() simu.env.Lock() c <- true - // If dead - if !<-c { + isAlive := <-c + // If dead, remove agent from simulation + if !isAlive { + fmt.Printf("{{SIMULATION}} - [%s] is dead\n", agt.ID()) simu.agts = append(simu.agts[:i], simu.agts[i+1:]...) } simu.env.Unlock() diff --git a/config.yaml b/config.yaml index f6a07c5..4fa1ffe 100644 --- a/config.yaml +++ b/config.yaml @@ -1,15 +1,15 @@ # Environment -MapDimension: 40 +MapDimension: 25 # Bees -NumberBees: 20 +NumberBees: 10 BeeAgentVisionRange: 4.0 MaxNectar: 70 # in mg -> https://wildflowermeadows.com/2024/01/how-far-do-honeybees-fly/ # Hornets -NumberHornets: 2 +NumberHornets: 1 HornetAgentVisionRange: 4.0 # Flowers -NumberFlowers: 20 +NumberFlowers: 15 NumberFlowerPatches: 3 -- GitLab From 222d8c49cb4f1bef0cc9d5a43bc40d5cde88e528 Mon Sep 17 00:00:00 2001 From: StutenEXE <alexandre.bidaux78@gmail.com> Date: Thu, 2 Jan 2025 09:53:31 +0100 Subject: [PATCH 5/7] Fixes : agt locked on position & flowers spawn over hives (& other possible objects) --- backend/simulation/agent/agent.go | 50 +++++++++++++++------- backend/simulation/agent/beeAgent.go | 6 ++- backend/simulation/agent/hornetAgent.go | 19 +++++--- backend/simulation/environment/position.go | 13 ------ backend/simulation/simulation.go | 2 +- 5 files changed, 54 insertions(+), 36 deletions(-) diff --git a/backend/simulation/agent/agent.go b/backend/simulation/agent/agent.go index 9feb8e3..1445545 100644 --- a/backend/simulation/agent/agent.go +++ b/backend/simulation/agent/agent.go @@ -168,8 +168,14 @@ func (agt *Agent) goSouthWest() bool { // https://web.archive.org/web/20171022224528/http://www.policyalmanac.org:80/games/aStarTutorial.htm func (agt *Agent) gotoNextStepTowards(pos *envpkg.Position) { + // If the position is already occupied and we are near it (by one), we don't move + if agt.env.GetAt(pos.X, pos.Y) != nil { + if agt.pos.Near(pos, 1) { + return + } + } chain := agt.env.PathFinding(agt.pos, pos, agt.speed) - // We remove the first element who is the current position of the agent + // We remove the first element (it is the current position of the agent) if len(chain) > 0 && chain[0].Equal(agt.pos) { chain = chain[1:] } @@ -250,6 +256,23 @@ func (agt *Agent) wander() { } else if closestBorder.Y == agt.env.GetMapDimension()-1 { keepAwayFromBorderPos.GoNorth(nil, nil) } + // If the position is already occupied by something, we find the closest available position + if agt.env.GetAt(keepAwayFromBorderPos.X, keepAwayFromBorderPos.Y) != nil { + surroundings := agt.pos.GetNeighbours(agt.speed) + closestPosition := agt.pos.Copy() + minDistance := agt.pos.DistanceFrom(keepAwayFromBorderPos) + for _, pos := range surroundings { + if agt.env.GetAt(pos.X, pos.Y) != nil { + continue + } + distance := pos.DistanceFrom(keepAwayFromBorderPos) + if distance <= minDistance { + closestPosition = pos.Copy() + minDistance = distance + } + } + keepAwayFromBorderPos = closestPosition.Copy() + } agt.objective.Position = keepAwayFromBorderPos.Copy() agt.objective.Type = Position fmt.Printf("[%s] Too close to border (%d %d), going to (%d %d)\n", agt.id, closestBorder.X, closestBorder.Y, agt.objective.Position.X, agt.objective.Position.Y) @@ -269,16 +292,8 @@ func (agt *Agent) wander() { } func (agt *Agent) getNextWanderingPosition() *envpkg.Position { + // We get the accessible surroundings of the agent surroundings := agt.pos.GetNeighbours(agt.speed) - // We remove the positions that are occupied - removeCpt := 0 - for i := 0; i < len(surroundings); i++ { - idx := i - removeCpt - if agt.env.GetAt(surroundings[idx].X, surroundings[idx].Y) != nil || surroundings[idx].Equal(agt.lastPos) { - surroundings = append(surroundings[:idx], surroundings[idx+1:]...) - removeCpt++ - } - } nextWanderingOrientation := agt.orientation // Chances : 3/4 th keeping the same orientation, 1/8th changing to the left, 1/8th changing to the right chancesToChangeOrientation := rand.Intn(8) @@ -359,10 +374,17 @@ func (agt *Agent) getNextWanderingPosition() *envpkg.Position { newObjective.GoSouthWest(nil, nil) } } - // Find the closest available position in surroundings + // If this position is a valid one, we return it + if agt.env.GetAt(newObjective.X, newObjective.Y) == nil && !newObjective.Equal(agt.lastPos) { + return newObjective + } + // Else we find the closest available position in surroundings closestPosition := agt.pos.Copy() minDistance := agt.pos.DistanceFrom(newObjective) for _, pos := range surroundings { + if agt.env.GetAt(pos.X, pos.Y) == nil && !pos.Equal(agt.lastPos) { + continue + } distance := pos.DistanceFrom(newObjective) if distance < minDistance { closestPosition = pos.Copy() @@ -370,10 +392,8 @@ func (agt *Agent) getNextWanderingPosition() *envpkg.Position { } } newObjective = closestPosition - - // We add the new last position to avoid cycles - agt.lastPos = newObjective.Copy() - + // We remember the current position to avoid cycles + agt.lastPos = agt.pos.Copy() return newObjective } diff --git a/backend/simulation/agent/beeAgent.go b/backend/simulation/agent/beeAgent.go index 1154495..f13788b 100644 --- a/backend/simulation/agent/beeAgent.go +++ b/backend/simulation/agent/beeAgent.go @@ -2,7 +2,6 @@ package agent import ( "fmt" - "reflect" "time" "math/rand" @@ -118,7 +117,7 @@ func (agt *BeeAgent) foragerDeliberation() { if seen.Elem != nil { switch elem := (seen.Elem).(type) { case *HornetAgent: - fmt.Printf("[%s] *****************Close to hornet %v\n", agt.id, reflect.TypeOf(seen.Elem)) + fmt.Printf("[%s] Close to hornet %v\n", agt.id, elem.ID()) closestHornet = elem case *obj.Flower: if !hasAlreadySeenCloserFlower { @@ -169,6 +168,9 @@ func (agt *BeeAgent) foragerAction() { case Position: if agt.pos.Equal(objf.Position) { agt.objective.Type = None + } else if agt.env.GetAt(objf.Position.X, objf.Position.Y) != nil && agt.pos.Near(objf.Position, 1) { + // In some cases, the agent wants to go to a position where there is already an element (agent or object) + agt.objective.Type = None } else { agt.gotoNextStepTowards(objf.Position.Copy()) if agt.pos.Equal(objf.Position) { diff --git a/backend/simulation/agent/hornetAgent.go b/backend/simulation/agent/hornetAgent.go index 8bc574f..bf3b053 100644 --- a/backend/simulation/agent/hornetAgent.go +++ b/backend/simulation/agent/hornetAgent.go @@ -109,17 +109,26 @@ func (agt *HornetAgent) Deliberate() { } func (agt *HornetAgent) Act() { - if agt.objective.Type == None { + objf := &agt.objective + if objf.Type == None { return } - switch agt.objective.Type { + switch objf.Type { case Position: - agt.gotoNextStepTowards(agt.objective.Position.Copy()) - if agt.Position().Equal(agt.objective.Position) { + if agt.pos.Equal(objf.Position) { + agt.objective.Type = None + } else if agt.pos.Equal(agt.lastPos) { + // In some cases, the agent gets stuck on an unattaignable position + agt.lastPos = objf.Position.Copy() agt.objective.Type = None + } else { + agt.gotoNextStepTowards(objf.Position.Copy()) + if agt.pos.Equal(objf.Position) { + agt.objective.Type = None + } } case Bee: - bee := agt.objective.TargetedElem.(*Agent) + bee := objf.TargetedElem.(*Agent) fmt.Printf("[%s] Hornet attacking %s !\n", agt.id, bee.ID()) agt.gotoNextStepTowards(bee.Position().Copy()) if agt.Position().Near(bee.pos.Copy(), 1) { diff --git a/backend/simulation/environment/position.go b/backend/simulation/environment/position.go index ed4dde2..d008f7b 100644 --- a/backend/simulation/environment/position.go +++ b/backend/simulation/environment/position.go @@ -125,17 +125,4 @@ func (p Position) GetNeighbours(distance int) []*Position { } } return neighbours - // return []*Position{ - // // NW N NE - // NewPosition(p.X-1, p.Y-1, p.maxX, p.maxY), - // NewPosition(p.X, p.Y-1, p.maxX, p.maxY), - // NewPosition(p.X+1, p.Y-1, p.maxX, p.maxY), - // // W E - // NewPosition(p.X-1, p.Y, p.maxX, p.maxY), - // NewPosition(p.X+1, p.Y, p.maxX, p.maxY), - // // SW S SE - // NewPosition(p.X-1, p.Y+1, p.maxX, p.maxY), - // NewPosition(p.X, p.Y+1, p.maxX, p.maxY), - // NewPosition(p.X+1, p.Y+1, p.maxX, p.maxY), - // } } diff --git a/backend/simulation/simulation.go b/backend/simulation/simulation.go index ceca97c..8dda702 100644 --- a/backend/simulation/simulation.go +++ b/backend/simulation/simulation.go @@ -67,7 +67,7 @@ func NewSimulation(nbees int, nflowers int, nhornets int, maWs *websocket.Conn) availablePositions := make([]*envpkg.Position, 0) for x := centerOfPatchX - offset; x < centerOfPatchX+offset; x++ { for y := centerOfPatchY - offset; y < centerOfPatchY+offset; y++ { - if simu.env.IsValidPosition(x, y) { + if simu.env.IsValidPosition(x, y) && simu.env.GetAt(x, y) == nil { availablePositions = append(availablePositions, envpkg.NewPosition(x, y, mapDimension, mapDimension)) } } -- GitLab From f3d9879e7e94bbc5141d3973ab07c5fad06f8558 Mon Sep 17 00:00:00 2001 From: Quentin Valakou <quentin.valakou@gmail.com> Date: Fri, 3 Jan 2025 21:14:26 +0100 Subject: [PATCH 6/7] Comportement Hornet attaque Hive v0 --- backend/simulation/agent/agent.go | 1 - backend/simulation/agent/hornetAgent.go | 57 ++++++++++++++++++- backend/simulation/environment/environment.go | 10 ++++ backend/simulation/environment/iobject.go | 7 +++ backend/simulation/object/flower.go | 4 ++ backend/simulation/object/hive.go | 23 +++++--- backend/simulation/simulation.go | 2 +- 7 files changed, 93 insertions(+), 11 deletions(-) diff --git a/backend/simulation/agent/agent.go b/backend/simulation/agent/agent.go index 1445545..909b4af 100644 --- a/backend/simulation/agent/agent.go +++ b/backend/simulation/agent/agent.go @@ -401,7 +401,6 @@ func (agt *Agent) Kill() { agt.env.RemoveAgent(agt) agt.alive = false agt.pos = nil - } func (agt Agent) Type() envpkg.AgentType { diff --git a/backend/simulation/agent/hornetAgent.go b/backend/simulation/agent/hornetAgent.go index bf3b053..3aef7c2 100644 --- a/backend/simulation/agent/hornetAgent.go +++ b/backend/simulation/agent/hornetAgent.go @@ -7,6 +7,7 @@ import ( "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/agent/vision" envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" + obj "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/object" ) // HornetAgent hérite de /simulation/agent/agent.go "struct Agent" @@ -41,6 +42,25 @@ func NewHornetAgent(id string, env *envpkg.Environment, syncChan chan bool, s in return hAgent } +// A simple way to organize target priority for the hornet. +// If the hornet sees the Hive, and is surroundered by at least 2 of its kind, it will prioritize it. +// false : bee +// true : hive +func PriorityTarget(hornet HornetAgent) bool { + nbHornet := 0 + for _, seen := range hornet.seenElems { + switch elem := seen.Elem.(type) { + case *HornetAgent: + if elem.ID() != hornet.ID() { + fmt.Printf("HornetAgent %s seen by %s\n", elem.ID(), hornet.ID()) + nbHornet++ + } + } + } + fmt.Printf("nbHornet = %d\n", nbHornet) + return nbHornet >= 1 +} + func (agt *HornetAgent) Percept() { if agt.pos == nil { chance := rand.Intn(2) @@ -78,7 +98,8 @@ func (agt *HornetAgent) Percept() { } } -// The hornet agent always targets the closest bee +// The hornet agent always targets the closest bee unless : +// Sees a hive + is close to at least 2 of its kind func (agt *HornetAgent) Deliberate() { distanceToTarget := float64(agt.env.GetMapDimension()) if agt.objective.Type == Bee { @@ -86,11 +107,17 @@ func (agt *HornetAgent) Deliberate() { distanceToTarget = bee.Position().DistanceFrom(agt.Position()) } } + if agt.objective.Type == Hive { + if hive, ok := agt.objective.TargetedElem.(*obj.Hive); ok { + distanceToTarget = hive.Position().DistanceFrom(agt.Position()) + } + } for _, seen := range agt.seenElems { if seen.Elem != nil && seen.Elem != agt { + priority := PriorityTarget(*agt) switch elem := seen.Elem.(type) { case *Agent: - if elem.Type() == envpkg.Bee { + if elem.Type() == envpkg.Bee && !priority { fmt.Printf("[%s] Found a close bee (%s) \n", agt.id, elem.ID()) distance := elem.Position().DistanceFrom(agt.Position()) if distance < distanceToTarget { @@ -100,9 +127,22 @@ func (agt *HornetAgent) Deliberate() { distanceToTarget = distance } } + case *obj.Hive: + if elem.TypeObject() == envpkg.Hive && priority { + fmt.Printf("[%s] Found a close hive (%s) \n", agt.id, elem.ID()) + distance := elem.Position().DistanceFrom(agt.Position()) + if distance < distanceToTarget { + agt.objective.Type = Hive + agt.objective.TargetedElem = elem + agt.objective.Position = elem.Position().Copy() + distanceToTarget = distance + + } + } } } } + fmt.Printf(string(agt.objective.Type)) if agt.objective.Type == None { agt.wander() } @@ -136,6 +176,19 @@ func (agt *HornetAgent) Act() { agt.objective.Type = None fmt.Printf("[%s] Hornet killed %s !!!\n", agt.id, bee.ID()) } + + case Hive: + hive := objf.TargetedElem.(*obj.Hive) + fmt.Printf("[%s] Hornet attacking hive %s !\n", agt.id, hive.ID()) + agt.gotoNextStepTowards(hive.Position().Copy()) + if agt.Position().Near(hive.Pos.Copy(), 1) { + if hive.IsAlive() { + hive.Die() + } + agt.objective.Type = None + fmt.Printf("[%s] Hornet destructed %s !!!\n", agt.id, hive.ID()) + } + fmt.Println("Hornet attacking Hive") } } diff --git a/backend/simulation/environment/environment.go b/backend/simulation/environment/environment.go index 207609a..a7742f7 100644 --- a/backend/simulation/environment/environment.go +++ b/backend/simulation/environment/environment.go @@ -81,6 +81,16 @@ func (env *Environment) RemoveAgent(agt IAgent) { } } +func (env *Environment) RemoveObject(obj IObject) { + for i, a := range env.objs { + if a.ID() == obj.ID() { + env.objs = append(env.objs[:i], env.objs[i+1:]...) + env.grid[obj.Position().X][obj.Position().Y] = nil + break + } + } +} + func (env *Environment) AddObject(obj IObject) bool { pos := obj.Position() if env.GetAt(pos.X, pos.Y) != nil { diff --git a/backend/simulation/environment/iobject.go b/backend/simulation/environment/iobject.go index a1c8624..6f65d04 100644 --- a/backend/simulation/environment/iobject.go +++ b/backend/simulation/environment/iobject.go @@ -2,6 +2,13 @@ package environment type ObjectID string +type ObjectType string + +const ( + Flower ObjectType = "Flower" + Hive ObjectType = "Hive" +) + type IObject interface { ID() ObjectID Position() *Position diff --git a/backend/simulation/object/flower.go b/backend/simulation/object/flower.go index 9de0d4c..abbf692 100644 --- a/backend/simulation/object/flower.go +++ b/backend/simulation/object/flower.go @@ -116,3 +116,7 @@ func (f *Flower) RetreiveNectar(nectar int) int { func (f *Flower) GetNectar() int { return f.nectar } + +func (f Flower) ObjectType() envpkg.ObjectType { + return envpkg.Flower +} diff --git a/backend/simulation/object/hive.go b/backend/simulation/object/hive.go index 9cee815..405ee7b 100644 --- a/backend/simulation/object/hive.go +++ b/backend/simulation/object/hive.go @@ -6,12 +6,13 @@ import ( type Hive struct { id envpkg.ObjectID - pos *envpkg.Position + Pos *envpkg.Position qHoney int qNectar int qPollen int queen bool minHoney int + env *envpkg.Environment } type HiveJson struct { @@ -24,15 +25,16 @@ type HiveJson struct { MinHoney int `json:"min_honey"` } -func NewHive(id string, pos *envpkg.Position, qHoney int, qNectar int, qPollen int, minHoney int) *Hive { +func NewHive(id string, pos *envpkg.Position, qHoney int, qNectar int, qPollen int, minHoney int, environment *envpkg.Environment) *Hive { return &Hive{ id: envpkg.ObjectID(id), - pos: pos.Copy(), + Pos: pos.Copy(), qHoney: qHoney, qNectar: qNectar, qPollen: qPollen, queen: true, minHoney: minHoney, + env: environment, } } @@ -41,13 +43,13 @@ func (h Hive) ID() envpkg.ObjectID { } func (h Hive) Position() *envpkg.Position { - return h.pos.Copy() + return h.Pos.Copy() } func (h Hive) Copy() interface{} { return &Hive{ id: h.id, - pos: h.pos.Copy(), + Pos: h.Pos.Copy(), qHoney: h.qHoney, qNectar: h.qNectar, qPollen: h.qPollen, @@ -65,7 +67,7 @@ func (h *Hive) Become(h_alt interface{}) { if ok { h.id = altered_hive.id - h.pos = altered_hive.pos.Copy() + h.Pos = altered_hive.Pos.Copy() h.qHoney = altered_hive.qHoney h.qNectar = altered_hive.qNectar h.qPollen = altered_hive.qPollen @@ -77,7 +79,7 @@ func (h *Hive) Become(h_alt interface{}) { func (h Hive) ToJsonObj() interface{} { return HiveJson{ ID: string(h.id), - Position: *h.pos.Copy(), + Position: *h.Pos.Copy(), QuantityHoney: h.qHoney, QuantityNectar: h.qNectar, QuantityPollen: h.qPollen, @@ -99,4 +101,11 @@ func (h Hive) IsAlive() (isAlive bool) { func (h *Hive) Die() { h.queen = false + h.env.RemoveObject(h) + h.IsAlive() + h.Pos = nil +} + +func (h Hive) TypeObject() envpkg.ObjectType { + return envpkg.Hive } diff --git a/backend/simulation/simulation.go b/backend/simulation/simulation.go index 8dda702..3e854be 100644 --- a/backend/simulation/simulation.go +++ b/backend/simulation/simulation.go @@ -46,7 +46,7 @@ func NewSimulation(nbees int, nflowers int, nhornets int, maWs *websocket.Conn) id := fmt.Sprintf("Hive #%d", i) x, y := rand.Intn(mapDimension), rand.Intn(mapDimension) pos := envpkg.NewPosition(x, y, mapDimension, mapDimension) - hive := obj.NewHive(id, pos, 0, 0, 0, 10) + hive := obj.NewHive(id, pos, 0, 0, 0, 10, env) // ajout de l'objet à la simulation simu.objs = append(simu.objs, hive) // ajout de l'objet à l'environnement -- GitLab From 093f8a7b9f6ed76d18e949455e56a5a1eb59a53f Mon Sep 17 00:00:00 2001 From: Quentin Valakou <quentin.valakou@gmail.com> Date: Fri, 3 Jan 2025 21:52:00 +0100 Subject: [PATCH 7/7] Ajout d'un killcount --- backend/simulation/agent/hornetAgent.go | 45 +++++++++++++++---------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/backend/simulation/agent/hornetAgent.go b/backend/simulation/agent/hornetAgent.go index 3aef7c2..e8d325b 100644 --- a/backend/simulation/agent/hornetAgent.go +++ b/backend/simulation/agent/hornetAgent.go @@ -2,12 +2,11 @@ package agent import ( "fmt" - "math/rand" - "time" - "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/agent/vision" envpkg "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/environment" obj "gitlab.utc.fr/bidauxal/ai30_valakou_martins_chartier_bidaux/backend/simulation/object" + "math/rand" + "time" ) // HornetAgent hérite de /simulation/agent/agent.go "struct Agent" @@ -16,6 +15,7 @@ type HornetAgent struct { Agent birthDate time.Time seenElems []*vision.SeenElem + killCount int } type HornetAgentJson struct { @@ -24,6 +24,7 @@ type HornetAgentJson struct { Orientation envpkg.Orientation `json:"orientation"` Objective objective `json:"objective"` SeenElems []*vision.SeenElem `json:"seenElems"` + KillCount int `json:"killCount"` } func NewHornetAgent(id string, env *envpkg.Environment, syncChan chan bool, s int) *HornetAgent { @@ -39,6 +40,7 @@ func NewHornetAgent(id string, env *envpkg.Environment, syncChan chan bool, s in objective: objective{Position: nil, Type: None}, } hAgent.birthDate = time.Now() + hAgent.killCount = 0 return hAgent } @@ -48,6 +50,9 @@ func NewHornetAgent(id string, env *envpkg.Environment, syncChan chan bool, s in // true : hive func PriorityTarget(hornet HornetAgent) bool { nbHornet := 0 + if hornet.killCount >= 5 { + return true + } for _, seen := range hornet.seenElems { switch elem := seen.Elem.(type) { case *HornetAgent: @@ -169,26 +174,30 @@ func (agt *HornetAgent) Act() { } case Bee: bee := objf.TargetedElem.(*Agent) - fmt.Printf("[%s] Hornet attacking %s !\n", agt.id, bee.ID()) - agt.gotoNextStepTowards(bee.Position().Copy()) - if agt.Position().Near(bee.pos.Copy(), 1) { - bee.Kill() - agt.objective.Type = None - fmt.Printf("[%s] Hornet killed %s !!!\n", agt.id, bee.ID()) + if bee != nil { + fmt.Printf("[%s] Hornet attacking %s !\n", agt.id, bee.ID()) + agt.gotoNextStepTowards(bee.Position().Copy()) + if agt.Position().Near(bee.pos.Copy(), 1) { + bee.Kill() + agt.killCount++ + agt.objective.Type = None + fmt.Printf("[%s] killed %s !!!, Has %d kill(s) \n", agt.id, bee.ID(), agt.killCount) + } } case Hive: - hive := objf.TargetedElem.(*obj.Hive) - fmt.Printf("[%s] Hornet attacking hive %s !\n", agt.id, hive.ID()) - agt.gotoNextStepTowards(hive.Position().Copy()) - if agt.Position().Near(hive.Pos.Copy(), 1) { - if hive.IsAlive() { - hive.Die() + if agt.pos.Near(objf.Position, 1) { + if hive, ok := objf.TargetedElem.(*obj.Hive); ok { + if hive.IsAlive() { + hive.Die() + } else { + fmt.Printf("Hive is already killed \n !") + } } - agt.objective.Type = None - fmt.Printf("[%s] Hornet destructed %s !!!\n", agt.id, hive.ID()) + } else { + fmt.Printf("Hornet [%s] going to kill hive %v\n", agt.id, objf.TargetedElem.(envpkg.IObject).ID()) + agt.gotoNextStepTowards(objf.Position.Copy()) } - fmt.Println("Hornet attacking Hive") } } -- GitLab