The damage map

The damage map

> Programming

Along with evaluating moves and handling attack combinations, the damage map is one of the great essentials of Leek Wars. Its main goal is to rate how dangerous each cell is, depending on the opponent, so that you can fall back to a less dangerous cell at the end of your turn. The simplest way is to create an associative array: dangerMap=[ cell1:danger1 , cell2:danger2 , ... ], possibly limited to your accessible cells. Several algorithms can do this.

The "naive" algorithm

This is usually the first idea that comes to mind when you want to build a danger evaluation algorithm. The idea is the following:

/* -for each of my accessible cells -for each opponent -for each accessible cell of the opponent -for each weapon of the opponent -if they can shoot at my cell from their cell add a "danger score" to my cell */

The choice of score is an integral part of your algorithm: you can simply add "+1", or compute the potential damage dealt, ... There are as many ways to refine this evaluation as there are players 😉

The big problem with this algorithm is that it is extremely greedy in operations! So you will usually need to optimize it very quickly.

Optimization ideas

The first thing to do is to keep your leek from "crashing"... So, somewhere in your code, you need to check getOperations() and break out of the loops if the number of operations gets too close to the limit: you will only get a partial evaluation of the danger, but your leek will still be able to move a little 😉

As for the optimization itself, one of the first principles that should guide you is, above all, to avoid redoing the same operations several times inside loops: in that case, it is better to prepare things in arrays and reuse them later. For example, in the innermost loop that computes the "danger score", if you put a call to getStrength(enemy) in there, you will call this function several thousand times. The idea is then to compute the enemy's strength only once and store it in a variable before running the inner loops. That alone will save you a few hundred thousand operations! So hunt down the parts of the "inner" loops that don't depend on the loop variable, but only on an "outer" loop: you can then move these parts up to that loop.

If your algorithm is still expensive, another fairly elegant optimization is to lower the resolution of this evaluation. For example, instead of testing all the accessible cells (yours and/or the opponent's), you can test only those that require an even number of movement points. Then you "just" need to fill in the gaps by interpolating the values of the neighboring cells (average of the neighbors, maximum of the neighbors, maximum of the average between two rows, ...). This approach is less accurate but, with the even-MP example, uses about 4x fewer operations than the first algorithm.

To push the optimizations further, you will need to think about precomputing things, usually on the first turn: storing in big (or many) arrays the things you would otherwise keep recomputing and that never change, or even updating some values each turn (in our example, the strength of the enemy leeks).

The "once and for all" algorithm

This algorithm precomputes a danger map only once, at the start of the fight. It is cheap but much less accurate, yet it quickly gives you the list of the areas of the fight map that are dangerous overall. It consists in evaluating, on the first turn, for each cell of the fight map, all the cells from which it can be shot with the weapons of the enemy (or enemies): a cell that can only be reached from 5 other cells will then get a much lower score than a cell that can be reached from 200 other cells! It may be wise to try to fall back to the "low danger" areas, and to lure the enemy into staying in the "high danger" areas.