Slime: The Territory War

18

Você é uma bola de lodo. Naturalmente, sendo lodo, você quer escorrer pela maior área possível. Mas existem outros 3 slimes que querem fazer exatamente a mesma coisa. Quem será o lodo superior?

Descrição

Todos os slimes serão reunidos em uma arena. Os juízes (isto é, o programa de controle) farão uma lista exaustiva de todas as combinações possíveis de 4 gosmas, as colocarão nos cantos de uma mesa e observarão para ver qual gosma escorre pela maior área.

Seus slimes podem executar uma das 3 ações a cada turno: espalhar, pular ou mesclar. Uma descrição mais detalhada sobre o que isso significa será fornecida na seção Saída .

Board / Arena

A arena será um tabuleiro quadrado (atualmente 8x8, mas isso pode mudar no futuro). Aqui está um exemplo de arena de um jogo em andamento:

11111222
11111444
11.22444
.1222.4.
333.3244
33333.44
333...44
333....4

Slime é representado pelos números de 1 a 4 (jogadores de 1 a 4) e o espaço vazio é representado por um ponto ( .). Inicialmente, o tabuleiro começa como todo o espaço vazio, exceto por uma única unidade do lodo do jogador 1 no canto superior esquerdo, jogador 2 no canto superior direito, jogador 3 no canto inferior esquerdo e jogador 4 no canto inferior direito.

As coordenadas são representadas pelo índice de linha e coluna com base em 0, para facilitar a leitura no código. Por exemplo, as coordenadas (3, 6) representam o 7º quadrado na 4ª linha (no exemplo acima, a 4). (Isso facilita o acesso aos quadrados:. board[coords.x][coords.y]) Aqui está uma ilustração visual:

(0, 0) (0, 1) (0, 2)
(1, 0) (1, 1) (1, 2)
(2, 0) (2, 1) (2, 2)

Entrada

A entrada do seu programa será qual jogador você é (1, 2, 3 ou 4), uma vírgula ( ,) e o conteúdo do tabuleiro / arena (com novas linhas substituídas por vírgulas). Por exemplo, se você fosse o jogador 3 no cenário acima, sua entrada seria:

3,11111222,11111444,11.22444,.1222.4.,333.3244,33333.44,333...44,333....4

Resultado

Seu programa deve gerar 4 números inteiros. Os dois primeiros são o índice de linha e coluna, respectivamente, do slime que você deseja mover, e os dois seguintes são o índice de linha e coluna de onde você deseja movê-los.

Você tem três opções em cada turno: Espalhe, pule ou junte.

  • Espalhar

    Para se espalhar, as coordenadas alvo devem estar exatamente a um quadrado do lodo que está sendo movido e o quadrado nas coordenadas alvo deve ter um espaço vazio. Ao espalhar, um novo lodo é criado nas coordenadas de destino e o lodo antigo não é removido. Depois que o novo slime é criado, todos os slimes inimigos nos 8 quadrados ao redor desse novo slime são convertidos no jogador que se moveu.

    Por exemplo, com o quadro na Fig. 1, se o jogador 1 produzir 0 1 1 2, o resultado será o quadro na Fig. 2.

    1.         2.
      11.22      11.12
      1..22      1.112
      ..22.      ..11.
      .....      .....
    
  • Saltar

    Para pular, as coordenadas alvo devem estar exatamente a dois quadrados do slime que está sendo movido e o quadrado nas coordenadas alvo deve ter espaço vazio. Ao jupar, um novo lodo é criado nas coordenadas de destino e o lodo antigo é removido. Depois que o novo slime é criado, todos os slimes inimigos nos 8 quadrados ao redor desse novo slime são convertidos no jogador que se moveu.

    Por exemplo, com o quadro na Fig. 1, se o jogador 1 produzir 0 1 2 3, o resultado será o quadro na Fig. 2.

    1.         2.    
      11..2      1...2
      1...2      1...1
      ....2      ...11
      ...22      ...11
    
  • Mesclar

    Para mesclar, as coordenadas alvo devem estar exatamente a um quadrado do slime que está sendo movido, e o quadrado nas coordenadas alvo deve ser o mesmo slime do jogador. Ao mesclar, o lodo antigo é removido. Então, todos os espaços vazios nos 8 quadrados ao redor do slime alvo são convertidos no jogador que se moveu (não incluindo o slime antigo sendo movido).

    Por exemplo, com o quadro na Fig. 1, se o jogador 1 produzir 0 1 1 2, o resultado será o quadro na Fig. 2.

    1.         2.
      11..2      1.112
      1.1.2      11112
      ....2      .1112
      ..222      ..222
    

Você também pode passar simplesmente emitindo coordenadas inválidas (ex. 0 0 0 0).

Regras e restrições

Regras adicionais são:

  • Você pode ler e gravar arquivos em sua própria pasta para manter os dados (os envios serão armazenados players/YourBotName/yourBotName.language), mas não pode modificar ou acessar qualquer outra coisa fora dele. O acesso à Internet é proibido.
  • Seu envio não pode ser codificado especificamente para ajudar ou prejudicar outro envio. (Você pode ter vários envios, mas eles não devem interagir especificamente entre si de forma alguma.)
  • Seu envio não deve demorar mais de 0,1 segundos por turno. Se o seu envio demorar 0,105 segundos ocasionalmente, tudo bem, mas isso pode não demorar significativamente mais do que esse limite de tempo. (Isso é principalmente uma verificação de sanidade para evitar que os testes demorem muito tempo).
  • Seu envio não deve ser uma duplicata exata (ou seja, usar exatamente a mesma lógica) de outra, mesmo que esteja em um idioma diferente.
  • O envio deve ser sério. Isso é baseado em opiniões, mas se a sua inscrição claramente não estiver tentando resolver o desafio (por exemplo, se você passar a cada turno), será desqualificada.

Se o seu envio quebrar alguma dessas regras ou não seguir a especificação, será desqualificado, removido playerlist.txte o jogo será reiniciado desde o início. Se o seu envio for desqualificado, deixarei um comentário no seu post explicando o porquê. Caso contrário, seu envio será adicionado à tabela de classificação. (Se você não vê sua submissão no placar, não tem comentários explicativos sobre sua postagem e postou sua submissão antes da hora da "Última atualização" abaixo, diga-me! Talvez eu tenha ignorado isso inadvertidamente.)

Na sua inscrição, inclua:

  • Um nome.
  • Um comando shell para executar o seu programa (por exemplo, java MyBot.java, ruby MyBot.rb, python3 MyBot.py, etc.).
    • Observe que a entrada (seu player e mapa) será anexada a isso como um argumento de linha de comando.
    • Os programas serão testados no Ubuntu 14.04, portanto, verifique se o seu código pode ser executado (livremente) nele.
  • Um número de versão, se o seu código funcionar de maneira diferente em versões diferentes do seu idioma.
  • O código do seu bot.
  • Instruções sobre como compilar o código, se necessário.

Código / teste do controlador, exemplo bot

O código do controlador está escrito em C ++ e pode ser encontrado no Github . Mais instruções sobre como executar e testar seu código podem ser encontradas lá.

simplebot.rb, um bot muito simples que espalha ou salta um lodo aleatório para um local aleatório a cada turno também é publicado no Github .

Pontuação e placar

Quando todos os quadrados do tabuleiro estão preenchidos, o jogo termina e as pontuações são calculadas. A pontuação final de um jogador é a quantidade de quadrados que contêm seu lodo no final do jogo. Se já passaram 2000 turnos (500 para cada jogador) e o jogo ainda não acabou, o jogo terminará assim mesmo e as pontuações serão relatadas como se o jogo tivesse terminado.

No final do torneio, a pontuação de todos os jogos será calculada em média para calcular a pontuação final de cada jogador, que será publicada na tabela de classificação. Não há prazo para envio; Continuarei atualizando a tabela de classificação periodicamente à medida que novos envios forem recebidos.

São necessários 4 envios até que um cabeçalho real apareça.

+--------------------------+-----------+--------------------+
| Name                     | Avg Score | Last Updated (UTC) |
+--------------------------+-----------+--------------------+
| GreedySlime              | 47.000    | Jul 22 10:27 PM    |
| Jumper                   | 12.000    | Jul 22 10:27 PM    |
| ShallowBlue              | 5.000     | Jul 22 10:27 PM    |
| Lichen                   | 0.000     | Jul 22 10:27 PM    |
+--------------------------+-----------+--------------------+

Última atualização: 22 de julho às 22:27 (UTC).

Maçaneta da porta
fonte
Hmm, eu posso ter perdido, mas você explicou como será a interação entre os jogadores? Todos se movem ao mesmo tempo? Jogador 1 primeiro?
Just just
1
Talvez seja apenas eu que ache isso um pouco obscuro, mas como você define exatamente "dois quadrados de distância"?
Arshajii
Me lembra muito de um jogo baseado em uma bebida dos anos 90. ;-)
Benny
O @justhalf Player 1 se move primeiro.
Maçaneta
1
@arshajii "Dois quadrados de distância" significa, formalmente, "em qualquer posição em que o máximo da variação em X e da variação em Y seja igual a 2."
Maçaneta

Respostas:

4

GreedySlime

Simplesmente faz o movimento que produz o maior ganho líquido de unidades de lodo.

Note que este é escrito em Python 2.x .

def gen_moves(board, pos):
    """Generate valid moves for a given position.

    Return value is a tuple of the form
       (type, from_x, from_y, to_x, to_y)

    The move 'type' is a single character with:
        - 's' = spread
        - 'j' = jump
        - 'm' = merge
    """

    N = len(board)
    x0, y0 = pos
    player = board[x0][y0]

    for i in -2,-1,0,1,2:
        for j in -2,-1,0,1,2:
            if (i == 0 and j == 0):
                continue

            x1, y1 = x0 + i, y0 + j

            if not ((0 <= x1 < N) and (0 <= y1 < N)):
                continue

            c = board[x1][y1]

            if -1 <= i <= 1 and -1 <= j <= 1:
                if c == '.':
                    yield ('s', x0, y0, x1, y1)
                elif c == player:
                    yield ('m', x0, y0, x1, y1)
            else:
                if c == '.':
                    yield ('j', x0, y0, x1, y1)

def eval_move(board, move, initial_net={'s': 1, 'j': 0, 'm': -1}):
    """Evaluates given move in given context.

    - Assumes move is valid.
    - `move` argument is a tuple of the form
       (type, from_x, from_y, to_x, to_y)
    - The move 'type' is a single character with:
        - 's' = spread
        - 'j' = jump
        - 'm' = merge
    """

    N = len(board)
    move_type = move[0]
    x0, y0, x1, y1 = move[1:]
    player = board[x0][y0]

    net = initial_net[move_type]
    for i in -1,0,1:
        for j in -1,0,1:
            if (i == 0 and j == 0):
                continue

            x2, y2 = x1 + i, y1 + j

            if not ((0 <= x2 < N) and (0 <= y2 < N)):
                continue

            c = board[x2][y2]

            if (move_type == 'm' and c == '.') or (move_type != 'm' and c != player and c != '.'):
                net += 1

    return net

def main():
    from sys import argv
    data = argv[1]

    player, board = data.split(',', 1)
    board = map(list, board.split(','))
    N = len(board)

    all_pos_gen = ((a,b) for a in range(N) for b in range(N) if board[a][b] == player)
    all_move_gen = (move for pos in all_pos_gen for move in gen_moves(board, pos))
    move = max(all_move_gen, key=lambda move: eval_move(board, move))

    print move[1], move[2], move[3], move[4]

if __name__ == "__main__":
    main()

Execução de exemplo (usando o exemplo fornecido na descrição do desafio e assumindo que o código seja salvo em um arquivo chamado slime.py):

$ python slime.py 3,11111222,11111444,11.22444,.1222.4.,333.3244,33333.44,333...44,333....4
4 0 2 2
arshajii
fonte
3

Azul raso

O raso azul tenta descobrir o que pode acontecer no futuro, fazendo uma n exaustiva busca em árvore de possíveis movimentos; infelizmente, ele não avança mais que o próximo turno. Depois, ele bate uma pontuação meio-boba em cada possível estado do conselho depois do próximo turno, calcula uma pontuação para cada ramo individual com uma fórmula igualmente ridícula e: voila, o movimento ideal é conhecido!

EDIT: O código original foi executado muito lentamente, então eu o modifiquei para que ele pegue apenas uma amostra aleatória de todos os movimentos possíveis. Ele tentará quase todos os movimentos quando houver poucos movimentos possíveis e uma porcentagem menor quando houver mais movimentos possíveis.

import java.awt.Point;  

    public class ShallowBlue {
        private static final int MAX_ROUNDS = 5, PLAYERS = 4;
        static int me = 0;

        public static void main(String[] args) {
            if (args[0] == null) {
                return;
            }

            me = Integer.parseInt(args[0].split(",", 2)[0]);
    String board = args[0].split(",", 2)[1];

    System.out.println(getBestMove(board, me, MAX_ROUNDS - 1));
}

private static String getBestMove(String board, int player, int rounds) {
    String [] boards = new String[24];
    int checkedBoards = 1;
    char playerChar = Integer.toString(player).charAt(0);
    String tempMove = getMove(0, 0, 0, 0);
    String tempBoard = calculateMove(board, tempMove); 
    boards[0] = tempBoard;
    String bestMove = tempMove;
    double us = numberOfUs(board, playerChar); 
    double skip = (us*2.5/(us*2.5 + 1))/4 + 0.735;
    if (rounds == MAX_ROUNDS - 2) {
        skip = skip*skip;
    }

    float bestScore, worstScore, averageScore, tempScore;
    int scores;

    if (rounds == 0) {
        tempScore = calculateScore(tempBoard, MAX_ROUNDS - rounds - 1);
    } else {
        tempScore = getScore(getBestMove(tempBoard, player%PLAYERS + 1, rounds - 1));
    }

    scores = 1;
    bestScore = tempScore;
    worstScore = tempScore;
    averageScore = tempScore;

    for (int x = 0; x < 8; x++) {
        for (int y = 0; y < 8; y++) {
            if (getCharAt(board, x, y) == playerChar) {
                Point[] possibleMergers = getNeighboringMatches(board, new Point(x, y), playerChar);
                if (possibleMergers[0] != null) {
                    tempMove = getMove(possibleMergers[0].x, possibleMergers[0].y, x, y); 
                    tempBoard = calculateMove(board, tempMove);
                    if (addIfUnique(boards, tempBoard, checkedBoards)) {
                        checkedBoards++;
                        if ((rounds != MAX_ROUNDS - 1) && (rounds == 0 || Math.random() < skip)) {
                            tempScore = calculateScore(tempBoard, MAX_ROUNDS - rounds - 1);
                        } else {
                            tempScore = getScore(getBestMove(tempBoard, player%PLAYERS + 1, rounds - 1));
                        }

                        if (tempScore > bestScore) {
                            bestMove = tempMove;
                        }
                        bestScore = Math.max(tempScore, bestScore);
                        worstScore = Math.min(tempScore, worstScore);

                        scores++;
                        averageScore = (averageScore*(scores - 1) + tempScore)/scores;
                    }
                }
            } else if (getCharAt(board, x, y) == '.') {
                Point[] possibleSpreaders = getNeighboringMatches(board, new Point(x, y), playerChar);
                int i = 0;
                while (i < possibleSpreaders.length && possibleSpreaders[i] != null) {
                    tempMove = getMove(possibleSpreaders[i].x, possibleSpreaders[i].y, x, y); 
                    tempBoard = calculateMove(board, tempMove);
                    if ((rounds != MAX_ROUNDS - 1) && (rounds == 0 || Math.random() < skip)) {
                        tempScore = calculateScore(tempBoard, MAX_ROUNDS - rounds - 1);
                    } else {
                        tempScore = getScore(getBestMove(tempBoard, player%PLAYERS + 1, rounds - 1));
                    }

                    if (tempScore > bestScore) {
                        bestMove = tempMove;
                    }
                    bestScore = Math.max(tempScore, bestScore);
                    worstScore = Math.min(tempScore, worstScore);

                    scores++;
                    averageScore = (averageScore*(scores - 1) + tempScore)/scores;

                    i++;
                }
                Point[] possibleJumpers = getNextNeighboringMatches(board, new Point(x, y), playerChar);
                i = 0;
                while (i < possibleJumpers.length && possibleJumpers[i] != null) {
                    tempMove = getMove(possibleJumpers[i].x, possibleJumpers[i].y, x, y); 
                    tempBoard = calculateMove(board, tempMove);
                    if ((rounds != MAX_ROUNDS - 1) && (rounds == 0 || Math.random() < skip)) {
                        tempScore = calculateScore(tempBoard, MAX_ROUNDS - rounds - 1);
                    } else {
                        tempScore = getScore(getBestMove(tempBoard, player%PLAYERS + 1, rounds - 1));
                    }

                    if (tempScore > bestScore) {
                        bestMove = tempMove;
                    }
                    bestScore = Math.max(tempScore, bestScore);
                    worstScore = Math.min(tempScore, worstScore);

                    scores++;
                    averageScore = (averageScore*(scores - 1) + tempScore)/scores;

                    i++;
                }
            }
        }
    }

    if (rounds == MAX_ROUNDS - 1) {
        return (bestMove);
    } else {
        return getScoreString(bestScore, worstScore, averageScore);
    }
}

private static int numberOfUs(String board, char playerChar) {
    int us = 0;

    for (int i = 0; i < board.length(); i++ ) {
         if (board.charAt(i) == playerChar) {
            us++;
        }
    }

    return us;
}

private static float calculateScore(String board, int roundsPassed) {
    int empties = 0;
    int us = 0;
    int enemy1 = 0;
    int enemy2 = 0;
    int enemy3 = 0;
    for (int i = 0; i < board.length(); i++ ) {
        if (board.charAt(i) == '.') {
            empties++;
        } else if (board.charAt(i) == Integer.toString(me).charAt(0)) {
            us++;
        } else if (board.charAt(i) == Integer.toString(me%PLAYERS + 1).charAt(0)) {
            enemy1++;
        } else if (board.charAt(i) == Integer.toString(me%PLAYERS + 2).charAt(0)) {
            enemy2++;
        } else if (board.charAt(i) == Integer.toString(me%PLAYERS + 3).charAt(0)) {
            enemy3++;
        }
    }

    if (us != 0) {
        us += roundsPassed;
    }

    if (enemy1 != 0) { 
        enemy1 = enemy1 + (roundsPassed + 3)%PLAYERS;
    }

    if (enemy2 != 0) { 
        enemy2 = enemy2 + (roundsPassed + 2)%PLAYERS;
    }

    if (enemy3 != 0) { 
        enemy3 = enemy3 + (roundsPassed + 1)%PLAYERS;
    }

    return us*(empties + 1)/(Math.max(Math.max(enemy1, enemy2), enemy3) + 1);
}

private static float getScore(String scoreString) {
    float bestScore, worstScore, averageScore;
    String[] scores = new String[3];

    scores = scoreString.split(",");
    bestScore = Float.parseFloat(scores[0]);
    worstScore = Float.parseFloat(scores[1]);
    averageScore = Float.parseFloat(scores[2]);


    return (float) Math.sqrt(Math.sqrt(bestScore*averageScore*worstScore*worstScore));
}

private static String getScoreString(float bestScore, float worstScore, float averageScore) {
    return Float.toString(bestScore) + ',' + Float.toString(worstScore) + ',' + Float.toString(averageScore);
}

private static boolean addIfUnique(String[] boards, String board, int checkedBoards) {
    int i = 0;

    while (i < boards.length && boards[i] != null) {
        if (boards[i].equals(board)) {
            return false;
        }
        i++;
    }

    if (i < boards.length) {
        boards[i] = board;
    } else {
        boards[checkedBoards%boards.length] = board;
    }

    return true;
}

private static String calculateMove(String board, String move) {
    int x1 = Integer.parseInt(Character.toString(move.charAt(0)));
    int y1 = Integer.parseInt(Character.toString(move.charAt(2)));
    int x2 = Integer.parseInt(Character.toString(move.charAt(4)));
    int y2 = Integer.parseInt(Character.toString(move.charAt(6)));

    if ((Math.abs(y1 - y2) == 2 || Math.abs(x1 - x2) == 2) 
            &&  getCharAt(board, x2, y2) == '.') {
        Point[] enemies = new Point[8];

        enemies = getNeighboringEnemies(board, new Point(x1, y1), Integer.parseInt(Character.toString(getCharAt(board, x1, y1))));

        board = replace(board, enemies, getCharAt(board, x1, y1));
        Point[] middle = {new Point(x1, y1)};
        board = replace(board, middle, '.');
    }

    if ((Math.abs(y1 - y2) == 1 || Math.abs(x1 - x2) == 1)) { 
        if (getCharAt(board, x2, y2) == '.' || getCharAt(board, x1, y1) == getCharAt(board, x2, y2)) {
            boolean merge = true;
            if (getCharAt(board, x2, y2) == '.') {
                merge = false;
            }

            Point[] spaces = new Point[8];
            spaces = getNeighboringMatches(board, new Point(x1, y1), '.');
            board = replace(board, spaces, getCharAt(board, x1, y1));

            if (merge) {
                Point[] source = {new Point(x1, y1)};
                board = replace(board, source, '.');
            }
        }
    }

    return board;
}

private static String replace(String board, Point[] targets, char source) {
    int i = 0;

    while (i < targets.length && targets[i] != null) {
        if (targets[i].x == 7 && targets[i].y == 7) {
            board = board.substring(0, getIndexAt(targets[i].x, targets[i].y)) + source;
        } else if (targets[i].x == 0 && targets[i].y == 0) {
            board = source + board.substring(getIndexAt(targets[i].x, targets[i].y) + 1);
        } else {
            board = board.substring(0, getIndexAt(targets[i].x, targets[i].y)) + source + board.substring(getIndexAt(targets[i].x, targets[i].y) + 1);
        }
        i++;
    }

    return board;
}

private static Point[] getNeighboringMatches(String board, Point coord, char match) {
    Point[] matches = new Point[8];

    int i = 0;
    for (int x = coord.x - 1; x <= coord.x + 1; x++) {
        for (int y = coord.y - 1; y <= coord.y + 1; y++) {
            if ((y != coord.y || x != coord.x ) && getCharAt(board, x, y) == match){
                matches[i] = new Point(x, y);
                i++;
            }
        }
    }

    return matches;
}

private static Point[] getNeighboringEnemies(String board, Point coord, int player) {
    Point[] enemies = new Point[8];

    for (int i = 1; i <= PLAYERS; i++){
        enemies = mergeArr(enemies, getNeighboringMatches(board, coord, Integer.toString((player + i - 1)%PLAYERS + 1).charAt(0)));
    }

    return enemies;
}

private static Point[] getNextNeighboringMatches(String board, Point coord, char match) {
    Point[] matches = new Point[16];

    int i = 0;
    for (int x = coord.x - 2; x <= coord.x + 2; x++) {
        for (int y = coord.y - 2; y <= coord.y + 2; y++) {
            if ((Math.abs(y - coord.y) == 2 || Math.abs(x - coord.x) == 2) && getCharAt(board, x, y) == match){
                matches[i] = new Point(x, y);
                i++;
            }
        }
    }

    return matches;
}

private static char getCharAt(String board, int x, int y) {

    if (x >= 0 && x < 8 && y >= 0 && y < 8) {
        return board.charAt(9*x + y);
    } else {
        return '\0';
    }
}

private static int getIndexAt(int x, int y) {
    return 9*x + y;
}

private static Point[] mergeArr(Point[] arr1, Point[] arr2) {
    int i = 0;
    int j = 0;

    while (i < arr1.length && arr1[i] != null) {
        i++;
    }

    while (j < arr2.length && arr2[j] != null) {
        arr1[i + j] = arr2[j];
        j++;
    }

    return arr1;
}

private static String getMove(int x1, int y1, int x2, int y2) {
    return Integer.toString(x1) + " " + Integer.toString(y1) + " " + Integer.toString(x2) + " " + Integer.toString(y2);
    }
}
overactor
fonte
Não sou muito programador e essa abordagem foi muito mais complicada do que eu esperava, ainda não testei esse código (já que são 3 horas da manhã) e preciso trabalhar amanhã cedo. É bem possível que meu bot atinja o tempo limite ou simplesmente não funcione. Eu também posso ter entendido errado as coordenadas, amanhã darei outra olhada com novos olhos, mas um par extra (ou mais) é sempre bem-vindo, ou seja, um par extra de olhos (mais experientes).
overactor
Estou recebendo várias exceções para isso (consulte o bate-papo para o stacktrace).
Maçaneta
Meu código está finalmente em execução, mas preciso desesperadamente aumentar a velocidade para poder diminuir a porcentagem de ramificações que pulo. Alguém sabe como melhorar essa bagunça?
overactor
Como não é um jogo de golfe, você pode publicá-lo na Revisão de Código ... Isso estaria dentro das regras ou seria desaprovado?
Trichoplax
1
Vi essa resposta há dois dias, mas acabei de perceber que "Shallow Blue" é um trocadilho com o famoso "Deep Blue".
justhalf
2

Saltador

Gosta de pular, ainda mais no meio.

Passará se nenhum lodo puder pular.

C ++ , deve compilar simplesmente comg++ jumper.cpp -o jumper

#include <math.h>
#include <algorithm>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#define maxn(x, y) ((x) > (y) ? (x) : (y))
#define absn(x) ((x) < 0 ? -(x) : (x))
class Board {
 public:
    Board(std::string input_string);
    void Move();
 private:
    void ParseBoardState(std::string console_string);
    int Slimes(int cell);
    void GetXY(int cell, int& r, int& c);
    bool CanJumpFromHere(int cell, int& jump_to_cell, int& rad);
    int CalcRadius(int cell);
    bool CheckJumpDist(int x, int y);

    int player_num_;
    std::size_t board_dim_;
    std::size_t sq_;
    std::vector< std::vector<int> > slimes_;
};
Board::Board(std::string input_string) 
    : player_num_(0), 
      board_dim_(0),
      slimes_() {
    board_dim_ = std::count(input_string.begin(), input_string.end(), ',');
    sq_ = board_dim_ * board_dim_;
    std::istringstream temp(input_string.substr(0,1));
    temp >> player_num_;
    ParseBoardState(input_string);
}
void Board::ParseBoardState(std::string console_string) {
    int place = 0;
    for (std::size_t row = 0; row < board_dim_; ++row ) {
        slimes_.push_back(std::vector<int>());
        place = console_string.find(",",place+1);
        std::string temp2 = console_string.substr(place+1, 8);
        for (std::size_t col = 0; col < board_dim_; ++col ) {
            int sl = 0;
            std::istringstream bint(temp2.substr(col,1));
            bint >> sl;
            slimes_[row].push_back(sl);
        }
    }
}
int Board::Slimes(int cell) {
    int r = 0;
    int c = 0;
    GetXY(cell, r, c);
    return  slimes_[r][c];
}
void Board::GetXY(int cell, int& r, int& c) {
    for (std::size_t row = 0; row < board_dim_; ++row ) {
        for (std::size_t col = 0; col < board_dim_ ; ++col ) {
            if ( (row * board_dim_ + col) == cell) {
                r = row;
                c = col;
            }
        }
    }
}
void Board::Move() {

    // go through each cell:
    int index = 0;
    int jump_to_cell = 0;
    int rad = 0;
    int min_rad = 1000;
    int best_jump_to = -1;
    int best_jump_from = -1;
    for (int c = 0; c < sq_; ++c) {
        if (Slimes(c) == player_num_) {
            if (CanJumpFromHere(c, jump_to_cell , rad)) {
                if (rad < min_rad) {
                    best_jump_from = c;
                    best_jump_to = jump_to_cell;
                    min_rad = rad;
                }
                index += 1;
            }
        }
    }

    int ret_row = 0;
    int ret_col = 0;

    if (index == 0) {
        // can't jump so dont bother:
        std::cout << "0 0 0 0" << std::endl;
    } else {
        GetXY(best_jump_from, ret_row, ret_col);
        std::cout << ret_row << " " << ret_col  << " ";
        GetXY(best_jump_to, ret_row, ret_col);
        std::cout << ret_row << " " << ret_col << std::endl;
    }
}
bool Board::CanJumpFromHere(int cell, int& ret_jump_to_cell, int & ret_rad) {
    int r = 0;
    int c = 0;
    int rad = 10000;
    int jump_to_cell = 0;
    int rad_min_for_this_cell = 10000;
    GetXY(cell, r, c);
    bool jumpable = false;
    for (int row_test = -2; row_test < 3; ++row_test) {
        for (int col_test = -2; col_test < 3; ++col_test) {
            if ( (r + row_test) > 0 &
                 (r + row_test) < board_dim_ &&
                 (c + col_test) > 0 &&
                 (c + col_test) < board_dim_ &&
                 (CheckJumpDist(col_test, row_test)) &&
                 (slimes_[r+row_test][c+col_test] == 0)) {

                jumpable = true;
                jump_to_cell = (r + row_test) * board_dim_ + c + col_test;
                rad = CalcRadius(jump_to_cell);

                if (rad < rad_min_for_this_cell) {
                    ret_rad = rad;
                    ret_jump_to_cell = jump_to_cell;
                    rad_min_for_this_cell = ret_rad;
                }
            }
        }
    }
    return jumpable;
}
bool Board::CheckJumpDist(int x, int y) {
    int maxDelta = maxn(absn(x), absn(y));
    if (maxDelta <= 0 || maxDelta > 2) {
        return false;
    } else {
        return true;
    }
}
int Board::CalcRadius(int cell) {
    int r = 0;
    int c = 0;
    GetXY(cell, r, c);
    // unnecessary accuracy considering how bad this bot is:
    float mid = static_cast<float>(board_dim_) / 2;
    float rad = sqrt((r - mid) * (r - mid) + (c-mid)*(c-mid));
    int ret = static_cast<int>(rad + 0.5);
    return ret;
}
int main(int argc, char* argv[]) {
    if (argc != 2) {
        return 0;
    } else {
        std::string input_string(argv[1]);
        Board board(input_string);
        board.Move();
    }
    return 0;
}

Roubei sua verificação de movimento, desculpe. Também desisti das práticas corretas de codificação logo após o início, por isso não olhe. No entanto, parece funcionar em qualquer tamanho de placa.

energia eólica
fonte
2

DeathSlime :

Descrição : Tenta caçar o inimigo mais fraco e destruí-lo. Repetir.

Como executar : ruby ​​DeathSlime.rb

Versão Ruby : 2.1.2

#!/usr/bin/env ruby
class PlayerPosition;
  attr_accessor :x, :y;
  def initialize(x, y) @x = x; @y = y; end
  def distance(pos) Math.sqrt((pos.x - @x)**2 + (pos.y - @y)**2); end
end

class Board
  attr_reader :player, :empty_positions
  def initialize(player_id, game_state_string)
    @player_positions = {}
    @empty_positions = []

    @enemies = []
    @player = Player.new

    row = 0
    col = 0
    game_state_string.chars.each do |tile|
      row += 1 and col = 0 and next if tile == ','
      @empty_positions << PlayerPosition.new(col, row) and col += 1 and next if tile == '.'

      @player_positions[tile] ||= []
      @player_positions[tile] << PlayerPosition.new(col, row)
      col += 1
    end

    @player_positions.each do |id, positions|
      @enemies << Player.new(id, positions) if id != player_id
      @player = Player.new(id, positions) if id == player_id
    end
  end

  def border_space(player_positions, possible_border, allowance = 1)
    near = []
    possible_border.each do |border|
      is_near = false
      player_positions.each {|pos| is_near = true and break if pos.distance(border) <= allowance}
      near << border if is_near
    end
    near
  end

  def closest_to(player_positions, enemy_positions)
    player_closest_block = nil
    shortest_distance = 1000
    enemy_closest_block = nil
    player_positions.each do |player|
      enemy_positions.each do |enemy|
        if player.distance(enemy) < shortest_distance
          shortest_distance = player.distance(enemy)
          enemy_closest_block = enemy
          player_closest_block = player
        end
      end
    end
    return player_closest_block, enemy_closest_block
  end

  def empty_space_near(player_positions, allowance = 1); border_space(player_positions, @empty_positions, allowance); end
  def weakest_enemy; @enemies.select{|enemy| !enemy.dead? }.sort {|x,y| x.strength <=> y.strength}.first; end
end

class Player
  attr_reader :positions
  def initialize(id = -1, positions = []); @id = id; @positions = positions; end
  def dead?; @positions.length == 0; end
  def strength; @positions.length; end
  def can_hurt?(enemy)
    is_close_enough = false
    self.positions.each do |my_pos|
      enemy.positions.each {|enemy_pos| is_close_enough = true and break if my_pos.distance(enemy_pos) <= 2 }
    end
    is_close_enough
  end
end




class DeathSlime

  def initialize(arg_string)
    game_state = arg_string[2..-1]
    player_id = arg_string[0]
    @board = Board.new(player_id, game_state)
  end

  def attack
    if @board.weakest_enemy
      try_to_spread_to_weakest || try_to_jump_to_weakest || try_to_merge_to_weakest || try_to_move_to_weakest
    else
      try_to_move if @empty_positions.length > 0
    end
  end


  def try_to_spread_to_weakest
    mine = @board.empty_space_near(@board.player.positions, 1)
    theirs = @board.empty_space_near(@board.weakest_enemy.positions, 1)
    target_space = mine.detect{|space| theirs.include?(space) }
    return move(@board.closest_to(@board.player.positions, [target_space]).first, target_space) if target_space
    false
  end

  def try_to_jump_to_weakest
    mine = @board.empty_space_near(@board.player.positions, 2)
    theirs = @board.empty_space_near(@board.weakest_enemy.positions, 1)
    target_space = mine.detect{|space| theirs.include?(space) }
    return move(@board.closest_to(@board.player.positions, [target_space]).first, target_space) if target_space
    false
  end

  def try_to_merge_to_weakest
    definite_border = nil
    definite_merge = nil
    possible_border = @board.border_space(@board.weakest_enemy.positions, @board.player.positions)
    possible_border.each do |border|
      possible_merges = @board.border_space([ border ], @board.player.positions.select{|space| space != border })
      definite_merge = possible_merges.first and definite_border = border and break if possible_merges.length > 0
    end
    return move(definite_merge, definite_border) if definite_border && definite_merge
    false
  end

  def try_to_move_to_weakest
    player_closest, enemy_closest = @board.closest_to(@board.player.positions, @board.weakest_enemy.positions)
    spreading_distance = @board.empty_space_near([player_closest], 1)
    jumping_distance = @board.empty_space_near([player_closest], 2)
    theirs = @board.empty_space_near(@board.player.positions, 2)

    spreading_space = spreading_distance.detect{|space| theirs.include?(space) }
    return move(@board.closest_to(@board.player.positions, [spreading_space]).first, spreading_space) if spreading_space

    jumping_space = jumping_distance.detect{|space| theirs.include?(space) }
    return move(@board.closest_to(@board.player.positions, [jumping_space]).first, jumping_space) if jumping_space

    return move(@board.closest_to(@board.player.positions, [spreading_distance]).first, spreading_distance) if spreading_distance.length > 0
    return move(@board.closest_to(@board.player.positions, [jumping_distance]).first, jumping_distance) if jumping_distance.length > 0

    #merge randomly
    closest_enemy = @board.closest_to(@board.player.positions, @board.weakest_enemy.positions).first
    return move(@board.closest_to(@board.player.positions.select{|space| space != closest_enemy }, [closest_enemy]).first, closest_enemy)
  end

  def try_to_move
    spreading_distance = @board.empty_space_near(board.player.positions, 1)
    jumping_distance = @board.empty_space_near(board.player.positions, 2)

    return move(@board.closest_to(@board.player.positions, [spreading_distance]).first, spreading_distance) if spreading_distance.length > 0
    return move(@board.closest_to(@board.player.positions, [jumping_distance]).first, jumping_distance) if jumping_distance.length > 0
  end

  def move(start_block, end_block)
    STDOUT.write "#{start_block.x} #{start_block.y} #{end_block.x} #{end_block.y}"
    true
  end
end

slime_of_death = DeathSlime.new(ARGV[0])
slime_of_death.attack
fingerco
fonte
1

Líquen

Este é um bot escrito em R. Ele precisa ser acionado usando Rscript Lichen.R.

input <- strsplit(commandArgs(TRUE),split=",")[[1]]
me <- input[1]
arena <- do.call(rbind,strsplit(input[-1],""))
n <- sum(arena==me)
where <- which(arena==me,arr.ind=TRUE)
closest <- function(a,b){
    x <- abs(outer(a[,1],b[,1],`-`))
    y <- abs(outer(a[,2],b[,2],`-`))
    matrix(which(x<2&y<2,arr.ind=TRUE),ncol=2)
    }
if(n==0){ #No slime on the board
    out <- "0 0 0 0"
    }else if(n==1){ #One slime on the board
        x <- where[1]+c(1,-1)
        y <- where[2]+c(1,-1)
        out <- paste(where[1]-1,where[2]-1,x[x%in%2:(nrow(arena)-1)]-1,y[y%in%2:(nrow(arena)-1)]-1,sep=" ")
    }else{
        area <- apply(which(arena==me,arr.ind=TRUE),2,range,na.rm=TRUE)
        empty <- matrix(which(arena==".",arr.ind=TRUE),ncol=2)
        opponents <- c("1","2","3","4")[c("1","2","3","4")!=me]
        for(i in seq_along(opponents)){
            if(i==1){
                other <- which(arena==opponents[i],arr.ind=TRUE)
                }else{other <- rbind(other,which(arena==opponents[i],arr.ind=TRUE))}
            }
        fillable <- matrix(empty[empty[,1]%in%area[1,1]:area[2,1]&empty[,2]%in%area[1,2]:area[2,2],],ncol=2)
        enemies <- matrix(other[other[,1]%in%area[1,1]:area[2,1]&other[,2]%in%area[1,2]:area[2,2],],ncol=2)
        if(length(unique(where[,2]))==1 | length(unique(where[,2]))==1){ #Slimes form a line
            W <- closest(where,empty)
            if(nrow(W)){
                out <- paste(c(where[W[1,1],]-1,empty[W[1,2],]-1),collapse=" ")
            }else{out <- "0 0 0 0"}
        }else if(length(enemies)&length(fillable)){ #There are enemies and empty spaces in habitable area
            w <- closest(enemies, fillable)
            if(nrow(w)){
                X <- abs(where[,1]-fillable[w[1,2],1])
                Y <- abs(where[,2]-fillable[w[1,2],2])
                W <- which(X<2&Y<2)
                out <- paste(c(where[W[1],]-1,fillable[w[1,2],]-1),collapse=" ")
            }else{out <- "0 0 0 0"}
        }else if(length(fillable)){ #There are empty spaces in habitable area
            w <- closest(fillable,where)
            out <- paste(c(where[w[1,2],]-1,fillable[w[1,1],]-1),collapse=" ")
        }else{
            x <- area[!area[,1]%in%c(1,nrow(arena)),1]
            y <- area[!area[,2]%in%c(1,ncol(arena)),2]
            if(sum(arena[x+(-1:1),y+(-1:1)]==".")>1){
                w <- where[where[,1]%in%(x+c(1,-1))&where[,2]%in%(y+c(1,-1)),]
                out <- paste(w[1]-1,w[2]-1,x-1,y-1,sep=" ")
            }else{
                W <- closest(where, empty)
                if(nrow(W)){
                    out <- paste(c(where[W[1,1],]-1,empty[W[1,2],]-1),collapse=" ")
                }else{out <- "0 0 0 0"}
            }
        }
    }
cat(out)

O algoritmo pretendido é que ele tenta cobrir uma área retangular (preenchendo o espaço em branco usando spread). Quando o retângulo está completo, mergesos dois slimes em um de seus cantos (o mais distante do canto do quadro) expandem a área "habitável" e preenchem esse retângulo recém-definido etc. Ele não usa jump.

.....   .....   .....   .....   .....   ..333
.....   .333.   3333.   3333.   3333.   33333
333..   3333.   3333.   3333.   3333.   33.33
333..   3.33.   3.33.   3333.   3333.   3333.
333..   333..   333..   333..   3333.   3333.

Se um inimigo estiver na área habitável e também houver um espaço vazio na área, ele preencherá o espaço vazio ao lado dele. Se o lodo que deve ser fundido ao expandir a área habitável estiver cercado por inimigos, então um lodo será apenas em spreadvez deste fundido.

plannapus
fonte
Estou recebendo vários erros por isso (consulte o bate-papo sobre o stacktrace).
Maçaneta
Bot agora envia 0 0 0 0quando não há lodo a bordo.
plannapus
0

CornerSlime

Esse lodo tem uma noção de cantos, ou pelo menos quando o escrevi pela primeira vez em C #, não tenho mais certeza.

Escrito em C ++, presumivelmente compilará bem com o gcc e quase sem argumentos; espero que eu não tenha usado nada específico da MSVC por acidente.

Testado exclusivamente contra ele próprio em um servidor modificado (nenhum compilador C ++ novo e sofisticado onde estou), por isso não tenho idéia de como ele será executado, espero que não seja desqualificado por ser muito lento. No momento, não há aleatoriedade neste bot, mas posso adicionar alguns posteriormente.

Isso é portado em C # para C ++ (devido a preocupações de velocidade) por alguém que realmente não conhece C ++ e é horrível. Começa construindo uma matriz de células, que depois preenche todo o tipo de informações inúteis sobre as células ao seu redor (número de minhas células, número de meus slimes, esse tipo de coisa). Em seguida, ele usa essas informações para decidir se precisa examinar mais de perto as informações usadas para criar essas informações e, em seguida, potencialmente usa essas informações para produzir uma saída significativa.

#include <iostream>

#define min(a,b) a>b?b:a;
#define max(a,b) a>b?a:b;

#define null 0 // fun times

struct Cell
{
public:
    int t;
    int x, y;
    int counts1[5];
    int counts2[5];
    int ecount1;
    int ecount2;
    bool safe1;
    bool safe2;

    bool canspread;
    bool canjump;
    bool canmerge;

    bool spreadable;
    bool jumpable;
    bool mergeable;

    Cell()
    {
        for (int i = 0; i < 5; i++)
        {
            counts2[i]=counts1[i]=0;
        }
        ecount1=ecount2=0;
        safe1=safe2=mergeable=jumpable=spreadable=canmerge=canjump=canspread=false;
    }

    Cell(int tN, int xN, int yN) // not sure why I can't call () constructor here
    {
        for (int i = 0; i < 5; i++)
        {
            counts2[i]=counts1[i]=0;
        }
        ecount1=ecount2=0;
        safe1=safe2=mergeable=jumpable=spreadable=canmerge=canjump=canspread=false;

        t = tN;
        x = xN;
        y = yN;
    }

    void findOptions(int moi)
    {
        if (t == 0)
        {
            if (counts1[moi] > 0)
                spreadable = true;
            if (counts2[moi] > 0)
                jumpable = true;
        }
        else if (t == moi)
        {
            if (counts1[moi] > 0)
                mergeable = canmerge = true;
            if (counts1[0] > 0)
                canspread = true;
            if (counts2[0] > 0)
                canjump = true;
        }
    }
};

const int dim = 8;
const int hdim = 4;

int moi;
int chezMoi;

int target;
int chezTarget;

Cell cells[dim][dim];

int cornerCounts[4][5];
int totalCounts[5];

// ring ness - why why why

// end ring ness

int tlx;
int tly;
int thx;
int thy;

int alx;
int aly;
int ahx;
int ahy;

int rj;
int rstate;

void ring(int x, int y, int dist)
{   
    tlx=x-dist;
    tly=y-dist;
    thx=x+dist;
    thy=y+dist;

    alx=max(0, tlx);
    aly=max(0, tly);
    ahx=min(dim-1, thx);
    ahy=min(dim-1, thy);

    rstate = 0;
}

bool nextR(Cell** outc)
{
    if (rstate == 1)
    {
        goto state1;
    }
    if (rstate == 2)
    {
        goto state2;
    }
    if (rstate == 3)
    {
        goto state3;
    }
    if (rstate == 4)
    {
        goto state4;
    }

    if (alx == tlx)
    {
        rj = aly - 1;
        rstate = 1;
    }
state1:
    if (alx == tlx)
    {
        if (++rj <= ahy)
        {
            *outc = (cells[alx]+rj);
            return true;
        }
        alx++;
    }

    if (ahx == thx)
    {
        rj = aly - 1;
        rstate = 2;
    }
state2:
    if (ahx == thx)
    {
        if (++rj <= ahy)
        {
            *outc = (cells[ahx]+rj);
            return true;
        }
        ahx--;
    }

    if (aly == tly)
    {
        rj = alx - 1;
        rstate = 3;
    }
state3:
    if (aly == tly)
    {
        if (++rj <= ahx)
        {
            *outc = (cells[rj]+aly);
            return true;
        }
    }

    if (ahy == thy)
    {
        rj = alx - 1;
        rstate = 4;
    }
state4:
    if (ahy == thy)
    {
        if (++rj <= ahx)
        {
            *outc = (cells[rj]+ahy);
            return true;
        }
    }

    return null;
}

int cox;
int coy;

int ci;
int cj;

void corner(int idx)
{
    cox = (idx / 2) * hdim;
    coy = (idx % 2) * hdim;

    ci = 0;
    cj = -1;
}

bool nextC(Cell** outc)
{
    for (;ci < hdim;ci++)
    {
        for (;++cj < hdim;)
        {
            *outc = (cells[ci+cox]+cj+coy);
            return true;
        }
        cj = -1;
    }

    return false;
}

void cornerCount(int idx, int* c)
{
    int ox = (idx / 2) * hdim;
    int oy = (idx % 2) * hdim;

    for (int i = 0; i < hdim; i++)
    {
        for (int j = 0; j < hdim; j++)
        {
            c[cells[i+ox][j+oy].t]++;
        }
    }
}

void ringCount(int x, int y, int dist, int* c)
{
    int tlx=x-dist;
    int tly=y-dist;
    int thx=x+dist;
    int thy=y+dist;

    int alx=max(0, tlx);
    int aly=max(0, tly);
    int ahx=min(dim-1, thx);
    int ahy=min(dim-1, thy);

    if (alx == tlx)
    {
        for (int j = aly; j <= ahy; j++)
            c[cells[alx][j].t]++;
        alx++;
    }
    if (ahx == thx)
    {
        for (int j = aly; j <= ahy; j++)
            c[cells[ahx][j].t]++;
        ahx--;
    }
    if (aly == tly)
    {
        for (int i = alx; i <= ahx; i++)
            c[cells[i][aly].t]++;
    }
    if (ahy == thy)
    {
        for (int i = alx; i <= ahx; i++)
            c[cells[i][ahy].t]++;
    }
}

int trans(char c)
{
    return c<48?0:c-48;
}

std::string res(Cell* ca, Cell* cb)
{
    char buff[100]; // shhh
    sprintf_s(buff, "%d %d %d %d\n", ca->x, ca->y, cb->x, cb->y);
    return std::string(buff);
}

std::string go(char* inp)
{
    moi = trans(inp[0]);

    int a = 2;

    for (int i = 0; i < dim; i++)
    {
        for (int j = 0; j < dim; j++)
        {
            cells[i][j] = Cell(trans(inp[a]), i, j);
            a++;
        }
        a++;
    }

    // count corners and totals
    for (int i = 0; i < 4; i++)
    {
        cornerCount(i, cornerCounts[i]);
        for (int j = 0; j < 5; j++)
        {
            totalCounts[j] += cornerCounts[i][j];
        }
    }

    // count and find cell options
    for (int i = 0; i < dim; i++)
    {
        for (int j = 0; j < dim; j++)
        {
            Cell* c = cells[i]+j;

            ringCount(i, j, 1, c->counts1);
            ringCount(i, j, 2, c->counts2);

            // safeness
            for (int r = 1; r < 5; r++)
            {
                if (r != moi)
                {
                    c->ecount1 += c->counts1[r];
                    c->ecount2 += c->counts2[r];
                }
            }
            c->safe1 = c->ecount1 == 0 && c->counts1[0] == 0; // surrounded by moi
            c->safe2 = c->ecount1 == 0 && c->ecount2 == 0; // no enemies in sight

            // that funcion which does stuff
            c->findOptions(moi);
        }
    }

    // find chezMoi
    chezMoi = moi-1; // might work, can't be bothered to work it out
    for (int i = 1; i < 4; i++)
    {
        if (cornerCounts[i][moi] > cornerCounts[chezMoi][moi])
            chezMoi = i;
    }

    int best = 0;

    if (cornerCounts[chezMoi][moi] < hdim * hdim)
    {
        // fill our corner
        best = 0;
        Cell* ac = null;
        Cell* bc = null;

        corner(chezMoi);
        Cell* c;
        while (nextC(&c))
        {
            if (c->spreadable && c->ecount1 + 1 > best)
            {
                ring(c->x, c->y, 1);
                Cell* oc;
                while (nextR(&oc))
                {
                    if (oc->canspread)
                    {
                        best = c->ecount1 + 1;
                        ac = oc;
                        bc = c;
                        break;
                    }
                }
            }
            if (c->mergeable && c->counts1[0] - 1 > best)
            {
                ring(c->x, c->y, 1);
                Cell* oc;
                while (nextR(&oc))
                {
                    if (oc->safe2 && oc->canmerge && c->counts1[0] > 0)
                    {
                        best = c->counts1[0] - 1;
                        ac = oc;
                        bc = c;
                        break;
                    }
                }
            }
        }

        if (bc != null)
        {
            return res(ac, bc);
        }
    }

    // pick target (why?)
    target = -1;
    best = 0;
    for (int i = 0; i < 4; i++)
    {
        if (i == moi)
            continue;
        int cur = totalCounts[i];
        if (target == -1 || cur > best)
        {
            target = i;
            best = cur; 
        }
    }

    if (target != -1)
    {
        for (int i = 0; i < 4; i++)
        {
            if (i == chezMoi)
                continue;
            int cur = cornerCounts[i][target];
            if (chezTarget == -1 || cur > best)
            {
                chezTarget = i;
                best = cur;
            }
        }

        // attack chosen target (not sure it does this anymore...)
        best = 0;
        Cell* ac = null;
        Cell* bc = null;

        for (int i = 0; i < dim; i++)
        {
            for (int j = 0; j < dim; j++)
            {
                Cell* c = cells[i]+j;

                if (c->spreadable && c->ecount1 + 1 > best)
                {
                    ring(c->x, c->y, 1);
                    Cell* oc;
                    while (nextR(&oc))
                    {
                        if (oc->canspread)
                        {
                            best = c->ecount1 + 1;
                            ac = oc;
                            bc = c;
                            break;
                        }
                    }
                }
                if (c->jumpable && c->ecount1 - 1 > best)
                {
                    ring(c->x, c->y, 2);
                    Cell* oc;
                    while (nextR(&oc))
                    {
                        if (oc->safe2 && oc->canjump)
                        {
                            best = c->ecount1 - 1;
                            ac = oc;
                            bc = c;
                            break;
                        }
                    }
                }
            }
        }

        if (bc != null)
        {
            return res(ac, bc);
        }
    }

    return "0 0 0 0\n";
}

int main(int argc, char* args[])
{
    printf(go(args[1]).c_str());
    return 0;
}
VisualMelon
fonte