KOTH assimétrico: pegue o gato (linha do gato)

14

KOTH assimétrico: pegue o gato

ATUALIZAÇÃO : Os arquivos gist são atualizados (incluindo novas submissões), pois o Controller.java não capturou Exceções (apenas erros). Agora ele captura erros e exceções e também os imprime.

Esse desafio consiste em dois tópicos, este é o tópico do gato, o tópico do coletor pode ser encontrado aqui .

O controlador pode ser baixado aqui .

Este é um KOTH assimétrico: Cada envio é um gato ou um coletor . Existem jogos entre cada par de gato e apanhador. Os gatos e os coletores têm classificações separadas.

Apanhador

Há um gato em uma grade hexagonal. Sua tarefa é capturá-lo o mais rápido possível. A cada turno, você pode colocar um balde de água em uma célula da grade para impedir que o gato possa ir até lá. Mas o gato não é (talvez) tão burro, e sempre que você coloca um balde, ele se move para outra célula da grade. Como a grade é hexagonal, o gato pode ir em 6 direções diferentes. Seu objetivo é cercar o gato com baldes de água, quanto mais rápido, melhor.

Gato

Você sabe que o apanhador quer pegá-lo colocando baldes de água ao seu redor. Claro que você tenta fugir, mas como você é um gato preguiçoso (como os gatos são), dá exatamente um passo de cada vez. Isso significa que você não pode ficar no mesmo lugar que você, mas você precisa se mudar para um dos seis pontos circundantes. Sempre que vir que o coletor colocou um novo balde de água, você vai para outra célula. Claro que você tenta fugir o maior tempo possível.

Rede

A grade é hexagonal, mas como não temos estruturas de dados hexagonais, pegamos uma 11 x 11matriz quadrada em 2d e imitamos o 'comportamento' hexagonal de que o gato só pode se mover em 6 direções:

insira a descrição da imagem aqui

A topologia é toroidal, ou seja, se você pisar em uma célula 'fora' da matriz, será transferido apenas para a célula correspondente do outro lado da matriz.

jogos

O gato começa em uma determinada posição na grade. O apanhador pode fazer o primeiro movimento, depois o gato e seu apanhador se movem alternadamente até que o gato seja pego. O número de etapas é a pontuação para esse jogo. O gato tenta obter uma pontuação o melhor possível, o apanhador tenta obter uma pontuação o mais baixa possível. A soma média de todos os jogos em que você participou será a pontuação do seu envio. Existem dois rankings separados, um para o gato e outro para os coletores.

Controlador

O controlador fornecido é escrito em Java. Como apanhador ou gato, cada um de vocês deve implementar uma classe Java (cada uma com exemplos primitivos) e colocá-la no playerspacote (e atualizar a lista de gatos / apanhadores da classe Controller), mas você também pode escrever funções adicionais dentro dessa classe. O controlador vem com cada dois exemplos de trabalho de classes simples de gatos / coletores.

O campo é uma matriz 11 x 112D intque armazena os valores dos estados atuais das células. Se uma célula estiver vazia, ela terá valor 0, se houver um gato, ela terá valor -1e, se houver um balde, haverá a 1.

Existem algumas funções que você pode usar: isValidMove()/ serve isValidPosition()para verificar se a sua jogada (gato) / posição (apanhador) é válida.

Cada vez que é a sua vez, sua função takeTurn()é chamada. O argumento contém uma cópia da grade atual e métodos como read(i,j)para ler a célula em (i,j), além de isValidMove()/ isValidPosition()verificar a validade de sua resposta. Isso também gerencia o encapsulamento da topologia toroidal, ou seja, mesmo que a grade seja apenas 11 x 11, você ainda pode acessar a célula (-5,13).

O método deve retornar uma intmatriz de dois elementos, que representam possíveis movimentos. Para os gatos, estes são os {-1,1},{0,1},{-1,0},{1,0},{0,-1},{1,-1}que representam a posição relativa de onde o gato quer ir, e os coletores retornam as coordenadas absolutas de onde querem colocar um balde {i,j}.

Se seu método produzir uma movimentação inválida, seu envio será desqualificado. A movimentação é considerada inválida, se o seu destino já for um depósito ou a movimentação não for permitida / o destino já estiver ocupado (como um gato) ou se já houver um depósito / gato (como um coletor). Você pode verificar isso antes com as funções fornecidas.

Seu envio deve funcionar razoavelmente rápido. Se o seu método demorar mais de 200 ms para cada etapa, ele também será desqualificado. (De preferência muito menos ...)

Os programas têm permissão para armazenar informações entre as etapas.

Submissões

  • Você pode fazer quantos envios quiser.
  • Não altere significativamente os envios que você já enviou.
  • Por favor, cada envio em uma nova resposta.
  • Cada envio deve preferencialmente ter seu nome exclusivo.
  • O envio deve consistir no código da sua turma, bem como uma descrição que nos diga como o seu envio funciona.
  • Você pode escrever a linha do <!-- language: lang-java -->seu código fonte para obter o realce automático da sintaxe.

Pontuação

Todos os gatos competirão contra todos os coletores no mesmo número de vezes. Vou tentar atualizar as pontuações atuais com frequência, os vencedores serão determinados quando a atividade diminuir.

Este desafio é inspirado neste jogo flash antigo

Agradecemos à @PhiNotPi por testar e fornecer alguns comentários construtivos.

Pontuações atuais (100 jogos por emparelhamento)

Name              Score      Rank   Author

RandCatcher       191962     8      flawr   
StupidFill        212688     9      flawr
Achilles          77214      6      The E
Agamemnon         74896      5      The E
CloseCatcher      54776      4      randomra
ForwordCatcher    93814      7      MegaTom  
Dijkstra          47558      2      TheNumberOne
HexCatcher        48644      3      randomra
ChoiceCatcher     43834      1      randomra

RandCat            77490     9      flawr
StupidRightCat     81566     6      flawr
SpiralCat          93384     5      CoolGuy
StraightCat        80930     7      CoolGuy
FreeCat           106294     3      randomra
RabidCat           78616     8      cain
Dijkstra's Cat    115094     1      TheNumberOne
MaxCat             98400     4      Manu
ChoiceCat         113612     2      randomra
flawr
fonte
1
Eu acho que esse tipo de desafio é para que serve a etiqueta de policiais e ladrões.
SuperJedi224
4
@flawr Eu seria a favor de estender a tag CnR para todos os desafios que envolvem dois sub-desafios adversos (e usar tanto isso quanto o KotH como tags nisso). O wiki de tags CnR é muito influenciado pelos primeiros desafios que tivemos nesse gênero. (Além disso, você tem os polícias e ladrões a maneira errada redonda;.))
Martin Ender
1
O que impede os gatos de importar main.Controller, ligar getCatchers()e simular / sabotar as respostas dos apanhadores por meio de seus takeTurnmétodos?
LegionMammal978
12
@ LegionMammal978 Esportividade.
Martin Ender
2
@feersum isso ajuda? (Os pontos pretos (resp. Azul) representam a mesma célula.)
flawr

Respostas:

5

FreeCat

Escolhe a movimentação que daria os caminhos mais possíveis após 3 etapas, se o campo não fosse alterado.

FreeCat vs Aquiles:

Achilles vs FreeCat

package players;
/**
 * @author randomra
 */

import java.util.Arrays;

import main.Field;

public class FreeCat implements Cat {

    final int[][] turns = { { -1, 1 }, { 0, 1 }, { -1, 0 }, { 1, 0 },
            { 0, -1 }, { 1, -1 } };// all valid moves
    final int turnCheck = 3;

    public String getName() {
        return "FreeCat";
    }

    public int[] takeTurn(Field f) {

        int[] pos = f.findCat();
        int[] bestMove = { 0, 1 };
        int bestMoveCount = -1;
        for (int[] t : turns) {
            int[] currPos = { pos[0] + t[0], pos[1] + t[1] };
            int moveCount = free_count(currPos, turnCheck, f);
            if (moveCount > bestMoveCount) {
                bestMoveCount = moveCount;
                bestMove = t;
            }
        }
        return bestMove;
    }

    private int free_count(int[] pos, int turnsLeft, Field f) {
        if (f.isValidPosition(pos) || Arrays.equals(pos, f.findCat())) {
            if (turnsLeft == 0) {
                return 1;
            }
            int routeCount = 0;
            for (int[] t : turns) {
                int[] currPos = { pos[0] + t[0], pos[1] + t[1] };
                int moveCount = free_count(currPos, turnsLeft - 1, f);
                routeCount += moveCount;
            }
            return routeCount;
        }
        return 0;
    }
}
randomra
fonte
3

Gato de Dijkstra

Ele aprendeu e aplica o algoritmo mestre de seu mestre. Note que ele é dependente de alguns dos métodos da classe correspondente do seu catcher.

Gato de Dijkstra vs Hexcatcher (necessidades atualizadas):

insira a descrição da imagem aqui

package players;

import main.Field;
import players.Dijkstra; //Not needed import. Should already be available.

/**
 * @author TheNumberOne
 *
 * Escapes from the catcher.
 * Uses Dijkstras methods.
 */

public class DijkstrasCat implements Cat{

    private static final int[][] possibleMoves = {{-1,1},{0,1},{-1,0},{1,0},{0,-1},{1,-1}};
    @Override
    public String getName() {
        return "Dijkstra's Cat";
    }

    @Override
    public int[] takeTurn(Field f) {
        int[] me = f.findCat();
        int[] bestMove = {-1,1};
        int bestOpenness = Integer.MAX_VALUE;
        for (int[] move : possibleMoves){
            int[] newPos = Dijkstra.normalize(new int[]{me[0]+move[0],me[1]+move[1]});
            if (!f.isValidMove(move)){
                continue;
            }
            int openness = Dijkstra.openness(newPos, f, true)[1];
            if (openness < bestOpenness || (openness == bestOpenness && Math.random() < .5)){
                bestOpenness = openness;
                bestMove = move;
            }
        }
        return bestMove;
    }
}

Como ele trabalha:

Ele tenta encontrar o movimento que minimiza a rigidez do quadro em relação a si mesmo. Para mais informações, consulte a postagem do coletor correspondente.

Com atualização:

Agora, ele evita as estranhas formas geométricas que os baldes de água às vezes formam.

O número um
fonte
3

MaxCat

Eu tentei implementar o algoritmo Minimax. No entanto, ele não funciona muito bem devido ao tempo limitado. Edit: Ele agora usa multithreading, mas (pelo menos no meu computador) não consigo definir a profundidade mais alta. Caso contrário, ocorre um tempo limite. Usando um PC com 6 ou mais núcleos, esse envio seria muito melhor :)

MaxCat vs Dijkstra:

MaxCat vs Dijkstra

package players;

import java.util.ArrayList;
import java.util.List;

import main.Field;

public class MaxCat implements Cat {
    final int[][] turns = { { -1, 1 }, { 0, 1 }, { -1, 0 }, { 1, 0 }, { 0, -1 }, { 1, -1 } };

    public String getName() {
        return "MaxCat";
    }

    public int[] takeTurn(Field f) {
        List<CatThread> threads = new ArrayList<>();
        int[] pos = f.findCat();
        for (int[] turn : turns) {
            if(f.read(pos[0]+turn[0], pos[1]+turn[1]) == Field.EMPTY){
                CatThread thread = new CatThread();
                thread.bestMove = turn;
                thread.field = new Field(f);
                thread.start();
                threads.add(thread);
            }
        }
        for (CatThread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {}
        }
        int best = Integer.MIN_VALUE;
        int[] bestMove = { -1, 1 };
        for (CatThread thread : threads) {
            if (thread.score > best) {
                best = thread.score;
                bestMove = thread.bestMove;
            }
        }
        return bestMove;
    }

    class CatThread extends Thread {
        private Field field;
        private int[] bestMove;
        private int score;
        private final int DEPTH = 3;

        @Override
        public void run() {
            score = max(DEPTH, Integer.MIN_VALUE, Integer.MAX_VALUE);       
        }

        int max(int depth, int alpha, int beta) {
            int pos[] = field.findCat();
            if (depth == 0 || field.isFinished()) {
                int moveCount = 0;
                for (int[] turn : turns) {
                    if(field.read(pos[0]+turn[0], pos[1]+turn[1]) == Field.EMPTY)
                        moveCount++;
                }
                return DEPTH-depth + moveCount;
            }
            int maxValue = alpha;
            for (int[] turn : turns) {
                if(field.read(pos[0]+turn[0], pos[1]+turn[1]) == Field.EMPTY) {
                    field.executeMove(turn);
                    int value = min(depth-1, maxValue, beta);
                    field.executeMove(new int[]{-turn[0], -turn[1]});
                    if (value > maxValue) {
                        maxValue = value;
                        if (maxValue >= beta)
                            break;
                    }
                }
            }
            return maxValue;
        }

        int min(int depth, int alpha, int beta) {
            if (depth == 0 || field.isFinished()) {
                int moveCount = 0;
                for (int[] turn : turns) {
                    int pos[] = field.findCat();
                    if(field.read(pos[0]+turn[0], pos[1]+turn[1]) == Field.EMPTY)
                        moveCount++;
                }   
                return -depth - moveCount;
            }
            int[][] f = field.field;
            int minValue = beta;
            List<int[]> moves = generateBucketMoves();
            for (int[] move : moves) {
                f[move[0]][move[1]] = Field.BUCKET;
                int value = max(depth-1, alpha, minValue);
                f[move[0]][move[1]] = Field.EMPTY;
                if (value < minValue) {
                    minValue = value;
                    if (minValue <= alpha)
                        break;
                }
            }
            return minValue;
        }

        private List<int[]> generateBucketMoves() {
            int[][] f = field.field;
            List<int[]> list = new ArrayList<>();
            for (int i = 0; i < f.length; i++) {
                for (int j = 0; j < f[i].length; j++) {
                    if (f[i][j] == Field.EMPTY) {
                        list.add(new int[]{i,j});
                    }
                }
            }
            return list;
        }
    }
}
CommonGuy
fonte
Na verdade, você pode tornar o construtor Fieldpúblico. Lamento não atualizar os arquivos ainda, mas discutimos isso antes!
flawr
@ flawr Oh legal, obrigado!
usar o seguinte comando
2

SpiralCat

Move-se em espiral. isto

  • Tenta mover para o círculo superior esquerdo
  • Se não for possível, tenta se mover para o círculo superior direito
  • Se não for possível, tenta se mover para o círculo direito
  • Se não for possível, tenta se mover para o círculo inferior direito
  • Se não for possível, tenta se mover para o círculo inferior esquerdo

SpiralCat vs Agamemnon:

SpiralCat vs Agamemnon

package players;
/**
 * @author Cool Guy
 */

import main.Field;

public class SpiralCat implements Cat{
    public String getName(){
        return "SpiralCat";
    }
    public int[] takeTurn(Field f){
        int[][] turns = {{-1,1},{0,1},{1,0},{1,-1},{0,-1},{-1,0}};//all valid moves
        int[] move;
        int i = -1;
        do {
            i++;
            move = turns[i];
        } while(f.isValidMove(move) == false);
        return move;
    }
}
Spikatrix
fonte
Você sabe quais erros encontrou? A única coisa que eu mudaria seria a alteração, turns[i]a turns[i%6]fim de evitar fora dos limites (o que NÃO deve ocorrer nesta declaração).
Flawr 31/05
@flawr, droga. Má escolha de palavras. Eu quis dizer que este gato não é muito inteligente. Às vezes, este gato simplesmente alterna entre o canto superior esquerdo círculo e círculo canto inferior direito, mesmo quando há uma saída ...
Spikatrix
@ flawr, eu tenho que usar turns[i%6]? Quero dizer, takeTurnnão será chamado se o gato estiver bloqueado, certo?
Spikatrix
Não, eu pensei que você queria dizer que encontrou um bug no programa, então eu estava procurando por possíveis razões. Mas você está certo, obviamente (se tudo estiver correto) i>=6nunca deve acontecer.
precisa saber é
2

RabidCat

RabidCat tem hidrofobia, então ele tem medo dos baldes de água. Ele encontra o mais próximo e corre na direção oposta.

RabidCat vs ForwordCatcher:

rabidcat_vs_forwordcatcher

package players;

import java.util.Random;

import main.Field;

/**
* Run away from water buckets
* @author cain
*
*/

public class RabidCat implements Cat {

public RabidCat() {
}

@Override
public String getName() {
    return "RabidCat";
}

@Override
public int[] takeTurn(Field f) {
    int[][] directions = {{-1,1},{0,1},{-1,0},{1,0},{0,-1},{1,-1}};

    //where am I?
    int[] position = {0,0};
    for(int i = 0; i < 12; i++){
        for(int j = 0; j < 12; j++){
            if(f.read(i,j) == -1){
                position[0] = i;
                position[1] = j;
            }
        }
    }

    //Find the closest water
    int direction = 0;
    for(int d = 0; d < 10; d++){
        if(f.read(position[0] + d, position[1] - d) == 1 && f.isValidMove(directions[0])){
            direction = 1;
            break;
        }
        if(f.read(position[0], position[1] - d) == 1 && f.isValidMove(directions[1])){
            direction = 2;
            break;
        }
        if(f.read(position[0] + d, position[1]) == 1 && f.isValidMove(directions[2])){
            direction = 3;
            break;
        }
        if(f.read(position[0] - d, position[1]) == 1 && f.isValidMove(directions[3])){
            direction = 4;
            break;
        }
        if(f.read(position[0], position[1] + d) == 1 && f.isValidMove(directions[4])){
            direction = 5;
            break;
        }
        if(f.read(position[0] - d, position[1] + d) == 1 && f.isValidMove(directions[5])){
            direction = 6;
            break;
        }
    }

    //If there is no water near, wander
    while(direction == 0){
        Random rand = new Random();
        direction = rand.nextInt(6) + 1;
        if(!f.isValidMove(directions[direction - 1])){
            direction = 0;
        }
    }
    return directions[direction - 1];
}

}
Caim
fonte
Uau, realmente é destruído por CloseCatcher embora
Caim
2

ChoiceCat

Para todas as novas posições possíveis de gatos, verificamos sua bondade e escolhemos a melhor. A bondade é a função das duas melhores células vizinhas que estão mais distantes da posição do gato do que a posição cuja pontuação calculamos. Usamos apenas duas células porque uma pode ser bloqueada e o gato precisa apenas de mais uma para fugir. Nossa função prefere duas células razoavelmente boas do que uma ótima e outra ruim. As posições com baldes têm pontuação 0 e as células livres mais distantes têm pontuação 1.

O ChoiceCat parece ter uma pontuação melhor do que os gatos atuais.

ChoiceCat vs ChoiceCatcher:

ChoiceCat vs ChoiceCatcher

package players;
/**
 * @author randomra
 */
import java.util.Arrays;

import main.Field;

public class ChoiceCat implements Cat {

    private class Values {
        public final int size;
        private double[][] f;

        Values(int size) {
            this.size = size;
            f = new double[size][size];
        }

        public double read(int[] p) {
            int i = p[0];
            int j = p[1];
            i = (i % size + size) % size;
            j = (j % size + size) % size;
            return f[i][j];
        }

        private double write(int[] p, double v) {
            int i = p[0];
            int j = p[1];
            i = (i % size + size) % size;
            j = (j % size + size) % size;
            return f[i][j] = v;
        }
    }

    final int[][] turns = { { -1, 1 }, { 0, 1 }, { 1, 0 }, { 1, -1 },
            { 0, -1 }, { -1, 0 } };// all valid moves CW order
    final int stepCheck = 5;

    public String getName() {
        return "ChoiceCat";
    }

    public int[] takeTurn(Field f) {

        int[] pos = f.findCat();
        int[] bestMove = { 0, 1 };
        double bestMoveValue = -1;
        for (int[] t : turns) {
            int[] currPos = { pos[0] + t[0], pos[1] + t[1] };
            double moveValue = movePosValue(currPos, f);
            if (moveValue > bestMoveValue) {
                bestMoveValue = moveValue;
                bestMove = t;
            }
        }
        return bestMove;
    }

    private double movePosValue(int[] pos, Field f) {

        Values v = new Values(f.SIZE);

        for (int ring = stepCheck; ring >= 0; ring--) {
            for (int phase = 0; phase < 2; phase++) {
                for (int sidepos = 0; sidepos < Math.max(1, ring); sidepos++) {
                    for (int side = 0; side < 6; side++) {
                        int[] evalPos = new int[2];
                        for (int coord = 0; coord < 2; coord++) {
                            evalPos[coord] = pos[coord] + turns[side][coord]
                                    * sidepos + turns[(side + 1) % 6][coord]
                                    * (ring - sidepos);
                        }
                        if (phase == 0) {
                            if (ring == stepCheck) {
                                // on outmost ring, init value
                                v.write(evalPos, -1);
                            } else {
                                v.write(evalPos, posValue(evalPos, v, f));
                            }
                        } else {
                            // finalize position value for next turn
                            v.write(evalPos, -v.read(evalPos));
                        }
                    }
                }
            }
        }

        return -v.read(pos);
    }

    private double posValue(int[] pos, Values v, Field f) {
        if (f.read(pos[0], pos[1]) == Field.BUCKET) {
            return 0;
        }
        int count = 0;
        double[] product = new double[6];
        for (int[] t : turns) {
            int[] tPos = new int[] { pos[0] + t[0], pos[1] + t[1] };
            if (v.read(tPos) > 0) {
                product[count] = 1 - 1 / (v.read(tPos) + 1);
                count++;
            }
        }
        Arrays.sort(product);
        double fp = 1;
        for (int i = 0; i < Math.min(count,2); i++) {
            fp *= product[5-i];
        }
        double retValue = Math.min(count,2) + fp;
        return -retValue;
    }
}
randomra
fonte
1

StupidRightCat

Isso foi feito apenas para testar o controlador. O gato se move para a direita sempre que possível, caso contrário, se move em uma direção aleatória.

package players;

import main.Field;

public class StupidRightCat implements Cat{
    public String getName(){
        return "StupidRightCat";
    }
    public int[] takeTurn(Field f){
        int[][] turns = {{-1,1},{0,1},{-1,0},{1,0},{0,-1},{1,-1}};//all valid moves
        int[] move;

        if(f.isValidMove(turns[3])){
            return turns[3];
        } else {
            do {
                move = turns[(int) (turns.length * Math.random())];
            } while(f.isValidMove(move)==false);
            return move;//chose one at random
        }
    }
}
flawr
fonte
1

RandCat

Isso foi feito apenas para testar o controlador. O gato apenas se move aleatoriamente.

package players;

import main.Field;

public class RandCat implements Cat{
    public String getName(){
        return "RandCat";
    }
    public int[] takeTurn(Field f){
        int[][] turns = {{-1,1},{0,1},{-1,0},{1,0},{0,-1},{1,-1}};//all valid moves
        int[] move;
        do {
            move = turns[(int) (turns.length * Math.random())];
        } while(f.isValidMove(move)==false);
        return move;//chose one at random
    }
}
flawr
fonte
1

StraightCat

Este gato se move em linha reta.

No início, ele escolhe uma direção aleatória e continua se movendo nessa direção até que não possa; nesse caso, muda a direção no sentido horário para a próxima direção válida e repete esse processo.

StraightCat vs Agamemnon:

Agamemnon vs StraightCat

package players;
/**
 * @author Cool Guy
 */

import main.Field;

public class StraightCat implements Cat{

    int lastDirection = -1; //Holds the last direction the cat moved
    public String getName(){
        return "StraightCat";
    }
    public int[] takeTurn(Field f){
        int[][] turns = {{-1,1},{0,1},{1,0},{1,-1},{0,-1},{-1,0}};//all valid moves

        if(lastDirection == -1)
          lastDirection = (int) (turns.length * Math.random());

        int[] move = turns[lastDirection];
        int i = lastDirection;

        while(true)
        {
            if(f.isValidMove(move))
                break;
            i = (i+1)%6;
            lastDirection = i;
            move = turns[i];
        }
        return move;
    }
}
Spikatrix
fonte