Bitwa o szalkę Petriego

32

W tym wyzwaniu musisz zaprojektować gatunek organizmów jednokomórkowych, które będą walczyć na śmierć i życie na arenie Petriego. Arena jest reprezentowana jako prostokątna siatka, w której każda komórka zajmuje jedno miejsce:

.....x....
...x...o..
...x.c..o.
.......o..

Atrybuty

Każda komórka ma trzy atrybuty. Określając gatunek komórek na początku gry, przypisujesz 12 punktów do tych atrybutów.

  • Punkty wytrzymałości (HP): Jeśli HP komórki spadnie do zera, umiera. Nowe komórki mają pełne HP.
    • Kiedy komórka umiera, pozostawia zwłoki, które mogą zostać zjedzone przez inne komórki w celu uzyskania energii.
    • Komórka nie może odzyskać utraconego HP, ale może utworzyć nową komórkę z pełnym HP, dzieląc.
  • Energia : większość działań, jakie może podjąć komórka, wymaga energii. Aktywnie odpoczywając, komórka może odzyskać utraconą energię do maksimum swojego gatunku.
    • Gatunek komórek o energii mniejszej niż 5 prawdopodobnie zawiedzie, ponieważ nie może się podzielić, aby utworzyć nowe komórki.
    • Komórka nie może odzyskać energii przekraczającej maksymalną wartość swojego gatunku.
    • Nowo utworzona komórka ma początkową wartość energii skopiowaną z jej rodzica (i maksymalną wartość podyktowaną specyfikacją gatunku).
  • Kwasowość : Jeśli komórka zdecyduje się eksplodować, poziom kwasowości komórki jest wykorzystywany do obliczania uszkodzeń sąsiednich komórek.

działania

W każdej turze każda komórka może wykonać jedną akcję:

  • Przesuń: komórka porusza się o jedno pole w dowolnym kierunku (N / S / E / W / NE / NW / SE / SW) kosztem 1 energii.

    • Komórka nie może przejść na pole zajmowane przez inną żywą komórkę.
    • Komórka nie może zejść z siatki.
    • Przeniesienie na zwłoki komórkowe niszczy zwłoki.
  • Atak: Komórka atakuje sąsiednią komórkę, zadając 1 do 3 obrażeń, wydając 1 do 3 punktów energii.

    • Komórka może atakować w dowolnym kierunku (N / S / E / W / NE / NW / SE / SW).
    • Atakowanie przyjaznych komórek jest legalne.
  • Podziel: Komórka dzieli się i tworzy nową komórkę na sąsiedniej przestrzeni, kosztem 5 energii.

    • Komórka może dzielić się w dowolnym kierunku (N / S / E / W / NE / NW / SE / SW).
    • Nowa komórka ma pełne HP zgodnie z oryginalną specyfikacją komórki.
    • Nowa komórka ma tyle samo energii, co jej komórka macierzysta po odjęciu kosztu podziału. (Na przykład komórka rodzicielska z początkowymi 8 punktami energii zostanie zredukowana do 3 energii i wytworzy komórkę potomną z 3 energiami).
    • Nowa komórka nie może działać do następnej tury.
    • Komórka nie może podzielić się na przestrzeń zajmowaną przez żywą komórkę, ale może podzielić się na przestrzeń zajmowaną przez zwłoki martwej komórki (to niszczy zwłoki).
  • Jeść: Komórka zjada zwłoki sąsiedniej komórki, zyskując 4 energię.

    • Komórka może jeść w dowolnym kierunku (N / S / E / W / NE / NW / SE / SW).
  • Odpoczynek: Komórka nic nie robi przez jedną turę, odzyskując 2 energię.

  • Eksploduj: Gdy komórka ma 3 lub mniej PW i więcej energii niż HP, może wybuchnąć, zadając obrażenia wszystkim ośmiu sąsiednim komórkom.

    • Uszkodzenie każdej sąsiedniej komórki wynosi (exploding cell HP) + (explodng cell acidity)
    • Eksplodowana komórka umiera i pozostawia zwłoki, podobnie jak wszystkie komórki zabite w wyniku eksplozji.

Protokół

Ustawiać

Twój program będzie działał z ciągiem BEGINpodanym na stdin. Twój program musi napisać na standardowe miejsce oddzieloną spacją listę 3 nieujemnych liczb całkowitych reprezentujących HP, energię i kwasowość dla gatunków komórek: np 5 6 1. Liczby muszą się sumować do 12. Kwasowość może być 0, jeśli chcesz. (Inne atrybuty mogą być również zerowe, ale funkcjonalnie powoduje to utratę gry!)

Zaczynasz z jedną komórką, w północno-zachodnim lub południowo-wschodnim rogu, jedną przestrzeń od każdej krawędzi. Początkowa komórka ma pełne HP i energię.

Każda komórka działa

W każdej turze twój program będzie wywoływany raz dla każdej komórki żyjącej w twoim zespole (oprócz komórek właśnie utworzonych w tej turze), aby komórka mogła działać. Twój program zawiera dane o stdin, które obejmują stan płytki Petriego i informacje o tej konkretnej komórce:

10 4
..........
..xx.c....
...c...o..
......o...

6 3 5 7

Pierwsze dwie liczby wskazują szerokość i wysokość areny: tutaj jest arena 10 na 4.

  • Te okomórki są twoje; te xkomórki są twoi wrogowie. (To zawsze jest prawda; każdy gracz zawsze widzi swoje własne komórki jako o.)
  • Miejsca .są puste.
  • Te cprzestrzenie stanowią jadalne ciał komórkowych.

Liczby po pustym wierszu reprezentują informacje o tej komórce:

  • Pierwsze dwie liczby to x,ywspółrzędne, indeksowane od 0,0lewego górnego rogu ( 6 3tutaj odnosi się do okomórki na południe ).
  • Trzecią liczbą jest HP komórki; czwarta liczba to energia komórki.

Twój program powinien wypisać (na standardowe wyjście) działanie. W poniższych przykładach użyjemy Njako przykładowego kierunku, ale może to być dowolny kierunek zgodny z prawem dla tej akcji ( N/ S/ E/ W/ NE/ NW/ SE/ SW). Wszystkie dane wyjściowe programu nie rozróżniają wielkości liter, ale przykłady będą używać wielkich liter. Każda akcja wyjściowa, która jest niepoprawna (ponieważ ma niepoprawną składnię lub podejmuje próbę nielegalnej akcji) jest ignorowana i skutkuje RESTłączeniem komórek (i w ten sposób zyskuje 2 energię).

  • MOVE N
  • DIVIDE N
  • EAT N
  • ATTACK N 2 - liczba reprezentuje siłę ataku (1–3)
  • REST
  • EXPLODE

Tura twojego zespołu polega na tym, że wszystkie komórki mają możliwość działania, jeden po drugim. Wszystkie twoje komórki działają zanim zadziała którakolwiek z komórek przeciwnika. Gdy wszystkie komórki zadziałają, twoja tura się kończy i tura twojego przeciwnika. Gdy wszystkie komórki przeciwnika zaczną działać, twoja tura zaczyna się od nowa. W twojej turze każda komórka ma pierwszeństwo działania na podstawie jej wieku: najstarsze komórki w twoim zespole działają najpierw przed młodszymi komórkami.

Przykład

Oto jak może się zachowywać program. Dane wejściowe ze standardowego wejścia oznaczone są tutaj >strzałkami wiodącymi (oddzielonymi od rzeczywistych danych wejściowych spacją wyjaśniającą), a dane wyjściowe ze standardowego wejścia mają <strzałki.

> BEGIN
< 5 6 1

Następnie program jest wywoływany ponownie:

> 10 4
> ..........
> .o........
> ........x.
> ..........
>
> 1 1 5 6
< DIVIDE SE

Po turze przeciwnika (który zdecydował się DIVIDE Wna pojedynczą komórkę początkową), twój program jest wywoływany dwukrotnie, raz dla każdej komórki:

> 10 4
> ..........
> .o........
> ..o....xx.
> ..........
>
> 1 1 5 1
< MOVE E

Dla drugiego wywołania w twojej turze:

> 10 4
> ..........
> ..o.......
> ..o....xx.
> ..........
>
> 2 2 5 1
< MOVE SE

Zauważ, że ta druga komórka widzi zaktualizowany stan planszy na podstawie ruchu drugiej komórki wcześniej w twojej turze. Zauważ też, że komórka ta została stworzona z 1 energią, ponieważ komórka macierzysta miała 6 energii podczas wykonywania podziału w ostatniej turze (tak więc pierwotna 6, minus 5-energetyczny koszt podziału, stworzyła komórkę potomną z 1 energią).

Teraz twoja tura się skończyła i rozpoczyna się tura przeciwnika. Dwie przeciwne komórki otrzymają szansę działania, a następnie rozpocznie się kolejna tura.

Zwycięstwo

Możesz wygrać poprzez:

  • Niszczenie wszystkich komórek przeciwnych lub
  • Posiadanie większej liczby komórek niż przeciwnik po ukończeniu 150 tur przez każdego gracza

Punktacja będzie oparta na liczbie wygranych w 100 grach przeciwko sobie. W połowie symulacji twój program będzie mógł zacząć jako pierwszy.

Gry remisowe (tj. Dokładnie taka sama liczba komórek po 150 turach lub jedyne pozostałe komórki zostają zabite razem w wyniku eksplozji) nie są liczone w łącznych wygranych żadnego z graczy.

Inne informacje

  • Wasz program nie powinien próbować utrzymywać stanu (poza wykorzystaniem stanu płytki Petriego): organizmy jednokomórkowe nie mają bardzo dobrej pamięci i reagują na chwile na świecie z każdą chwilą. W szczególności zapisywanie do pliku (lub innego magazynu danych), komunikacja ze zdalnym serwerem lub ustawianie zmiennych środowiskowych są wyraźnie zabronione.
  • Zgłoszenia będą uruchamiane / kompilowane na Ubuntu 12.04.4.
  • Specyfika 100 gier punktowanych nie została jeszcze potwierdzona, ale prawdopodobnie będą obejmować wiele rozmiarów aren (na przykład 50 biegów na małej arenie i 50 biegów na większej arenie). W przypadku większej areny mogę zwiększyć maksymalną liczbę tur, aby zapewnić prawidłową bitwę.

Zasoby

Oto kod sterownika, który uruchamia symulację, napisany dla Node.js, wywoływany przez node petri.js 'first program' 'second program'. Na przykład może wyglądać skierowanie komórki napisanej w języku Python na komórkę napisaną w języku Java node petri.js 'python some_cell.py' 'java SomeCellClass'.

Ponadto rozumiem, że czytanie i analizowanie wielu wierszy na stdin może być ogromnym bólem, dlatego stworzyłem kilka pełnych przykładowych komórek w różnych językach, na których podstawie możesz swobodnie rozbudowywać, całkowicie modyfikować lub całkowicie ignorować.

Oczywiście możesz napisać komórkę w innym języku; to po prostu trzy języki, dla których postanowiłem napisać prosty kod, aby zaoszczędzić czas.

Jeśli masz problemy z uruchomieniem sterownika, możesz wysłać mi ping do pokoju czatu, który utworzyłem dla tego wyzwania . Jeśli nie masz wystarczającej reputacji na czacie, po prostu zostaw komentarz.

apsillery
źródło
1
@ Ryan Musisz podać w pełni uruchamialne polecenie, tak jak w 'node c:/cell/cell_template.js'przypadku każdego argumentu, tak samo jak 'java CellTemplate'w przypadku kodu Java. Wyjaśnię to w tekście wyzwania. Jeśli nadal masz problemy, my (i każdy z problemami technicznymi) możemy kontynuować tę dyskusję w właśnie utworzonym przeze mnie czacie .
apsillers,
1
@Moogie Tylko 2 przeciwników na grę.
apsillers
3
Człowieku, przykłady są świetne!
CommonGuy
3
@apillers poprosiliśmy cię na czacie, ale zapomnieliśmy cię pingować, abyś mógł nie zauważyć: zastanawialiśmy się, kiedy planujesz uruchomić grę?
plannapus
2
@Manu Wreszcie tak! Przepraszam za bardzo duże opóźnienie. Mam skonfigurowany kod swatania / zapisywania i teraz usuwam wszelkie problemy z przesyłaniem, próbując uruchomić kod każdego. Potem pozwolę mu działać na moim serwerze przez około jeden dzień, aby ukończyć rundy.
apsillers

Odpowiedzi:

3

Oto mój stosunkowo prosty bot, który zaprogramowałem w Ruby. Zasadniczo priorytetem jest dzielenie jako pierwsze i próba dzielenia się na wrogów w celu uzyskania kontroli nad polem. Drugim priorytetem jest jedzenie, a trzeci atakowanie. Z łatwością pobił próbkę komórki Pythona.

def surroundingCells(x, y)
  result = Hash.new
  if x >= 1
    if y >= 1
      # northwest
      result["NW"] = $petriDish[x - 1][y - 1]
    end
    if y < ($sizeY - 1) # $sizeY - 1 is the farthest south square
      # southwest
      result["SW"] = $petriDish[x - 1][y + 1]
    end
      # west
      result["W"] = $petriDish[x - 1][y]
  end
  if x < ($sizeX - 1)
    if y >= 1
      # northeast
      result["NE"] = $petriDish[x + 1][y - 1]
    end
    if y < ($sizeY - 1)
      # southeast
      result["SE"] = $petriDish[x + 1][y + 1]
    end
    # east
    result["E"] = $petriDish[x + 1][y]
  end
  # north
  result["N"] = $petriDish[x][y - 1] if y >= 1
  # south
  result["S"] = $petriDish[x][y + 1] if y < ($sizeY - 1)
  return result
end

def directionTowardsEnemies(locX, locY)
  totalXDirections = 0
  totalYDirections = 0
  totalTargetsFound = 0 # enemies or corpses
  optimalDirections = []
  for x in 0..($petriDish.length - 1)
    for y in 0..($petriDish[0].length - 1)
      if $petriDish[x][y] == 'c' or $petriDish[x][y] == 'x'
        totalXDirections += (x - locX)
        totalYDirections += (y - locY)
        totalTargetsFound += 1
      end
    end
  end
  if totalXDirections == 0
    if totalYDirections > 0
      optimalDirections << "S" << "SE" << "SW"
    else
      optimalDirections << "N" << "NE" << "NW"
    end
    return optimalDirections
  end
  if totalYDirections == 0
    if totalXDirections > 0
      optimalDirections << "E" << "NE" << "SE"
    else
      optimalDirections << "W" << "NW" << "SW"
    end
    return optimalDirections
  end
  if totalXDirections > 0
    if totalYDirections > 0
      optimalDirections << "SE"
      if totalYDirections > totalXDirections
        optimalDirections << "S" << "E"
      else
        optimalDirections << "E" << "S"
      end
    else
      optimalDirections << "NE"
      if -totalYDirections > totalXDirections
        optimalDirections << "N" << "E"
      else
        optimalDirections << "E" << "N"
      end
    end
    return optimalDirections
  end
  if totalXDirections < 0
    if totalYDirections > 0
      optimalDirections << "SW"
      if totalYDirections > -totalXDirections
        optimalDirections << "S" << "W"
      else
        optimalDirections << "W" << "S"
      end
    else
      optimalDirections << "NW"
      if -totalYDirections > -totalXDirections
        optimalDirections << "N" << "W"
      else
        optimalDirections << "W" << "N"
      end
    end
  end
  return optimalDirections
end

firstLine = gets
if firstLine == "BEGIN"
  puts "5 7 0"
  exit 0
end
$sizeX, $sizeY = firstLine.split(' ')[0].to_i, firstLine.split(' ')[1].to_i
$petriDish = Array.new($sizeX) { Array.new($sizeY) }
for y in 0..($sizeY - 1)
  line = gets
  chars = line.split('').reverse.drop(1).reverse # this gets every character but     the last
  for x in 0..(chars.length - 1)
    $petriDish[x][y] = chars[x]
  end
end
gets # blank line
info = gets
locX = info.split(' ')[0].to_i
locY = info.split(' ')[1].to_i
hp = info.split(' ')[2].to_i
energy = info.split(' ')[3].to_i

# dividing is our first priority
if(energy >= 5)
  # try to divide towards enemies
  dirs = directionTowardsEnemies(locX, locY)
  directions = { "N" => [0, -1], "NE" => [1, -1], "E" => [1, 0],
    "SE" => [1, 1], "S" => [0, 1], "SW" => [-1, 1],
    "W" => [-1, 0], "NW" => [-1, -1] }
  for dir in dirs
    potentialNewX = locX + directions[dir][0]
    potentialNewY = locY + directions[dir][1]
    if $petriDish[potentialNewX][potentialNewY] == '.'
      puts "DIVIDE #{dir}"
      exit 0
    end
  end
  # otherwise, just divide somewhere.
  surroundingCells(locX, locY).each do |k, v|
    if v == '.'
      puts "DIVIDE #{k}"
      exit 0
    end
  end
end

# next, eating
surroundingCells(locX, locY).each do |k, v|
  if v == 'c'
    puts "EAT #{k}"
    exit 0
  end
end

# next, attacking
surroundingCells(locX, locY).each do |k, v|
  attackStrength = 0
  if (energy > 5) then # we want to save energy for dividing
    attackStrength = [(energy - 5), 3].min
  else
    attackStrength = [energy, 3].min
  end
  if v == 'x'
    puts "ATTACK #{k} #{attackStrength}"
    exit 0
  end
end

# otherwise, rest
puts "REST"
Alex
źródło
Nie jestem programistą Ruby, więc zastanawiam się, dlaczego niektóre zmienne są normalne, a niektóre zaczynają się na $.
patrz
$służy do wskazania zmiennej globalnej. Tak, są źli, ale w tym małym programie nie ma to większego znaczenia.
Alex
Zmienne globalne są złe tylko w kodzie produkcyjnym. Kto się tym zajmuje w takich skryptach?
patrz
Czy moja komórka jest naprawdę jedyną, której rozprzestrzenianie się umiejętności nie wynosi 4-8-0?
Alex
To jak dotąd najlepszy pretendent do moich CoordinatedBacteria! Swoją strategię zbudowałem na podstawie wyniku testu na organizmie jednokomórkowym. =)
justhalf
3

Ameba

Najpierw dzieli się na cztery, a następnie próbuje dostać się na środkowy grunt, aby ograniczyć przestrzeń replikacji przeciwników. Następnie rozpoczyna się replikacja. Podczas przenoszenia lub replikacji znajduje optymalną ścieżkę do najbliższego wroga i przesuwa się w jej kierunku, aby spróbować odciąć wroga dostępną przestrzeń.

Jeśli wróg znajduje się w sąsiedztwie lub jest o jedno pole dalej, zawsze atakuje lub zbliża się do niego, pozwalając rzędowi za sobą nie robić nic, aby wypełnić puste pola.

Nie testowałem tego na żadnym innym zgłoszeniu, więc nie mam pojęcia, jak dobrze sobie poradzi.

var MAX_HP = 2;
var MAX_ENERGY = 10;
var ACIDITY = 0;

function PathfindingNode(_x, _y, _prevNode, _distance, _adjacentEnemies) {
    this.x = _x;
    this.y = _y;
    this.prevNode = _prevNode;
    this.distance = _distance;
    this.adjacentEnemies = _adjacentEnemies;
}

PathfindingNode.prototype.GetDistance = function()
{
    return this.distance;
}

var evaluatedNodes = {};
var initialNode = {};
var firstEval = true;

function evaluateNode(x, y, arena)
{
    //get surrounding reachable nodes that havent already been checked
    var adjacentEmpties = arena.getAdjacentMatches(arena.get(x, y), [".", "c"]);

    //if this node is adjacent to the start node - special case because the start node isnt an empty
    if (firstEval)
        adjacentEmpties.push({ 'x': initialNode.x, 'y': initialNode.y });

    //find the optimal node to reach this one
    var prevNode = null;
    for (var i in adjacentEmpties)
    {
        if(evaluatedNodes[adjacentEmpties[i].x + "," + adjacentEmpties[i].y])
        {
            var currentNode = evaluatedNodes[adjacentEmpties[i].x + "," + adjacentEmpties[i].y];

            if (!prevNode) {
                prevNode = currentNode;
            }
            else {
                if(currentNode.GetDistance() < prevNode.GetDistance())
                {
                    prevNode = currentNode;
                }
            }
        }
    }

    var adjacentEnemies = arena.getAdjacentMatches(arena.get(x, y), ["x"]);
    var newNode = new PathfindingNode(x, y, prevNode, prevNode.GetDistance() + 1, adjacentEnemies.length);
    evaluatedNodes[x + "," + y] = newNode;
}

function evaluateNeighbours(arena) {
    //breadth first search all reachable cells
    var nodesToEvaluate = [];
    for (var i in evaluatedNodes) {
        var emptyNodes = arena.getAdjacentMatches(arena.get(evaluatedNodes[i].x, evaluatedNodes[i].y), [".", "c"]);
        //only ones that havent already been eval'd
        for (var j in emptyNodes)
            if (!evaluatedNodes[emptyNodes[j].x + "," + emptyNodes[j].y])
                nodesToEvaluate.push(emptyNodes[j])
    }

    //have all available nodes been evaluated
    if (nodesToEvaluate.length === 0)
        return false;

    for (var i in nodesToEvaluate)
    {
        evaluateNode(parseInt(nodesToEvaluate[i].x), parseInt(nodesToEvaluate[i].y), arena)
    }

    firstEval = false;
    return true;
}

function getAllReachableNodes(arena, cell) {
    //return a list of all reachable cells, with distance and optimal path
    evaluatedNodes = {};

    //add the first node to get started
    var adjacentEnemies = arena.getAdjacentMatches(arena.get(cell.x, cell.y), ["x"]);
    var newNode = new PathfindingNode(parseInt(cell.x), parseInt(cell.y), null, 0, adjacentEnemies.length);
    evaluatedNodes[cell.x + "," + cell.y] = newNode;
    initialNode.x = parseInt(cell.x);
    initialNode.y = parseInt(cell.y);
    firstEval = true;

    while (evaluateNeighbours(arena))
        ;

    return evaluatedNodes;
}

function passedMiddleGround(arena)
{
    for (var i = (parseInt(arena.width) / 2) - 1; i < parseInt(arena.width); i++)
    {
        for(var j = 0; j < parseInt(arena.height); j++)
        {
            if (arena.get(i, j).symbol == "o")
                return true;
        }
    }
    return false;
}

function decide(arena, cell, outputCallback) {

    var nearbyEmpties = arena.getAdjacentMatches(cell.point, [".", "c"]);
    var nearbyEnemies = arena.getAdjacentMatches(cell.point, ["x"]);
    var nearbyCorpses = arena.getAdjacentMatches(cell.point, ["c"]);

    if (nearbyEnemies.length > 4 && cell.energy >= cell.hp && cell.hp <= 3) {
        outputCallback("EXPLODE");
        return;
    }

    //attack whenever we get the chance. leave the replication to the cells doing nothing
    if (cell.energy > 0 && nearbyEnemies.length > 0){
        outputCallback("ATTACK " + arena.getDirection(cell, nearbyEnemies[(nearbyEnemies.length * Math.random()) | 0]) + " " + Math.min(cell.energy, 3));
        return;
    }

    //if we are close to an enemy, move towards it. let the back line fill the new space
    if (cell.energy > 2) {
        for (var i = 0; i < nearbyEmpties.length; ++i) {
            var space = nearbyEmpties[i];
            if (arena.getAdjacentMatches(space, ["x"]).length) {
                outputCallback("MOVE " + arena.getDirection(cell, space));
                return;
            }
        }
    }

    //yum
    if (nearbyCorpses.length > 0) {
        outputCallback("EAT " + arena.getDirection(cell, nearbyCorpses[(nearbyCorpses.length * Math.random()) | 0]));
        return;
    }

    //until we pass the middle ground, just keep moving into tactical position. afterwards we can start replication
    if (passedMiddleGround(arena) && cell.energy < 5 && nearbyEmpties.length > 0)
    {
        outputCallback("REST");
        return;
    }

    //try to block the opponent cells - interrupt their replication
    //if we have enough energy to move, choose the best spot
    if (nearbyEmpties.length > 0 && ((cell.energy >= 2 && nearbyEnemies.length == 0) || cell.energy >= 5)) {

        var nextMove = null;

        if (nearbyEmpties.length === 1) {
            nextMove = nearbyEmpties[0];
        }
        else {
            var reachableNodes = getAllReachableNodes(arena, cell);

            //select nodes that have an adjacent enemy
            var enemyAdjacentNodes = {};
            var enemyNodesReachable = false;
            for (var node in reachableNodes) {
                if (reachableNodes.hasOwnProperty(node) && reachableNodes[node].adjacentEnemies > 0) {
                    enemyAdjacentNodes[node] = reachableNodes[node];
                    enemyNodesReachable = true;
                }
            }

            if (enemyNodesReachable)
            {
                //if there are any then select the closest one
                var closest = null;
                for (var node in enemyAdjacentNodes) {
                    if(!closest)
                    {
                        closest = enemyAdjacentNodes[node];
                    }
                    else{
                        if(enemyAdjacentNodes[node].GetDistance() < closest.GetDistance())
                        {
                            closest = enemyAdjacentNodes[node];
                        }
                    }

                }

                //select the first move of the nodes path
                //trace the selected node back to the first node to select the first move towards the cell.
                while (closest.prevNode != null && closest.prevNode.prevNode != null)
                {
                    closest = closest.prevNode;
                }
                nextMove = arena.get(closest.x, closest.y);
            }
        }

        //a path to the enemy was found
        if(nextMove)
        {
            //do this until we get half way across the board, then we just replicate
            if (!passedMiddleGround(arena)) {
                if (cell.energy >= 5) {
                    outputCallback("DIVIDE " + arena.getDirection(cell, nextMove));
                    return;
                }

                outputCallback("MOVE " + arena.getDirection(cell, nextMove));
                return;
            }
            else {
                outputCallback("DIVIDE " + arena.getDirection(cell, nextMove));
                return;
            }

        }

    }

    //if theres no path to an enemy available, just divide anywhere
    if (cell.energy >= 5 && nearbyEmpties.length > 0) {
        outputCallback("DIVIDE " + arena.getDirection(cell, nearbyEmpties[(nearbyEmpties.length * Math.random()) | 0]));
        return;
    }

    outputCallback("REST");
    return;
}

var input = "";
// quiet stdin EPIPE errors
process.stdin.on("error", function(err) {
    log("slight error: " + err);
});
process.stdin.on("data", function(data) {
    input += data;
});
process.stdin.on("end", function() {
    if(input == "BEGIN") {
        // output space-separated attributes
        process.stdout.write([MAX_HP, MAX_ENERGY, ACIDITY].join(" "));
        clearLog();
    } else {
        // read in arena and decide on an action
        var arena = new Arena();
        var lines = input.split("\n");
        var dimensions = lines[0].split(" ").map(function(d) { return parseInt(d); });
        arena.width = dimensions[0];
        arena.height = dimensions[1];
        for(var y=1; y<=dimensions[1]; ++y) {
            for(var x=0; x<lines[y].length; ++x) {
                arena.set(x, y-1, lines[y][x]);
            }
        }

        var stats = lines[dimensions[1]+2].split(" ");
        var cell = { x: stats[0], y: stats[1], hp: stats[2], energy: stats[3], point: arena.get(stats[0], stats[1]) };

        // decide on an action and write the action to stdout
        decide(arena, cell, function(output) { process.stdout.write(output); })
    }
});

var Arena = function() {
    this.dict = {};
};
Arena.prototype = {
    // get Point object
    get: function(x,y) {
        if(!this.dict[x+","+y])
            return 'w';
        return this.dict[x+","+y];
    },

    // store Point object
    set: function(x,y,d) {
        this.dict[x+","+y] = new Point(x,y,d);
    },

    // get an array of all Points adjacent to this one whose symbol is contained in matchList
    // if matchList is omitted, return all Points
    getAdjacentMatches: function(point, matchList) {
        var result = [];
        for(var i=-1; i<=1; ++i) {
            for(var j=-1; j<=1; ++j) {
                var inspectedPoint = this.get(point.x+i, point.y+j);
                if(inspectedPoint && 
                   (i!=0 || j!=0) &&
                   (!matchList || matchList.indexOf(inspectedPoint.symbol) != -1)) {
                    result.push(inspectedPoint);
                }
            }
        }
        return result;
    },

    // return the direction from point1 to point2
    getDirection: function(point1, point2) {
        var dx = point2.x - point1.x;
        var dy = point2.y - point1.y;
        dx = Math.abs(dx) / (dx || 1);
        dy = Math.abs(dy) / (dy || 1);

        c2d = { "0,0":"-",
                "0,-1":"N", "0,1":"S", "1,0":"E", "-1,0":"W",
                "-1,-1":"NW", "1,-1":"NE", "1,1":"SE", "-1,1":"SW" };

        return c2d[dx + "," + dy];
    }
}

var Point = function(x,y,d) {
    this.x = x;
    this.y = y;
    this.symbol = d;
}
Point.prototype.toString = function() {
    return "(" + this.x + ", " + this.y + ")";
}
rdans
źródło
To właściwie całkiem dobra strategia, jeśli tylko zmniejszysz ryzyko, biorąc pod uwagę liczbę sąsiednich przyjaciół przed przeprowadzką, w przeciwnym razie inni pchacze mogą łatwo przedrzeć się przez twoją cienką linię obrony we wczesnej fazie gry (a zatem mają zastosowanie tylko na małych planszach)
justhalf
Btw, to nie wydaje się działać zgodnie z przeznaczeniem, jeśli jest to odtwarzacz 2.
pół
3

Prosta komórka wykonana w node.js. Przetestowany pod kątem komórek węzłów przykładów i przeciwko Kostronorowi bije je.

Aktualizacja

Wciąż dość proste, spróbuj zbliżyć się do wroga lub podzielić.

// used in defining cell spec
var MAX_HP = 4;
var MAX_ENERGY = 8;
var ACIDITY = 0;

function decide(arena, cell, outputCallback) {

    var nearbyEmpties = arena.getAdjacentMatches(cell.point, [".", "c"]);
    var nearbyEnemies = arena.getAdjacentMatches(cell.point, ["x"]);
    var nearbyCorpses = arena.getAdjacentMatches(cell.point, ["c"]);
    var nearbyFriends = arena.getAdjacentMatches(cell.point, ["o"]);

    if (nearbyFriends.length >= 8) {
        outputCallback("REST");
        return;
    }

    if (nearbyFriends.length >= 7 && nearbyEnemies.length < 0 && nearbyCorpses.length > 0 && energy < MAX_ENERGY) {
        outputCallback("EAT " + arena.getDirection(cell, nearbyCorpses[(nearbyCorpses.length*Math.random())|0]));
        return;
    }

    // if you have two or more nearby enemies, explode if possible
    if(nearbyEnemies.length >= 1
        && cell.energy >= cell.hp 
        && cell.hp <= 1 
        && nearbyEnemies.length > nearbyFriends.length) {
        outputCallback("EXPLODE");
        return;
    }

    // if you have two or more nearby enemies, explode if possible
    if(nearbyEnemies.length >= 3 && cell.energy >= cell.hp && nearbyEnemies.length > nearbyFriends.length) {
        outputCallback("EXPLODE");
        return;
    }

    // if you have the energy and space to divide, do it
    if(cell.energy >= 5 && nearbyEmpties.length > 0) {
        var ed = arena.getEnemyDirection(cell);
        if (nearbyEmpties.indexOf(ed) >= 0 && Math.random() < 0.5){
            outputCallback("DIVIDE " + ed);
        } else{
            outputCallback("DIVIDE " + arena.getDirection(cell, nearbyEmpties[(nearbyEmpties.length*Math.random())|0]));
        }
        return;
    }

    // if at least one adjacent enemy, attack if possible
    if(cell.energy > 0 && nearbyEnemies.length > 0) {
        outputCallback("ATTACK " + arena.getDirection(cell, nearbyEnemies[(nearbyEnemies.length*Math.random())|0]) + " " + Math.min(cell.energy, 3));
        return;
    }

    if (Math.random() < 0.5) {
        for(var i=0; i<nearbyEmpties.length; ++i) {
            outputCallback("MOVE " + arena.getEnemyDirection(cell));
            return;
        }
    } 

    if (nearbyEmpties.length > 0 && nearbyEnemies.length <= 6) {
        outputCallback("REST"); // because next turn is divide time
        return;
    }

    // if there's a nearby corpse, eat it if your energy is below max
    if(nearbyCorpses.length > 0) {
        outputCallback("EAT " + arena.getDirection(cell, nearbyCorpses[(nearbyCorpses.length*Math.random())|0]));
        return;
    }

    outputCallback("REST");
    return;
}

var input = "";
// quiet stdin EPIPE errors
process.stdin.on("error", function(err) {
    console.log("slight error: " + err);
});
process.stdin.on("data", function(data) {
    input += data;
});
process.stdin.on("end", function() {
    if(input == "BEGIN") {
        // output space-separated attributes
        process.stdout.write([MAX_HP, MAX_ENERGY, ACIDITY].join(" "));
    } else {
        // read in arena and decide on an action
        var arena = new Arena();
        var lines = input.split("\n");
        var dimensions = lines[0].split(" ").map(function(d) { return parseInt(d); });
        arena.width = dimensions[0];
        arena.height = dimensions[1];
        for(var y=1; y<=dimensions[1]; ++y) {
            for(var x=0; x<lines[y].length; ++x) {
                arena.set(x, y-1, lines[y][x]);
            }
        }

        var stats = lines[dimensions[1]+2].split(" ");
        var cell = { x: stats[0], y: stats[1], hp: stats[2], energy: stats[3], point: arena.get(stats[0], stats[1]) };

        // decide on an action and write the action to stdout
        decide(arena, cell, function(output) { process.stdout.write(output); })
    }
});

var Arena = function() {
    this.dict = {};
};
Arena.prototype = {
    // get Point object
    get: function(x,y) {
        return this.dict[x+","+y];
    },

    // store Point object
    set: function(x,y,d) {
        this.dict[x+","+y] = new Point(x,y,d);
    },

    // get an array of all Points adjacent to this one whose symbol is contained in matchList
    // if matchList is omitted, return all Points
    getAdjacentMatches: function(point, matchList) {
        var result = [];
        for(var i=-1; i<=1; ++i) {
            for(var j=-1; j<=1; ++j) {
                var inspectedPoint = this.get(point.x+i, point.y+j);
                if(inspectedPoint && 
                   (i!=0 || j!=0) &&
                   (!matchList || matchList.indexOf(inspectedPoint.symbol) != -1)) {
                    result.push(inspectedPoint);
                }
            }
        }
        return result;
    },

    // return the direction from point1 to point2
    getDirection: function(point1, point2) {
        var dx = point2.x - point1.x;
        var dy = point2.y - point1.y;
        dx = Math.abs(dx) / (dx || 1);
        dy = Math.abs(dy) / (dy || 1);

        c2d = { "0,0":"-",
                "0,-1":"N", "0,1":"S", "1,0":"E", "-1,0":"W",
                "-1,-1":"NW", "1,-1":"NE", "1,1":"SE", "-1,1":"SW" };

        return c2d[dx + "," + dy];
    },

    getEnemyDirection: function(p) {
        for (var i = 0; i < this.width; i++) {
            for (var j = 0; j < this.height; j++) {
                var found = this.get(i,j);
                if (found != null && found.symbol == "x") {
                    return this.getDirection(p, found);
                }
            }
        }
        return "N"; //should never happen
    }
}

var Point = function(x,y,d) {
    this.x = x;
    this.y = y;
    this.symbol = d;
}
Point.prototype.toString = function() {
    return "(" + this.x + ", " + this.y + ")";
}
RMalke
źródło
justhalf zidentyfikował kilka poważnych błędów w programie sterownika (MOVE był bezpłatny, a EXPLODE nie uwzględniało kwasowości). Jeśli chcesz ponownie przetestować zaktualizowany kod sterownika i zaktualizować zgłoszenie, daj mi znać. Jeśli nie, to też w porządku.
apsillers
2

Ewolucja

To poddanie ewoluowało i nie jest już prostym organizmem jednokomórkowym! Próbuje atakować / eksplodować, gdy tylko jest to możliwe, w przeciwnym razie dzieli lub zbliża się do wroga. Poruszanie powinno rozwiązać problem komórki otoczonej przyjaznymi komórkami o maksymalnej energii, niezdolnymi do zrobienia czegoś pożytecznego.
Po przeprowadzce zawsze pozostają 3 energie, by trafić wroga tak mocno, jak to możliwe.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;

public class Evolution {
    public static final int MAX_HP = 4;
    public static final int MAX_ENERGY = 8;
    public static final int ACIDITY = 0;

    // given arena state and cell stats, return an action string (e.g., "ATTACK NW 2", "DIVIDE S")
    public static String decide(Arena arena, Point cell, int hp, int energy) {
        ArrayList<Point> nearbyEmpty = arena.getAdjacentMatches(cell, ".");
        ArrayList<Point> nearbyEnemies = arena.getAdjacentMatches(cell, "x");
        ArrayList<Point> nearbyCorpses = arena.getAdjacentMatches(cell, "c");
        ArrayList<Point> nearbyFriends = arena.getAdjacentMatches(cell, "o");

        // more than 1 enemy around => explode if possible and worth it
        if(nearbyEnemies.size() > 1 && energy > hp && hp <= 3 && nearbyEnemies.size() > nearbyFriends.size()) {
            return "EXPLODE";
        }

        // enemies around => always attack with max strength
        if(energy > 0 && nearbyEnemies.size() > 0) {
            int attackStrength = Math.min(energy, 3);
            Point enemy = nearbyEnemies.get(0);
            return "ATTACK " + arena.getDirection(cell, enemy) + " " + attackStrength;
        }       

        // safe spot => divide if possible
        if(energy >= 5 && nearbyEmpty.size() > 0) {
            Point randomEmpty = nearbyEmpty.get((int)Math.floor(nearbyEmpty.size()*Math.random()));
            return "DIVIDE " + arena.getDirection(cell, randomEmpty);
        }

        // nearby corpse and missing energy => eat
        if(nearbyCorpses.size() > 0 && energy < MAX_ENERGY) {
            Point corpse = nearbyCorpses.get(0);
            return "EAT " + arena.getDirection(cell, corpse);
        }

        // move towards enemy => constant flow of attacks
        if(energy == 4) {
            return "MOVE " + arena.getEnemyDirection(cell);
        }

        return "REST";
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br =
            new BufferedReader(new InputStreamReader(System.in));

        String firstLine;

        firstLine = br.readLine();
        if(firstLine.equals("BEGIN")) {
            System.out.println(MAX_HP + " " + MAX_ENERGY + " " + ACIDITY);
        } else {
            String[] dimensions = firstLine.split(" ");
            int width = Integer.parseInt(dimensions[0]);
            int height = Integer.parseInt(dimensions[1]);
            Point[][] arena = new Point[height][];
            String input;
            int lineno = 0;

            while(!(input=br.readLine()).equals("")) {
                String[] charList = input.substring(1).split("");
                arena[lineno] = new Point[width];
                for(int i=0; i<charList.length; ++i) {
                    arena[lineno][i] = new Point(i, lineno, charList[i]);
                }
                lineno++;
            }

            String[] stats = br.readLine().split(" ");
            int x = Integer.parseInt(stats[0]);
            int y = Integer.parseInt(stats[1]);
            int hp = Integer.parseInt(stats[2]);
            int energy = Integer.parseInt(stats[3]);

            Arena arenaObj = new Arena(arena, width, height);
            System.out.print(decide(arenaObj, arenaObj.get(x,y), hp, energy));
        }
    }

    public static class Arena {
        public Point[][] array;
        public HashMap<String, String> c2d;
        public int height;
        public int width;

        public Arena(Point[][] array, int width, int height) {
            this.array = array;
            this.width = width;
            this.height = height;

            this.c2d = new HashMap<String, String>();
            this.c2d.put("0,0", "-");
            this.c2d.put("0,-1", "N");
            this.c2d.put("0,1", "S");
            this.c2d.put("1,0", "E");
            this.c2d.put("-1,0", "W");
            this.c2d.put("-1,-1", "NW");
            this.c2d.put("1,-1", "NE");
            this.c2d.put("-1,1", "SW");
            this.c2d.put("1,1", "SE");
        }

        // get the character at x,y
        // or return empty string if out of bounds
        public Point get(int x, int y) {
            if(y < 0 || y >= this.array.length){
                return null;
            }

            Point[] row = this.array[y];

            if(x < 0 || x >= row.length) {
                return null;
            }

            return row[x];
        }

        // get arraylist of Points for each adjacent space that matches the target string
        public ArrayList<Point> getAdjacentMatches(Point p, String match) {
            ArrayList<Point> result = new ArrayList<Point>();
            for(int i=-1; i<=1; ++i) {
                for(int j=-1; j<=1; ++j) {
                    Point found = this.get(p.x+i, p.y+j);
                    if((i!=0 || j!=0) && found != null && found.symbol.equals(match)) {
                        result.add(found);
                    }
                }
            }
            return result;
        }

        // get the direction string from point 1 to point 2
        public String getDirection(Point p1, Point p2) {
            int dx = p2.x - p1.x;
            int dy = p2.y - p1.y;
            dx = Math.abs(dx) / (dx==0?1:dx);
            dy = Math.abs(dy) / (dy==0?1:dy);

            return this.c2d.get(dx + "," + dy);
        }

        public String getEnemyDirection(Point p) {
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    Point found = this.get(x,y);
                    if (found != null && found.symbol.equals("x")) {
                        return getDirection(p, found);
                    }
                }
            }
            return "N"; //should never happen
        }
    }

    public static class Point {
        int x, y;
        String symbol;

        public Point(int x, int y, String sym) {
            this.x=x;
            this.y=y;
            this.symbol=sym;
        }
    }
}
CommonGuy
źródło
justhalf zidentyfikował kilka poważnych błędów w programie sterownika (MOVE był bezpłatny, a EXPLODE nie uwzględniało kwasowości). Jeśli chcesz ponownie przetestować zaktualizowany kod sterownika i zaktualizować zgłoszenie, daj mi znać. Jeśli nie, to w porządku.
apsillers
2

Berserker

Ponieważ korzystałem z Clojure, który ma pewne ograniczenia, głównie z powodu ogromnego czasu uruchamiania, trochę się ożywiłem. Gdy program jest podany BEGIN, wyświetla dane wyjściowe 4 6 2 LOOP, co oznacza, że ​​się nie zatrzymuje. Następnie przyjmuje dane wejściowe jako ciągły strumień i kończy się na END. Nie zapisuje żadnego stanu, co jest wyjaśnione przez brak użycia zmiennych globalnych lub ponowne użycie wartości zwracanych. Ponieważ implementacja tego działania pętli nie została jeszcze wykonana, nie mogłem w pełni przetestować kodu (mam nadzieję, że kod jest wystarczająco jasny).

Komórka zyskała swoją nazwę od charakteru eksplozji, gdy tylko jest to możliwe (a tym samym kwasowości) i poprzez priorytetowe atakowanie zaraz po podziale.

Przesłałem wygenerowany plik jar do mojego Dropbox . Biegnij zjava -jar petridish-clojure.jar

Aby wyjaśnic:

> BEGIN
< 4 6 2 LOOP
> 10 4
> ..........
> ..xx.c....
> ...c...O..
> ......o...
> 
> 3 4 6
< DIVIDE NW
> 10 4
> ..........
> ..xx.c....
> ...c.o.o..
> ......o...
>
> 5 2 4 1
< EAT N
> END
(ns petridish.core
  (:require [clojure.string :as s])
  (:gen-class))

(def ^:const maxhp     4)
(def ^:const maxenergy 6)
(def ^:const acidity   2)

(defn str->int
  [x]
  (if (empty? x)
    0
    (Integer. (re-find #"\d+" x))))

(defn sum-vectors [vec1 vec2]
  (vec (map #(vec (map + % vec2)) vec1)))

(defn find-adjacent [[width height] board pos target]
  (let [cells (sum-vectors [[-1 -1] [0 -1] [1 -1]
                            [-1  0]        [1  0]
                            [-1  1] [0  1] [1  1]]
                           pos)
        directions ["NW" "N" "NE"
                    "W"      "E"
                    "SW" "S" "SE"]]
    (for [cell cells
          :when (and (> width  (cell 0) -1)
                     (> height (cell 1) -1)
                     (= target (get-in board (reverse cell))))]
      (directions (.indexOf cells cell)))))

(defn decide [size board [x y hp energy]]
  (let [friends (find-adjacent size board [x y] \o)
        enemies (find-adjacent size board [x y] \x)
        corpses (find-adjacent size board [x y] \c)
        empty   (find-adjacent size board [x y] \.)]
    (cond
      (and (<= hp 3) (> energy hp) (seq enemies))
        "EXPLODE"
      (and (>= energy 5) (seq empty))
        (str "DIVIDE " (first empty))
      (and (>= energy 3) (seq enemies))
        (str "ATTACK " (first enemies) " " (min 3 energy))
      (and (< energy maxenergy) (seq corpses))
        (str "EAT " (first corpses))
      (or (and (<= 5 energy maxenergy) (not (seq empty))) (< energy 5))
        "REST"
      (seq empty)
        (str "MOVE " (rand-nth empty)))))

(defn read-board [[width height]]
  (let [result (vec (for [i (range height)]
                        (read-line)))]
    (read-line) ; Skip the empty line
    result))

(defn reader []
  (loop []
    (let [firstline (read-line)]
      (when (not= firstline "END")
        (println
          (if (= firstline "BEGIN")
            (str maxhp " " maxenergy " " acidity " LOOP")
            (let [size   (map str->int (s/split firstline #"\s+"))
                  board  (read-board size)
                  status (map str->int (s/split (read-line) #"\s+"))]
              (decide size board status))))
        (recur)))))

(defn -main []
  (reader))

Zaktualizuj dziennik

1. Fixed the logic a little and removed redundancies.
patrz
źródło
Ładne użycie kwasowości - w rzeczywistości myślę, że to jedyny bot, który w ogóle używa kwasowości.
Alex
@Alex Zobaczymy, jak to działa, ale myślę, że powinno to być w stanie usunąć Amoebę. Co sądzisz o kodzie? Jestem taki nowy w clojure.
patrz
Jak w twoim przykładzie porusza się nowo odrodzona komórka? Myślałem, że musisz poczekać jeszcze jedną turę?
justhalf
@ justhalf Eh, komórki nie wiedzą, ile mają lat.
patrz
Tak, ale kontroler wie, prawda? To nie powinno obrócić nowopowstałej komórki.
justhalf
2

Głodny, głodny bot

Oto wpis w R. Mam nadzieję, że dobrze zrozumiałem, jakie były specyfikacje techniczne dotyczące komunikacji z twoim programem. Powinny zostać uruchomione za pomocą Rscript Hungryhungrybot.R.
Jeśli ma co najmniej 6 energii, dzieli się, najlepiej w kierunku wroga. W przeciwnym razie zjada to, co jest obok lub co jest osiągalne. Jeśli jedzenie nie jest osiągalne, albo eksploduje, gdy w pobliżu jest więcej wrogów niż komórki siostrzane, albo walczy z pobliskimi wrogami. Odpoczywa tylko wtedy, gdy energia wynosi 0 i nie ma nic do jedzenia.

infile <- file("stdin")
open(infile)
input1 <- readLines(infile,1)
if(input1=="BEGIN"){
    out <- "4 7 1"
    }else{
        nr <- as.integer(strsplit(input1," ")[[1]][2])
        nc <- as.integer(strsplit(input1," ")[[1]][1])
        input2 <- readLines(infile, 2+as.integer(nr))
        arena <- do.call(rbind,strsplit(input2[1:nr],""))
        stats <- strsplit(input2[nr+2]," ")[[1]]
        coords <- as.integer(stats[2:1])+1
        hp <- as.integer(stats[3])
        nrj <- as.integer(stats[4])
        closest <- function(coords,arena,object){
            a <- which(arena==object,arr.ind=TRUE)
            if(length(a)){
                d <- apply(a,1,function(x)max(abs(x-coords)))
                b <- which.min(d)
                f <- a[b,]
                dir <- f-coords
                where <- ""
                if(dir[1]<0)where <- paste(where,"N",sep="")
                if(dir[1]>0)where <- paste(where,"S",sep="")
                if(dir[2]<0)where <- paste(where,"W",sep="")
                if(dir[2]>0)where <- paste(where,"E",sep="")
                dist <- d[b]
                }else{dist <- NA; where <- ""}
            list(dist,where)
            }
        near <- expand.grid((coords[1]-1):(coords[1]+1),(coords[2]-1):(coords[2]+1))
        near <- near[near[,1]<=nr&near[,2]<=nc,]
        adjacent <- t(matrix(apply(near,1,function(x)arena[x[1],x[2]]),nr=3,byrow=TRUE))
        w <- matrix(c('NW','N','NE','W','','E','SW','S','SE'),nr=3,byrow=TRUE)
        if(coords[1]==1) w <- w[-1,]
        if(coords[1]==nr) w <- w[-3,]
        if(coords[2]==1) w <- w[,-1]
        if(coords[2]==nc) w <- w[,-3]
        if(any(arena=="c")){food <- closest(coords,arena,"c")}else{food <- list(nrj+2,"")}
        enemies <- closest(coords,arena,"x")
        if(nrj>=6){
            empties <- w[adjacent=="."]
            if(!length(empties)){
                if(sum(adjacent=="x")>sum(adjacent=="o") & hp<=3 & nrj>=hp){
                    out <- "EXPLODE"
                    }else{out <- "REST"}
                }else if(enemies[[2]]%in%empties & enemies[[1]]!=1){
                out <- paste("DIVIDE", enemies[[2]])
                }else{
                out <- paste("DIVIDE", empties[1])
                }
            }else{
                if(nrj==0 & !any(adjacent=="c")){
                    out <- "REST"
                    }else{
                        if(any(adjacent=="c")){
                            out <- paste("EAT",w[adjacent=="c"][1])
                            }else if(any(arena=="c") & food[[1]]<=(nrj+1)){
                                    out <- paste("MOVE",food[[2]])
                            }else if(sum(adjacent=="x")>sum(adjacent=="o") & hp<=3 & nrj>=hp){
                                out <- "EXPLODE"
                            }else if(any(adjacent=="x")){
                                out <- paste("ATTACK",w[adjacent=="x"][1],max(nrj,3))
                            }else{
                                out <- paste("MOVE", enemies[[2]])
                            }
                    }
            }
        }
cat(out)
flush(stdout())
plannapus
źródło
Próbuję (w końcu) podjąć wyzwanie i ciągle wprowadzam Error: unexpected 'else' in "else"kod. Obawiam się, że w ogóle nie znam R. Tak więc nie mogę rozpocząć rozwiązywania tego błędu. Dla porównania, ten błąd pojawia się zarówno po uruchomieniu go w sterowniku, jak i po prostu po uruchomieniu programu i ręcznym wpisaniu BEGIN.
apsillers
@apsillers arf Dodałem nowy wiersz, w którym nie powinienem mieć: powinien działać teraz.
plannapus
Naprawiono ten błąd, abyśmy mogli przejść przez inicjację komórki; teraz dostaję kolejny, gdy gra się naprawdę rozpoczyna:Error in if (dir[1] < 0) where <- paste(where, "N", sep = "") : missing value where TRUE/FALSE needed
apsillers
Teraz pierwsza tura działa dobrze, ale kolejne tury dają Error: object 'food' not found(w starciu z poddaniem się Ruby Alexa, ewentualnie innymi)
apsillers
Twoja komórka działa teraz dobrze, dzięki! :) Jednak justhalf zidentyfikował kilka poważnych błędów w programie sterownika (MOVE był bezpłatny, a EXPLODE nie uwzględniało kwasowości). Jeśli chcesz ponownie przetestować zaktualizowany kod sterownika i zaktualizować zgłoszenie, daj mi znać. Jeśli nie, to w porządku.
apsillers
2

Skoordynowane bakterie

Mam nadzieję, że się nie spóźnię.

Wygraj z innymi przeciwnikami (i zawsze zabijając ich wszystkich), w moich testach, a bitwa nigdy się nie skończy, jeśli stanie w obliczu samej siebie, dowód na to, że strategia jest silna.

Kiedy jesteś jednokomórkowy, możesz zapamiętać poprzedni stan, ale możesz wykorzystać własną pozycję, by zachowywać się inaczej! =)

Spowoduje to podział bakterii na dzielnik i ruch, a dzięki temu więcej bakterii będzie użytecznych zamiast samej linii frontu, a linia obrony pozostanie w linii.

Koordynuje także swoje ataki, aby skupić się na konkretnym wrogu, dzięki czemu wrogowie szybciej się zabijają (ma to na celu stawienie czoła mojej innej pojedynczej komórce, która skupia się na HP).

W środkowej fazie gry, która jest wykrywana przez liczbę komórek na planszy, będą próbować wepchnąć się na terytorium wroga, oskrzydlając je. To jest kluczowa strategia wygrywania.

Ma to najwyższe tempo wzrostu w porównaniu do wszystkich innych przeciwników obecnie, ale ma powolny start, więc działa to lepiej na dużej arenie.

Uruchom to z java CoordinatedBacteria

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

public class CoordinatedBacteria {
    public static final int MAX_HP = 6;
    public static final int MAX_ENERGY = 6;
    public static final int ACIDITY = 0;

    // given arena state and cell stats, return an action string (e.g., "ATTACK NW 2", "DIVIDE S")
    public static String decide(final Arena arena, Point cell, int hp, int energy) {
        // empty and corpses are free for movement and division
        final Point2D enemyCenter = arena.getCenterOf("x");
        final Point2D ourCenter = arena.getCenterOf("o");
        final int moverPos = (enemyCenter.x <= ourCenter.x || enemyCenter.y <= ourCenter.y) ? (arena.width+arena.height+1)%2 : 1;
        final int attackPos = (enemyCenter.x <= ourCenter.x || enemyCenter.y <= ourCenter.y) ? (arena.width+arena.height)%2 : 1;

        int selfCount = arena.count("o");
        boolean isMidWay = selfCount > (arena.width*arena.height/2-1);

        if(!isMidWay){
            if(enemyCenter.x < ourCenter.x){
                enemyCenter.x = 0;
                enemyCenter.y = 0;
                ourCenter.x = arena.width;
                ourCenter.y = arena.height;
            } else {
                enemyCenter.x = arena.width;
                enemyCenter.y = arena.height;
                ourCenter.x = 0;
                ourCenter.y = 0;
            }
        }
        ArrayList<Point> nearbyEmpty = arena.getAdjacentMatches(cell, ".");
        Collections.sort(nearbyEmpty, new Comparator<Point>(){
            @Override
            public int compare(Point o1, Point o2) {
                Double score1 = arena.getAdjacentMatches(o1, ".").size()
                        + arena.getAdjacentMatches(o1, "c").size()
                        + arena.getAdjacentMatches(o1, "x").size()
                        - arena.getAdjacentMatches(o1, "o").size()
                        + distance(o1.x, o1.y, enemyCenter.x, enemyCenter.y)*100;
                Double score2 = arena.getAdjacentMatches(o2, ".").size()
                        + arena.getAdjacentMatches(o2, "c").size()
                        + arena.getAdjacentMatches(o2, "x").size()
                        - arena.getAdjacentMatches(o2, "o").size()
                        + distance(o1.x, o1.y, enemyCenter.x, enemyCenter.y)*100;
                return Double.compare(score2, score1);
            }
        });
        ArrayList<Point> nearbyEnemies = arena.getAdjacentMatches(cell, "x");
        Collections.sort(nearbyEnemies, new Comparator<Point>(){
            @Override
            public int compare(Point o1, Point o2) {
                Integer score1 = (arena.getAdjacentMatches(o1, ".").size()
                        + arena.getAdjacentMatches(o1, "c").size()
                        - arena.getAdjacentMatches(o1, "x").size()
                        + arena.getAdjacentMatches(o1, "o").size())
                        *10
                        + (isAtBoundary(o1, arena)?1000:0)
                        + (o1.x + o1.y + attackPos + 1)%2;
                Integer score2 = (arena.getAdjacentMatches(o2, ".").size()
                        + arena.getAdjacentMatches(o2, "c").size()
                        - arena.getAdjacentMatches(o2, "x").size()
                        + arena.getAdjacentMatches(o2, "o").size())
                        *10
                        + (isAtBoundary(o2, arena)?1000:0)
                        + (o2.x + o2.y + attackPos + 1)%2;
                return Integer.compare(score2, score1);
            }
        });
        ArrayList<Point> nearbyCorpses = arena.getAdjacentMatches(cell, "c");
        Collections.sort(nearbyCorpses, new Comparator<Point>(){
            @Override
            public int compare(Point o1, Point o2) {
                Integer score1 = arena.getAdjacentMatches(o1, "x").size()
                        - arena.getAdjacentMatches(o1, "o").size();
                Integer score2 = arena.getAdjacentMatches(o2, "x").size()
                        - arena.getAdjacentMatches(o2, "o").size();
                return Integer.compare(score1, score2);
            }
        });
        ArrayList<Point> nearbyFriends = arena.getAdjacentMatches(cell, "o");

        for(Point empty: nearbyEmpty){
            if(nearbyFriends.size()>=2 && energy >= 1 && arena.getAdjacentMatches(empty, "x").size()==3 && isAtBoundary(empty, arena)){
                return "MOVE "+arena.getDirection(cell, empty);
            }
        }

        for(Point empty: nearbyCorpses){
            if(nearbyFriends.size()>=2 && energy >= 1 && arena.getAdjacentMatches(empty, "x").size()==3 && isAtBoundary(empty, arena)){
                return "MOVE "+arena.getDirection(cell, empty);
            }
        }

        if ((cell.x+cell.y)%2 == moverPos && energy >= 1 && energy <= 5){
            if(nearbyEmpty.size()>0){
                Point foremost = nearbyEmpty.get(0);
                if(nearbyFriends.size() >= 4){
                    return "MOVE "+arena.getDirection(cell, foremost);
                }
            }
            if(nearbyCorpses.size() > 0) {
                Point corpse = nearbyCorpses.get(0);
                return "EAT " + arena.getDirection(cell, corpse);
            }

            if(energy > 0 && nearbyEnemies.size() > 0) {
                int attackStrength = Math.min(energy, 3);
                Point enemy = nearbyEnemies.get(0);
                return "ATTACK " + arena.getDirection(cell, enemy) + " " + attackStrength;
            }

            if(nearbyFriends.size() >= 4 && nearbyEmpty.size() > 0){
                Point movePoint = getBestPointToDivide(arena, nearbyEmpty);
                return "MOVE " + arena.getDirection(cell, movePoint);
            }
        }

        if(energy >= 5 && nearbyEmpty.size() > 0) {
            Point divisionPoint = getBestPointToDivide(arena, nearbyEmpty);
            if(energy == MAX_ENERGY && nearbyFriends.size() >= 5
                    && distance(enemyCenter.x, enemyCenter.y, cell.x, cell.y) > distance(enemyCenter.x, enemyCenter.y, divisionPoint.x, divisionPoint.y)){
                return "MOVE " + arena.getDirection(cell, divisionPoint);
            }
            return "DIVIDE " + arena.getDirection(cell, divisionPoint);
        }

        if(nearbyCorpses.size() > 0) {
            Point corpse = nearbyCorpses.get(0);
            if (energy < MAX_ENERGY){
                return "EAT " + arena.getDirection(cell, corpse);
            } else {
                return "DIVIDE " + arena.getDirection(cell, corpse);
            }
        }

        if(energy >= 5 && nearbyCorpses.size() > 0) {
            Point divisionPoint = getBestPointToDivide(arena, nearbyCorpses);
            if(energy == MAX_ENERGY && nearbyFriends.size() >= 5
                    && distance(enemyCenter.x, enemyCenter.y, cell.x, cell.y) < distance(enemyCenter.x, enemyCenter.y, divisionPoint.x, divisionPoint.y)){
                return "MOVE " + arena.getDirection(cell, divisionPoint);
            }
            return "DIVIDE " + arena.getDirection(cell, divisionPoint);
        }

        // if at least one adjacent enemy, attack if possible
        if(energy > 0 && nearbyEnemies.size() > 0) {
            int attackStrength = Math.min(energy, 3);
            Point enemy = nearbyEnemies.get(0);
            return "ATTACK " + arena.getDirection(cell, enemy) + " " + attackStrength;
        }

        return "REST";

    }

    public static boolean isAtBoundary(Point point, Arena arena){
        return point.x==0 || point.x==arena.width-1 || point.y==0 || point.y==arena.height-1;
    }

    public static double distance(double x1, double y1, double x2, double y2){
        return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
    }

    public static Point getBestPointToDivide(Arena arena, List<Point> nearbyEmpty){
        Point result = null;
        double minDist = 100000;
        List<Point> mostEmpty = new ArrayList<Point>();
        int max = -1000;
        List<Point> neighbor = nearbyEmpty;
        for(Point point: neighbor){
            int emptyNeighborScore = arena.getAdjacentMatches(point, ".").size()
                    + arena.getAdjacentMatches(point, "c").size()
                    + arena.getAdjacentMatches(point, "x").size()
                    - arena.getAdjacentMatches(point, "o").size();
            if(emptyNeighborScore > max){
                mostEmpty = new ArrayList<Point>();
                mostEmpty.add(point);
                max = emptyNeighborScore;
            } else if(emptyNeighborScore == max){
                mostEmpty.add(point);
            }
        }
        for(Point point: mostEmpty){
            Point2D enemyCenter = arena.getCenterOf("x");
            double dist = Math.pow(point.x-enemyCenter.x, 2) + Math.pow(point.y-enemyCenter.y, 2);
            if(dist < minDist){
                minDist = dist;
                result = point;
            }
        }
        return result;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br =
                new BufferedReader(new InputStreamReader(System.in));

        String firstLine;

        firstLine = br.readLine();
        if(firstLine.equals("BEGIN")) {
            System.out.println(MAX_HP + " " + MAX_ENERGY + " " + ACIDITY);
        } else {
            String[] dimensions = firstLine.split(" ");
            int width = Integer.parseInt(dimensions[0]);
            int height = Integer.parseInt(dimensions[1]);
            Point[][] arena = new Point[height][];
            String input;
            int lineno = 0;

            while(!(input=br.readLine()).equals("")) {
                char[] charList = input.toCharArray();
                arena[lineno] = new Point[width];
                for(int i=0; i<charList.length; ++i) {
                    arena[lineno][i] = new Point(i, lineno, charList[i]);
                }
                lineno++;
            }

            String[] stats = br.readLine().split(" ");
            int x = Integer.parseInt(stats[0]);
            int y = Integer.parseInt(stats[1]);
            int hp = Integer.parseInt(stats[2]);
            int energy = Integer.parseInt(stats[3]);

            Arena arenaObj = new Arena(arena, width, height);
            System.out.print(decide(arenaObj, arenaObj.get(x,y), hp, energy));
        }
    }

    public static class Arena {
        public Point[][] array;
        public HashMap<String, String> c2d;
        public int height;
        public int width;

        public Arena(Point[][] array, int width, int height) {
            this.array = array;
            this.width = width;
            this.height = height;


            this.c2d = new HashMap<String, String>();
            this.c2d.put("0,0", "-");
            this.c2d.put("0,-1", "N");
            this.c2d.put("0,1", "S");
            this.c2d.put("1,0", "E");
            this.c2d.put("-1,0", "W");
            this.c2d.put("-1,-1", "NW");
            this.c2d.put("1,-1", "NE");
            this.c2d.put("-1,1", "SW");
            this.c2d.put("1,1", "SE");
        }

        // get the character at x,y
        // or return empty string if out of bounds
        public Point get(int x, int y) {
            if(y < 0 || y >= this.array.length){
                return null;
            }

            Point[] row = this.array[y];

            if(x < 0 || x >= row.length) {
                return null;
            }

            return row[x];
        }

        // get arraylist of Points for each adjacent space that matches the target string
        public ArrayList<Point> getAdjacentMatches(Point p, String match) {
            ArrayList<Point> result = new ArrayList<Point>();
            for(int i=-1; i<=1; ++i) {
                for(int j=-1; j<=1; ++j) {
                    Point found = this.get(p.x+i, p.y+j);
                    if((i!=0 || j!=0) && found != null && found.symbol.equals(match)) {
                        result.add(found);
                    }
                }
            }
            return result;
        }

        public ArrayList<Point> getAdjacents(Point p){
            ArrayList<Point> result = new ArrayList<Point>();
            for(int i=-1; i<=1; ++i) {
                for(int j=-1; j<=1; ++j) {
                    Point found = this.get(p.x+i, p.y+j);
                    if((i!=0 || j!=0) && found != null) {
                        result.add(found);
                    }
                }
            }
            return result;
        }

        public int count(String sym){
            int result = 0;
            for(int y=0; y<array.length; y++){
                for(int x=0; x<array[y].length; x++){
                    Point cur = this.get(x, y);
                    if(cur!=null && cur.symbol.equals(sym)){
                        result++;
                    }
                }
            }
            return result;
        }

        // get the direction string from point 1 to point 2
        public String getDirection(Point p1, Point p2) {
            int dx = p2.x - p1.x;
            int dy = p2.y - p1.y;
            dx = Math.abs(dx) / (dx==0?1:dx);
            dy = Math.abs(dy) / (dy==0?1:dy);

            return this.c2d.get(dx + "," + dy);
        }

        public Point2D getCenterOf(String sym){
            Point2D result = new Point2D(0,0);
            int count = 0;
            for(int y=0; y<array.length; y++){
                for(int x=0; x<array[y].length; x++){
                    if(this.get(x,y).symbol.equals(sym)){
                        result.x += x;
                        result.y += y;
                        count++;
                    }
                }
            }
            result.x /= count;
            result.y /= count;
            return result;
        }

    }

    public static class Point {
        int x, y;
        String symbol;

        public Point(int x, int y, String sym) {
            this.x=x;
            this.y=y;
            this.symbol=sym;
        }

        public Point(int x, int y, char sym){
            this(x, y, ""+sym);
        }
    }

    public static class Point2D{
        double x,y;
        public Point2D(double x, double y){
            this.x = x;
            this.y = y;
        }
    }
}
justhalf
źródło
1

Myślę, że opublikuję moje zgłoszenie, ponieważ jesteście tak hojni, aby dodać logikę płyty kotłowej ...

Wystąpił problem w twojej logice, polegający na tym, że akcja jedzenia wydawałaby ATAK zamiast JEDZENIA i marnowała zwłoki.

Zmodyfikowałem twoją treść tak samo, aby mieć działające rozwiązanie, które powinno działać stosunkowo dobrze. Zaczyna się od 4 hp i 8 energii, więc po podziale i odpoczynku oba ogniwa mogą się ponownie podzielić. Będzie próbował się rozmnażać, atakować wrogów, jeść zwłoki i odpoczywać w tej kolejności. Tak więc komórki wewnętrzne będą przechowywać 8 punktów energii, aby szybko zastąpić zabite komórki zewnętrzne i pozostawić im 3 punkty energii, aby wykonać 3-punktowy atak lub pomnożyć się po odpoczynku jednej tury. 4 hp mają przetrwać co najmniej jeden atak pełnej siły.

kwas wydaje mi się marnowaniem punktów, więc trzymałem go z daleka ...

Nie przetestowałem zgłoszenia, ponieważ było to 2 minuty;)

oto mój kod:

/*
 Sample code for a "Battle for the Petri Dish" cell

 Released under the terms of the WTF Public License
 No warranty express or implied is granted, etc, etc.

 I just hacked this together very quickly; improvements are welcome, so please fork the Gist if you like.

 used this code for a submission @kostronor

 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;

public class SlimeCell {
    public static final int MAX_HP = 4;
    public static final int MAX_ENERGY = 8;
    public static final int ACIDITY = 0;

    // given arena state and cell stats, return an action string (e.g., "ATTACK NW 2", "DIVIDE S")
    public static String decide(final Arena arena, final Point cell, final int hp, final int energy) {
        // empty and corpses are free for movement and division
        ArrayList<Point> nearbyEmpty = arena.getAdjacentMatches(cell, ".");
        nearbyEmpty.addAll(arena.getAdjacentMatches(cell, "c"));

        ArrayList<Point> nearbyEnemies = arena.getAdjacentMatches(cell, "x");
        ArrayList<Point> nearbyCorpses = arena.getAdjacentMatches(cell, "c");
        ArrayList<Point> nearbyFriends = arena.getAdjacentMatches(cell, "o");

        // if you have energy and space to divide, divide into a random space
        if((energy >= 5) && (nearbyEmpty.size() > 0)) {
            Point randomEmpty = nearbyEmpty.get((int)Math.floor(nearbyEmpty.size()*Math.random()));
            return "DIVIDE " + arena.getDirection(cell, randomEmpty);
        }

        // if at least one adjacent enemy, attack if possible
        if((energy > 0) && (nearbyEnemies.size() > 1)) {
            int attackStrength = Math.min(energy, 3);
            Point enemy = nearbyEnemies.get((int)Math.floor(nearbyEnemies.size()*Math.random()));
            return "ATTACK " + arena.getDirection(cell, enemy) + " " + attackStrength;
        }

        // if there's a nearby corpse, eat it if your energy is below max
        if(nearbyCorpses.size() > 0) {
            Point corpse = nearbyCorpses.get((int)Math.floor(nearbyCorpses.size()*Math.random()));
            return "EAT " + arena.getDirection(cell, corpse);
        }

        return "REST";

    }

    public static void main(final String[] args) throws IOException {
        BufferedReader br =
                new BufferedReader(new InputStreamReader(System.in));

        String firstLine;

        firstLine = br.readLine();
        if(firstLine.equals("BEGIN")) {
            System.out.println(MAX_HP + " " + MAX_ENERGY + " " + ACIDITY);
        } else {
            String[] dimensions = firstLine.split(" ");
            int width = Integer.parseInt(dimensions[0]);
            int height = Integer.parseInt(dimensions[1]);
            Point[][] arena = new Point[height][];
            String input;
            int lineno = 0;

            while(!(input=br.readLine()).equals("")) {
                String[] charList = input.substring(1).split("");
                arena[lineno] = new Point[width];
                for(int i=0; i<charList.length; ++i) {
                    arena[lineno][i] = new Point(i, lineno, charList[i]);
                }
                lineno++;
            }

            String[] stats = br.readLine().split(" ");
            int x = Integer.parseInt(stats[0]);
            int y = Integer.parseInt(stats[1]);
            int hp = Integer.parseInt(stats[2]);
            int energy = Integer.parseInt(stats[3]);

            Arena arenaObj = new Arena(arena, width, height);
            System.out.print(decide(arenaObj, arenaObj.get(x,y), hp, energy));
        }
    }

    public static class Arena {
        public Point[][] array;
        public HashMap<String, String> c2d;
        public int height;
        public int width;

        public Arena(final Point[][] array, final int width, final int height) {
            this.array = array;
            this.width = width;
            this.height = height;

            this.c2d = new HashMap<String, String>();
            this.c2d.put("0,0", "-");
            this.c2d.put("0,-1", "N");
            this.c2d.put("0,1", "S");
            this.c2d.put("1,0", "E");
            this.c2d.put("-1,0", "W");
            this.c2d.put("-1,-1", "NW");
            this.c2d.put("1,-1", "NE");
            this.c2d.put("-1,1", "SW");
            this.c2d.put("1,1", "SE");
        }

        // get the character at x,y
        // or return empty string if out of bounds
        public Point get(final int x, final int y) {
            if((y < 0) || (y >= this.array.length)){
                return null;
            }

            Point[] row = this.array[y];

            if((x < 0) || (x >= row.length)) {
                return null;
            }

            return row[x];
        }

        // get arraylist of Points for each adjacent space that matches the target string
        public ArrayList<Point> getAdjacentMatches(final Point p, final String match) {
            ArrayList<Point> result = new ArrayList<Point>();
            for(int i=-1; i<=1; ++i) {
                for(int j=-1; j<=1; ++j) {
                    Point found = this.get(p.x+i, p.y+j);
                    if(((i!=0) || (j!=0)) && (found != null) && found.symbol.equals(match)) {
                        result.add(found);
                    }
                }
            }
            return result;
        }

        // get the direction string from point 1 to point 2
        public String getDirection(final Point p1, final Point p2) {
            int dx = p2.x - p1.x;
            int dy = p2.y - p1.y;
            dx = Math.abs(dx) / (dx==0?1:dx);
            dy = Math.abs(dy) / (dy==0?1:dy);

            return this.c2d.get(dx + "," + dy);
        }

    }

    public static class Point {
        int x, y;
        String symbol;

        public Point(final int x, final int y, final String sym) {
            this.x=x;
            this.y=y;
            this.symbol=sym;
        }
    }
}
reggaemuffin
źródło
1

Cienko rozłożony bombowiec

Ponieważ tak uprzejmie dostarczyłeś kod bojlera, postanowiłem stworzyć własną prostą komórkę; Komórka ma 4 kwasowości, tylko 1 KM i 7 energii. Próbuje wyjść poza zasięg towarzyski, a następnie czeka tam (lub je, jeśli to możliwe), aż pojawi się szansa wysadzenia w powietrze lub replikacji. Atakuje tylko, jeśli jest to jedyna opcja.

Jest to dość ballsy strategia i prawdopodobnie będzie źle działać, ale jestem ciekawy, jak to działa. Być może przetestuję go i poprawię później.

/*
 Sample code for a "Battle for the Petri Dish" cell

 Released under the terms of the WTF Public License,
 No warranty express or implied is granted, etc, etc.

 I just hacked this together very quickly; improvements are welcome, so please fork the Gist if you like.
*/

// used in defining cell spec
var MAX_HP = 1;
var MAX_ENERGY = 7;
var ACIDITY = 4;

/*
   The decide function takes an Arena object (see below for prototype methods), a cell object,
   and an outputCallback, which accepts a command string to output
*/
function decide(arena, cell, outputCallback) {
    var nearbyEmpties = arena.getAdjacentMatches(cell.point, [".", "c"]);
    var nearbyEnemies = arena.getAdjacentMatches(cell.point, ["x"]);
    var nearbyCorpses = arena.getAdjacentMatches(cell.point, ["c"]);
    var nearbyFriendlies = arena.getAdjacentMatches(cell.point, ["o"]);

    //attempt to move away from friendlies if possible
    if(nearbyFriendlies.length>1 && cell.energy>0)
    {
        for(var i=0; i<nearbyEmpties.length; ++i)
        {
            var space = nearbyEmpties[i];
            if(arena.getAdjacentMatches(space, ["o"]).length == 1)
            {
                outputCallback("MOVE " + arena.getDirection(cell,space));
                return;
            }
        }
    }

    // Explode if there are two more adjacent enemies than friendlies or enemies and no friendlies.
    if((nearbyEnemies.length - nearbyFriendlies.length > 1 || (nearbyEnemies.length>0 && nearbyFriendlies.length == 0)) 
        && cell.energy >= cell.hp && cell.hp <= 3)
    {
        outputCallback("EXPLODE");
        return;
    }

    // if you have the energy and space to divide, and there's a way for the child to get away from friendlies, do it.
    if(cell.energy >= 5 && nearbyEmpties.length > 0)
    {
        for(var i=0; i<nearbyEmpties.length; ++i)
        {
            var space = nearbyEmpties[i];
            var possiblePositions = arena.getAdjacentMatches(space, ["o"]);
            for(var i=0; i<possiblePositions.length; ++i)
            {
                if(arena.getAdjacentMatches(possiblePositions[i], ["o"]).length == 0)
                {
                    outputCallback("DIVIDE " + arena.getDirection(cell,space));
                    return;
                }
            }
        }
    }

    // if at least one adjacent enemy, attack if possible
    if(cell.energy > 0 && nearbyEnemies.length > 0)
    {
        outputCallback("ATTACK " + arena.getDirection(cell, nearbyEnemies[(nearbyEnemies.length*Math.random())|0]) + " " + Math.min(cell.energy, 3));
        return;
    }

    // if there's a nearby corpse, eat it if your energy is below max
    if(nearbyCorpses.length > 0)
    {
        outputCallback("EAT " + arena.getDirection(cell, nearbyCorpses[(nearbyCorpses.length*Math.random())|0]));
        return;
    }

    outputCallback("REST");
    return;
}

var input = "";
// quiet stdin EPIPE errors
process.stdin.on("error", function(err) {
    //console.log("slight error: " + err);
});
process.stdin.on("data", function(data) {
    input += data;
});
process.stdin.on("end", function() {
    if(input == "BEGIN") {
        // output space-separated attributes
        process.stdout.write([MAX_HP, MAX_ENERGY, ACIDITY].join(" "));
    } else {
        // read in arena and decide on an action
        var arena = new Arena();
        var lines = input.split("\n");
        var dimensions = lines[0].split(" ").map(function(d) { return parseInt(d); });
        arena.width = dimensions[0];
        arena.height = dimensions[1];
        for(var y=1; y<=dimensions[1]; ++y) {
            for(var x=0; x<lines[y].length; ++x) {
                arena.set(x, y-1, lines[y][x]);
            }
        }

        var stats = lines[dimensions[1]+2].split(" ");
        var cell = { x: stats[0], y: stats[1], hp: stats[2], energy: stats[3], point: arena.get(stats[0], stats[1]) };

        // decide on an action and write the action to stdout
        decide(arena, cell, function(output) { process.stdout.write(output); })
    }
});

var Arena = function() {
    this.dict = {};
};
Arena.prototype = {
    // get Point object
    get: function(x,y) {
        return this.dict[x+","+y];
    },

    // store Point object
    set: function(x,y,d) {
        this.dict[x+","+y] = new Point(x,y,d);
    },

    // get an array of all Points adjacent to this one whose symbol is contained in matchList
    // if matchList is omitted, return all Points
    getAdjacentMatches: function(point, matchList) {
        var result = [];
        for(var i=-1; i<=1; ++i) {
            for(var j=-1; j<=1; ++j) {
                var inspectedPoint = this.get(point.x+i, point.y+j);
                if(inspectedPoint && 
                   (i!=0 || j!=0) &&
                   (!matchList || matchList.indexOf(inspectedPoint.symbol) != -1)) {
                    result.push(inspectedPoint);
                }
            }
        }
        return result;
    },

    // return the direction from point1 to point2
    getDirection: function(point1, point2) {
        var dx = point2.x - point1.x;
        var dy = point2.y - point1.y;
        dx = Math.abs(dx) / (dx || 1);
        dy = Math.abs(dy) / (dy || 1);

        c2d = { "0,0":"-",
                "0,-1":"N", "0,1":"S", "1,0":"E", "-1,0":"W",
                "-1,-1":"NW", "1,-1":"NE", "1,1":"SE", "-1,1":"SW" };

        return c2d[dx + "," + dy];
    }
}

var Point = function(x,y,d) {
    this.x = x;
    this.y = y;
    this.symbol = d;
}
Point.prototype.toString = function() {
    return "(" + this.x + ", " + this.y + ")";
}
overactor
źródło
Próbuję to przetestować, ale nie mogę uruchomić. Zainstalowałem node.js próbowałem z wiersza poleceń node c:/cells/petri.js 'node c:/cells/bomber.js' 'node c:/cells/sample.js. Kiedy piszę to w konsoli aplikacji węzła, dostaję tylko trzy kropki, kiedy próbuję uruchomić go w cmd systemu Windows, otrzymuję: „węzeł” nie jest rozpoznawany jako polecenie wewnętrzne lub zewnętrzne, program operacyjny lub plik wsadowy. Zapisałem wszystkie pliki jako pliki .js w odpowiednim folderze. JAKAŻ pomoc dla nooba? Chodziłbym na czat lub komentował gdzie indziej, ale mój przedstawiciel jest zbyt niski.
overactor
Ponieważ nie mogę na razie przetestować, fajnie byłoby, gdyby ktoś mógł mi powiedzieć, jak radzą sobie moje komórki. Po drugie zgaduję moją taktykę, a przynajmniej sądzę, że wymaga dopracowania.
overactor
Wygląda na to, że masz na linii typ if((nearbyEnemies.length - nearbyFriendlies.length > 1 ¦¦ - ¦¦nie wydają się być poprawnym operatorem i masz niedopasowane nawiasy. Myślę, że może formatowanie kodu zostało pomieszane po opublikowaniu?
apsillers
To działa dość źle według moich testów. Masz wiele zadań ( =), gdy chcesz porównać równość ( ==).
justhalf
Och, kurde. W większości programowałem w języku, w którym (=) to zadanie, kiedy to napisałem, czy teraz działa lepiej? Nigdy nie spodziewałem się, że będzie świetnie.
overactor