Survival Game - AlienWar
Manager
Here's a manager. Naturally, the cleverness is 0 and he always strives to go where the sun don't shine. And, of course, he will only fight the weak and is very good in dodging trouble:
public class Manager extends Alien {
private static final int STRENGTH = 5;
@Override
public void setAbilityPoints( float[] abilities ) {
abilities[/* strength */ 1] = STRENGTH;
abilities[/* defense */ 2] = 5;
abilities[/* cleverness */ 4] = 0; // just to make sure
}
@Override
public Move move( char[][] fields ) {
return Move.WEST;
}
@Override
public boolean wantToFight( int[] enemyAbilities ) {
return enemyAbilities[1] < STRENGTH;
}
}
CartographyLongVisionAlien
This Alien is a consistant performing variant of one of my attempts to produce an alien that is of winning potential.
It has initial 5x5 vision which it uses to build up an internal map of the surrounding area giving it superior enemy avoidance ability.
In my testing of 100 rounds, on average, It sneaks ahead of all other aliens. (09/07/2014)
UPDATED GIF showing the repell/attract field effect
I have modified the Game code to produce animated GIFs of the simulation. You can view such a simluation here
package alien;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import planet.Move;
import planet.Planet;
/**
* Created by moogie on 09/07/14.
*
* This Alien attempts to map the visible and guess the movements within the immediate non-visible area around the alien.
* To this end, the alien can initially see 5x5 grid. It applies weights on the cells of its internal map based on the
* prey/alien/blanks within its field of view. It then performs a simple blur to guess movements and then chooses the optimum move
* based on the contents of its internal map.
*
* If it is asked to fight, it performs battle simulations to determine whether it should nominate to fight.
*/
public class CartographerLongVisionAlien extends Alien
{
private final static byte LIFE = 0, STR = 1, DEF = 2, VIS = 3, CLV = 4;
// Plant Entity symbols
private static final char TURTLE = 'T';
private static final char HUMAN = 'H';
private static final char WHALE = 'W';
private static final char COW = 'C';
private static final char EAGLE = 'E';
private static final char ALIEN = 'A';
private static final HashMap<Character, Move> preyMovement = new HashMap<>();
static
{
preyMovement.put(WHALE, Move.STAY);
preyMovement.put(TURTLE, Move.SOUTHWEST);
preyMovement.put(HUMAN, Move.NORTHEAST);
};
// Map constants
public static final int MAP_SIZE_DIV_2 = 10;
public static final int MAP_SIZE = MAP_SIZE_DIV_2 * 2 + 1;
private static final int MAP_SIZE_MINUS_ONE = MAP_SIZE - 1;
private static final double FADE_FACTOR=0.85;
// Planet constants
private static final int STARTING_HP = 10;
private static final int START_HEALING_FACTOR = 5;
private static final int MAX_DEFENCE = 50;
// Battle Simulation constants
private static final double AMBIGUOUS_ENEMY_HP_FACTOR = 2;
private static final int SIMULATED_BATTLE_COUNT = 100;
private static final int SIMULATED_BATTLE_THREASHOLD = (int)(SIMULATED_BATTLE_COUNT*1.0);
private Random rand = new Random(Planet.rand.nextLong());
/** The alien's map of the immediate and mid-range area */
public double[][] map = new double[MAP_SIZE][MAP_SIZE];
public void setAbilityPoints( float[] abilities )
{
// +0.5 gain due to rounding trick "borrowed" from PredicatClaw http://codegolf.stackexchange.com/a/32925/20193
abilities[LIFE] = 3.5f; // We do need some hit points to ensure that we can survive the melee of the beginning game.
abilities[STR] = 4.5f; // A Moderate attack strength means that we do not have to go through as many fight rounds.
abilities[DEF] = 0; // We will get from prey and other aliens
abilities[VIS] = 2; // A minimum of 2 is required to get a 5x5 field of view
abilities[CLV] = 0; // We will get from prey and other aliens
}
/**
* simulate alien memory fade. This allows an alien to eventually venture back to areas already traversed.
*/
private void fadeMap()
{
for ( int y = 0; y < MAP_SIZE; y++ )
{
for ( int x = 0; x < MAP_SIZE; x++ )
{
map[x][y] *= FADE_FACTOR;
}
}
}
/**
* shift the maps with the movement of the alien so that the alien is always at the centre of the map
*
* @param move
*/
private void shiftMaps( Move move )
{
int i, j;
final int offsetX = -move.getXOffset();
final int offsetY = -move.getYOffset();
double[][] tempMap = new double[MAP_SIZE][MAP_SIZE];
for ( int y = 0; y < MAP_SIZE; y++ )
{
for ( int x = 0; x < MAP_SIZE; x++ )
{
i = x + offsetX;
j = y + offsetY;
if ( i >= 0 && i <= MAP_SIZE_MINUS_ONE && j >= 0 && j <= MAP_SIZE_MINUS_ONE )
{
tempMap[i][j] = map[x][y];
}
}
}
map = tempMap;
}
/**
* Updates a cell in the alien's map with the desirability of the entity in the cell
*
* @param x
* @param y
* @param chr
*/
private void updateMap( int x, int y, char chr )
{
// Note that these desire values are just guesses... I have not performed any analysis to determine the optimum values!
// That said, they seem to perform adequately.
double desire = 0;
int i=x;
int j=y;
switch ( chr )
{
case WHALE:
desire=2;
break;
case TURTLE:
case HUMAN:
desire=1;
Move preyMove = preyMovement.get( chr );
// predict movement into the future
while ( i >= 0 && i <= MAP_SIZE_MINUS_ONE && j >= 0 && j <= MAP_SIZE_MINUS_ONE )
{
map[i][j] = ( map[i][j] + desire ) / 2;
i+=preyMove.getXOffset();
j+=preyMove.getYOffset();
desire/=2;
}
break;
case COW:
desire = 0.5;
break;
case EAGLE:
desire = 1;
break;
case ALIEN:
desire = -10;
break;
}
map[x][y] = ( map[x][y] + desire ) / 2;
}
/**
* Apply a blur the map to simulate the movement of previously seen entities that are no longer within the field of view.
*/
private void convolve()
{
double[][] tempMap = new double[MAP_SIZE][MAP_SIZE];
int count;
double temp;
for ( int y = 0; y < MAP_SIZE; y++ )
{
for ( int x = 0; x < MAP_SIZE; x++ )
{
count = 0;
temp = 0;
for ( int i = x - 1; i < x + 2; i++ )
{
for ( int j = y - 1; j < y + 2; j++ )
{
if ( i >= 0 && i <= MAP_SIZE_MINUS_ONE && j >= 0 && j <= MAP_SIZE_MINUS_ONE )
{
temp += map[i][j];
count++;
}
}
}
temp += map[x][y] * 2;
count += 2;
tempMap[x][y] = temp / count;
}
}
map = tempMap;
}
/**
* Determine the move that minimises the risk to this alien and maximises any potential reward.
*
* @param fields
* @return
*/
private Move findBestMove( char[][] fields )
{
List<Move> moveOptions = new ArrayList<>();
double bestMoveScore = -Double.MAX_VALUE;
double score;
// find the moves that have the best score using the alien's map
for ( Move move : Move.values() )
{
int x = MAP_SIZE_DIV_2 + move.getXOffset();
int y = MAP_SIZE_DIV_2 + move.getYOffset();
score = map[x][y];
if ( score == bestMoveScore )
{
moveOptions.add( move );
}
else if ( score > bestMoveScore )
{
bestMoveScore = score;
moveOptions.clear();
moveOptions.add( move );
}
}
Move move = moveOptions.get( rand.nextInt( moveOptions.size() ) );
// if the best move is to stay...
if ( move == Move.STAY )
{
// find whether there are no surrounding entities in field of vision...
int midVision = getVisionFieldsCount();
boolean stuck = true;
out: for ( int i = 0; i < fields.length; i++ )
{
for ( int j = 0; j < fields.length; j++ )
{
if ( !( i == midVision && j == midVision ) && fields[i][j] != ' ' )
{
stuck = false;
break out;
}
}
}
// there there are no other entities within field of vision and we are healthy... choose a random move
if ( stuck && getCurrentHp() > getLifeLvl() * 2 )
{
move = Move.getRandom();
}
}
return move;
}
/**
* Update the alien's map with the current field of vision
*
* @param fields
*/
private void mapVisibleSurroundings( char[][] fields )
{
int midVision = getVisionFieldsCount();
// update the map with currently visible information
for ( int y = -midVision; y <= midVision; y++ )
{
for ( int x = -midVision; x <= midVision; x++ )
{
char chr = fields[midVision + x][midVision + y];
updateMap( MAP_SIZE_DIV_2 + x, MAP_SIZE_DIV_2 + y, chr );
}
}
// ensure that the map where this alien currently sits is marked as empty.
updateMap( MAP_SIZE_DIV_2, MAP_SIZE_DIV_2, ' ' );
}
public Move move( char[][] fields )
{
Move returnMove = null;
// pre-move decision processing
mapVisibleSurroundings( fields );
convolve();
returnMove = findBestMove( fields );
// post-move decision processing
fadeMap();
shiftMaps( returnMove );
return returnMove;
}
public boolean wantToFight( final int[] enemyAbilities )
{
double toughnessFactor =((double) enemyAbilities[STR])/(enemyAbilities[LIFE]*10); // a fudge-factor to ensure that whales are attacked.
if (enemyAbilities[VIS]>=3 && enemyAbilities[LIFE]>4.5f) toughnessFactor*=3.5; // make sure that we do not attack other Cartogapher aliens
return winsBattleSimulation( enemyAbilities, toughnessFactor );
}
/**
* Perform simulations to determine whether we will win against the enemy most of the time.
*
* @param enemyAbilities
* @return
*/
private boolean winsBattleSimulation( int[] enemyAbilities, double toughnessFactor )
{
int surviveCount = 0;
for ( int i = 0; i < SIMULATED_BATTLE_COUNT; i++ )
{
int estimatedEnemyHitPoints =
STARTING_HP + (int)( enemyAbilities[LIFE] * START_HEALING_FACTOR * AMBIGUOUS_ENEMY_HP_FACTOR * toughnessFactor );
int enemyPoints = estimatedEnemyHitPoints;
int myHitPoints = getCurrentHp();
int myDefenceLevel = getDefenseLvl() < MAX_DEFENCE ? getDefenseLvl() : MAX_DEFENCE;
int enemyDefenceLevel = enemyAbilities[DEF] < MAX_DEFENCE ? enemyAbilities[DEF] : MAX_DEFENCE;
while ( !( myHitPoints <= 0 || enemyPoints <= 0 ) )
{
if ( rand.nextInt( MAX_DEFENCE / myDefenceLevel + 1 ) != 0 )
{
myHitPoints -= rand.nextInt( enemyAbilities[STR] ) + 1;
}
if ( rand.nextInt( MAX_DEFENCE / enemyDefenceLevel + 1 ) != 0 )
{
enemyPoints -= rand.nextInt( getStrengthLvl() ) + 1;
}
}
if ( myHitPoints > 0 )
{
surviveCount++;
}
}
return ( surviveCount >= SIMULATED_BATTLE_THREASHOLD );
}
}
Choose Your Battles
This alien runs away from other aliens, but runs towards prey (so long as that wouldn't take it towards aliens).
I used a genetic algorithm to help me choose the starting values. The results suggest we should rely on strength and life to get through early battes. Vision is useful later, but we can pick that up from vanquished foes.
We only fight battles we think we can comfortably win - we estimate how many moves it would take to kill our alien, how many it would take to kill our enemy, and only join the battle if it we're "twice as hard" as our opponent.
package alien;
import planet.Move;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static java.lang.Math.*;
public class ChooseYourBattles extends Alien {
private static final boolean DEBUG = false;
private static final int LIFE = 0;
private static final int STRENGTH = 1;
private static final int DEFENCE = 2;
private static final int VISION = 3;
private static final int CLEVERNESS = 4;
private static final Set<Character> prey = new HashSet<>();
{
Collections.addAll(prey, 'H', 'T', 'W', 'C', 'E');
}
public void setAbilityPoints(float[] abilities) {
// Rounding promotes these to 4 and 7 - 11 points!
abilities[LIFE] = 3.5f;
abilities[STRENGTH] = 6.5f;
}
@Override
public Move move(char[][] fields) {
if (DEBUG) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < 2 * getVisionFieldsCount() + 1; j++) {
for (int i = 0; i < 2 * getVisionFieldsCount() + 1; i++) {
char chr = fields[i][j];
if (chr == ' ') chr = '.';
if (i == getVisionFieldsCount() && j == getVisionFieldsCount()) chr = 'X';
sb.append(chr);
}
sb.append('\n');
}
String view = sb.toString();
System.out.println(this.toString());
System.out.println(view);
}
Move bestMove = null;
int bestEnemyDistance = Integer.MIN_VALUE;
int bestPreyDistance = Integer.MAX_VALUE;
for (Move tryMove: Move.values()) {
int currentDistanceToEnemy = Integer.MAX_VALUE;
int currentDistanceToPrey = Integer.MAX_VALUE;
int x = getVisionFieldsCount() + tryMove.getXOffset();
int y = getVisionFieldsCount() + tryMove.getYOffset();
for (int i = 0; i < 2 * getVisionFieldsCount() + 1; i++) {
for (int j = 0; j < 2 * getVisionFieldsCount() + 1; j++) {
char chr = fields[i][j];
if (chr == 'A' && (i != getVisionFieldsCount() || j != getVisionFieldsCount())) {
// Use L-infinity distance, but fll back to L-1 distance
int distance = max(abs(i - x), abs(j - y)) * 100 + abs(i -x) + abs(j - y);
if (distance < currentDistanceToEnemy) currentDistanceToEnemy = distance;
} else if (prey.contains(chr)) {
int distance = max(abs(i - x), abs(j - y)) * 100 + abs(i -x) + abs(j - y);
if (distance < currentDistanceToPrey) currentDistanceToPrey = distance;
}
}
}
if (currentDistanceToEnemy > bestEnemyDistance
|| (currentDistanceToEnemy == bestEnemyDistance && currentDistanceToPrey < bestPreyDistance)) { // Prefer to stay put
bestMove = tryMove;
bestEnemyDistance = currentDistanceToEnemy;
bestPreyDistance = currentDistanceToPrey;
}
}
if (DEBUG) {
System.out.println("Going " + bestMove);
System.out.println();
}
return bestMove;
}
@Override
public boolean wantToFight(int[] enemyAbilities) {
// Estimate whether likely to survive the encounter - are we at least "twice as hard" as our opponent
return getCurrentHp() * (50.0 + getDefenseLvl()) / (enemyAbilities[STRENGTH])
> 2.0 * (enemyAbilities[LIFE] * 5 + 10) * (50.0 + enemyAbilities[DEFENCE])/ (getStrengthLvl());
}
@Override
public String toString() {
return "ChooseYourBattles" + System.identityHashCode(this)
+ ": HP " + getCurrentHp()
+ ", LFE " + getLifeLvl()
+ ", STR " + getStrengthLvl()
+ ", DEF " + getDefenseLvl()
+ ", VIS " + getVisionLvl()
+ ", CLV " + getClevernessLvl();
}
}
If you want to run your own genetic algorithm/breeding program, I've got a forked copy of the control program for this purpose.