KOTH asimmetrico: Catch the Cat (Cat Thread)


14

KOTH asimmetrico: cattura il gatto

AGGIORNARE : I file gist vengono aggiornati (comprese le nuove invii) in quanto Controller.java non ha rilevato eccezioni (solo errori). Ora rileva errori ed eccezioni e li stampa anche.

Questa sfida consiste in due thread, questo è il thread cat, il thread catcher può essere trovato qui .

Il controller può essere scaricato qui .

Questo è un KOTH asimmetrico: ogni sottomissione è o un gatto o un ricevitore . Ci sono giochi tra ogni coppia di ciascuno un gatto e un ricevitore. I gatti e i cacciatori hanno classifiche separate.

Catcher

C'è un gatto su una griglia esagonale. Il tuo compito è prenderlo il più velocemente possibile. Ad ogni giro, puoi posizionare un secchio d'acqua su una cella della griglia per impedire al gatto di poterci andare. Ma il gatto non è (forse) così stupido, e ogni volta che metti un secchio, il gatto si sposterà in un'altra cella della griglia. Poiché la griglia è esagonale, il gatto può andare in 6 direzioni diverse. Il tuo obiettivo è circondare il gatto con secchi d'acqua, più veloce è, meglio è.

Gatto

Sai che il ricevitore vuole catturarti mettendo secchi d'acqua intorno a te. Certo che cerchi di evadere, ma dato che sei un gatto pigro (come i gatti) fai esattamente un passo alla volta. Ciò significa che non puoi stare nello stesso posto in cui ti trovi, ma devi spostarti in uno dei sei punti circostanti. Ogni volta che vedi che il ricevitore ha messo un nuovo secchio d'acqua, vai in un'altra cella. Certo che cerchi di evadere il più a lungo possibile.

Griglia

La griglia è esagonale, ma poiché non abbiamo strutture di dati esagonali, prendiamo una 11 x 11matrice quadrata 2d e imitiamo il "comportamento" esagonale che il gatto può muovere solo in 6 direzioni:

inserisci qui la descrizione dell'immagine

La topologia è toroidale, ciò significa che se passi su una cella "esterna" dell'array, verrai trasferito nella cella corrispondente sull'altro lato dell'array.

Gioco

Il gatto inizia in una determinata posizione nella griglia. Il ricevitore può fare la prima mossa, quindi il gatto e il suo ricevitore si alternano fino a quando il gatto viene catturato. Il numero di passaggi è il punteggio per quel gioco. Il gatto cerca di ottenere un punteggio il più grande possibile, il ricevitore cerca di ottenere un punteggio il più basso possibile. La somma media di tutti i giochi a cui hai partecipato sarà il punteggio della tua presentazione. Esistono due classifiche separate, una per il gatto, una per i cacciatori.

controllore

Il controller dato è scritto in Java. Come catcher o cat devi ciascuno di implementare completamente una classe Java (ci sono già alcuni esempi primitivi) e metterlo nel playerspacchetto (e aggiornare l'elenco di gatti / catcher nella classe Controller), ma puoi anche scrivere funzioni aggiuntive all'interno di quella classe. Il controller viene fornito con ogni due esempi funzionanti di semplici classi gatti / catcher.

Il campo è un array 11 x 112D intche memorizza i valori degli stati correnti delle celle. Se una cella è vuota, ha valore 0, se c'è un gatto ha valore -1e se c'è un bucket c'è un 1.

Ci sono alcune funzioni che puoi usare: isValidMove()/ isValidPosition()sono per verificare se la tua mossa (gatto) / posizione (ricevitore) è valida.

Ogni volta che è il tuo turno, la tua funzione takeTurn()viene chiamata. L'argomento contiene una copia della griglia corrente e ha metodi come read(i,j)per leggere la cella (i,j), oltre a isValidMove()/ isValidPosition()verificare la validità della tua risposta. Questo gestisce anche il wrapping della topologia toroidale, il che significa che anche se la griglia è solo 11 x 11, è ancora possibile accedere alla cella (-5,13).

Il metodo dovrebbe restituire una intmatrice di due elementi, che rappresentano possibili mosse. Per i gatti sono questi {-1,1},{0,1},{-1,0},{1,0},{0,-1},{1,-1}che rappresentano la posizione relativa di dove il gatto vuole andare, e i raccoglitori restituiscono le coordinate assolute di dove vogliono posizionare un secchio {i,j}.

Se il tuo metodo produce una mossa non valida, la tua richiesta sarà squalificata. La mossa è considerata non valida, se a destinazione è già un bucket o la mossa non è consentita / destinazione già occupata (come un gatto) o se esiste già un bucket / gatto (come un catcher). Puoi verificarlo prima con le funzioni indicate.

Il tuo invio dovrebbe funzionare abbastanza velocemente. Se il metodo richiede più di 200 ms per ogni passaggio, verrà anche squalificato. (Preferibilmente molto meno ...)

I programmi sono autorizzati a memorizzare informazioni tra i passaggi.

Inseriti

  • Puoi inviare tutte le richieste che desideri.
  • Si prega di non modificare in modo significativo gli invii già inviati.
  • Per favore, ogni invio in una nuova risposta.
  • Ogni invio dovrebbe preferibilmente avere un nome univoco.
  • L'invio deve consistere nel codice della tua classe e in una descrizione che ci dice come funziona l'invio.
  • È possibile scrivere la riga <!-- language: lang-java -->prima del codice sorgente per ottenere l'evidenziazione automatica della sintassi.

punteggio

Tutti i gatti competeranno contro tutti i cacciatori lo stesso numero di volte. Proverò ad aggiornare frequentemente i punteggi attuali, i vincitori saranno determinati quando l'attività sarà diminuita.

Questa sfida è ispirata a questo vecchio gioco flash

Grazie a @PhiNotPi per il test e il feedback costruttivo.

Punteggi attuali (100 partite per accoppiamento)

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

1
Penso che questo tipo di sfida sia quello a cui serve il tag poliziotti e ladri.
SuperJedi224,

4
@flawr Sarei a favore dell'estensione del tag CnR a tutte le sfide che coinvolgono due sotto-sfide avversarie (e usando sia quello che KotH come tag su questo). Il tag CnR wiki è fortemente influenzato dalle prime due sfide che abbiamo avuto in quel genere. (Inoltre, hai i poliziotti e i ladri nella direzione sbagliata.;))
Martin Ender,

1
Cosa impedisce ai gatti di importare main.Controller, chiamare getCatchers()e simulare / sabotare le risposte dei catturatori con i loro takeTurnmetodi?
LegionMammal978

12
@ LegionMammal978 Sportività.
Martin Ender

2
@feersum fa questo aiuto? (I punti neri (o blu) rappresentano la stessa cella.)
flawr

Risposte:


5

Freecat

Scegli la mossa che gli darebbe i percorsi più possibili dopo 3 passaggi se il campo non cambia.

FreeCat vs Achilles:

FreeCat vs Achilles

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;
    }
}

3

Dijkstra di gatto

Ha imparato e applica l'algoritmo principale del suo padrone. Si noti che dipende da alcuni dei metodi nella classe del ricevitore corrispondente.

Dijkstra's Cat vs Hexcatcher (necessita di aggiornamento):

inserisci qui la descrizione dell'immagine

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;
    }
}

Come lavora:

Cerca di trovare la mossa che minimizza la rigidità della tavola rispetto a se stesso. Per ulteriori informazioni, vedere il post del destinatario corrispondente.

Con aggiornamento:

Ora evita le strane forme geometriche che a volte formano i secchi d'acqua.


3

Maxcat

Ho provato a implementare l'algoritmo Minimax. Tuttavia, non funziona molto bene a causa del tempo limitato. Modifica: ora utilizza il multithreading, ma (almeno sul mio computer) non riesco a impostare la profondità più in alto. Altrimenti si verifica un timeout. Utilizzando un PC con 6 o più core, questa presentazione sarebbe molto meglio :)

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;
        }
    }
}

In realtà puoi rendere Fieldpubblico il costruttore . Mi dispiace di non aver ancora aggiornato i file, ma ne abbiamo discusso in precedenza!
flawr

@flawr Oh fantastico, grazie!
CommonGuy

2

SpiralCat

Si muove a spirale. esso

  • Cerca di spostarsi sul cerchio in alto a sinistra
  • Se non è possibile, prova a spostarsi nel cerchio in alto a destra
  • Se non è possibile, prova a spostarsi sul cerchio destro
  • Se non è possibile, prova a spostarsi nel cerchio in basso a destra
  • Se non è possibile, prova a spostarsi nel cerchio in basso a sinistra

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;
    }
}

Sai quali bug hai riscontrato? L'unica cosa che cambierei sarebbe quella di alterare turns[i]al turns[i%6]fine di evitare fuori limite (cosa che NON dovrebbe verificarsi in questa fase).
flawr

@flawr, dannazione. Scarsa scelta delle parole. Intendevo dire che questo gatto non è molto intelligente. A volte, questo gatto si alterna semplicemente tra il cerchio in alto a sinistra e il cerchio in basso a destra anche quando c'è una via d'uscita ...
Spikatrix

@flawr, devo usare turns[i%6]? Voglio dire, takeTurnnon verrà chiamato se il gatto è bloccato, giusto?
Spikatrix,

No, pensavo volessi dire che hai riscontrato un bug nel programma, quindi stavo cercando possibili motivi. Ma hai ragione, ovviamente (se tutto è corretto) i>=6non dovrebbe mai accadere.
flawr

2

RabidCat

RabidCat ha l'idrofobia, quindi ha paura dei secchi d'acqua. Trova quello più vicino e corre nella direzione opposta.

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];
}

}

Wow, sono stato distrutto da CloseCatcher però
Caino

2

ChoiceCat

Per ogni possibile nuova posizione del gatto controlliamo la sua bontà e scegliamo quella migliore. La bontà è la funzione delle due migliori celle vicine che sono più lontane dalla posizione del gatto rispetto alla posizione di cui calcoliamo il punteggio. Usiamo solo due celle perché una può essere bloccata e il gatto ha bisogno solo di un'altra per scappare. La nostra funzione preferisce due celle abbastanza buone di una grande e una cattiva. Le posizioni con bucket hanno un punteggio di 0 e le celle libere più lontane hanno un punteggio di 1.

ChoiceCat sembra segnare meglio dei gatti attuali.

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;
    }
}

1

StupidRightCat

Questo è stato fatto solo per testare il controller. Il gatto si sposta a destra quando possibile, altrimenti si sposta in una direzione casuale.

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

1

RandCat

Questo è stato fatto solo per testare il controller. Il gatto si muove casualmente.

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

1

StraightCat

Questo gatto si muove dritto.

All'inizio, sceglie una direzione casuale e continua a muoversi in questa direzione fino a quando non può in tal caso, sposta la direzione in senso orario alla successiva direzione valida e ripete questo processo.

StraightCat vs Agamemnon:

StraightCat vs Agamemnon

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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.