Battle for the Petri Dish

32

Dalam tantangan ini, Anda harus merancang spesies organisme bersel tunggal untuk bertarung sampai mati di arena cawan petri. Arena direpresentasikan sebagai kotak persegi panjang, di mana setiap sel menempati satu ruang:

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

Atribut

Setiap sel memiliki tiga atribut. Saat menentukan spesies sel Anda di awal permainan, Anda mengalokasikan 12 poin di antara atribut ini.

  • Hit Points (HP): Jika HP sel jatuh ke nol, ia mati. Sel-sel baru memiliki HP penuh.
    • Ketika sebuah sel mati, ia meninggalkan mayat yang bisa dimakan oleh sel-sel lain untuk energi.
    • Sel tidak bisa mendapatkan kembali HP yang hilang, tetapi bisa membuat sel baru dengan HP penuh dengan membaginya.
  • Energi : Sebagian besar tindakan yang dilakukan sel membutuhkan energi. Dengan beristirahat secara aktif, sel dapat memperoleh kembali energi yang hilang hingga maksimum spesiesnya.
    • Spesies sel dengan energi kurang dari 5 cenderung gagal, karena tidak dapat membelah untuk membuat sel baru.
    • Sel tidak bisa mendapatkan kembali energi di luar nilai maksimum spesiesnya.
    • Sel yang baru dibuat memiliki nilai energi awal yang disalin dari induknya (dan nilai maksimum ditentukan oleh spesifikasi spesiesnya).
  • Keasaman : Jika suatu sel memilih untuk meledak, tingkat keasaman sel digunakan dalam menghitung kerusakan sel-sel yang berdekatan.

Tindakan

Setiap belokan, setiap sel dapat mengambil satu tindakan:

  • Pindah: Sel bergerak satu ruang ke segala arah (N / S / E / W / NE / NW / SE / SW) dengan biaya 1 energi.

    • Sel tidak bisa bergerak ke ruang yang ditempati sel hidup lain.
    • Sel tidak bisa bergerak dari grid.
    • Pindah ke mayat sel menghancurkan mayat.
  • Serangan: Sebuah sel menyerang sel yang berdekatan, menghasilkan 1 hingga 3 kerusakan, dengan mengeluarkan 1 hingga 3 poin energi.

    • Sel dapat menyerang ke segala arah (N / S / E / W / NE / NW / SE / SW).
    • Adalah legal untuk menyerang sel-sel yang bersahabat.
  • Membagi: Sel membelah dan menciptakan sel baru pada ruang yang berdekatan, dengan biaya 5 energi.

    • Sebuah sel dapat membelah ke segala arah (N / S / E / W / NE / NW / SE / SW).
    • Sel baru memiliki HP lengkap sesuai dengan spesifikasi sel asli Anda.
    • Sel baru memiliki energi sebanyak yang dimiliki sel induknya setelah mengurangi biaya pembagian. (Misalnya, sel induk dengan 8 titik energi awal akan dikurangi menjadi 3 energi dan menghasilkan sel anak dengan 3 energi).
    • Sel baru tidak dapat bertindak sampai giliran Anda berikutnya.
    • Sel tidak dapat membelah menjadi ruang yang ditempati oleh sel hidup, tetapi dapat membelah menjadi ruang yang ditempati oleh mayat sel mati (ini menghancurkan mayat).
  • Makan: Sel memakan mayat sel yang berdekatan, mendapatkan 4 energi.

    • Sel bisa makan ke segala arah (N / S / E / W / NE / NW / SE / SW).
  • Istirahat: Sel tidak melakukan apa-apa untuk satu putaran, mendapatkan kembali 2 energi.

  • Meledak: Ketika sebuah sel memiliki 3 atau lebih sedikit HP dan lebih banyak energi daripada HP, ia dapat memilih untuk meledak, memberikan kerusakan pada semua delapan sel yang berdekatan.

    • Kerusakan pada setiap sel yang berdekatan adalah (exploding cell HP) + (explodng cell acidity)
    • Sel yang meledak mati dan meninggalkan mayat, seperti halnya sel yang terbunuh dalam ledakan.

Protokol

Mendirikan

Program Anda akan berjalan dengan string yang BEGINdisediakan di stdin. Program Anda harus menulis ke stdout daftar 3 bilangan bulat non-negatif yang dipisahkan oleh ruang, mewakili HP, energi, dan keasaman untuk spesies sel Anda: misalnya 5 6 1,. Angka-angka harus berjumlah 12. Keasaman mungkin 0, jika Anda inginkan. (Atribut lain mungkin juga nol, tetapi secara fungsional kehilangan permainan!)

Anda mulai dengan satu sel, di sudut barat laut atau tenggara, satu ruang dari kedua sisi. Sel awal memiliki HP dan energi penuh.

Setiap sel bertindak

Setiap belokan, program Anda akan dipanggil satu kali untuk setiap sel yang hidup di tim Anda (kecuali sel yang baru saja dibuat giliran ini) sehingga sel dapat bertindak. Program Anda dilengkapi dengan data tentang stdin yang mencakup status cawan petri dan informasi tentang sel khusus ini:

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

6 3 5 7

Dua angka pertama menunjukkan lebar dan tinggi arena: di sini, ada arena 10-oleh-4.

  • The osel adalah milikmu; yang xsel adalah musuh Anda. (Ini selalu benar; setiap pemain selalu melihat sel mereka sendiri o.)
  • The .ruang kosong.
  • The cruang mewakili mayat sel dimakan.

Angka-angka setelah baris kosong mewakili info tentang sel ini:

  • Dua angka pertama adalah x,ykoordinat, diindeks dari 0,0di kiri atas (jadi di 6 3sini mengacu pada osel paling selatan ).
  • Angka ketiga adalah HP sel; angka keempat adalah energi sel.

Program Anda harus menampilkan (untuk stdout) suatu tindakan. Pada contoh di bawah ini, kami akan menggunakan Nsebagai arahan contoh, tetapi bisa berupa arahan apa pun yang sah untuk tindakan tersebut ( N/ S/ E/ W/ NE/ NW/ SE/ SW). Semua keluaran program tidak peka huruf besar-kecil, tetapi contoh akan menggunakan huruf besar. Setiap tindakan keluaran yang tidak valid (baik karena memiliki sintaks yang tidak valid atau upaya tindakan ilegal) diabaikan dan menghasilkan sel REST(dan dengan demikian mendapatkan 2 energi).

  • MOVE N
  • DIVIDE N
  • EAT N
  • ATTACK N 2 - angka tersebut mewakili kekuatan serangan (1 - 3)
  • REST
  • EXPLODE

Giliran tim Anda terdiri dari semua sel Anda diberi kesempatan untuk bertindak, satu per satu. Semua sel Anda bertindak sebelum sel lawan bertindak. Setelah semua sel Anda bertindak, giliran Anda berakhir, dan giliran lawan Anda dimulai. Setelah semua sel lawan Anda bertindak, giliran Anda mulai lagi. Dalam giliran Anda, setiap sel diberi prioritas untuk bertindak berdasarkan usianya: sel tertua di tim Anda bertindak terlebih dahulu sebelum sel yang lebih muda.

Contoh

Begini cara suatu program berperilaku. Input dari stdin dilambangkan di sini dengan >panah utama (dipisahkan dari input aktual oleh ruang klarifikasi) dan output pada stdout memiliki <panah.

> BEGIN
< 5 6 1

Kemudian, program dipanggil lagi:

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

Setelah giliran lawan (yang memutuskan untuk DIVIDE Wmenggunakan sel awal tunggal), program Anda dipanggil dua kali, satu kali untuk setiap sel:

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

Untuk doa kedua giliran Anda:

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

Perhatikan sel kedua ini melihat status papan yang diperbarui berdasarkan gerakan sel lain sebelumnya pada giliran Anda. Juga perhatikan sel ini telah dibuat dengan 1 energi, karena sel induk memiliki 6 energi ketika melakukan pembelahan terakhir (jadi yang asli 6, dikurangi biaya pembagian 5-energi, menciptakan sel anak dengan 1 energi).

Sekarang giliranmu selesai dan giliran lawanmu dimulai. Dua sel yang berlawanan akan diberi kesempatan untuk bertindak, dan kemudian giliran Anda berikutnya dimulai.

Kemenangan

Anda bisa menang dengan:

  • Menghancurkan semua sel yang berlawanan, atau
  • Memiliki lebih banyak sel daripada lawan Anda setelah setiap pemain menyelesaikan 150 putaran

Penilaian akan didasarkan pada jumlah kemenangan dalam 100 pertandingan melawan satu sama lain pengajuan. Dalam setengah dari simulasi, program Anda akan diizinkan untuk pergi dulu.

Permainan seri (yaitu jumlah sel yang sama persis setelah 150 putaran, atau satu-satunya sel yang tersisa terbunuh bersama dalam ledakan) tidak dihitung dalam total kemenangan salah satu pemain.

Informasi lainnya

  • Program Anda seharusnya tidak berusaha mempertahankan status (melampaui penggunaan cawan Petri): organisme monoseluler tidak memiliki ingatan yang sangat baik dan bereaksi terhadap dunia saat demi saat. Secara khusus, menulis ke file (atau penyimpanan data lainnya), berkomunikasi dengan server jarak jauh, atau mengatur variabel lingkungan tidak diizinkan secara eksplisit.
  • Kiriman akan dijalankan / dikompilasi di Ubuntu 12.04.4.
  • Spesifik dari 100 game penilaian belum dikonfirmasi, tetapi mereka kemungkinan akan melibatkan beberapa ukuran arena (misalnya, 50 berjalan di arena kecil dan 50 berjalan di arena yang lebih besar). Untuk arena yang lebih besar, saya dapat menambah hitungan turn turn untuk memastikan bahwa pertarungan yang tepat dapat terjadi.

Sumber daya

Berikut adalah kode driver yang menjalankan simulasi, ditulis untuk Node.js, dipanggil oleh node petri.js 'first program' 'second program'. Misalnya, mengadu sel yang ditulis Python dengan sel yang ditulis Java mungkin terlihat seperti node petri.js 'python some_cell.py' 'java SomeCellClass'.

Selain itu, saya mengerti bahwa membaca dan menguraikan banyak baris pada stdin bisa sangat menyebalkan, jadi saya telah menyusun beberapa sel sampel lengkap dalam berbagai bahasa yang Anda bebas untuk membangun, sepenuhnya merombak, atau mengabaikan sepenuhnya.

Tentu saja Anda bebas menulis sel dalam bahasa yang berbeda; ini hanya tiga bahasa yang saya putuskan untuk menulis kode boilerplate sebagai bantuan hemat waktu.

Jika Anda memiliki masalah dalam menjalankan driver, silakan ping saya di ruang obrolan yang saya buat untuk tantangan ini . Jika Anda tidak memiliki reputasi yang cukup untuk mengobrol, maka tinggalkan saja komentar.

apsillers
sumber
1
@Ryan Anda harus menentukan perintah yang sepenuhnya bisa dijalankan seperti 'node c:/cell/cell_template.js'untuk setiap argumen, sama seperti Anda harus menentukan 'java CellTemplate'untuk kode Java. Saya akan membuatnya lebih jelas dalam teks tantangan. Jika Anda masih mengalami masalah, kami (dan siapa pun yang memiliki masalah teknis) dapat melanjutkan diskusi ini di ruang obrolan yang baru saja saya buat .
apsillers
1
@ Moogie Hanya 2 lawan per game.
apsillers
3
Sobat, contohnya luar biasa!
CommonGuy
3
@apsillers kami bertanya dalam obrolan tetapi lupa melakukan ping sehingga Anda mungkin tidak memperhatikan: kami bertanya-tanya kapan Anda berencana menjalankan game?
plannapus
2
@ Manu Akhirnya, ya! Saya minta maaf atas keterlambatan yang sangat lama. Saya sudah menyiapkan kode perjodohan / skor dan saya sekarang sedang menyelesaikan masalah dengan kiriman dalam upaya saya untuk membuat kode semua orang berjalan. Setelah itu, saya akan membiarkannya berjalan di server saya selama satu hari atau lebih untuk menyelesaikan putaran.
apsillers

Jawaban:

3

Inilah bot saya yang relatif sederhana, yang saya programkan di Ruby. Pada dasarnya, ini memprioritaskan membagi terlebih dahulu, dan mencoba untuk membagi terhadap musuh untuk mendapatkan kontrol atas lapangan. Prioritas kedua adalah makan, dan yang ketiga menyerang. Itu dengan mudah mengalahkan sampel sel Python.

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
sumber
Saya bukan programmer Ruby, jadi saya heran mengapa beberapa variabel normal dan beberapa mulai dengan a $.
seequ
$digunakan untuk menunjukkan variabel global. Ya, mereka jahat, tetapi dalam program kecil ini, tidak masalah.
Alex
Variabel global hanya jahat dalam kode produksi. Siapa yang keberatan dengan skrip seperti ini?
seequ
Apakah sel saya benar-benar satu-satunya yang kemampuannya menyebar tidak 4-8-0?
Alex
Ini adalah pesaing terbaik untuk bakteri terkoordinasi saya sejauh ini! Saya membangun strategi saya berdasarkan hasil tes pada organisme sel tunggal Anda. =)
justhalf
3

Amuba

Pertama dibagi menjadi empat dan kemudian mencoba untuk sampai ke jalan tengah untuk membatasi ruang replikasi lawan. Kemudian mulai mereplikasi. Ketika bergerak atau mereplikasi, menemukan jalur optimal ke musuh terdekat, dan bergerak atau membaginya, untuk mencoba memotong ruang yang tersedia musuh.

Jika musuh berdekatan atau satu ruang jauhnya, akan selalu menyerang atau bergerak ke arahnya, memungkinkan barisan di belakang tidak melakukan apa pun untuk mengisi ruang kosong apa pun.

Saya belum menguji ini terhadap kiriman lainnya sehingga tidak tahu seberapa baik kiriman itu.

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 + ")";
}
rans
sumber
Ini sebenarnya adalah strategi yang cukup baik, jika hanya Anda membuatnya lebih sedikit mengambil risiko dengan mempertimbangkan jumlah teman tetangga sebelum pindah, jika tidak, pemburu lain dapat dengan mudah menerobos garis pertahanan tipis Anda di awal permainan (dan karenanya hanya berlaku di papan kecil)
justhalf
Btw, ini sepertinya tidak berfungsi seperti yang dimaksudkan jika itu pemain 2.
justhalf
3

Sel sederhana dilakukan di node.js. Mengujinya contoh sel node dan melawan Kostronor itu mengalahkan mereka.

Memperbarui

Masih cukup sederhana, cobalah bergerak ke arah musuh atau memecah belah.

// 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
sumber
justhalf telah mengidentifikasi beberapa bug serius dalam program driver (MOVE bebas biaya dan EXPLODE tidak memperhitungkan keasaman). Jika Anda tertarik untuk menguji ulang terhadap kode driver yang diperbarui dan memperbarui kiriman Anda, beri tahu saya. Jika tidak, itu juga baik-baik saja.
apsillers
2

Evolusi

Pengajuan ini telah berevolusi dan tidak lagi menjadi organisme sel tunggal yang sederhana! Itu mencoba untuk menyerang / meledak jika memungkinkan, jika tidak membagi atau bergerak ke arah musuh. Bergerak harus menyelesaikan masalah sel yang dikelilingi oleh sel ramah dengan energi maksimal, tidak dapat melakukan sesuatu yang bermanfaat.
Setelah bergerak selalu ada 3 energi tersisa untuk mengenai musuh sekeras mungkin.

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
sumber
justhalf telah mengidentifikasi beberapa bug serius dalam program driver (MOVE bebas biaya dan EXPLODE tidak memperhitungkan keasaman). Jika Anda tertarik untuk menguji ulang terhadap kode driver yang diperbarui dan memperbarui kiriman Anda, beri tahu saya. Jika tidak, itu baik-baik saja.
apsillers
2

Pengamuk

Karena saya menggunakan Clojure, yang memiliki beberapa keterbatasan, terutama waktu startup yang besar, saya mengambil sedikit hati. Ketika program diberikan BEGINoutput 4 6 2 LOOP, menunjukkan bahwa itu tidak berhenti. Kemudian dibutuhkan input sebagai aliran berkelanjutan dan diakhiri dengan END. Itu tidak menyimpan keadaan apa pun, yang dibuat jelas dengan tidak menggunakan variabel global apa pun atau menggunakan kembali nilai pengembalian. Karena implementasi untuk tindakan gila ini belum dilakukan, saya tidak dapat sepenuhnya menguji kode (saya harap kode cukup jelas).

Sel mendapatkan namanya dari sifatnya meledak kapan saja memungkinkan (dan dengan demikian memiliki keasaman) dan dengan memprioritaskan serangan tepat setelah membelah.

Saya mengunggah file jar yang dihasilkan ke Dropbox saya . Jalankan denganjava -jar petridish-clojure.jar

Hanya untuk mengklarifikasi:

> 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))

Perbarui log

1. Fixed the logic a little and removed redundancies.
seequ
sumber
Penggunaan asam yang bagus - sebenarnya, saya pikir ini adalah satu-satunya bot yang menggunakan keasaman sama sekali.
Alex
@Alex Kita akan melihat cara kerjanya, tapi saya pikir ini harus bisa membersihkan Amuba. Apa pendapat Anda tentang kode tersebut? Saya sangat baru untuk clojure.
seequ
Dalam contoh Anda, bagaimana bisa sel yang baru lahir bergerak? Saya pikir Anda perlu menunggu satu putaran lagi?
justhalf
@ justhalf Eh, sel tidak tahu berapa umur mereka.
seequ
Ya, tapi pengontrolnya tahu, kan? Seharusnya tidak mengubah sel yang baru terbentuk.
justhalf
2

Bot lapar dan lapar

Berikut ini adalah entri dalam R. Saya harap saya mengerti dengan benar apa spesifikasi teknis tentang cara berkomunikasi dengan program Anda. Harus dipicu dengan Rscript Hungryhungrybot.R.
Jika memiliki setidaknya 6 energi yang terbagi, lebih disukai ke arah musuh. Kalau tidak, ia memakan apa pun di sebelahnya atau apa pun yang bisa dijangkau. Jika tidak ada makanan yang dapat dijangkau maka itu akan meledak ketika ada lebih banyak musuh di sekitar daripada sel saudara, atau berkelahi dengan musuh di dekatnya. Beristirahat hanya jika energi 0 dan tidak ada makanan yang tersedia.

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
sumber
Saya (akhirnya) mencoba menjalankan tantangan, dan saya terus mendapatkan Error: unexpected 'else' in "else"kode Anda. Saya khawatir saya tidak tahu R sama sekali, jadi saya tidak bisa mulai menyelesaikan kesalahan ini. Sebagai referensi, saya mendapatkan kesalahan ini ketika saya menjalankannya di driver dan ketika saya hanya menjalankan program dan mengetik secara manual BEGIN.
apsillers
@apsillers jika saya menambahkan baris baru di mana saya tidak harus memiliki: itu harus bekerja sekarang.
plannapus
Itu memperbaiki kesalahan itu sehingga kita bisa melewati sel init; sekarang saya mendapatkan satu lagi ketika permainan benar-benar dimulai:Error in if (dir[1] < 0) where <- paste(where, "N", sep = "") : missing value where TRUE/FALSE needed
apsillers
Sekarang belokan pertama berjalan dengan baik, tetapi belokan berikutnya menghasilkan Error: object 'food' not found(ketika berhadapan dengan pengajuan Ruby dari Alex, mungkin yang lain)
apsillers
Sel Anda sekarang berjalan dengan baik, terima kasih! :) Namun, justhalf telah mengidentifikasi beberapa bug serius dalam program driver (MOVE bebas biaya dan EXPLODE tidak memperhitungkan keasaman). Jika Anda tertarik untuk menguji ulang terhadap kode driver yang diperbarui dan memperbarui kiriman Anda, beri tahu saya. Jika tidak, itu baik-baik saja.
apsillers
2

Bakteri terkoordinasi

Saya harap saya tidak terlambat.

Menang melawan lawan lain (dan selalu dengan membunuh mereka semua), dalam ujian saya, dan pertempuran tidak akan pernah berakhir jika menghadapi dirinya sendiri, bukti bahwa strateginya kuat.

Ketika Anda bersel tunggal, Anda dapat menghafal keadaan sebelumnya, tetapi Anda dapat memanfaatkan posisi Anda sendiri untuk berperilaku berbeda! =)

Ini akan membagi bakteri menjadi pembagi dan penggerak, dan dengan melakukan hal itu akan membuat lebih banyak bakteri berguna daripada hanya garis depan, sambil menjaga garis pertahanan naik.

Itu juga mengoordinasikan serangannya untuk fokus pada musuh tertentu, sehingga musuh terbunuh lebih cepat (ini untuk menghadapi sel tunggal saya yang berfokus pada HP).

Di pertengahan permainan, yang terdeteksi oleh jumlah sel di papan, mereka akan mencoba untuk mendorong ke wilayah musuh dengan mengalahkan mereka. Ini adalah strategi pemenang utama.

Ini memiliki tingkat pertumbuhan tertinggi dibandingkan dengan semua lawan lain saat ini, tetapi memiliki awal yang lambat, jadi ini bekerja lebih baik di arena besar.

Jalankan dengan 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
sumber
1

Saya pikir saya akan memposting kiriman saya, karena Anda sangat murah hati untuk menambahkan logika boilerplate ...

Ada masalah dalam logika Anda, di mana aksi makan akan mengeluarkan SERANGAN alih-alih MAKAN dan akan membuang-buang mayat.

Saya telah memodifikasi inti Anda sebanyak mungkin untuk memiliki solusi yang berfungsi, yang seharusnya berkinerja relatif baik. Dimulai dengan 4 hp dan 8 energi, jadi setelah dibelah dan istirahat, kedua sel dapat membelah lagi. Ini akan mencoba untuk menggandakan dirinya sendiri, menyerang musuh, memakan mayat dan beristirahat, dalam urutan ini. Jadi sel-sel dalam akan menyimpan 8 titik energi mereka, untuk dengan cepat mengganti sel terluar yang terbunuh dan meninggalkannya 3 titik energi untuk membuat serangan 3 titik atau menggandakan diri setelah satu giliran istirahat. 4 hp akan bertahan setidaknya satu serangan kekuatan penuh.

asam tampaknya menjadi buang-buang poin bagi saya jadi saya tetap keluar ...

Saya tidak menguji pengiriman, karena itu adalah 2 menit;)

ini kode saya:

/*
 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
sumber
1

Pembom yang menyebar tipis

Karena Anda dengan baik hati memberikan kode boilerplate, saya memutuskan untuk membuat sel sederhana saya sendiri; Sel ini memiliki keasaman 4, hanya 1 hp dan 7 energi. Ia mencoba keluar dari jangkauan teman dan kemudian menunggu di sana (atau makan jika memungkinkan) sampai mendapat kesempatan untuk meledak atau bereplikasi. Menyerang hanya jika itu satu-satunya pilihan.

Ini adalah strategi yang sangat berani dan mungkin akan berkinerja buruk, tapi saya ingin tahu bagaimana melakukannya. Saya akan mengujinya dan memperbaikinya hari ini, mungkin.

/*
 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
sumber
Saya mencoba mengujinya tetapi saya tidak menjalankannya. Saya menginstal node.js mencoba baris perintah node c:/cells/petri.js 'node c:/cells/bomber.js' 'node c:/cells/sample.js. Ketika saya mengetik ini di konsol aplikasi node, saya hanya mendapatkan tiga titik, ketika saya mencoba dan menjalankannya di windows cmd, saya mendapatkan: 'node' tidak dikenali sebagai perintah internal atau eksternal, program yang dapat dijalankan atau file batch. Saya memang menyimpan semua file sebagai file .js di folder yang benar. Setiap bantuan untuk pemula? Saya akan pergi ke obrolan atau komentar di tempat lain, tetapi perwakilan saya terlalu rendah.
overactor
Karena saya tidak dapat menguji, untuk saat ini, itu keren jika seseorang dapat memberi tahu saya bagaimana sel saya lakukan terhadap sel mereka. Saya menebak taktik saya, atau setidaknya berpikir itu perlu disempurnakan.
overactor
Anda sepertinya memiliki tipe di telepon if((nearbyEnemies.length - nearbyFriendlies.length > 1 ¦¦ - yang ¦¦sepertinya bukan operator yang valid dan Anda memiliki tanda kurung yang tidak cocok. Saya pikir mungkin pemformatan kode menjadi kacau ketika Anda mempostingnya?
apsillers
Ini berkinerja sangat buruk menurut tes saya. Anda memiliki banyak tugas ( =) ketika yang Anda inginkan adalah perbandingan kesetaraan ( ==).
justhalf
Oh, sial. Saya sebagian besar pemrograman dalam bahasa di mana (=) adalah penugasan kembali ketika saya menulis ini, apakah itu berjalan lebih baik sekarang? Tapi saya tidak pernah berharap itu akan berhasil.
overactor