11. MMO Online Game AOI Algorithm
Tags: zinx, go
Published: 2023-08-17 02:19:04

### [Zinx]

[<1.Building Basic Services with Zinx Framework>](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)
[<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>](https://dev.to/aceld/2-zinx-v02-simple-connection-encapsulation-and-binding-with-business-4p9g)
[<3.Design and Implementation of the Zinx Framework's Routing Module>](https://dev.to/aceld/3design-and-implementation-of-the-zinx-frameworks-routing-module-583n)
[<4.Zinx Global Configuration>](https://dev.to/aceld/4zinx-global-configuration-3jam)
[<5.Zinx Message Encapsulation Module Design and Implementation>](https://dev.to/aceld/5zinx-message-encapsulation-module-design-and-implementation-3fmc)
[<6.Design and Implementation of Zinx Multi-Router Mode>](https://dev.to/aceld/6design-and-implementation-of-zinx-multi-router-mode-19nn)
[<7. Building Zinx's Read-Write Separation Model>](https://dev.to/aceld/7-building-zinxs-read-write-separation-model-ihj)
[<8.Zinx Message Queue and Task Worker Pool Design and Implementation>](https://dev.to/aceld/8zinx-message-queue-and-task-worker-pool-design-and-implementation-12gl)
[<9. Zinx Connection Management and Property Setting>](https://dev.to/aceld/9-zinx-connection-management-and-property-setting-28ha)

### [Zinx Application - MMO Game Case Study]

[<10. Application Case Study using the Zinx Framework>](https://dev.to/aceld/10-application-case-study-using-the-zinx-framework-3ich)
[<11. MMO Online Game AOI Algorithm>](https://dev.to/aceld/11-mmo-online-game-aoi-algorithm-l7d)
[<12.Data Transmission Protocol: Protocol Buffers>](https://dev.to/aceld/12data-transmission-protocol-protocol-buffers-1jek)
[<13. MMO Game Server Application Protocol>](https://dev.to/aceld/13-mmo-game-server-application-protocol-5adl)
[<14. Building the Project and User Login>](https://dev.to/aceld/14-building-the-project-and-user-login-b7p)
[<15. World Chat System Implementation>](https://dev.to/aceld/157-world-chat-system-implementation-18li)
[<16. Online Location Information Synchronization>](https://dev.to/aceld/16-online-location-information-synchronization-3cp1)
[<17. Moving position and non-crossing grid AOI broadcasting>](https://dev.to/aceld/17-moving-position-and-non-crossing-grid-aoi-broadcasting-4nb9)
[<18. Player Logout>](https://dev.to/aceld/18-player-logout-3eln)
[<19. Movement and AOI Broadcast Across Grids>](https://dev.to/aceld/19-movement-and-aoi-broadcast-across-grids-46fe)

---
### MMO-GAME Source Code

https://github.com/aceld/zinx/tree/master/zinx_app_demo/mmo_game

---

Before diving into the current project case, this section will provide an introduction to the basic map algorithm used in MMO games.

The AOI (Area Of Interest) algorithm in games can be considered a fundamental core, as many game logics are driven by AOI entry and exit events. Additionally, numerous network synchronization data stem from AOI entry and exit events. Thus, a robust AOI algorithm and optimization based on it are crucial for enhancing game performance.

To achieve this, it is necessary to establish an AOI for each player. When an object's state changes, the information needs to be broadcast to all players. Players within the coverage of the AOI will receive this broadcast message and respond accordingly.

The desired functionality for the current game demo's AOI is to broadcast messages to nearby players when the state of players or NPCs on the server changes. When a player enters an NPC's alert zone, the AOI module sends a message to the NPC, which then responds with appropriate AI actions.

Next, let's proceed with the project build for the Zinx application server demo. Start by creating the "mmo_game" directory as the main project directory for the server-side game application.

## 11.1 Network-Based Implementation of AOI Algorithm

Before delving into the implementation of the AOI algorithm, let's start by creating a 2D map to visualize the concept of AOI, as shown in Figure 11.1.


![Figure 11.1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m47y2mtikokvffrkvn2u.jpeg)
> Figure 11.1

We will divide the current coordinate system into 20 equally sized cells. The x-axis and y-axis coordinates range from 0 to 250. Each axis is divided into 5 sections, with each section considered a cell along that axis. This results in a total of 25 cell IDs in the 2D coordinate system, formed by combining the x and y axis indices.

Let's define some variables and formulas used in the calculations:

(1) The dimensions of the AOI map, with a total size of 250x250:

`w` for the width of the x-axis
`l` for the length of the y-axis
```
w = 250, l = 250
```

(2) The number of cells on the x-axis:
```
nx = 5
```

(3) The number of cells on the y-axis:
```
ny = 5
```

(4) The width of each cell along the x-axis:
```
dx = w / nx
(i.e., dx = 250 / 5 = 50)
```
(5) The length of each cell along the y-axis:
```
dy = l / ny
(i.e., dy = 250 / 5 = 50)
```

(6) The x-axis index of a cell:idx with a range of 0 to 4
(7) The y-axis index of a cell:idy with a range of 0 to 4

(8) The cell ID:
```
id = idy * nx + idx
```
From the above formulas, you can determine the cell ID based on the cell's coordinates.

(9) The coordinates of a cell:
```
x-axis coordinate index: idx = id % nx
y-axis coordinate index: idy = id / nx
```
It is essential to carefully read and perform basic calculations based on Figure 11.1 to understand the provided formulas. This will facilitate comprehension when implementing the subsequent code logic.

_**Please note that the aforementioned formulas should be referenced against Figure 11.1. Readers are encouraged to read through and perform simple calculations based on the figure to better grasp the concepts and prepare for the upcoming code implementation.**_

## 11.2 Implementing AOI Grid Structure

In this section, we will implement the data structure, algorithms, and interfaces related to the AOI grid, based on the formulas provided in the previous section. We will organize the AOI module within a "core" module. To begin, create a "core" folder in the "mmo_game" directory and create a "grid.go" file. This file will contain the implementation of the AOI grid-related algorithms. The code is as follows:

```go
// mmo_game/core/grid.go

package core

import "sync"

/*
   A grid class within a map
*/
type Grid struct {
	GID       int             // Grid ID
	MinX      int             // Left boundary coordinate of the grid
	MaxX      int             // Right boundary coordinate of the grid
	MinY      int             // Upper boundary coordinate of the grid
	MaxY      int             // Lower boundary coordinate of the grid
	playerIDs map[int]bool    // IDs of players or objects in the current grid
	pIDLock   sync.RWMutex    // Lock to protect the playerIDs map
}

// Initialize a grid
func NewGrid(gID, minX, maxX, minY, maxY int) *Grid {
	return &Grid{
		GID:       gID,
		MinX:      minX,
		MaxX:      maxX,
		MinY:      minY,
		MaxY:      maxY,
		playerIDs: make(map[int]bool),
	}
}
```
The provided code defines the Grid data structure. Each Grid represents a cell, and a map can be divided into multiple such grids. The `MinX`, `MaxX`, `MinY`, and `MaxY` values represent the boundaries of each grid. The `playerIDs` map keeps track of the `IDs` of players or objects present within the grid. The `pIDLock` is a concurrent read-write lock that protects the `playerIDs` map. The `GID` represents the grid's unique identifier.

Please note that the `playerIDs` map uses the IDs of players or objects as keys within the map data structure. This code serves as the foundation for implementing the `AOI` grid structure and is essential for the upcoming steps in the `AOI` algorithm implementation.

The Grid class needs to possess several abilities:

（1）Adding a player to the current Grid cell, method named Add(), implemented as follows:
```go
//mmo_game/core/grid.go

// Add a player to the current grid
func (g *Grid) Add(playerID int) {
	g.pIDLock.Lock()
	defer g.pIDLock.Unlock()

	g.playerIDs[playerID] = true
}
```
(2) Removing a player from the current `Grid` cell, method named `Remove()`, implemented as follows:

```go
//mmo_game/core/grid.go

// Remove a player from the grid
func (g *Grid) Remove(playerID int) {
	g.pIDLock.Lock()
	defer g.pIDLock.Unlock()

	delete(g.playerIDs, playerID)
}
```
(3) Retrieving all player IDs stored in the current grid, method named `GetPlayerIDs()`, returning a slice of player IDs, implemented as follows:
```go
//mmo_game/core/grid.go

// Get all players in the current grid
func (g *Grid) GetPlayerIDs() (playerIDs []int) {
	g.pIDLock.RLock()
	defer g.pIDLock.RUnlock()

	for k := range g.playerIDs {
		playerIDs = append(playerIDs, k)
	}

	return
}
```

(4) Logging method for printing the current grid's self-parameter information, method named `String()`, implemented as follows:
```go
//mmo_game/core/grid.go

// Print information method
func (g *Grid) String() string {
	return fmt.Sprintf("Grid id: %d, minX:%d, maxX:%d, minY:%d, maxY:%d, playerIDs:%v",
		g.GID, g.MinX, g.MaxX, g.MinY, g.MaxY, g.playerIDs)
}
```

## 11.3 Implementing AOI Management Module

If a `Grid` is considered an element, then many `Grid` cells can compose an `AOI` (Area of Interest) region. This section defines the `AOI` management module that provides the capability to manage all grids. With the AOI management module, created `Grid` objects can be added for management.

Create a file named `aoi.go` in the `mmo_game/core` directory. This file primarily implements functionalities related to the AOI management module. Define the data structure for the AOI manager in the following code:

```go
// mmo_game/core/aoi.go
package core

/*
   AOI Management Module
*/
type AOIManager struct {
	MinX  int           // Area's left boundary coordinate
	MaxX  int           // Area's right boundary coordinate
	CntsX int           // Number of grids in the x-direction
	MinY  int           // Area's upper boundary coordinate
	MaxY  int           // Area's lower boundary coordinate
	CntsY int           // Number of grids in the y-direction
	grids map[int]*Grid // All grids present in the current area, key = grid ID, value = grid object
}

/*
   Initialize an AOI area
*/
func NewAOIManager(minX, maxX, cntsX, minY, maxY, cntsY int) *AOIManager {
	aoiMgr := &AOIManager{
		MinX:  minX,
		MaxX:  maxX,
		CntsX: cntsX,
		MinY:  minY,
		MaxY:  maxY,
		CntsY: cntsY,
		grids: make(map[int]*Grid),
	}

	// Initialize all grids in the AOI initialization area
	for y := 0; y < cntsY; y++ {
		for x := 0; x < cntsX; x++ {
			// Calculate grid ID
			// Grid number: id = idy *nx + idx (using grid coordinates to get grid number)
			gid := y*cntsX + x

			// Initialize a grid and place it in the AOI map, key is the current grid's ID
			aoiMgr.grids[gid] = NewGrid(gid,
				aoiMgr.MinX+x*aoiMgr.gridWidth(),
				aoiMgr.MinX+(x+1)*aoiMgr.gridWidth(),
				aoiMgr.MinY+y*aoiMgr.gridLength(),
				aoiMgr.MinY+(y+1)*aoiMgr.gridLength())
		}
	}

	return aoiMgr
}
```


The `AOIManager` still contains boundary coordinates such as `MinX`, `MinY`, `MaxX`, and `MaxY`, as well as `CntsX` and `CntsY` representing the number of grid cells on the x and y axes. The member attribute grids manages which grids are present in the current `AOIManager` area using a map. The keys are the `IDs of each grid, and the values are the grid objects.

The `NewAOIManager()` method is used to initialize an AOI area and returns an `AOIManager` object. During initialization, the `AOIManager` creates and calculates grid `IDs` and boundaries for all grids in the area. It creates a total of `CntsX * CntsY` grids, calculates each grid's `ID` and boundaries using the formula mentioned earlier, and adds the created grids to the grids collection.

The `AOIManager` area grid management module also includes other methods, such as obtaining the width and length of each grid in the x-axis and printing the coordinates and information of all grids in the current area. 

The code implementation is as follows:
```go
// mmo_game/core/aoi.go

// Get the width of each grid in the x-axis direction
func (m *AOIManager) gridWidth() int {
	return (m.MaxX - m.MinX) / m.CntsX
}

// Get the length of each grid in the x-axis direction
func (m *AOIManager) gridLength() int {
	return (m.MaxY - m.MinY) / m.CntsY
}

// String() method to print information
func (m *AOIManager) String() string {
	s := fmt.Sprintf("AOIManager:\nminX:%d, maxX:%d, cntsX:%d, minY:%d, maxY:%d, cntsY:%d\n Grids in AOI Manager:\n",
		m.MinX, m.MaxX, m.CntsX, m.MinY, m.MaxY, m.CntsY)
	for _, grid := range m.grids {
		s += fmt.Sprintln(grid)
	}

        return s
}
```
The above code creates an `AOI` management module, which can be understood as a 2D rectangular map containing multiple grids. The `NewAOIManager()` constructor method divides the map into multiple smaller grids and initializes their coordinates. The calculation method is straightforward and involves basic geometry calculations.

## 11.4 Calculating the Nine-Grid

An essential concept in the AOI algorithm is the "nine-grid," which involves determining all the grids adjacent to a given grid. Since a `Grid` is a quadrilateral, if the grid is not located on the boundary of the AOI area, the combination of itself with the eight other surrounding grids results in a set of nine grids. The concept of the nine-grid is primarily applied in MMO games to trigger a series of logic when a player moves from one area to another. This includes tasks such as displaying the player's field of view, transitioning maps, and determining skill ranges. Therefore, the ability to calculate the nine-grid for each grid in the AOI area is a crucial functionality that the `AOIManager` must possess.

### 11.4.1 Finding the Neighboring Grids Based on Grid ID

When determining the neighboring grids based on a given grid ID, several scenarios need to be considered:

(1) When the center grid is not on the boundary of the AOI area, the surrounding nine grids can be fully populated, as shown in Figure 11.3.


![Figure 11.3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1vioic1mc1ehl7d84boj.jpeg)

> Figure 11.3

(2) When the center grid is located at the four corners of the AOI area, as illustrated in Figure 11.4.

![Figure 11.4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/89flxdn48o0fzllzw345.jpeg)

> Figure 11.4


(3) When the center grid is located on the AOI boundary (not at the corners), the surrounding grids are missing one column or one row, as shown in Figure 11.5.


![Figure 11.5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/we7illhv6pvbkbgkh3cc.jpeg)

> Figure 11.5
All three cases can be solved using a unified algorithm. The approach involves dynamically drawing vectors. To determine the surrounding nine grids of a particular Grid, you can first check whether there are grids on the left and right sides of the same row as the current Grid. Then, perform the same check for the row above and below. This way, by performing two loops of checks, you can determine the neighboring grids around the current Grid, as illustrated in Figure 11.6.


![Figure 11.6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0rxdlcd85x5mpaq7gh42.jpeg)
> Figure 11.6

In the case depicted in Figure 11.6, with the central node being the Grid with `GID 16`, the process begins by traversing the row where the `Grid 16 is located to identify the grids to its left and right. This results in a set of Grids with `IDs 15, 16, and 17`. Next, the column where `Grid 15` is situated is examined to identify any grids above and below it, leading to a set of Grids with `IDs 10, 15, and 20`. Similar logic is applied to `Grids 11, 16, and 21`, as well as `Grids 12, 17, and 22`. The resulting set of grids, including `Grid 16` itself, forms the complete nine-grid neighborhood.

For situations where the central grid lies at a corner of the `AOI` region, as shown in Figure 11.7, the same logic applies.


![Figure 11.7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6ghfts0go5474hpxdox.jpeg)
> Figure 11.7


If a grid is located at a corner of the AOI region, the algorithm remains unchanged, except that during the first vector traversal, there will be some portion that cannot be included. For example, when dealing with a grid with GID 0 and determining its nine-grid neighborhood, the initial step involves checking if there are grids to its left and right, resulting in a set containing `IDs 0 and 1`. Next, based on grids 0 and 1, a check is performed to determine whether there are grids above and below them, leading to sets `0, 5`, and `1, 6`, respectively. The combination of these two iterations represents the nine-grid neighborhood of grid 0. Similar considerations apply to other corner cases.

The final scenario involves the central grid being located on the boundary of the AOI region. In this situation, the same approach described above can be employed to determine the nine-grid region. For instance, if we want to find the nine-grid neighborhood of grid 5, we first check if there are grids to its left and right, resulting in the set `5, 6`. Then, based on grids `5 and 6`, we respectively determine if there are grids above and below them, resulting in sets `0, 5, 10`, and `1, 6, 11`. Ultimately, all the grids that are traversed during this process constitute the nine-grid neighborhood of the central grid with GID 5.


![Figure 11.8](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pi6m4c17zijxe1clxy7c.jpeg)

The algorithm to determine the surrounding grids as described above is implemented in the function `GetSurroundGridsByGid()`and is defined in the following code:

```go
//mmo_game/core/aoi.go

// Get the surrounding nine grids information based on the given grid's GID
func (m *AOIManager) GetSurroundGridsByGid(gID int) (grids []*Grid) {
	// Check if the gID exists
	if _, ok := m.grids[gID]; !ok {
		return
	}

	// Add the current gid to the list of grids
	grids = append(grids, m.grids[gID])

	// Get the X-axis index of the current grid based on gid
	idx := gID % m.CntsX

	// Check if there's a grid to the left of the current idx
	if idx > 0 {
		grids = append(grids, m.grids[gID-1])
	}
	// Check if there's a grid to the right of the current idx
	if idx < m.CntsX-1 {
		grids = append(grids, m.grids[gID+1])
	}

	// Extract all grids from the current x-axis for further traversal,
	// and determine whether each grid has a grid above and below it

	// Get the collection of grid ids in the current x-axis
	gidsX := make([]int, 0, len(grids))
	for _, v := range grids {
		gidsX = append(gidsX, v.GID)
	}

	// Traverse the grids on the x-axis
	for _, v := range gidsX {
		// Calculate in which row the grid is located
		idy := v / m.CntsX

		// Check if there's a grid above the current idy
		if idy > 0 {
			grids = append(grids, m.grids[v-m.CntsX])
		}
		// Check if there's a grid below the current idy
		if idy < m.CntsY-1 {
			grids = append(grids, m.grids[v+m.CntsX])
		}
	}

	return
}
```
When checking for left-right and top-bottom adjacent grids in the code, it's important to consider boundary points. If the boundary conditions are not met, it indicates that there are no grids in that particular direction.


## 11.4.2 Determining the Nine Grids Based on Coordinates

Another scenario is when a player knows only their own coordinates. In this case, how can we determine which players are present in the surrounding nine grids of the player's AOI? To achieve this, we need to design an interface that calculates the players in the neighboring nine grids based on coordinates.

The overall approach is to first calculate the grid ID to which the current player's coordinates belong. Then, based on the obtained grid ID, we can retrieve information about the surrounding nine grids. This process is illustrated in Figure 11.9.

![Figure 11.9](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/41c8xch9yzrpofb0kn9s.jpeg)

> Figure 11.9

Obtaining the Current Grid ID Based on Player Coordinates is done through the interface `GetGIDByPos()`, as implemented in the following code:
```go
//mmo_game/core/aoi.go

// Get the corresponding grid ID based on x and y coordinates
func (m *AOIManager) GetGIDByPos(x, y float32) int {
	gx := (int(x) - m.MinX) / m.gridWidth()
	gy := (int(y) - m.MinY) / m.gridLength()

	return gy * m.CntsX + gx
}
```
Additionally, the interface to get all player IDs within the surrounding nine grids based on player's x and y coordinates is defined as follows:

```go
//mmo_game/core/aoi.go

// Get all PlayerIDs within the surrounding nine grids based on x and y coordinates
func (m *AOIManager) GetPIDsByPos(x, y float32) (playerIDs []int) {
	// Get the grid ID to which the coordinates belong
	gID := m.GetGIDByPos(x, y)

	// Get information about the surrounding nine grids based on grid ID
	grids := m.GetSurroundGridsByGid(gID)
	for _, v := range grids {
		playerIDs = append(playerIDs, v.GetPlayerIDs()...)
		fmt.Printf("===> grid ID : %d, pids : %v  ====", v.GID, v.GetPlayerIDs())
	}

	return
}
```
The approach of this interface is to first call `GetGIDByPos()` to determine the grid to which the coordinates belong, obtaining the GID. Next, by invoking the function `GetSurroundGridsByGid()` which retrieves information about the surrounding nine grids, it obtains the grid collection. Finally, it extracts all player ID information from each grid in the collection.

## 11.5 AOI Region Grid Addition and Deletion Operations

In this section, we will implement the grid-related addition and removal operations within the AOIManager. The implementation of these methods is relatively straightforward, as shown in the following code:

```go
//mmo_game/core/aoi.go

// Get all player IDs within a current grid based on GID
func (m *AOIManager) GetPidsByGid(gID int) (playerIDs []int) {
	playerIDs = m.grids[gID].GetPlayerIDs()
	return
}

// Remove a player ID from a grid
func (m *AOIManager) RemovePidFromGrid(pID, gID int) {
	m.grids[gID].Remove(pID)
}

// Add a player ID to a grid
func (m *AOIManager) AddPidToGrid(pID, gID int) {
	m.grids[gID].Add(pID)
}

// Add a player to a grid based on x and y coordinates
func (m *AOIManager) AddToGridByPos(pID int, x, y float32) {
	gID := m.GetGIDByPos(x, y)
	grid := m.grids[gID]
	grid.Add(pID)
}

// Remove a player from the corresponding grid based on x and y coordinates
func (m *AOIManager) RemoveFromGridByPos(pID int, x, y float32) {
	gID := m.GetGIDByPos(x, y)
	grid := m.grids[gID]
	grid.Remove(pID)
}
```

## 11.6 Unit Testing of the AOI Module

After implementing all the functionalities related to AOI, it's important to perform unit tests to verify the capabilities provided by the AOI module. Create a unit testing file named `aoi_test.go` in the mmo_game/core directory and implement two test cases: TestNewAOIManager() and `TestAOIManagerSuroundGridsByGid()`. Here's the code:

```go
//mmo_game/core/aoi_test.go

package core

import (
	"fmt"
	"testing"
)

func TestNewAOIManager(t *testing.T) {
	aoiMgr := NewAOIManager(100, 300, 4, 200, 450, 5)
	fmt.Println(aoiMgr)
}

func TestAOIManagerSuroundGridsByGid(t *testing.T) {
	aoiMgr := NewAOIManager(0, 250, 5, 0, 250, 5)

	for k, _ := range aoiMgr.grids {
		// Get the surrounding grids for the current grid
		grids := aoiMgr.GetSurroundGridsByGid(k)
		// Get all the IDs of the surrounding grids
		fmt.Println("gid : ", k, " grids len = ", len(grids))
		gIDs := make([]int, 0, len(grids))
		for _, grid := range grids {
			gIDs = append(gIDs, grid.GID)
		}
		fmt.Printf("grid ID: %d, surrounding grid IDs are %v\n", k, gIDs)
	}
}

```

The test `TestNewAOIManager()` is used to validate the construction of AOI (Area of Interest) regions and provides a detailed output of the information after AOI region construction. The test results are as follows:

```bash
AOIManager:
minX: 100, maxX: 300, cntsX: 4, minY: 200, maxY: 450, cntsY: 5
Grids in AOI Manager:
Grid id: 1, minX: 150, maxX: 200, minY: 200, maxY: 250, playerIDs: map[]
Grid id: 5, minX: 150, maxX: 200, minY: 250, maxY: 300, playerIDs: map[]
Grid id: 6, minX: 200, maxX: 250, minY: 250, maxY: 300, playerIDs: map[]
Grid id: 12, minX: 100, maxX: 150, minY: 350, maxY: 400, playerIDs: map[]
Grid id: 19, minX: 250, maxX: 300, minY: 400, maxY: 450, playerIDs: map[]
Grid id: 7, minX: 250, maxX: 300, minY: 250, maxY: 300, playerIDs: map[]
Grid id: 8, minX: 100, maxX: 150, minY: 300, maxY: 350, playerIDs: map[]
Grid id: 10, minX: 200, maxX: 250, minY: 300, maxY: 350, playerIDs: map[]
Grid id: 11, minX: 250, maxX: 300, minY: 300, maxY: 350, playerIDs: map[]
Grid id: 15, minX: 250, maxX: 300, minY: 350, maxY: 400, playerIDs: map[]
Grid id: 18, minX: 200, maxX: 250, minY: 400, maxY: 450, playerIDs: map[]
Grid id: 0, minX: 100, maxX: 150, minY: 200, maxY: 250, playerIDs: map[]
Grid id: 3, minX: 250, maxX: 300, minY: 200, maxY: 250, playerIDs: map[]
Grid id: 4, minX: 100, maxX: 150, minY: 250, maxY: 300, playerIDs: map[]
Grid id: 14, minX: 200, maxX: 250, minY: 350, maxY: 400, playerIDs: map[]
Grid id: 16, minX: 100, maxX: 150, minY: 400, maxY: 450, playerIDs: map[]
Grid id: 2, minX: 200, maxX: 250, minY: 200, maxY: 250, playerIDs: map[]
Grid id: 9, minX: 150, maxX: 200, minY: 300, maxY: 350, playerIDs: map[]
Grid id: 13, minX: 150, maxX: 200, minY: 350, maxY: 400, playerIDs: map[]
Grid id: 17, minX: 150, maxX: 200, minY: 400, maxY: 450, playerIDs: map[]
```
The test `TestAOIManagerSuroundGridsByGid()` validates whether the results of the surrounding grid calculations for the nine-grid system are correct. The execution results are as follows:

```bash
gid :  3  grids len =  6
grid ID: 3, surrounding grid IDs are [3 2 4 8 7 9]
gid :  5  grids len =  6
grid ID: 5, surrounding grid IDs are [5 6 0 10 1 11]
gid :  6  grids len =  9
grid ID: 6, surrounding grid IDs are [6 5 7 1 11 0 10 2 12]
gid :  11  grids len =  9
grid ID: 11, surrounding grid IDs are [11 10 12 6 16 5 15 7 17]
gid :  18  grids len =  9
grid ID: 18, surrounding grid IDs are [18 17 19 13 23 12 22 14 24]
gid :  2  grids len =  6
grid ID: 2, surrounding grid IDs are [2 1 3 7 6 8]
gid :  4  grids len =  4
grid ID: 4, surrounding grid IDs are [4 3 9 8]
gid :  7  grids len =  9
grid ID: 7, surrounding grid IDs are [7 6 8 2 12 1 11 3 13]
gid :  8  grids len =  9
grid ID: 8, surrounding grid IDs are [8 7 9 3 13 2 12 4 14]
gid :  19  grids len =  6
grid ID: 19, surrounding grid IDs are [19 18 14 24 13 23]
gid :  22  grids len =  6
grid ID: 22, surrounding grid IDs are [22 21 23 17 16 18]
gid :  0  grids len =  4
grid ID: 0, surrounding grid IDs are [0 1 5 6]
gid :  1  grids len =  6
grid ID: 1, surrounding grid IDs are [1 0 2 6 5 7]
gid :  13  grids len =  9
grid ID: 13, surrounding grid IDs are [13 12 14 8 18 7 17 9 19]
gid :  14  grids len =  6
grid ID: 14, surrounding grid IDs are [14 13 9 19 8 18]
gid :  16  grids len =  9
grid ID: 16, surrounding grid IDs are [16 15 17 11 21 10 20 12 22]
gid :  17  grids len =  9
grid ID: 17, surrounding grid IDs are [17 16 18 12 22 11 21 13 23]
gid :  23  grids len =  6
grid ID: 23, surrounding grid IDs are [23 22 24 18 17 19]
gid :  24  grids len =  4
grid ID: 24, surrounding grid IDs are [24 23 19 18]
gid :  9  grids len =  6
grid ID: 9, surrounding grid IDs are [9 8 4 14 3 13]
gid :  10  grids len =  6
grid ID: 10, surrounding grid IDs are [10 11 5 15 6 16]
gid :  12  grids len =  9
grid ID: 12, surrounding grid IDs are [12 11 13 7 17 6 16 8 18]
gid :  15  grids len =  6
grid ID: 15, surrounding grid IDs are [15 16 10 20 11 21]
gid :  20  grids len =  4
grid ID: 20, surrounding grid IDs are [20 21 15 16]
gid :  21  grids len =  6
grid ID: 21, surrounding grid IDs are [21 20 22 16 15 17]
PASS
ok  	zinx/zinx_app_demo/mmo_game/core	0.002s
```

The results of `TestAOIManagerSuroundGridsByGid()` match the structure shown in Case and Figure 11.2. The validation confirms that the nine-grid calculation results align with the depicted structure in the diagram.


---
### MMO-GAME Source Code
https://github.com/aceld/zinx/tree/master/zinx_app_demo/mmo_game

---
### [Zinx]

[<1.Building Basic Services with Zinx Framework>](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)
[<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>](https://dev.to/aceld/2-zinx-v02-simple-connection-encapsulation-and-binding-with-business-4p9g)
[<3.Design and Implementation of the Zinx Framework's Routing Module>](https://dev.to/aceld/3design-and-implementation-of-the-zinx-frameworks-routing-module-583n)
[<4.Zinx Global Configuration>](https://dev.to/aceld/4zinx-global-configuration-3jam)
[<5.Zinx Message Encapsulation Module Design and Implementation>](https://dev.to/aceld/5zinx-message-encapsulation-module-design-and-implementation-3fmc)
[<6.Design and Implementation of Zinx Multi-Router Mode>](https://dev.to/aceld/6design-and-implementation-of-zinx-multi-router-mode-19nn)
[<7. Building Zinx's Read-Write Separation Model>](https://dev.to/aceld/7-building-zinxs-read-write-separation-model-ihj)
[<8.Zinx Message Queue and Task Worker Pool Design and Implementation>](https://dev.to/aceld/8zinx-message-queue-and-task-worker-pool-design-and-implementation-12gl)
[<9. Zinx Connection Management and Property Setting>](https://dev.to/aceld/9-zinx-connection-management-and-property-setting-28ha)

### [Zinx Application - MMO Game Case Study]

[<10. Application Case Study using the Zinx Framework>](https://dev.to/aceld/10-application-case-study-using-the-zinx-framework-3ich)
[<11. MMO Online Game AOI Algorithm>](https://dev.to/aceld/11-mmo-online-game-aoi-algorithm-l7d)
[<12.Data Transmission Protocol: Protocol Buffers>](https://dev.to/aceld/12data-transmission-protocol-protocol-buffers-1jek)
[<13. MMO Game Server Application Protocol>](https://dev.to/aceld/13-mmo-game-server-application-protocol-5adl)
[<14. Building the Project and User Login>](https://dev.to/aceld/14-building-the-project-and-user-login-b7p)
[<15. World Chat System Implementation>](https://dev.to/aceld/157-world-chat-system-implementation-18li)
[<16. Online Location Information Synchronization>](https://dev.to/aceld/16-online-location-information-synchronization-3cp1)
[<17. Moving position and non-crossing grid AOI broadcasting>](https://dev.to/aceld/17-moving-position-and-non-crossing-grid-aoi-broadcasting-4nb9)
[<18. Player Logout>](https://dev.to/aceld/18-player-logout-3eln)
[<19. Movement and AOI Broadcast Across Grids>](https://dev.to/aceld/19-movement-and-aoi-broadcast-across-grids-46fe)

---
Author:
discord: https://discord.gg/xQ8Xxfyfcz
zinx:   https://github.com/aceld/zinx
github: https://github.com/aceld
aceld's home: https://yuque.com/aceld