# A * search for the road gamedev.net

Source: Internet
Author: User

MULINB by: The classic intelligent pathfinding algorithm, a foreigner wrote very thorough very clear, it is easy to let people understand the mysterious A * algorithm. The following is a Chinese translation version.

A * search for the road gamedev.net

Patrick Lester

Translator: Panic March 18, 2005

Translator: A * algorithm was known a long time ago, but never read the relevant article, nor read the code, but the mind has a vague concept. This decision starts from scratch and studies the simple method of being praise as the beginning of learning AI.

This article is very well-known, there should be a lot of people to translate it, I did not find, I think the translation itself is the level of their own English exercise. After hard work, finally finished the document, also understand the principle of a * algorithm. Undoubtedly, the author of the image of the description, concise and witty language to the simple and easy to tell the magic of the algorithm, I believe that everyone who read it will be aware of this (if not, it is my translation is too poor--b).

Now is the July 19, 2005 version, the original author's request, the text of some of the algorithm details have been modified.

The following is the text of the translation.

is not difficult, a * (read as A Star) algorithm for beginners is indeed A bit difficult.

This article does not attempt to make an authoritative statement on this topic. Instead, it simply describes the principles of the algorithm so that you can understand other relevant information in a further reading.

Finally, this article has no procedural details. You can do it in any computer programming language. As you wish, I have included a link to the example program at the end of the article. The ZIP package includes versions of the C + + and blitz basic two languages, and if you just want to see how it works, it also contains the executable file.

We are improving ourselves. Let's start from the beginning ...

Order: Search area

Suppose someone wants to move from point A to point B of separated, for example, Green is the starting point A, red is the end point B, and the Blue Square is the middle wall.

[Figure 1]

You first notice that the search area is divided into square grids. Simplifying the search area, like this, is the first step in finding a way. This method simplifies the search area into a two-dimensional array. Each element of the array is a square of the grid, and the squares are marked as accessible and non-approved. The path is described as the set of squares we pass from A to B. Once the path is found, our people move from one square center to another until they reach their destination.

These midpoints are referred to as "nodes". When you read other pathfinding data, you will often see people talking about nodes. Why not describe them as squares? Because it is possible that your path is divided into other structures that are not squares. They can be rectangular, hexagonal, or any other shape. Nodes can be placed anywhere in the shape-either in the center, or along the boundary, or somewhere else. We use this system anyway, because it is the simplest.

Start Search

As we approach the grid, once the search area is transformed into an easy-to-handle node, the next step is to guide a search that finds the shortest path. In the A * pathfinding algorithm, we check the way of adjacent squares from point A, and scale out until we find the target.

We do the following to start the search:

1, start with point A and put it as a pending point into an "open list". Opening a list is like a shopping list. Although there is only one element in the list, there will be more in the future. Your path may pass through the squares it contains, or it may not. Basically, this is a list of squares to check.
2, look for all reachable or accessible squares around the starting point, skipping walls, water, or other squares that cannot pass through the terrain. Also add them to the open list. Save point A as a "parent square" for all of these squares. When we want to describe the path, the data of the parent square is very important. The specific purpose of this will be explained later.
3, remove point A from the open list, add it to a "close list", and save all squares in the list that do not need to be checked again.

At this point, you should form the structure. In the picture, the dark green squares are the center of your starting square. It is stroked with a light blue color to indicate that it is added to the closed list. All adjacent cells are now in the open list, and they are stroked with a light green color. Each square has a gray pointer that refers to their parent square, which is the starting square.

[Figure 2]

Next, we choose to open the adjacent squares in the list, roughly repeating the previous procedure, as follows. But what is the grid we want to choose? That's the lowest F value.

Path scoring

The key to choosing which squares to go through in the path is the following equation:

F = G + H

Over here:
* G = move from Start a, along the resulting path, to the specified squares on the grid.
* H = estimated movement cost of moving from that square on the grid to end B. This is often referred to as heuristic, and may make you a little confused. That's why it's called because it's just a guess. We have no way of knowing the length of the path beforehand, because there may be various obstacles (walls, water, etc.). Although this article only provides a way to calculate h, you can find many other methods on the Web.

Our path is generated by iterating through the open list and selecting the squares with the lowest f value. The article will describe the process in more detail. First, let's look more deeply at how to calculate this equation.

As stated above, G represents the cost of moving along a path from the starting point to the current point. In this example, we make the horizontal or vertical movement cost 10, and the diagonal direction is 14. We take these values because the distance along the diagonal is the square root 2 (not afraid) that moves horizontally or vertically, or about 1.414 times times. To simplify, we approximate with 10 and 14. The proportions are basically correct, and we avoid finding root operations and decimals. It's not just because we're afraid of trouble or we don't like maths. Using such integers is also faster for the computer. You will find that if you do not use these simplified methods, the pathfinding will become very slow.

Now that we are calculating the G value to a square along a particular path, the method of evaluating it is to take the G value of its parent node, and then increase by 14 and 10, respectively, by its diagonal or right-angled direction (non-diagonal) relative to the parent node. In the example, the demand for this method becomes more, because we get more than one square from the starting square.

H values can be estimated in different ways. The method we use here is called the Manhattan method, which calculates the sum of the horizontal and vertical squares from the current grid to the destination, ignoring the diagonal direction. Then multiply the result by 10. This is the Manhattan approach because it looks like the number of blocks from one place to another in the city, where you can't cross the block diagonally. It is important that we neglect all the obstacles. This is an estimate of the remaining distance, not the actual value, which is why this method is called heuristic. Want to know more? You can find equations and additional annotations here.

The value of f is the and of the G and H. The results of the first step search can be seen in the chart below. The scores of F,g and H are written in each square. As indicated by the squares immediately to the right of the starting lattice, F is printed in the upper left corner, G in the lower left corner, and h in the lower right corner.

[Figure 3]

Now let's take a look at these squares. The letter-writing box, G = 10. This is because it deviates only horizontally from the starting lattice. The G value of the lower and left squares is equal to 10, immediately above the start grid. The G value in the diagonal direction is 14.

The H value is obtained by solving the Manhattan distance to the red target, which moves only horizontally and vertically, and ignores the middle wall. In this way, the square to the right of the starting point is 3 blocks from the Red Square and the H value is 30. The square above the square has 4 blocks (remember, it can only be moved horizontally and vertically), and H value is 40. You should generally know how to calculate the h value of other squares.

The F-value of each lattice, or simply the addition of G and H

Continue Search

To continue the search, we simply select the lowest f-value squares from the open list. Then, do the following with the selected squares:

4, remove it from the open list, and then add it to the close list.
5, check all adjacent squares. Skip those that are already in the closed list or not (with walls, water, or other inaccessible terrain) and add them to the open list if they are not there. Use the selected squares as the parent of the new squares.
6, if an adjacent lattice is already open in the list, check if the current path is better. In other words, check if we reach it with a new path, will the G value be lower. If not, then do nothing.
On the other hand, if the new G value is low, change the parent node of the adjacent square to the currently selected square (in the chart above, change the direction of the arrow to the square). Finally, the values of f and g are recalculated. If this doesn't seem clear enough, you can look at the illustration below.

Well, let's see how it works. In our original 9 squares, 8 blocks left in the open list after the start point was switched to the off list. In this, the lowest f value is the lattice to the right of the starting lattice, and its F value is 40. So we chose this as the next square to be processed. In the diagram that follows, it is highlighted in blue.

[Figure 4]

First, we take it out of the open list and put it in the close list (that's why he was highlighted in blue). Then we check the adjacent lattice. Oh, the lattice on the right is the wall, so we skipped over. The lattice on the left is the start grid. It is in the closed list, so we also skip it.

The other 4 grids are already open in the list, so we check the G value to determine if it is better to get there through this grid. Let's look at the grid below the check box. Its G-value is 14. If we move from the current grid to there, the G value will be equal to 20 (the G value reaching the current lattice is 10, and moving to the upper lattice will increase the G value by 10). Because the G value 20 is greater than 14, this is not a better path. If you look at the picture, you can understand it. Rather than moving horizontally one grid at a level and moving vertically one, it's easier to move directly diagonally.

When we repeat this process for the 4 adjacent squares that already exist in the open list, we find that no path can be improved by using the current lattice, so we do not make any changes. Now that we've checked all the neighboring cells, we can move to the next one.

So we retrieved the open list, and now we have only 7 cells in it, and we still choose the lowest F value. Interestingly, this time, there are two squares of the values are 54. How do we choose? It's not a hassle. In terms of speed, it is quicker to select the last grid to add to the list. This leads to the preference of the newly found lattice when approaching the target in the process of seeking the path. But it doesn't matter. (Different treatment of the same value causes different versions of a * algorithm to find different paths of equal length.) ）

Then we'll select the grid at the bottom right of the starting lattice.

[Figure 5]

This time, when we checked the adjacent lattice, we found that the right side was a wall, so we skipped over. The upper one is also skipped. We also skipped over the lattice below the wall. Why is it? Because you can't go straight to that grid without crossing the corner. You really need to go down and get to that one and step through that corner. (Note: Rules that traverse corners are optional.) It depends on how your node is placed. )

In this way, there are 5 other cells left. The other two squares below the current grid are not currently in the open list, so we add them and designate the current grid as their parent. The remaining 3 squares, two are already in the closed list (the start grid, and the lattice above the current grid, highlighted in blue in the table), so we skipped over them. The last one, on the left side of the current lattice, is checked through this path, and the G value is lower. Don't worry, we're ready to check the next box in the Open list.

We repeat this process until the target is added to the close list (note), as seen in the figure below.

[Figure 6]

Notice that the parent node of the grid below the start lattice is different from the previous. Its G-value was 28 and pointed to the upper-right lattice. Now its G value is 20, pointing to the lattice above it. This occurs somewhere in the pathfinding process, and when the new path is applied, the G value is checked for low-so the parent node is re-specified and the G and F values are recalculated. Although this change is not important in this case, in many cases, this change can lead to great changes in the search results.

So, how do we determine this path? Very simple, start with the red target, and move in the direction of the arrow toward the parent node. This will eventually lead you back to the start grid, which is your path! It should look like that in the picture. Moving from start cell A to target B is simply a simple move from the midpoint of each lattice (node) to the next, until you reach the target point. It's so simple.

[Figure 7]

A * Method summary

Well, now that you've finished the whole description, let's write each step together:

1. Add the start grid to the open list.
2, repeat the following work:
A) Look for the grid with the lowest F value in the Open list. We call it the current lattice.
b) switch it to the off list.
c) For each of the adjacent 8 cells?
* If it is not available or is already in the close list, skip it. The reverse is as follows.
* If it is not in the open list, add it. Use the current grid as the parent node for this grid. Record the f,g, and H values for this grid.
* If it is already in the open list, it is better to check the new path with the G value for reference. A lower G-value means a better path. If so, change the parent node of the grid to the current grid and recalculate the G and F values of the grid. If you keep your open list sorted by F-value, you may need to re-order the open list after the change.

d) Stop, when you
* Add the target to the close list (note), when the path is found, or
* No target grid found, open list is empty. At this time, the path does not exist.
3. Save the path. Starting from the target, move along the parent node of each cell until you return to the start grid. This is your path.

(: In earlier versions of this article, the recommended practice is to stop pathfinding when the target grid (or node) is added to the open list instead of closing the list. This will be faster, and almost always find the shortest path, but not absolute. When the move from the penultimate node to the last (target node) is very large--for example, there is a river crossing two nodes, the old practice and the new approach will be significantly different. )

Off Topic

It is worth mentioning that when you see different discussions about a * on the Internet or in related forums, you sometimes see code that is treated as a * algorithm and actually they are not. To use a *, you must include all the elements discussed above--specific open and close lists, with F,g and H as the path evaluation. There are many other pathfinding algorithms, but they are not a*,a* are considered the best of them. Bryan Stout part of the reference document later in this article, including some of their pros and cons. Sometimes the other algorithms are better on a particular occasion, but you have to be very specific about what you are doing. All right, that's enough. Back to the article.

implemented Annotations

Now that you understand the fundamentals, there are some extra things to consider when writing your program. Some of the following materials refer to my programs written in C + + and blitz Basic, but are also valid for code written in other languages.

1. Other units (Collision avoidance):

If you happen to see my example code, you'll see that it completely ignores the other units. My seeker can actually cross each other. Depending on the specific game, this may or may not be possible. If you are going to consider other units and want them to be able to bypass each other, I recommend that you only consider stationary or those units that are near the current unit when calculating the path, and mark their current position as being available. For nearby moving units, you can encourage these seekers to find different paths by punishing the nodes on their respective paths (more on the description, see page #).

If you choose to take into account other units that are moving and away from the current pathfinding unit, you will need to implement a method to predict when and where the collision may occur in order to properly avoid it. Otherwise, you will most likely get a weird path where the unit suddenly turns to avoid collisions with a unit that doesn't already exist.

Of course, you also need to write some code for collision detection, because it will change with time regardless of how perfect the path is when calculating. When a collision occurs, a unit must look for a new path, or, if another unit is moving and not a frontal collision, wait for that unit to leave before continuing to move along the current path.

These tips will probably get you started. If you want to learn more, here are some links you might find useful:

* Guiding behavior of autonomous roles: Craig Reynold's work on mentoring is somewhat different from pathfinding, but it can be integrated with pathfinding to form a more complete motion and collision detection system.
* Short distance guide in computer games: An interesting study of the work of guidance and pathfinding. This is a PDF file.
* Collaborative Unit movement: the first of a two-part series of articles on formations and group-based movement, the author of the Age of Empires, Dave Pottinger.
* Achieve Collaborative Mobility: The second chapter of the Dave Pottinger article series.

2. Different loss of terrain:

In this tutorial and my accompanying program, the terrain can only be both-accessible and not possible. But you may need some accessible terrain, but the move is expensive-swamp, hill, dungeon Staircase, and so on. These are the more expensive terrain that can be moved by but more than flat open spaces. Similarly, roads should be less expensive to move than natural terrain.

This problem is easy to solve, as long as you calculate the G value of any terrain to increase the loss of the terrain. Simply add some extra loss to it. Because A * algorithm has been designed to look for the lowest-cost path, it is easy to handle this situation. In this simple example I provide, the terrain can only be passed and not be passed through two, a * will find the shortest, most direct path. But where the terrain is expensive, the lowest-cost path may contain a long moving distance-as if bypassing the swamp along the way rather than directly through it.

An additional consideration is what the expert calls "influence mapping" (tentatively translated as an impact map). Just like the different terrain costs described above, you can create an extra fractional system and apply it to the pathfinding AI. Suppose you have a map with a large number of seekers who are going through a mountainous area. Every time a computer generates a path through that gateway, it becomes more crowded. If you want, you can create an impact map to adversely affect the lattice that has a large number of massacres. This makes the computer more prone to safer paths and helps it avoid consistently sending teams and pathfinding to a specific path simply because the path is short (but potentially more dangerous).

Another possible application is to punish the nodes that are on the path around the moving unit. One of the bottom limits of a * is that when a group of units simultaneously tries to find a way to a close location, this usually causes the path to overlap. Think that one or more units are trying to get to the same or approximate path to the destination. Adding some penalties to nodes that have been "claimed" by other units can help you isolate the path to a certain extent and reduce the likelihood of collisions. However, if necessary, do not look at those nodes as unreachable, because you still want multiple units to be able to go through the crowded exits in a single column. At the same time, you can only punish the paths of the neighboring units, not all the paths, otherwise you will get strange evasion behaviors such as units that are not already there in the unit Dodge path. Also, you should only punish the current and subsequent nodes of the path, instead of dealing with the nodes that have been traversed and left behind.

3. Handling Unknown Areas:

Have you ever played such a PC game that the computer always knows which way is right, even if it hasn't detected a map yet? For the game, finding a way too good will appear unreal. Fortunately, this is a problem that can be easily solved by a lattice.

The answer is to create a separate "knownwalkability" array for each of the different players and computers (each player, not each unit-that would consume a lot of memory), each containing an area that the player has explored, and other areas that are considered to be available through the area until it is confirmed. In this way, the unit hovers over the dead end of the road and leads to the wrong choice until they find their way around. Once the map has been explored, pathfinding is carried out as usual.

4. Smooth path:

Although A * provides the shortest, lowest-cost path, it does not automatically provide a path that looks smooth. Take a look at our example of the resulting path (in Figure 7). The first step is the bottom right of the start grid, and if this step is straight down, will the path be smoother?

There are several ways to solve this problem. When the path is calculated, it can adversely affect the direction of the lattice, adding an additional value to the G value. You can also do this in a different way, and you can run along it after the path has been calculated, looking for places where replacing the adjacent lattice will make the path look smoother. For full results, view toward more realistic pathfinding, one (free, but need to register) Marco Pinter published in Gamasutra.com's article

5. Non-square search area:

In our example, we use a simple 2D square chart. You can not use this method. You can use an irregularly shaped area. Think of adventure chess games, and games in those countries. You can design a path-finding level like that. To do this, you may need to create a table of neighboring relationships in a country, and a G value to move from one country to another. You also need to estimate the H value method. The other things are exactly the same as in the example. When you need to add new elements to the open list, you don't need to use adjacent grids, instead of looking for neighboring countries from the table.

Similarly, you can create a path-point system for a certain topographic map, which is usually the turning point of the road, or Dungeon Channel. As a game designer, you can preset these path points. Two path points are considered adjacent if there are no obstacles in the line between them. In the case of adventure Chess, you can save the adjacent information in a table and use it when you need to add elements to the open list. You can then record the associated G-value (which may use a straight-line distance between two points), the H-value (which can be used to the straight-line distance to the target point), and the others as you originally did.

Amit Patel wrote a summary of other methods. Another example of searching for RPG maps in non-square areas, view my article two-tiered a * pathfinding. (Translator: * Layered pathfinding)

6. Some speed tips:

When you develop your own a * program, or rewrite mine, you will find that the pathfinding takes up a lot of CPU time, especially on large maps where a large number of objects are searching for the road. If you have read other materials on the Web, you will understand that even the development of StarCraft or empire-era experts, there is no alternative. If you find the path too slow, here are some suggestions that might work:

* Use smaller maps or fewer seekers.

* Do not seek paths for multiple objects at the same time. Instead, they are added to a queue and the pathfinding process is scattered over several game cycles. If your game runs at a rate of 40 cycles per second, no one can detect it. But when a large number of Pathfinder calculate their own path, they will find that the game speed suddenly slowed.

* Try to use a larger map grid. This reduces the total number of meshes searched in the pathfinding. If you have ambition, you can design two or more pathfinding systems for use in different situations, depending on the length of the path. It is also the practice of professionals to calculate long paths with large areas and then switch to fine pathfinding using small squares/areas when approaching targets. If you are interested in this view, check out my article two-tiered a * pathfinding. (Translator: * Layered pathfinding)

* Use the path point system to calculate the long path, or pre-calculate the path and join the game.
* Preprocess your map to indicate which areas of the map are unreachable. I call these areas "islands". In fact, they can be an island or any other inaccessible area surrounded by walls. The lower limit of a * is that when you tell it to find the path to those areas, it searches the entire map until all the reachable squares/nodes are computed by opening the list and closing the list. This can waste a lot of CPU time. This can be avoided by pre-identifying these areas (for example, through Flood-fill or similar methods), logging the information with certain kinds of arrays, and checking it before starting the pathfinding.
* In a crowded maze-like situation, the inability to connect nodes as dead end. These areas can be specified manually in the map editor, or if you have ambitions, develop an algorithm that automatically identifies these areas. All nodes of a given dead end can be given a unique number of flags. Then you can safely ignore all the dead ends during the pathfinding process, and only need to consider them when the starting point or end point happens to be at a node in the dead end.

7. Maintain the Open list:

This is the most important part of a * pathfinding algorithm. Every time you access the open list, you need to look for the lowest f-value squares. There are several different ways to achieve this. You can save the path element at will and traverse the open list when you need to find the element with the lowest f value. It's simple, but it's too slow, especially for long paths. This can be improved by maintaining a well-ordered list of squares, each looking for a square with the lowest F value to select only the first element of the list. This approach was my first choice when I realized it myself.

On the small map. This approach works fine, but it's not the quickest solution. The more demanding A * programmer uses a method called a two-fork heap, which is the method I use in my code. In my experience, this method is twice as fast in most cases and increases exponentially (more than 10 times times faster) in the long-run speed. If you want to learn more about binary stacks, check out my article, Using binary heaps in a * pathfinding. (Translator: Use two fork heap in A * pathfinding)

Another possible bottleneck is the way you purge and save your data structures between multiple pathfinding. I personally prefer to store everything in an array. While nodes can be dynamically generated, recorded and saved in an object-oriented style, I find that the amount of time that is added to creating and deleting objects, as well as the excess management hierarchy slows down the entire process. However, if you use an array, you need to clean up the data between calls. The last thing you want to do in this situation is to take the time to get everything back to zero after the pathfinding call, especially when your map is big.

I avoid this overhead by using a two-dimensional array called whichlist (x, y), and each element of the array indicates whether the node is in the open list or in the closed list. After trying to find the way, I didn't clear 0 of this array. Instead, I reset the values of Onclosedlist and onopenlist in the new pathfinding, with two each seeking +5 or something similar. In this way, the algorithm can safely skip the dirty data left by the front pathfinding. I also stored values such as F,g and h in the array. In this way, I simply rewrite any existing values without being disturbed by the operation of clearing the array. Storing the data in a multidimensional array requires more memory, so here are the pros and cons. Finally, you should use your most handy method.

8. Dijkstra algorithm:

Although A * is considered to be the usual best pathfinding algorithm (see the previous "digression"), there is an additional algorithm that has its desirable-dijkstra algorithm. The Dijkstra algorithm and A * essence are the same, only a little different, that is, the Dijkstra algorithm has no heuristic (h value is always 0). Because there is no heuristic, it searches on average in all directions. As you might expect, because the Dijkstra algorithm usually explores a larger area before it finds a target, it is generally slower than a * *.

So why use this algorithm? Because sometimes we don't know where the target is. For example, if you have a resource acquisition unit, you need to acquire some kind of resource. It may know several resource areas, but it wants to go to the nearest one. In this case, the Dijkstra algorithm is more suitable than a * because we don't know which one is closer. With a *, our only option is to make the path and calculate the distance for each target in turn, and then select the nearest path. The target we are looking for may have countless positions, we just want to find the nearest one, and we don't know where it is or what's nearest.

OK, now you have a preliminary understanding of some further points of view. At this point, I suggest you study my source code. The package contains two versions, one written in C + + and the other with Blitz Basic. By the way, the two versions are all annotated and easy to read, here is the link.

* Example code: * Pathfinder (2D) Version 1.9

If you don't use C + + or blitz Basic, there are two small executables in the C + + version. Blitz Basic can be run on a Blitz basic 3D (not Blitz Plus) demo from the Blitz Basic website for free download. Ben O ' Neill provides an online demo that can be found here.

You should also look at the following pages. After reading this tutorial, they should become much easier to understand.

* Amit A * page: This is made by Amit Patel, widely quoted page, if you do not read this article in advance, it may be a little difficult to understand. Worth a look. In particular, see Amit's own views on the subject.
* Smart Moves: Smart pathfinding: Bryan Stout published in gamasutra.com This article needs to be registered to read. Registration is free and is very good value for money compared to this article and other resources of the site. Bryan with Delphi program help me to learn a *, but also my A * code inspiration source. It also describes several variations of a *.
* Terrain Analysis: This is a high-level, but interesting topic, Dave Pottinge wrote, Ensemble Studios expert. This guy was involved in the development of Empires and Kings. Don't expect to understand everything here, but this is an interesting article that might make you think about it. It contains some ideas for mip-mapping,influence mapping and some other advanced ai/pathfinding. The discussion of "flood filling" gave me the inspiration for my own "dead End" and "island" code, which is contained in my blitz version of the code.

Some other sites worth looking at:

• Aiguru:pathfinding
• Game AI resource:pathfinding
• GameDev.net:Pathfinding

I also highly recommend the following books, which have many articles about pathfinding and other AI topics. They also come with a CD with the instance code attached. I have bought all the books. Plus, if you buy them through the links below, I'll get a few cents from Amazon. :)

Well, that's all. If you happen to write a program that uses these ideas, I'd like to read it. You can contact me like this:

Before that, good luck!

A * search for the road gamedev.net

Related Keywords:

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

## A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

• #### Sales Support

1 on 1 presale consultation

• #### After-Sales Support

24/7 Technical Support 6 Free Tickets per Quarter Faster Response

• Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.