1. // miridiusAi
  2. package miridiusAi
  3.  
  4. import (
  5.     common "github.com/zond/stockholm-ai/common"
  6.     state "github.com/zond/stockholm-ai/state"
  7. )
  8.  
  9. /*
  10. BalancedAi1 basically just aims to multiply as much as possible
  11. 1. For each node i in s:
  12.     a. i.Attraction = how much growth I would gain by sending 1 soldier there
  13. 2. For each node j in s where I have units:
  14.     a. For each node k, attraction to k = k.Attraction / (distance from j to k)
  15.     b. attraction of each edge connected to j is the sum of the attractions of nodes whos path start with that edge
  16.     c. attraction of not moving = j.Attraction
  17.     d. leave 1 unit to hold the node, and divide remaining units amongst all edges proportionally based on attraction ratios
  18.  
  19. Known issues:
  20. 1. Soldiers currently on edges are not considered in calculations, which causes the AI to send out units more often than really necessary.
  21. */
  22. type BalancedAi1 struct{}
  23.  
  24. /*
  25. Orders will analyze all nodes in s and return orders for each one
  26. */
  27. func (self BalancedAi1) Orders(logger common.Logger, me state.PlayerId, s *state.State) (result state.Orders) {
  28.  
  29.     var attraction, totalAttraction float64
  30.     var edge state.NodeId
  31.     // Calculate base attraction for all nodes
  32.     attractions := make(map[state.NodeId]float64, len(s.Nodes))
  33.     for _, node := range s.Nodes {
  34.         if node.Units[me] < 1 {
  35.             attraction = 1
  36.         } else {
  37.             attraction = 0
  38.         }
  39.         attraction = attraction + (0.2 * float64(node.Units[me]) / float64(node.Size))
  40.  
  41.         attractions[node.Id] = attraction
  42.     }
  43.  
  44.     // For each node in s
  45.     for _, node := range s.Nodes {
  46.         // If I have units there (after leaving 1 behind to defend)
  47.         if units := node.Units[me] - 1; units > 0 {
  48.             // Check my attraction to all other nodes and keep an attraction sum for each starting edge.
  49.             edgeAttractions := make(map[state.NodeId]float64, len(node.Edges)+1)
  50.             totalAttraction = 0
  51.             for _, destNode := range s.Nodes {
  52.                 path := s.Path(node.Id, destNode.Id, nil)
  53.                 if len(path) > 0 {
  54.                     edge = path[0]
  55.                     attraction = attractions[destNode.Id] / float64(len(path))
  56.                 } else {
  57.                     edge = node.Id
  58.                     attraction = attractions[destNode.Id]
  59.                 }
  60.                 edgeAttractions[edge] = edgeAttractions[edge] + attraction
  61.                 totalAttraction = totalAttraction + attraction
  62.             }
  63.             // go through all edges and send units accordingly
  64.             for edgeId, att := range edgeAttractions {
  65.                 // in case of rounding errors or some other hiccup, make sure current edge's attraction <= total
  66.                 if att > totalAttraction {
  67.                     totalAttraction = att
  68.                 }
  69.                 sendUnits := int(float64(units) * att / totalAttraction)
  70.                 units = units - sendUnits
  71.                 totalAttraction = totalAttraction - att
  72.                 result = append(result, state.Order{
  73.                     Src:   node.Id,
  74.                     Dst:   edgeId,
  75.                     Units: sendUnits,
  76.                 })
  77.             }
  78.         }
  79.     }
  80.     return
  81. }