Snowball Fight KoTH!


35

Resultados (22 de mayo de 2017 21:40:37 UTC)

Masterganó 18 rondas, perdió 2 rondas y empató 0 rondas
Save Oneganó 15 rondas, perdió 3 rondas y empató 2 rondas
Machine Gunganó 14 rondas, perdió 3 rondas y empató 3 rondas
Monte Botganó 14 rondas, perdió 3 rondas y empató 3 rondas
Amb Botganó 12 rondas, perdió 8 rondas y empató 0 rondas
Cowardganó 11 rondas, perdió 3 rondas y empató 6 rondas
Pain in the Nashganó 11 rondas, perdió 9 rondas y empató 0 rondas
Nece Botganó 10 rondas, perdió 7 rondas y empató 3 rondas
Naming Things is Hardganó 10 rondas, perdió 7 rondas y empató 3 rondas
The Procrastinatorganó 10 rondas, perdió 8 rondas y empató 2 rondas
Yggdrasilganó 10 rondas, perdió 10 rondas y empató 0 rondas
Simple Botganó 9 rondas, perdió 4 rondas y empató 7 rondas
Table Botganó 9 rondas, perdió 6 rondas rondas y empató 5 rondas
Prioritized Random Botganó 8 rondas, perdió 7 rondas y empató 5 rondas
Upper Hand Botganó 7 rondas, perdió 13 rondas y empató 0 rondas
Aggressorganó 6 rondas, perdió 10 rondas y empató 4 rondas
Insaneganó 5 rondas, perdió 15 rondas y empató 0 rondas
The Ugly Ducklingganó 4 rondas, perdió 16 rondas y empató 0 rondas
Know Botganadas 3 rondas, perdió 14 rondas y empató 3 rondas
Paranoid Botganó 0 rondas, perdió 19 rondas y empató 1 ronda
Panic Botganó 0 rondas, perdió 19 rondas y empató 1 ronda

Lamentablemente, no pude probar The Crazy X-Code Randomess porque no puedo ejecutarlo desde bash en Linux. Lo incluiré si puedo hacer que funcione.

Salida de controlador completo


El juego

Este es un juego de KoTH muy simple. Es una pelea de bolas de nieve uno a uno. Tiene un contenedor inicialmente vacío que puede soportar kbolas de nieve. Puedes agacharte hasta jveces. En cada turno, se les pide a ambos jugadores que den simultáneamente la opción de qué movimiento hacer. Hay tres movimientos:

  • recargar: te da otra bola de nieve (hasta k)
  • tirar: lanza una bola de nieve, que matará al otro jugador si deciden recargar. Si ambos jugadores lanzan una bola de nieve, nadie muere (tienen tan buena puntería que golpearán las bolas de nieve del otro)
  • pato: no hace nada y evita ser golpeado si el otro jugador lanza una bola de nieve. Si no te quedan más patos, entonces no pasa nada y si el otro jugador lanza una bola de nieve, mueres.

Objetivo

No te mueras

Especificaciones de Challlenge

Su programa se puede escribir en cualquier idioma. Debe tomar cada una de estas variables como argumento en cada ejecución:

[turn, snowballs, opponent_snowballs, ducks, opponent_ducks, max_snowballs]

turn- cuántas vueltas han transcurrido ( 0en la primera iteración)
snowballs- cuántas bolas de nieve tienes
opponent_snowballs- cuántas bolas de nieve tiene el oponente
ducks- cuántas veces más puedes esquivar
opponent_ducks- cuántas veces más puede esquivar el oponente
max_snowballs- la cantidad máxima de bolas de nieve que puedes tienda ( k)

La salida de la función clave debe ser 0para recargar, 1para lanzar y 2para agacharse. Debe generar su movimiento, nueva línea terminada. No genere movimientos no válidos, pero el controlador es muy resistente y no se romperá si genera movimientos no válidos (incluso si su movimiento ni siquiera es un número entero). Sin embargo, debe ser terminado en línea nueva. Si el movimiento no está activado [0, 1, 2], su movimiento estará predeterminado 0. El ganador se decidirá como el jugador con la mayor cantidad de victorias de un torneo completo de todos contra todos.

Reglas

Puede leer / escribir desde / a un archivo para el almacenamiento de memoria entre iteraciones. Su bot se colocará en su propio directorio para que no ocurran conflictos de nombre de archivo. No puede cambiar las funciones integradas (como el generador aleatorio). Fue bastante divertido la primera vez que se hizo , pero ya no lo será. Su programa no está autorizado a hacer cosas que son simplemente un estancamiento de ejecución flagrante. Se aplican lagunas estándar .

Pruebas

El código fuente del controlador se puede encontrar aquí . Ejemplo de ejecución: java Controller "python program1/test1.py" "python program2/test2.py" 10 5para 10 bolas de nieve y 5 patos.

Juzgar

El ganador se decidirá seleccionando a la persona con más victorias después de un round robin completo. Si bien esto es un empate, elimine a todas las personas que no tienen la mayor cantidad de victorias. Luego, repita hasta que una persona gane. El estándar de evaluación será de 50 bolas de nieve y 25 patos.

Happy KoTHing!

EDITAR : El juego se declarará empatado si pasan 1000 rondas. Su bot puede asumir eso turn < 1000.


Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Dennis

@HyperNeutrino Más preguntas: ¿Pensé que el "estándar de evaluación" sería 50 bolas de nieve y 25 patos? ¿Y por qué hay un empate a veces después de ~ 18 rondas?
CommonGuy

@Manu Ehh mierda Olvidé cambiar la configuración en mis argumentos de VM. Y también, eso es porque si entran en un bucle sin fin de colisiones de bolas de nieve, lo termina después de 10 rondas de repetir un bucle de período 1 o período 2.
HyperNeutrino

1
Entonces, ¿habrá otra ronda? Porque quiero subir mi bot y me gustaría saber qué tan bien funcionaría.
erbsenhirn

@erbsenhirn Si subes un bot y me haces ping en el chat o en The Nineteenth Byte , y ejecutaré otra ejecución.
HyperNeutrino

Respuestas:


13

Maestro, C #

Entrené una pequeña red neuronal (usando Sharpneat ). Parece que le gusta recoger bolas de nieve y agacharse ...

En una versión anterior del controlador, incluso encontró un error. Pasó de 0% de victorias a 100% cuando descubrió cómo ganar haciendo trampa.

Editar: Olvidé restablecer el estado interno de la red y entrené la red de manera incorrecta. La red recién entrenada es mucho más pequeña.

using System;
using System.Collections.Generic;

public class Master
{
    public CyclicNetwork _network;

    public static void Main(string[] args)
    {
        int s = int.Parse(args[1]);
        int os = int.Parse(args[2]);
        int d = int.Parse(args[3]);
        int od = int.Parse(args[4]);
        int ms = int.Parse(args[5]);

        var move = new Master().GetMove(s, os, d, od, ms);
        Console.WriteLine(move);
    }

    public Master()
    {
        var nodes = new List<Neuron>
        {
            new Neuron(0, NodeType.Bias),
            new Neuron(1, NodeType.Input),
            new Neuron(2, NodeType.Input),
            new Neuron(3, NodeType.Input),
            new Neuron(4, NodeType.Input),
            new Neuron(5, NodeType.Input),
            new Neuron(6, NodeType.Output),
            new Neuron(7, NodeType.Output),
            new Neuron(8, NodeType.Output),
            new Neuron(9, NodeType.Hidden)
        };
        var connections = new List<Connection>
        {
            new Connection(nodes[1], nodes[6], -1.3921811701131295),
            new Connection(nodes[6], nodes[6], 0.04683387519679514),
            new Connection(nodes[3], nodes[7], -4.746164930591382),
            new Connection(nodes[8], nodes[8], -0.025484025422054933),
            new Connection(nodes[4], nodes[9], -0.02084856381644095),
            new Connection(nodes[9], nodes[6], 4.9614062853759124),
            new Connection(nodes[9], nodes[9], -0.008672587457112968)
        };
        _network = new CyclicNetwork(nodes, connections, 5, 3, 2);
    }

    public int GetMove(int snowballs, int opponentBalls, int ducks, int opponentDucks, int maxSnowballs)
    {
        _network.InputSignalArray[0] = snowballs;
        _network.InputSignalArray[1] = opponentBalls;
        _network.InputSignalArray[2] = ducks;
        _network.InputSignalArray[3] = opponentDucks;
        _network.InputSignalArray[4] = maxSnowballs;

        _network.Activate();

        double max = double.MinValue;
        int best = 0;
        for (var i = 0; i < _network.OutputCount; i++)
        {
            var current = _network.OutputSignalArray[i];

            if (current > max)
            {
                max = current;
                best = i;
            }
        }

        _network.ResetState();

        return best;
    }
}

public class CyclicNetwork
{
    protected readonly List<Neuron> _neuronList;
    protected readonly List<Connection> _connectionList;
    protected readonly int _inputNeuronCount;
    protected readonly int _outputNeuronCount;
    protected readonly int _inputAndBiasNeuronCount;
    protected readonly int _timestepsPerActivation;
    protected readonly double[] _inputSignalArray;
    protected readonly double[] _outputSignalArray;
    readonly SignalArray _inputSignalArrayWrapper;
    readonly SignalArray _outputSignalArrayWrapper;

    public CyclicNetwork(List<Neuron> neuronList, List<Connection> connectionList, int inputNeuronCount, int outputNeuronCount, int timestepsPerActivation)
    {
        _neuronList = neuronList;
        _connectionList = connectionList;
        _inputNeuronCount = inputNeuronCount;
        _outputNeuronCount = outputNeuronCount;
        _inputAndBiasNeuronCount = inputNeuronCount + 1;
        _timestepsPerActivation = timestepsPerActivation;

        _inputSignalArray = new double[_inputNeuronCount];
        _outputSignalArray = new double[_outputNeuronCount];

        _inputSignalArrayWrapper = new SignalArray(_inputSignalArray, 0, _inputNeuronCount);
        _outputSignalArrayWrapper = new SignalArray(_outputSignalArray, 0, outputNeuronCount);
    }
    public int OutputCount
    {
        get { return _outputNeuronCount; }
    }
    public SignalArray InputSignalArray
    {
        get { return _inputSignalArrayWrapper; }
    }
    public SignalArray OutputSignalArray
    {
        get { return _outputSignalArrayWrapper; }
    }
    public virtual void Activate()
    {
        for (int i = 0; i < _inputNeuronCount; i++)
        {
            _neuronList[i + 1].OutputValue = _inputSignalArray[i];
        }

        int connectionCount = _connectionList.Count;
        int neuronCount = _neuronList.Count;
        for (int i = 0; i < _timestepsPerActivation; i++)
        {
            for (int j = 0; j < connectionCount; j++)
            {
                Connection connection = _connectionList[j];
                connection.OutputValue = connection.SourceNeuron.OutputValue * connection.Weight;
                connection.TargetNeuron.InputValue += connection.OutputValue;
            }
            for (int j = _inputAndBiasNeuronCount; j < neuronCount; j++)
            {
                Neuron neuron = _neuronList[j];
                neuron.OutputValue = neuron.Calculate(neuron.InputValue);
                neuron.InputValue = 0.0;
            }
        }
        for (int i = _inputAndBiasNeuronCount, outputIdx = 0; outputIdx < _outputNeuronCount; i++, outputIdx++)
        {
            _outputSignalArray[outputIdx] = _neuronList[i].OutputValue;
        }
    }
    public virtual void ResetState()
    {
        for (int i = 1; i < _inputAndBiasNeuronCount; i++)
        {
            _neuronList[i].OutputValue = 0.0;
        }
        int count = _neuronList.Count;
        for (int i = _inputAndBiasNeuronCount; i < count; i++)
        {
            _neuronList[i].InputValue = 0.0;
            _neuronList[i].OutputValue = 0.0;
        }
        count = _connectionList.Count;
        for (int i = 0; i < count; i++)
        {
            _connectionList[i].OutputValue = 0.0;
        }
    }
}
public class Connection
{
    readonly Neuron _srcNeuron;
    readonly Neuron _tgtNeuron;
    readonly double _weight;
    double _outputValue;

    public Connection(Neuron srcNeuron, Neuron tgtNeuron, double weight)
    {
        _tgtNeuron = tgtNeuron;
        _srcNeuron = srcNeuron;
        _weight = weight;
    }
    public Neuron SourceNeuron
    {
        get { return _srcNeuron; }
    }
    public Neuron TargetNeuron
    {
        get { return _tgtNeuron; }
    }
    public double Weight
    {
        get { return _weight; }
    }
    public double OutputValue
    {
        get { return _outputValue; }
        set { _outputValue = value; }
    }
}

public class Neuron
{
    readonly uint _id;
    readonly NodeType _neuronType;
    double _inputValue;
    double _outputValue;

    public Neuron(uint id, NodeType neuronType)
    {
        _id = id;
        _neuronType = neuronType;

        // Bias neurons have a fixed output value of 1.0
        _outputValue = (NodeType.Bias == _neuronType) ? 1.0 : 0.0;
    }
    public double InputValue
    {
        get { return _inputValue; }
        set
        {
            if (NodeType.Bias == _neuronType || NodeType.Input == _neuronType)
            {
                throw new Exception("Attempt to set the InputValue of bias or input neuron. Bias neurons have no input, and Input neuron signals should be passed in via their OutputValue property setter.");
            }
            _inputValue = value;
        }
    }
    public double Calculate(double x)
    {
        return 1.0 / (1.0 + Math.Exp(-4.9 * x));
    }
    public double OutputValue
    {
        get { return _outputValue; }
        set
        {
            if (NodeType.Bias == _neuronType)
            {
                throw new Exception("Attempt to set the OutputValue of a bias neuron.");
            }
            _outputValue = value;
        }
    }
}

public class SignalArray
{
    readonly double[] _wrappedArray;
    readonly int _offset;
    readonly int _length;

    public SignalArray(double[] wrappedArray, int offset, int length)
    {
        if (offset + length > wrappedArray.Length)
        {
            throw new Exception("wrappedArray is not long enough to represent the requested SignalArray.");
        }

        _wrappedArray = wrappedArray;
        _offset = offset;
        _length = length;
    }

    public double this[int index]
    {
        get
        {
            return _wrappedArray[_offset + index];
        }
        set
        {
            _wrappedArray[_offset + index] = value;
        }
    }
}

public enum NodeType
{
    /// <summary>
    /// Bias node. Output is fixed to 1.0
    /// </summary>
    Bias,
    /// <summary>
    /// Input node.
    /// </summary>
    Input,
    /// <summary>
    /// Output node.
    /// </summary>
    Output,
    /// <summary>
    /// Hidden node.
    /// </summary>
    Hidden
}

Aparentemente, restablecer el estado de la red mejoró mucho el rendimiento :)
HyperNeutrino

¿Contra qué entrenaste la red neuronal? Contra otros bots publicados aquí?
JAD

@JarkoDubbeldam Sí, porté varios de ellos a C # y entrené a la red contra ellos. Es por eso que probablemente perderá contra nuevos bots.
CommonGuy

O simplemente entrene otra red contra los bots y esta: p
JAD

Wat ¡8 votos para una red neuronal!
Christopher

6

Save One, Python

Lanza la mayoría de sus bolas de nieve de inmediato, pero siempre guarda una en caso de que el oponente esté buscando falta de munición. Luego, se agacha el mayor tiempo posible (nuevamente, guardando 1) antes de recargar, a menos que haya una recarga segura garantizada o muerte garantizada.

import sys
turn, snowballs, opponent_snowballs, ducks, opponent_ducks, max_snowballs = map(int, sys.argv[1:])

reload_snowball=0
throw=1
duck=2

if snowballs<=1:
    if opponent_snowballs==0:
        if opponent_ducks==0:
            print throw
        else:
            print reload_snowball
    elif ducks > 1:
        print duck
    else:
        print reload_snowball
else:
    print throw

2
si tienes 0 bolas de nieve, intentará lanzar 1
Carl Bosch

@CarlBosch que debería ser un estado imposible de alcanzar (aparte de comenzar con 0), pero de todos modos haré una edición para cubrir ese caso
SnoringFrog

2
@SnoringFrog para aclarar las reglas, comienza con 0 bolas de nieve
PhiNotPi

@PhiNotPi Debo haber pasado por alto eso por completo. Gracias por la aclaración
SnoringFrog

6

PrioritizedRandomBot, Java

import java.util.Random;

public class PrioritizedRandomBot implements SnowballFighter {
    static int RELOAD = 0;
    static int THROW = 1;
    static int DUCK = 2;
    static Random rand = new Random();

    public static void main(String[] args) {
        int t = Integer.parseInt(args[0]);
        int s = Integer.parseInt(args[1]);
        int os = Integer.parseInt(args[2]);
        int d = Integer.parseInt(args[3]);
        int od = Integer.parseInt(args[4]);
        int ms = Integer.parseInt(args[5]);
        if (s > os + od) {
            System.out.println(THROW);
            return;
        }
        if (os == 0) {
            if (s == ms || s > 0 && s == od && rand.nextInt(1001 - t) == 0) {
                System.out.println(THROW);
            } else {
                System.out.println(RELOAD);
            }
            return;
        }
        if (os == ms && d > 0) {
            System.out.println(DUCK);
            return;
        }
        int r = rand.nextInt(os + od);
        if (r < s) {
            System.out.println(THROW);
        } else if (r < s + d) {
            System.out.println(DUCK);
        } else {
            System.out.println(RELOAD);
        }
    }
}

Este bot selecciona un número entero aleatorio en el rango 0de os + od, y luego elige lanzar, agacharse o recargar, con los umbrales determinados por su número actual de bolas de nieve y patos.

Una cosa que es importante tener en cuenta es que una vez que un bot tiene más bolas de nieve que el otro tiene bolas de nieve + patos, puede forzar una victoria. A partir de esto, podemos llegar al concepto de "puntos":

my points = s - os - od
op points = os - s - d

 effects of moves on my points
        OPPONENT
       R    T    D
   R        L   ++
 M T   W          
 E D   -    +    +

Si cualquiera de estos números se vuelve positivo, entonces ese jugador puede forzar una victoria.

points dif = p - op = 2*(s - os) + d - od

 effects of moves on the difference in points (me - my opponent)
        OPPONENT
       R    T    D
   R        L   +++
 M T   W         -
 E D  ---   +   


points sum = p + op = - (d + od)

 effects of moves on the sum of points (me + my opponent)
        OPPONENT
       R    T    D
   R        L    +
 M T   W         +
 E D   +    +   ++

La tabla de "diferencia en puntos" forma la base de la teoría del juego para esta competencia. No captura toda la información, pero muestra cómo las bolas de nieve son fundamentalmente más valiosas que los patos (ya que las bolas de nieve son ofensivas y defensivas). Si el oponente lanza una bola de nieve y tú te agachas con éxito, entonces estás un paso más cerca de una victoria forzosa, ya que tu oponente agotó un recurso más valioso. Esta tabla también describe lo que debe hacer en muchos casos especiales, como cuando ciertas opciones de movimiento no están disponibles.

La tabla de "suma de puntos" muestra cómo, con el tiempo, la suma de puntos se acerca a cero (a medida que ambos jugadores se quedan sin patos), momento en el cual el primer jugador comete un error (recarga cuando no era necesario) pierde

Ahora, intentemos extender esta estrategia forzada a casos en los que no es realmente forzoso (como en el caso de que estemos ganando por un amplio margen, pero la lectura mental del oponente nos vencerá). Básicamente, tenemos sbolas de nieve, pero necesitamos bolas de nieve a nuestro oponente s+1(o s+2, etc.) tiempo consecutivamente para ganar. En este caso, queremos realizar algunos patos o algunas recargas para comprarnos algo de tiempo.

En este momento, este robot siempre intenta colarse en algunos patos, simplemente porque no corre el riesgo de una pérdida inmediata: suponemos que el oponente está siguiendo una estrategia similar de lanzar tantas bolas de nieve como sea posible, por lo que intentar recargar es realmente peligroso. Además, para evitar la previsibilidad, queremos escabullirlos siguiendo una distribución aleatoria uniforme: la probabilidad de esquivar está relacionada con cuántos patos debemos realizar en relación con la cantidad de bolas de nieve que necesitamos lanzar.

Si estamos perdiendo mucho, en cuyo caso s + d < os + od, necesitamos escabullir algunas recargas además de usar todos nuestros patos, en este caso, queremos recargar al azar, pero solo las veces que necesitemos.

Esta es la razón por la cual nuestros bots priorizan en el orden de lanzar, agacharse y recargar, y los usan os + odpara generar el número aleatorio, ya que ese es el número umbral de movimientos que necesitamos hacer.

Hay un caso de borde, y otros dos casos especiales, que el bot maneja actualmente. El caso límite es cuando el oponente no tiene bolas de nieve ni patos, por lo que la aleatorización no funciona, por lo que tiramos si es posible, de lo contrario, volvemos a cargar. Otro caso especial es cuando el oponente no puede recargar, por lo que no hay beneficio en lanzar (ya que el oponente se agachará o lanzará), por lo que siempre nos agachamos (ya que salvar nuestras bolas de nieve es más valioso que salvar a nuestros patos). El último caso especial es si el oponente no tiene bolas de nieve, en cuyo caso jugamos a lo seguro y lo recargamos si es posible.


Esto puede terminar imprimiendo múltiples números que pueden no funcionar correctamente.
HyperNeutrino

@HyperNeutrino Olvidé agregar un bloque "else" cuando reescribí este bot de usar declaraciones de devolución para imprimir.
PhiNotPi

1
@HyperNeutrino Lo hizo por mí, y lo consideré molesto ...
Erik the Outgolfer

Ah Sí, lo siento por haber estropeado tu código: P ¡Pero bueno, primer programa que usa aleatoriedad!
HyperNeutrino

6

NeceBot - Python

Aquí está la tabla de teoría de juegos para el juego:

        OPPONENT
       R    T     D
   R   ~    L   +D+S
 M T   W    ~   +D-S 
 E D -D-S  -D+S   ~

Donde ~significa que no hay ventaja, Wes ganar, Les perder, +-Ssignifica que se gana / pierde una bola de nieve sobre el oponente, y +-Dsignifica que se gana / pierde un pato sobre el oponente. Este es un juego completamente simétrico.

Tenga en cuenta que mi solución no tiene en cuenta esa tabla. Porque soy malo en matemáticas.

import sys

RELOAD = 0
THROW = 1
DUCK = 2

def main(turn, snowballs, opponent_snowballs, ducks, opponent_ducks, max_snowballs):
    if 2 + ducks <3:
        if 2 + snowballs <3:
            return RELOAD
        if 2 + opponent_ducks <3 or 2 + opponent_snowballs <3:
            return THROW
        return RELOAD
    if 2 + snowballs <3:
        if -opponent_snowballs <3 - 5 or 2 + abs(opponent_snowballs - 1) <3:
            return DUCK
        return RELOAD
    if 2 + opponent_ducks <3 or 2 + abs(snowballs - max_snowballs) <3:
        return THROW
    if -snowballs <3 - 6 or turn % 5 <3:
        return THROW
    return DUCK

print(main(*map(int, sys.argv[1:])))

Se llama NeceBot porque primero intenta reducir lo que es necesario. Tiene algunas estrategias arbitrarias después de eso, que espero funcionen.


44
Whee tantos <3s lol. +1 por tener una mesa de juego y luego no usarla: P Pero buena solución :)
HyperNeutrino

3 + opponent_snowballs <3esto puede ser un error?
PhiNotPi

@PhiNotPi Sí. Se suponía que debía ser 2. Solucionado ahora, ¡gracias!
Artyer

Desafortunadamente, la gran cantidad de <3correos
electrónicos

5

Cobarde - Scala

Lanza, si el oponente no tiene munición, de lo contrario (en orden de prioridad) se agacha, lanza o recarga.

object Test extends App {
  val s = args(1).toInt
  val os = args(2).toInt
  val d = args(3).toInt

  val move = 
    if(os == 0)
      if(s > 0)
        1
      else
        0
    else if(d > 0)
        2
    else if(s > 0)
      1
    else
      0

  println(move)
}

Parece que esto atasca mi bot ...
Erik the Outgolfer

5

TheUglyDuckling - Python

Siempre se agachará hasta que no pueda, entonces intenta lanzar si el oponente está vacío o recargar si ambos están vacíos. Utilizará la recarga como último recurso.

import sys

arguments = sys.argv;

turn = int(arguments[1])
snowballs = int(arguments[2])
opponent_snowballs = int(arguments[3])
ducks = int(arguments[4])
opponent_ducks = int(arguments[5])
max_snowballs = int(arguments[6])

if ducks > 0:
    print 2
elif opponent_snowballs == 0 and snowballs > 0:
    print 1
elif opponent_snowballs == 0 and snowballs <= 0:
    print 0
elif snowballs > 0:
    print 1
elif snowballs <= 0:
    print 0

5

SimpleBot - Python 2

import sys
turn, snowballs, opponent_snowballs, ducks, opponent_ducks, max_snowballs = map(int, sys.argv[1:])

if opponent_snowballs > 0 and ducks > 0: print 2
elif snowballs: print 1
else: print 0

Cosas simples

  • Si el oponente tiene bolas de nieve y tú tienes patos, entonces te agachas.
  • Si el oponente no tiene bolas de nieve y tú las tienes, entonces tiras.
  • En cualquier otro caso, vuelve a cargar.

5

El bot Naming-things-is-hard - VB.NET

Nombrar cosas es difícil, y no estoy seguro de tener una estrategia coherente para nombrarlo.

Intenta apostar las primeras rondas para obtener una victoria temprana. Después de eso, juega más seguro el resto del tiempo, tratando de ganar por desgaste.

Module SnowballFight

    Private Enum Action
        Reload = 0
        ThrowSnowball = 1
        Duck = 2
    End Enum

    Sub Main(args As String())
        Dim turn As Integer = args(0)
        Dim mySnowballs As Integer = args(1)
        Dim opponentSnowballs As Integer = args(2)
        Dim myDucks As Integer = args(3)
        Dim opponentDucks As Integer = args(4)
        Dim maxSnowballs As Integer = args(5)

        If mySnowballs = 0 AndAlso opponentSnowballs = 0 Then
            ' can't throw, no need to duck
            Console.WriteLine(Action.Reload)
            Exit Sub
        End If

        If turn = 2 AndAlso opponentSnowballs > 0 Then
            ' everyone will probably reload and then throw, so try and duck, and throw turn 3
            Console.WriteLine(Action.Duck)
            Exit Sub
        End If

        If turn = 3 AndAlso opponentSnowballs = 0 Then
            ' they threw on turn 2, get them!
            Console.WriteLine(Action.ThrowSnowball)
            Exit Sub
        End If

        If mySnowballs > 0 AndAlso opponentSnowballs = 0 Then
            ' hope they don't duck
            Console.WriteLine(Action.ThrowSnowball)
            Exit Sub
        End If

        If mySnowballs = 0 AndAlso opponentSnowballs > 0 Then
            If myDucks > 0 Then
                ' watch out!
                Console.WriteLine(Action.Duck)
                Exit Sub
            Else
                ' well, maybe we'll get lucky
                Console.WriteLine(Action.Reload)
                Exit Sub
            End If
        End If

        If opponentSnowballs > 0 AndAlso myDucks > 5 Then
            ' play it safe
            Console.WriteLine(Action.Duck)
            Exit Sub
        End If

        If mySnowballs > 5 OrElse opponentDucks < 5 Then
            ' have a bunch saved up, start throwing them
            Console.WriteLine(Action.ThrowSnowball)
            Exit Sub
        End If

        ' start saving up
        Console.WriteLine(Action.Reload)
    End Sub

End Module

5

MachineGun, Python 3

Intenta ahorrar bolas de nieve hasta que se garantice la muerte del oponente o hasta que se quede sin patos (en cuyo caso, comienza a disparar a ciegas todas sus bolas de nieve, como una ametralladora)

También se agacha cuando el oponente tiene una bola de nieve, porque no quiere morir.

from os import sys
args = sys.argv[1:]
turn = int(args[0])
snowballs = int(args[1])
opponent_snowballs = int(args[2])
ducks = int(args[3])
opponent_ducks = int(args[4])
max_snowballs = int(args[5])
if ducks > 0 and opponent_snowballs > 0:
    print("2")
elif snowballs > 0 and opponent_snowballs == 0 and opponent_ducks == 0:
    print("1")
elif ducks == 0 and snowballs > 0:
    print("1")
elif snowballs < max_snowballs:
    print("0")
elif snowballs == max_snowballs:
    print("1")
else:
    print("0")

5

Knowbot, Python3

Mantiene la frecuencia de seguimiento de movimientos anteriores, asume que el oponente volverá a hacer el más frecuente y se defiende contra eso.

** Actualizado para no esperar movimientos que el oponente no puede hacer **

import sys,pickle
TURN,BALLS,OTHROWS,DUCKS,ODUCKS,MAXB,OLOADS = [i for i in range(7)]

def save_state(data,prob):
    with open('snowball.pickle', 'wb') as f:
        pickle.dump((data,prob), f)

def load_state():
    with open('snowball.pickle', 'rb') as f:
        return pickle.load(f)

def reload(data = None):
    if not data or data[BALLS]<data[MAXB]:
        print(0)
        return True
    return False

def throw(data):
    if data[BALLS]>0:
        print(1)
        return True
    return False
def duck(data):
    if data[DUCKS]>0:
        print(2)
        return True
    return False


data = [int(v) for v in sys.argv[1:]]
data.append(0)

if data[TURN] > 0:
    last_data,prob = load_state()
    delta = [l-n for l,n in zip(last_data, data)]
    if delta[OTHROWS]<0:
        delta[OTHROWS]=0
        delta[OLOADS]=1
    prob = [p+d for p,d in zip(prob,delta)]
else:
    prob = [0]*7

expected = sorted(((prob[action],action) for action in [OTHROWS, ODUCKS, OLOADS]),
                      reverse=True)
expect = next( (a for p,a in expected if data[a]>0), OLOADS)

if expect == OTHROWS:
    duck(data) or throw(data) or reload()
elif expect == ODUCKS:
    reload(data) or duck(data) or throw(data) or reload()
else:
    throw(data) or reload(data) or duck(data) or reload()

save_state(data,prob);

No estoy seguro exactamente cómo funciona esto, pero si está almacenando datos entre rondas (en lugar de turnos), desafortunadamente, todos los datos se eliminan entre rondas. No invalida tu solución, pero
tenlo

No espera retener datos entre rondas, solo espera que el oponente actual sea consistente.
AShelly

Bien. Bueno. Solo quería asegurarme de que no hubiera conceptos erróneos. :)
HyperNeutrino

4

Braingolf , el agresor

<<?1:0

¡El agresor no es cobarde! Si tiene una bola de nieve, ¡lanzará! Si no tiene bolas de nieve, ¡hará más!

Braingolf , el loco

Esto no es en realidad un bot, es solo un programador que secuestré y forcé a portar todos los proyectos que ha realizado a braingolf. Ya no tiene ni una pizca de cordura.

<3r!?:1+|%

Genera un número aleatorio menor que 3 y genera t % rdonde t es el turno actual yr es el número aleatorio

Para ejecutarlos, deberá descargar braingolf.pydesde github, luego guardar el código de braingolf en un archivo y ejecutar

python3 braingolf.py -f filename <space separated inputs>

o simplemente inserte el código directamente así

python3 braingolf.py -c '<<?1:0' <space separated inputs>

Las entradas son bastante irrelevantes siempre que el segundo argumento después del código / nombre de archivo sea la cantidad de bolas de nieve que tiene el agresor.

Nota: El agresor en realidad se comporta de manera idéntica a TestBot, solo quería hacer una entrada en braingolf

Braingolf , The Brainy [Roto en este momento]

VR<<<!?v1:v0|R>!?v1:v0|>R<<!?v1:v0|>R<!?v1:v0|<VR<<.m<.m~v<-?~v0:~v1|>vc
VRv.<.>+1-?2_;|>.M<v?:0_;|1

Por supuesto, alguien tenía que hacer esto: D ¡Agradable, e incluso golf! : D
HyperNeutrino

Oh, espera, esto es lo mismo que el mío, excepto gofier. lol
HyperNeutrino

@HyperNeutrino sí, ahora voy a trabajar en uno real en un idioma real. Que haría uso de Braingolf de uno real, pero no puedo hacer las condiciones anidadas, por lo que dificulta las cosas
Skidsdev

2
Creo que deberías publicar "The Brainy" como una respuesta separada. Además, creo que se equivoca.
Erik the Outgolfer

"The Insane" no es un bot estable, por lo que no estoy seguro de cómo @HyperNeutrino lo comprobaría.
Erik the Outgolfer

3

TestBot - Python

Este es un envío de prueba para mostrarle cómo puede ser un envío válido. La estrategia: recarga alternativa y lanzamiento. Es una estrategia bastante mala, pero te da una idea de cómo debería funcionar tu programa.

from os import sys
arguments = sys.argv;
turn = int(arguments[1])
print(turn % 2)

¿Serían _, turn, snowballs, opponent_snowballs, ducks, opponent_ducks, max_snowballs = sys.argvlos argumentos?
Artyer

@Artyer Sí. Resulta que el primer argumento tiene el nombre del archivo.
HyperNeutrino

Puede usarlo sys.argv[1:]si no quiere meterse con eso_
sagiksp

2

UpperHandBot, Python 3

import sys
turn, snowballs, opponent_snowballs, ducks, opponent_ducks, max_snowballs = map(int, sys.argv[1:])

if snowballs <= opponent_snowballs:
  if opponent_snowballs > 0 and ducks > 0:
    print(2)
  else:
    if snowballs < max_snowballs:
      print(0)
    else:
      print(1)
else:
  print(1)

Este bot intenta recolectar más bolas de nieve que su oponente, y en ese punto comienza a lanzar. Si en algún momento UHB no tiene más bolas de nieve que su oponente, hará lo siguiente:

  • Pato, si el oponente tiene bolas de nieve y le quedan patos
  • De lo contrario, vuelva a cargar (a menos que UHB esté al máximo, entonces arroja en su lugar, aunque no creo que esta situación surja)

2

Yggdrasli, Java

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class Yggdrasil implements SnowballFighter {
    public static boolean debug = false;
    static int RELOAD = 0;
    static int THROW = 1;
    static int DUCK = 2;
    static int INVALID = -3;
    static Random rand = new Random();

    public static void main(String[] args) {
        int t = Integer.parseInt(args[0]);
        int s = Integer.parseInt(args[1]);
        int os = Integer.parseInt(args[2]);
        int d = Integer.parseInt(args[3]);
        int od = Integer.parseInt(args[4]);
        int ms = Integer.parseInt(args[5]);
        System.out.println((new Yggdrasil()).move(t, s, os, d, od, ms));
    }

    public final int move(int t, int s, int os, int d, int od, int ms) {
        State state = State.get(s, os, d, od);
        double val = state.val(4);
        double[] strat = state.strat;
        int move = INVALID;
        if (debug) {
            System.out.println(val + " : " + strat[0] + " " + strat[1] + " " + strat[2]);
        }
        while (move == INVALID) {
            double r = rand.nextDouble();
            if (r < strat[RELOAD] && strat[RELOAD] > 0.0001) {
                move = RELOAD;
            } else if (r < strat[RELOAD] + strat[THROW] && strat[THROW] > 0.0001) {
                move = THROW;
            } else if (r < strat[RELOAD] + strat[THROW] + strat[DUCK] && strat[DUCK] > 0.0001) {
                move = DUCK;
            }
        }
        return move;
    }

    public static class State {

        public static boolean debug = false;
        public static int ms = 50;
        public int s;
        public int os;
        public static int md = 25;
        public int d;
        public int od;

        public State(int s, int os, int d, int od) {
            super();
            this.s = s;
            this.os = os;
            this.d = d;
            this.od = od;
        }

        Double val;
        int valdepth;
        double[] strat = new double[3];

        public Double val(int maxdepth) {
            if (s < 0 || s > ms || d < 0 || d > md || os < 0 || os > ms || od < 0 || od > md) {
                return null;
            } else if (val != null && valdepth >= maxdepth) {
                return val;
            }
            if (s > os + od) {
                val = 1.0; // force win
                strat = new double[] { 0, 1, 0 };
            } else if (os > s + d) {
                val = -1.0; // force loss
                strat = new double[] { 1.0 / (1.0 + s + d), s / (1.0 + s + d), d / (1.0 + s + d) };
            } else if (d == 0 && od == 0) {
                val = 0.0; // perfect tie
                if (s > 0) {
                    strat = new double[] { 0, 1, 0 };
                } else {
                    strat = new double[] { 1, 0, 0 };
                }
            } else if (maxdepth <= 0) {
                double togo = 1 - s + os + od;
                double otogo = 1 - os + s + d;
                double per = otogo * otogo / (togo * togo + otogo * otogo);
                double oper = togo * togo / (togo * togo + otogo * otogo);
                val = per - oper;
            } else {
                Double[][] fullmatrix = new Double[3][3];
                boolean[] vm = new boolean[3];
                boolean[] ovm = new boolean[3];
                for (int i = 0; i < 3; i++) {
                    int dest_s = s;
                    int dest_d = d;
                    if (i == 0) {
                        dest_s++;
                    } else if (i == 1) {
                        dest_s--;
                    } else {
                        dest_d--;
                    }
                    for (int j = 0; j < 3; j++) {
                        int dest_os = os;
                        int dest_od = od;
                        if (j == 0) {
                            dest_os++;
                        } else if (j == 1) {
                            dest_os--;
                        } else {
                            dest_od--;
                        }
                        if (i == 0 && j == 1 && dest_os >= 0 && dest_s <= ms) {
                            fullmatrix[i][j] = -1.0; // kill
                        } else if (i == 1 && j == 0 && dest_s >= 0 && dest_os <= ms) {
                            fullmatrix[i][j] = 1.0; // kill
                        } else {
                            fullmatrix[i][j] = get(dest_s, dest_os, dest_d, dest_od).val(maxdepth - 1);
                        }
                        if (fullmatrix[i][j] != null) {
                            vm[i] = true;
                            ovm[j] = true;
                        }
                    }
                }

                if (debug) {
                    System.out.println();
                    System.out.println(maxdepth);
                    System.out.println(s + " " + os + " " + d + " " + od);
                    for (int i = 0; i < 3; i++) {
                        System.out.print(vm[i]);
                    }
                    System.out.println();
                    for (int i = 0; i < 3; i++) {
                        System.out.print(ovm[i]);
                    }
                    System.out.println();
                    for (int i = 0; i < 3; i++) {
                        for (int j = 0; j < 3; j++) {
                            System.out.printf(" %7.4f", fullmatrix[i][j]);
                        }
                        System.out.println();
                    }
                }
                // really stupid way to find an approximate best strategy
                val = -1.0;
                double[] p = new double[3];
                for (p[0] = 0; p[0] < 0.0001 || vm[0] && p[0] <= 1.0001; p[0] += 0.01) {
                    for (p[1] = 0; p[1] < 0.0001 || vm[1] && p[1] <= 1.0001 - p[0]; p[1] += 0.01) {
                        p[2] = 1.0 - p[0] - p[1];
                        if (p[2] < 0.0001 || vm[2]) {
                            double min = 1;
                            for (int j = 0; j < 3; j++) {
                                if (ovm[j]) {
                                    double sum = 0;
                                    for (int i = 0; i < 3; i++) {
                                        if (vm[i]) {
                                            sum += fullmatrix[i][j] * p[i];
                                        }
                                    }
                                    min = Math.min(min, sum);
                                }
                            }
                            if (min > val) {
                                val = min;
                                strat = p.clone();
                            }
                        }
                    }
                }
                if (debug) {
                    System.out.println("v:" + val);
                    System.out.println("s:" + strat[0] + " " + strat[1] + " " + strat[2]);
                }
            }
            valdepth = maxdepth;
            return val;
        }

        static Map<Integer, State> cache = new HashMap<Integer, State>();

        static State get(int s, int os, int d, int od) {
            int key = (((s) * 100 + os) * 100 + d) * 100 + od;
            if (cache.containsKey(key)) {
                return cache.get(key);
            }
            State res = new State(s, os, d, od);
            cache.put(key, res);
            return res;
        }
    }
}

Llamé a este bot "Yggdrasil" porque en realidad mira hacia abajo en el árbol del juego y realiza una valoración del estado, a partir del cual puede calcular una estrategia mixta aproximadamente ideal. Debido a que se basa en estrategias mixtas, es muy no determinista. No sé qué tan bien funcionará esto en una competencia real.

Algunas cosas sobre este bot:

  • El núcleo es una función recursiva que calcula el valor y la estrategia mixta casi ideal para cualquier estado particular del juego. En este momento lo tengo configurado para mirar 4 pasos adelante.
  • Juega extremadamente empalagoso, ya que en muchos casos este bot es equivalente a "elegir un movimiento aleatorio en piedra, papel o tijera". Se mantiene firme y espera que su oponente le dé una ventaja estadística. Si este bot fuera perfecto (que no lo es), lo mejor que podrías hacer contra él sería un 50% de victorias y un 50% de pérdidas. Como resultado, no hay ningún oponente que venza constantemente, pero tampoco ninguno que pierda constantemente.

Todavía no entiendo el nombre ...: P
HyperNeutrino

@HyperNeutrino Yggdrasil es un árbol mitológico, y en este caso me refiero al árbol del juego.
PhiNotPi

Ohhhh cierto, siento que debería haber recordado esto. : P ¡Bien!
HyperNeutrino

2

Dolor en el Nash (C ++)

Llamado así porque el hecho de que tenía que escribir mi propio solucionador de equilibrio de Nash era un verdadero dolor. ¡Estoy sorprendido de que no haya bibliotecas de resolución de Nash fácilmente disponibles!

#include <fstream>
#include <iostream>
#include <vector>
#include <array>
#include <random>
#include <utility>

typedef double NumT;
static const NumT EPSILON = 1e-5;

struct Index {
    int me;
    int them;

    Index(int me, int them) : me(me), them(them) {}
};

struct Value {
    NumT me;
    NumT them;

    Value(void) : me(0), them(0) {}

    Value(NumT me, NumT them) : me(me), them(them) {}
};

template <int subDimMe, int subDimThem>
struct Game {
    const std::array<NumT, 9> *valuesMe;
    const std::array<NumT, 9> *valuesThemT;

    std::array<int, subDimMe> coordsMe;
    std::array<int, subDimThem> coordsThem;

    Game(
        const std::array<NumT, 9> *valuesMe,
        const std::array<NumT, 9> *valuesThemT
    )
        : valuesMe(valuesMe)
        , valuesThemT(valuesThemT)
        , coordsMe{}
        , coordsThem{}
    {}

    Index baseIndex(Index i) const {
        return Index(coordsMe[i.me], coordsThem[i.them]);
    }

    Value at(Index i) const {
        Index i2 = baseIndex(i);
        return Value(
            (*valuesMe)[i2.me * 3 + i2.them],
            (*valuesThemT)[i2.me + i2.them * 3]
        );
    }

    Game<2, 2> subgame22(int me0, int me1, int them0, int them1) const {
        Game<2, 2> b(valuesMe, valuesThemT);
        b.coordsMe[0] = coordsMe[me0];
        b.coordsMe[1] = coordsMe[me1];
        b.coordsThem[0] = coordsThem[them0];
        b.coordsThem[1] = coordsThem[them1];
        return b;
    }
};

struct Strategy {
    std::array<NumT, 3> probMe;
    std::array<NumT, 3> probThem;
    Value expectedValue;
    bool valid;

    Strategy(void)
        : probMe{}
        , probThem{}
        , expectedValue()
        , valid(false)
    {}

    void findBestMe(const Strategy &b) {
        if(b.valid && (!valid || b.expectedValue.me > expectedValue.me)) {
            *this = b;
        }
    }
};

template <int dimMe, int dimThem>
Strategy nash_pure(const Game<dimMe, dimThem> &g) {
    Strategy s;
    int choiceMe = -1;
    int choiceThem = 0;
    for(int me = 0; me < dimMe; ++ me) {
        for(int them = 0; them < dimThem; ++ them) {
            const Value &v = g.at(Index(me, them));
            bool valid = true;
            for(int me2 = 0; me2 < dimMe; ++ me2) {
                if(g.at(Index(me2, them)).me > v.me) {
                    valid = false;
                }
            }
            for(int them2 = 0; them2 < dimThem; ++ them2) {
                if(g.at(Index(me, them2)).them > v.them) {
                    valid = false;
                }
            }
            if(valid) {
                if(choiceMe == -1 || v.me > s.expectedValue.me) {
                    s.expectedValue = v;
                    choiceMe = me;
                    choiceThem = them;
                }
            }
        }
    }
    if(choiceMe != -1) {
        Index iBase = g.baseIndex(Index(choiceMe, choiceThem));
        s.probMe[iBase.me] = 1;
        s.probThem[iBase.them] = 1;
        s.valid = true;
    }
    return s;
}

Strategy nash_mixed(const Game<2, 2> &g) {
    //    P    Q
    // p a A  b B
    // q c C  d D

    Value A = g.at(Index(0, 0));
    Value B = g.at(Index(0, 1));
    Value C = g.at(Index(1, 0));
    Value D = g.at(Index(1, 1));

    // q = 1-p, Q = 1-P
    // Pick p such that choice of P,Q is arbitrary

    // p*A+(1-p)*C = p*B+(1-p)*D
    // p*A+C-p*C = p*B+D-p*D
    // p*(A+D-B-C) = D-C
    // p = (D-C) / (A+D-B-C)

    NumT p = (D.them - C.them) / (A.them + D.them - B.them - C.them);

    // P*a+(1-P)*b = P*c+(1-P)*d
    // P*a+b-P*b = P*c+d-P*d
    // P*(a+d-b-c) = d-b
    // P = (d-b) / (a+d-b-c)

    NumT P = (D.me - B.me) / (A.me + D.me - B.me - C.me);

    Strategy s;
    if(p >= -EPSILON && p <= 1 + EPSILON && P >= -EPSILON && P <= 1 + EPSILON) {
        if(p <= 0) {
            p = 0;
        } else if(p >= 1) {
            p = 1;
        }
        if(P <= 0) {
            P = 0;
        } else if(P >= 1) {
            P = 1;
        }
        Index iBase0 = g.baseIndex(Index(0, 0));
        Index iBase1 = g.baseIndex(Index(1, 1));
        s.probMe[iBase0.me] = p;
        s.probMe[iBase1.me] = 1 - p;
        s.probThem[iBase0.them] = P;
        s.probThem[iBase1.them] = 1 - P;
        s.expectedValue = Value(
            P * A.me + (1 - P) * B.me,
            p * A.them + (1 - p) * C.them
        );
        s.valid = true;
    }
    return s;
}

Strategy nash_mixed(const Game<3, 3> &g) {
    //    P    Q    R
    // p a A  b B  c C
    // q d D  e E  f F
    // r g G  h H  i I

    Value A = g.at(Index(0, 0));
    Value B = g.at(Index(0, 1));
    Value C = g.at(Index(0, 2));
    Value D = g.at(Index(1, 0));
    Value E = g.at(Index(1, 1));
    Value F = g.at(Index(1, 2));
    Value G = g.at(Index(2, 0));
    Value H = g.at(Index(2, 1));
    Value I = g.at(Index(2, 2));

    // r = 1-p-q, R = 1-P-Q
    // Pick p,q such that choice of P,Q,R is arbitrary

    NumT q = ((
        + A.them * (I.them-H.them)
        + G.them * (B.them-C.them)
        - B.them*I.them
        + H.them*C.them
    ) / (
        (G.them+E.them-D.them-H.them) * (B.them+I.them-H.them-C.them) -
        (H.them+F.them-E.them-I.them) * (A.them+H.them-G.them-B.them)
    ));

    NumT p = (
        ((G.them+E.them-D.them-H.them) * q + (H.them-G.them)) /
        (A.them+H.them-G.them-B.them)
    );

    NumT Q = ((
        + A.me * (I.me-F.me)
        + C.me * (D.me-G.me)
        - D.me*I.me
        + F.me*G.me
    ) / (
        (C.me+E.me-B.me-F.me) * (D.me+I.me-F.me-G.me) -
        (F.me+H.me-E.me-I.me) * (A.me+F.me-C.me-D.me)
    ));

    NumT P = (
        ((C.me+E.me-B.me-F.me) * Q + (F.me-C.me)) /
        (A.me+F.me-C.me-D.me)
    );

    Strategy s;
    if(
        p >= -EPSILON && q >= -EPSILON && p + q <= 1 + EPSILON &&
        P >= -EPSILON && Q >= -EPSILON && P + Q <= 1 + EPSILON
    ) {
        if(p <= 0) { p = 0; }
        if(q <= 0) { q = 0; }
        if(P <= 0) { P = 0; }
        if(Q <= 0) { Q = 0; }
        if(p + q >= 1) {
            if(p > q) {
                p = 1 - q;
            } else {
                q = 1 - p;
            }
        }
        if(P + Q >= 1) {
            if(P > Q) {
                P = 1 - Q;
            } else {
                Q = 1 - P;
            }
        }
        Index iBase0 = g.baseIndex(Index(0, 0));
        s.probMe[iBase0.me] = p;
        s.probThem[iBase0.them] = P;
        Index iBase1 = g.baseIndex(Index(1, 1));
        s.probMe[iBase1.me] = q;
        s.probThem[iBase1.them] = Q;
        Index iBase2 = g.baseIndex(Index(2, 2));
        s.probMe[iBase2.me] = 1 - p - q;
        s.probThem[iBase2.them] = 1 - P - Q;
        s.expectedValue = Value(
            A.me * P + B.me * Q + C.me * (1 - P - Q),
            A.them * p + D.them * q + G.them * (1 - p - q)
        );
        s.valid = true;
    }
    return s;
}

template <int dimMe, int dimThem>
Strategy nash_validate(Strategy &&s, const Game<dimMe, dimThem> &g, Index unused) {
    if(!s.valid) {
        return s;
    }

    NumT exp;

    exp = 0;
    for(int them = 0; them < dimThem; ++ them) {
        exp += s.probThem[them] * g.at(Index(unused.me, them)).me;
    }
    if(exp > s.expectedValue.me) {
        s.valid = false;
        return s;
    }

    exp = 0;
    for(int me = 0; me < dimMe; ++ me) {
        exp += s.probMe[me] * g.at(Index(me, unused.them)).them;
    }
    if(exp > s.expectedValue.them) {
        s.valid = false;
        return s;
    }

    return s;
}

Strategy nash(const Game<2, 2> &g, bool verbose) {
    Strategy s = nash_mixed(g);
    s.findBestMe(nash_pure(g));
    if(!s.valid && verbose) {
        std::cerr << "No nash equilibrium found!" << std::endl;
    }
    return s;
}

Strategy nash(const Game<3, 3> &g, bool verbose) {
    Strategy s = nash_mixed(g);
    s.findBestMe(nash_validate(nash_mixed(g.subgame22(1, 2,  1, 2)), g, Index(0, 0)));
    s.findBestMe(nash_validate(nash_mixed(g.subgame22(1, 2,  0, 2)), g, Index(0, 1)));
    s.findBestMe(nash_validate(nash_mixed(g.subgame22(1, 2,  0, 1)), g, Index(0, 2)));
    s.findBestMe(nash_validate(nash_mixed(g.subgame22(0, 2,  1, 2)), g, Index(1, 0)));
    s.findBestMe(nash_validate(nash_mixed(g.subgame22(0, 2,  0, 2)), g, Index(1, 1)));
    s.findBestMe(nash_validate(nash_mixed(g.subgame22(0, 2,  0, 1)), g, Index(1, 2)));
    s.findBestMe(nash_validate(nash_mixed(g.subgame22(0, 1,  1, 2)), g, Index(2, 0)));
    s.findBestMe(nash_validate(nash_mixed(g.subgame22(0, 1,  0, 2)), g, Index(2, 1)));
    s.findBestMe(nash_validate(nash_mixed(g.subgame22(0, 1,  0, 1)), g, Index(2, 2)));
    s.findBestMe(nash_pure(g));
    if(!s.valid && verbose) {
        // theory says this should never happen, but fp precision makes it possible
        std::cerr << "No nash equilibrium found!" << std::endl;
    }
    return s;
}

struct PlayerState {
    int balls;
    int ducks;

    PlayerState(int balls, int ducks) : balls(balls), ducks(ducks) {}

    PlayerState doReload(int maxBalls) const {
        return PlayerState(std::min(balls + 1, maxBalls), ducks);
    }

    PlayerState doThrow(void) const {
        return PlayerState(std::max(balls - 1, 0), ducks);
    }

    PlayerState doDuck(void) const {
        return PlayerState(balls, std::max(ducks - 1, 0));
    }

    std::array<double,3> flail(int maxBalls) const {
        // opponent has obvious win;
        // try stuff at random and hope the opponent is bad

        (void) ducks;

        int options = 0;
        if(balls > 0) {
            ++ options;
        }
        if(balls < maxBalls) {
            ++ options;
        }
        if(ducks > 0) {
            ++ options;
        }

        std::array<double,3> p{};
        if(balls < balls) {
            p[0] = 1.0f / options;
        }
        if(balls > 0) {
            p[1] = 1.0f / options;
        }
        return p;
    }
};

class GameStore {
protected:
    const int balls;
    const int ducks;
    const std::size_t playerStates;
    const std::size_t gameStates;

public:
    static std::string filename(int turn) {
        return "nashdata_" + std::to_string(turn) + ".dat";
    }

    GameStore(int maxBalls, int maxDucks)
        : balls(maxBalls)
        , ducks(maxDucks)
        , playerStates((balls + 1) * (ducks + 1))
        , gameStates(playerStates * playerStates)
    {}

    std::size_t playerIndex(const PlayerState &p) const {
        return p.balls * (ducks + 1) + p.ducks;
    }

    std::size_t gameIndex(const PlayerState &me, const PlayerState &them) const {
        return playerIndex(me) * playerStates + playerIndex(them);
    }

    std::size_t fileIndex(const PlayerState &me, const PlayerState &them) const {
        return 2 + gameIndex(me, them) * 2;
    }

    PlayerState stateFromPlayerIndex(std::size_t i) const {
        return PlayerState(i / (ducks + 1), i % (ducks + 1));
    }

    std::pair<PlayerState, PlayerState> stateFromGameIndex(std::size_t i) const {
        return std::make_pair(
            stateFromPlayerIndex(i / playerStates),
            stateFromPlayerIndex(i % playerStates)
        );
    }

    std::pair<PlayerState, PlayerState> stateFromFileIndex(std::size_t i) const {
        return stateFromGameIndex((i - 2) / 2);
    }
};

class Generator : public GameStore {
    static char toDat(NumT v) {
        int iv = int(v * 256.0);
        return char(std::max(std::min(iv, 255), 0));
    }

    std::vector<Value> next;

public:
    Generator(int maxBalls, int maxDucks)
        : GameStore(maxBalls, maxDucks)
        , next()
    {}

    const Value &nextGame(const PlayerState &me, const PlayerState &them) const {
        return next[gameIndex(me, them)];
    }

    void make_probabilities(
        std::array<NumT, 9> &g,
        const PlayerState &me,
        const PlayerState &them
    ) const {
        const int RELOAD = 0;
        const int THROW = 1;
        const int DUCK = 2;

        g[RELOAD * 3 + RELOAD] =
            nextGame(me.doReload(balls), them.doReload(balls)).me;

        g[RELOAD * 3 + THROW] =
            (them.balls > 0) ? -1
            : nextGame(me.doReload(balls), them.doThrow()).me;

        g[RELOAD * 3 + DUCK] =
            nextGame(me.doReload(balls), them.doDuck()).me;

        g[THROW * 3 + RELOAD] =
            (me.balls > 0) ? 1
            : nextGame(me.doThrow(), them.doReload(balls)).me;

        g[THROW * 3 + THROW] =
            ((me.balls > 0) == (them.balls > 0))
            ? nextGame(me.doThrow(), them.doThrow()).me
            : (me.balls > 0) ? 1 : -1;

        g[THROW * 3 + DUCK] =
            (me.balls > 0 && them.ducks == 0) ? 1
            : nextGame(me.doThrow(), them.doDuck()).me;

        g[DUCK * 3 + RELOAD] =
            nextGame(me.doDuck(), them.doReload(balls)).me;

        g[DUCK * 3 + THROW] =
            (them.balls > 0 && me.ducks == 0) ? -1
            : nextGame(me.doDuck(), them.doThrow()).me;

        g[DUCK * 3 + DUCK] =
            nextGame(me.doDuck(), them.doDuck()).me;
    }

    Game<3, 3> make_game(const PlayerState &me, const PlayerState &them) const {
        static std::array<NumT, 9> globalValuesMe;
        static std::array<NumT, 9> globalValuesThemT;
        #pragma omp threadprivate(globalValuesMe)
        #pragma omp threadprivate(globalValuesThemT)

        make_probabilities(globalValuesMe, me, them);
        make_probabilities(globalValuesThemT, them, me);
        Game<3, 3> g(&globalValuesMe, &globalValuesThemT);
        for(int i = 0; i < 3; ++ i) {
            g.coordsMe[i] = i;
            g.coordsThem[i] = i;
        }
        return g;
    }

    Strategy solve(const PlayerState &me, const PlayerState &them, bool verbose) const {
        if(me.balls > them.balls + them.ducks) { // obvious answer
            Strategy s;
            s.probMe[1] = 1;
            s.probThem = them.flail(balls);
            s.expectedValue = Value(1, -1);
            return s;
        } else if(them.balls > me.balls + me.ducks) { // uh-oh
            Strategy s;
            s.probThem[1] = 1;
            s.probMe = me.flail(balls);
            s.expectedValue = Value(-1, 1);
            return s;
        } else if(me.balls == 0 && them.balls == 0) { // obvious answer
            Strategy s;
            s.probMe[0] = 1;
            s.probThem[0] = 1;
            s.expectedValue = nextGame(me.doReload(balls), them.doReload(balls));
            return s;
        } else {
            return nash(make_game(me, them), verbose);
        }
    }

    void generate(int turns, bool saveAll, bool verbose) {
        next.clear();
        next.resize(gameStates);
        std::vector<Value> current(gameStates);
        std::vector<char> data(2 + gameStates * 2);

        for(std::size_t turn = turns; (turn --) > 0;) {
            if(verbose) {
                std::cerr << "Generating for turn " << turn << "..." << std::endl;
            }
            NumT maxDiff = 0;
            NumT msd = 0;
            data[0] = balls;
            data[1] = ducks;
            #pragma omp parallel for reduction(+:msd), reduction(max:maxDiff)
            for(std::size_t meBalls = 0; meBalls < balls + 1; ++ meBalls) {
                for(std::size_t meDucks = 0; meDucks < ducks + 1; ++ meDucks) {
                    const PlayerState me(meBalls, meDucks);
                    for(std::size_t themBalls = 0; themBalls < balls + 1; ++ themBalls) {
                        for(std::size_t themDucks = 0; themDucks < ducks + 1; ++ themDucks) {
                            const PlayerState them(themBalls, themDucks);
                            const std::size_t p1 = gameIndex(me, them);

                            Strategy s = solve(me, them, verbose);

                            NumT diff;

                            data[2+p1*2  ] = toDat(s.probMe[0]);
                            data[2+p1*2+1] = toDat(s.probMe[0] + s.probMe[1]);
                            current[p1] = s.expectedValue;
                            diff = current[p1].me - next[p1].me;
                            msd += diff * diff;
                            maxDiff = std::max(maxDiff, std::abs(diff));
                        }
                    }
                }
            }

            if(saveAll) {
                std::ofstream fs(filename(turn).c_str(), std::ios_base::binary);
                fs.write(&data[0], data.size());
                fs.close();
            }

            if(verbose) {
                std::cerr
                    << "Expectations changed by at most " << maxDiff
                    << " (RMSD: " << std::sqrt(msd / gameStates) << ")" << std::endl;
            }
            if(maxDiff < 0.0001f) {
                if(verbose) {
                    std::cerr << "Expectations have converged. Stopping." << std::endl;
                }
                break;
            }
            std::swap(next, current);
        }

        // Always save turn 0 with the final converged expectations
        std::ofstream fs(filename(0).c_str(), std::ios_base::binary);
        fs.write(&data[0], data.size());
        fs.close();
    }
};

void open_file(std::ifstream &target, int turn, int maxDucks, int maxBalls) {
    target.open(GameStore::filename(turn).c_str(), std::ios::binary);
    if(target.is_open()) {
        return;
    }

    target.open(GameStore::filename(0).c_str(), std::ios::binary);
    if(target.is_open()) {
        return;
    }

    Generator(maxBalls, maxDucks).generate(200, false, false);
    target.open(GameStore::filename(0).c_str(), std::ios::binary);
}

int choose(int turn, const PlayerState &me, const PlayerState &them, int maxBalls) {
    std::ifstream fs;
    open_file(fs, turn, std::max(me.ducks, them.ducks), maxBalls);

    unsigned char balls = fs.get();
    unsigned char ducks = fs.get();
    fs.seekg(GameStore(balls, ducks).fileIndex(me, them));
    unsigned char p0 = fs.get();
    unsigned char p1 = fs.get();
    fs.close();

    // only 1 random number per execution; no need to seed a PRNG
    std::random_device rand;
    int v = std::uniform_int_distribution<int>(0, 254)(rand);
    if(v < p0) {
        return 0;
    } else if(v < p1) {
        return 1;
    } else {
        return 2;
    }
}

int main(int argc, const char *const *argv) {
    if(argc == 4) { // maxTurns, maxBalls, maxDucks
        Generator(atoi(argv[2]), atoi(argv[3])).generate(atoi(argv[1]), true, true);
        return 0;
    }

    if(argc == 7) { // turn, meBalls, themBalls, meDucks, themDucks, maxBalls
        std::cout << choose(
            atoi(argv[1]),
            PlayerState(atoi(argv[2]), atoi(argv[4])),
            PlayerState(atoi(argv[3]), atoi(argv[5])),
            atoi(argv[6])
        ) << std::endl;
        return 0;
    }

    return 1;
}

Compilar como C ++ 11 o mejor. Para el rendimiento, es bueno compilar con soporte OpenMP (pero esto es solo por velocidad; no es obligatorio)

g++ -std=c++11 -fopenmp pain_in_the_nash.cpp -o pain_in_the_nash

Esto usa equilibrios de Nash para decidir qué hacer en cada turno, lo que significa que en teoría siempre ganará o empatará a largo plazo (en muchos juegos), sin importar qué estrategia use el oponente. Si ese es el caso en la práctica depende de si cometí algún error en la implementación. Sin embargo, dado que esta competencia de KoTH solo tiene una ronda contra cada oponente, probablemente no le vaya muy bien en la clasificación.

Mi idea original era tener una función de valoración simple para cada estado del juego (por ejemplo, cada bola vale + b, cada pato es + d), pero esto lleva a problemas obvios al determinar cuáles deberían ser esas valoraciones, y significa que no puede actuar en rendimientos decrecientes de reunir más y más bolas, etc. Por lo tanto, esto analizará todo el árbol del juego , trabajando hacia atrás desde el turno 1000, y completará las valoraciones reales en función de cómo podría funcionar cada juego.

El resultado es que no tengo absolutamente ninguna idea de qué estrategia usa esto, excepto por un par de comportamientos "obvios" codificados (lanzar bolas de nieve si tiene más bolas de las que su oponente tiene bolas + patos, y recargar si ambos están fuera de bolas de nieve). Si alguien quiere analizar el conjunto de datos que produce, ¡imagino que hay un comportamiento interesante por descubrir!

Probar esto contra "Save One" muestra que sí gana a largo plazo, pero solo por un pequeño margen (514 victorias, 486 derrotas, 0 empates en el primer lote de 1000 juegos y 509 victorias, 491 derrotas, 0 dibuja en el segundo).


¡Importante!

Esto funcionará de forma inmediata, pero esa no es una gran idea. Se tarda unos 9 minutos en mi computadora portátil con especificaciones de desarrollador moderado para generar el árbol completo del juego. Pero guardará las probabilidades finales en un archivo una vez que se generan, y después de eso cada turno solo genera un número aleatorio y lo compara con 2 bytes, por lo que es súper rápido.

Para atajar todo eso, simplemente descargue este archivo (3.5MB) y póngalo en el directorio con el ejecutable.

O puede generarlo usted mismo ejecutando:

./pain_in_the_nash 1000 50 25

Lo que ahorrará un archivo por turno, hasta la convergencia. Tenga en cuenta que cada archivo tiene 3.5MB y convergerá en el turno 720 (es decir, 280 archivos, ~ 1GB), y dado que la mayoría de los juegos no se acercan al turno 720, los archivos de preconvergencia tienen muy poca importancia.


¿Es posible hacer que el programa solo muestre el resultado final? ¡Gracias!
HyperNeutrino

@HyperNeutrino todos los demás resultados deben ser stderr, por lo que no deberían tener ningún impacto, pero lo he actualizado para mostrar solo el progreso cuando se ejecuta en modo de preprocesamiento. Ahora solo escribirá en stdout cuando se ejecute normalmente. Sin embargo, sugiero seguir la sugerencia "importante", ya que de lo contrario simplemente se quedará en el primer turno durante varios minutos (al menos con el preprocesamiento puede ver el progreso).
Dave

Ah, vale. Seguiré esa sugerencia, gracias!
HyperNeutrino

Le agradecería si pudiera cargar los archivos de datos porque se tarda una eternidad en generarlos todos. Si pudieras hacer eso sería genial :)
HyperNeutrino

@HyperNeutrino OK, también tardó una eternidad en cargar en mi terrible Internet, pero el archivo convergente de 3.5MB está disponible aquí: github.com/davidje13/snowball_koth_pitn/blob/master/… (solo colóquelo en el mismo directorio).
Dave

1

Swift - TheCrazy_XcodeRandomness

Lamentablemente, esto solo se puede ejecutar localmente, en Xcode, porque contiene el Foundationmódulo y su función arc4random_uniform(),. Sin embargo, puedes decir cuál es el algoritmo:

import Foundation

func game(turn: Int, snowballs: Int, opponent_snowballs: Int, ducks: Int, opponent_ducks: Int, max_snowballs: Int) -> Int{
    let RELOAD = 0
    let THROW = 1
    let DUCK = 2
    if turn == 0{
        return arc4random_uniform(2)==0 ? THROW : DUCK
    }
    else if ducks == 0{
        if snowballs != 0{return THROW}
        else {return RELOAD}
    }
    else if snowballs < max_snowballs && snowballs != 0{
        if opponent_ducks == 0 && opponent_snowballs == 0{return THROW}
        else if opponent_snowballs == 0{
            return arc4random_uniform(2)==0 ? THROW : RELOAD
        }
        else if opponent_ducks == 0{return THROW}
        else { return arc4random_uniform(2)==0 ? THROW : RELOAD }
    }
    else if opponent_snowballs == max_snowballs{
        return DUCK
    }
    else if snowballs == max_snowballs || opponent_ducks < 1 || turn < max_snowballs{return THROW}
    return arc4random_uniform(2)==0 ? THROW : RELOAD
}

¿Se puede ejecutar esto desde bash en Linux?
HyperNeutrino

@HyperNeutrino Sé que puede en macOS, pero no sé si lo hace en Linux. Si puedes comprobar eso, sería genial. Pruebe el swiftcomando y luego compruebe si funciona
Sr. Xcoder

Parece no existir; hay un paquete con él pero no es Swift the language. Así que no puedo probar esto hasta que pueda hacer que algo funcione, lo siento.
HyperNeutrino

los únicos cumplidores posibles son Xcode e IntelliJ, pero no se puede ejecutar en línea debido a Foundation, lo siento: /
Sr. Xcoder

q.e.p.d. Necesitaría poder ejecutarlo desde la línea de comandos para ejecutar el controlador con él, pero si tengo tiempo, podría ejecutarlo manualmente nuevamente todos los otros bots.
HyperNeutrino

1

TableBot, Python 2

Llamado TableBot porque fue creado al implementar esta tabla:

snow   duck   osnow   oduck   move
0      0      0       0       0
0      0      0       1       0
0      0      1       0       0
0      0      1       1       0
0      1      0       0       0
0      1      0       1       0
0      1      1       0       2
0      1      1       1       2
1      0      0       0       1
1      0      0       1       1
1      0      1       0       1
1      0      1       1       1
1      1      0       0       1
1      1      0       1       1
1      1      1       0       1
1      1      1       1       1

Un 1 representa tener 1 o más, un 0 representa no tener ninguno.

El bot:

import sys

reload=0
throw=1
duck=2

t,snowballs,o_snowballs,ducks,o_ducks,m=map(int,sys.argv[1:])

if snowballs > 0:
	print throw
elif ducks==0:
	print reload
elif o_snowballs==0:
	print reload
else:
	print duck

Pruébalo en línea!


1

AmbBot - Esquema de raquetas

Principalmente quería probar usando amb, porque es genial. Este bot ordena aleatoriamente las opciones (recargar, lanzar y agacharse), filtra las que no tienen sentido y elige la primera opción. ¡Pero con amb, podemos usar continuaciones y retroceder!

#lang racket
(require racket/cmdline)

; Defining amb.
(define failures null)

(define (fail)
  (if (pair? failures) ((first failures)) (error "no more choices!")))

(define (amb/thunks choices)
  (let/cc k (set! failures (cons k failures)))
  (if (pair? choices)
    (let ([choice (first choices)]) (set! choices (rest choices)) (choice))
    (begin (set! failures (rest failures)) (fail))))

(define-syntax-rule (amb E ...) (amb/thunks (list (lambda () E) ...)))

(define (assert condition) (unless condition (fail)))

(define (!= a b)
  (not (= a b)))

(define (amb-list list)
  (if (null? list)
      (amb)
      (amb (car list)
           (amb-list (cdr list)))))

; The meaningful code!
; Start by defining our options.
(define reload 0)
(define throw 1)
(define duck 2)

; The heart of the program.
(define (make-choice turn snowballs opponent_snowballs ducks opponent_ducks max_snowballs)
  (let ((can-reload? (reload-valid? snowballs opponent_snowballs ducks opponent_ducks max_snowballs))
        (can-throw? (throw-valid? snowballs opponent_snowballs ducks opponent_ducks max_snowballs))
        (can-duck? (duck-valid? snowballs opponent_snowballs ducks opponent_ducks max_snowballs)))
    (if (not (or can-reload? can-throw? can-duck?))
        (random 3) ; something went wrong, panic
        (let* ((ls (shuffle (list reload throw duck)))
               (action (amb-list ls)))
          (assert (or (!= action reload) can-reload?))
          (assert (or (!= action throw) can-throw?))
          (assert (or (!= action duck) can-duck?))
          action))))

; Define what makes a move possible.
(define (reload-valid? snowballs opponent_snowballs ducks opponent_ducks max_snowballs)
  (not (or
        (= snowballs max_snowballs) ; Don't reload if we're full.
        (and (= opponent_ducks 0) (= opponent_snowballs max_snowballs)) ; Don't reload if opponent will throw.
        )))

(define (throw-valid? snowballs opponent_snowballs ducks opponent_ducks max_snowballs)
  (not (or
        (= snowballs 0) ; Don't throw if we don't have any snowballs.
        (= opponent_snowballs max_snowballs) ; Don't throw if our opponent won't be reloading.
        )))

(define (duck-valid? snowballs opponent_snowballs ducks opponent_ducks max_snowballs)
  (not (or
        (= ducks 0) ; Don't duck if we can't.
        (= opponent_snowballs 0) ; Don't duck if our opponent can't throw.
        )))

; Parse the command line, make a choice, print it out.
(command-line
 #:args (turn snowballs opponent_snowballs ducks opponent_ducks max_snowballs)
 (writeln (make-choice
           (string->number turn)
           (string->number snowballs)
           (string->number opponent_snowballs)
           (string->number ducks)
           (string->number opponent_ducks)
           (string->number max_snowballs))))

También hice un pequeño programa de prueba para ejecutar dos de estos bots uno contra el otro. Parece que el segundo bot gana con más frecuencia, por lo que es posible que haya cometido un error en alguna parte.

(define (run)
  (run-helper 0 0 0 5 5 5))                         

(define (run-helper turn snowballs opponent_snowballs ducks opponent_ducks max_snowballs)
  (printf "~a ~a ~a ~a ~a ~a ~n" turn snowballs opponent_snowballs ducks opponent_ducks max_snowballs)
  (let ((my-action (make-choice turn snowballs opponent_snowballs ducks opponent_ducks max_snowballs))
        (opponent-action (make-choice turn opponent_snowballs snowballs opponent_ducks ducks max_snowballs)))
    (cond ((= my-action reload)
           (cond ((= opponent-action reload)
                  (run-helper (+ turn 1) (+ snowballs 1) (+ opponent_snowballs 1) ducks opponent_ducks max_snowballs))
                 ((= opponent-action throw)
                  (writeln "Opponent wins!"))
                 ((= opponent-action duck)
                  (run-helper (+ turn 1) (+ snowballs 1) opponent_snowballs ducks (- opponent_ducks 1) max_snowballs))))
          ((= my-action throw)
           (cond ((= opponent-action reload)
                  (writeln "I win!"))
                 ((= opponent-action throw)
                  (run-helper (+ turn 1) (- snowballs 1) (- opponent_snowballs 1) ducks opponent_ducks max_snowballs))
                 ((= opponent-action duck)
                  (run-helper (+ turn 1) (- snowballs 1) opponent_snowballs ducks (- opponent_ducks 1) max_snowballs))))
          ((= my-action duck)
           (cond ((= opponent-action reload)
                  (run-helper (+ turn 1) snowballs (+ opponent_snowballs 1) (- ducks 1) opponent_ducks max_snowballs))
                 ((= opponent-action throw)
                  (run-helper (+ turn 1) snowballs (- opponent_snowballs 1) (- ducks 1) opponent_ducks max_snowballs))
                 ((= opponent-action duck)
                  (run-helper (+ turn 1) snowballs opponent_snowballs (- ducks 1) (- opponent_ducks 1) max_snowballs)))))))

1

MonteBot, C ++

Básicamente tomé el código de este koth y lo modifiqué para este desafío. Utiliza el algoritmo desacoplado UCT Monte Carlo Tree Search. Debería estar bastante cerca del equilibrio nash.

#include <cstdlib>
#include <cmath>
#include <random>
#include <cassert>
#include <iostream>


static const int TOTAL_ACTIONS = 3;
static const int RELOAD = 0;
static const int THROW = 1;
static const int DUCK = 2;

//The number of simulated games we run every time our program is called.
static const int MONTE_ROUNDS = 10000;

struct Game
{
    int turn;
    int snowballs;
    int opponentSnowballs;
    int ducks;
    int opponentDucks;
    int maxSnowballs;
    bool alive;
    bool opponentAlive;

    Game(int turn, int snowballs, int opponentSnowballs, int ducks, int opponentDucks, int maxSnowballs)
        : turn(turn),
          snowballs(snowballs),
          opponentSnowballs(opponentSnowballs),
          ducks(ducks),
          opponentDucks(opponentDucks),
          maxSnowballs(maxSnowballs),
          alive(true),
          opponentAlive(true)
    {
    }

    Game(int turn, int snowballs, int opponentSnowballs, int ducks, int opponentDucks, int maxSnowballs, bool alive, bool opponentAlive)
        : turn(turn),
        snowballs(snowballs),
        opponentSnowballs(opponentSnowballs),
        ducks(ducks),
        opponentDucks(opponentDucks),
        maxSnowballs(maxSnowballs),
        alive(alive),
        opponentAlive(opponentAlive)
    {
    }

    bool atEnd() const
    {
        return !(alive && opponentAlive) || turn >= 1000;
    }

    bool isValidMove(int i, bool me)
    {
        if (atEnd())
        {
            return false;
        }

        switch (i)
        {
        case RELOAD:
            return (me ? snowballs : opponentSnowballs) < maxSnowballs;
        case THROW:
            return (me ? snowballs : opponentSnowballs) > 0;
        case DUCK:
            return (me ? ducks : opponentDucks) > 0 && (me ? opponentSnowballs : snowballs) > 0;
        default:
            throw "This should never be executed.";
        }

    }

    Game doTurn(int my_action, int enemy_action)
    {
        assert(isValidMove(my_action, true));
        assert(isValidMove(enemy_action, false));

        Game result(*this);

        result.turn++;

        switch (my_action)
        {
        case RELOAD:
            result.snowballs++;
            break;
        case THROW:
            result.snowballs--;
            if (enemy_action == RELOAD)
            {
                result.opponentAlive = false;
            }
            break;
        case DUCK:
            result.ducks--;
            break;
        default:
            throw "This should never be executed.";
        }

        switch (enemy_action)
        {
        case RELOAD:
            result.opponentSnowballs++;
            break;
        case THROW:
            result.opponentSnowballs--;
            if (my_action == RELOAD)
            {
                result.alive = false;
            }
            break;
        case DUCK:
            result.opponentDucks--;
            break;
        default:
            throw "This should never be executed.";
        }

        return result;
    }
};

struct Stat
{
    int wins;
    int attempts;

    Stat() : wins(0), attempts(0) {}
};

/**
* A Monte tree data structure.
*/
struct MonteTree
{
    //The state of the game.
    Game game;

    //myStats[i] returns the statistic for doing the i action in this state.
    Stat myStats[TOTAL_ACTIONS];
    //opponentStats[i] returns the statistic for the opponent doing the i action in this state.
    Stat opponentStats[TOTAL_ACTIONS];
    //Total number of times we've created statistics from this tree.
    int totalPlays = 0;

    //The action that led to this tree.
    int myAction;
    //The opponent action that led to this tree.
    int opponentAction;

    //The tree preceding this one.
    MonteTree *parent = nullptr;

    //subtrees[i][j] is the tree that would follow if I did action i and the
    //opponent did action j.
    MonteTree *subtrees[TOTAL_ACTIONS][TOTAL_ACTIONS] = { { nullptr } };

    MonteTree(const Game &game) :
        game(game), myAction(-1), opponentAction(-1) {}


    MonteTree(Game game, MonteTree *parent, int myAction, int opponentAction) :
        game(game), myAction(myAction), opponentAction(opponentAction), parent(parent)
    {
        //Make sure the parent tree keeps track of this tree.
        parent->subtrees[myAction][opponentAction] = this;
    }

    //The destructor so we can avoid slow ptr types and memory leaks.
    ~MonteTree()
    {
        //Delete all subtrees.
        for (int i = 0; i < TOTAL_ACTIONS; i++)
        {
            for (int j = 0; j < TOTAL_ACTIONS; j++)
            {
                auto branch = subtrees[i][j];

                if (branch)
                {
                    branch->parent = nullptr;
                    delete branch;
                }
            }
        }
    }

    double scoreMove(int move, bool me)
    {

        const Stat &stat = me ? myStats[move] : opponentStats[move];
        return stat.attempts == 0 ?
            HUGE_VAL :
            double(stat.wins) / stat.attempts + sqrt(2 * log(totalPlays) / stat.attempts);
    }


    MonteTree * expand(int myAction, int enemyAction)
    {
        return new MonteTree(
            game.doTurn(myAction, enemyAction),
            this,
            myAction,
            enemyAction);
    }

    int bestMove() const
    {
        //Select the move with the highest win rate.
        int best;
        double bestScore = -1;
        for (int i = 0; i < TOTAL_ACTIONS; i++)
        {
            if (myStats[i].attempts == 0)
            {
                continue;
            }

            double score = double(myStats[i].wins) / myStats[i].attempts;
            if (score > bestScore)
            {
                bestScore = score;
                best = i;
            }
        }

        return best;
    }
};

int random(int min, int max)
{
    static std::random_device rd;
    static std::mt19937 rng(rd());

    std::uniform_int_distribution<int> uni(min, max - 1);

    return uni(rng);
}

/**
* Trickle down root until we have to create a new leaf MonteTree or we hit the end of a game.
*/
MonteTree * selection(MonteTree *root)
{
    while (!root->game.atEnd())
    {
        //First pick the move that my bot will do.

        //The action my bot will do.
        int myAction;
        //The number of actions with the same bestScore.
        int same = 0;
        //The bestScore
        double bestScore = -1;

        for (int i = 0; i < TOTAL_ACTIONS; i++)
        {
            //Ignore invalid or idiot moves.
            if (!root->game.isValidMove(i, true))
            {
                continue;
            }

            //Get the score for doing move i. Uses
            double score = root->scoreMove(i, true);

            //Randomly select one score if multiple actions have the same score.
            //Why this works is boring to explain.
            if (score == bestScore)
            {
                same++;
                if (random(0, same) == 0)
                {
                    myAction = i;
                }
            }
            //Yay! We found a better action.
            else if (score > bestScore)
            {
                same = 1;
                myAction = i;
                bestScore = score;
            }
        }

        //The action the enemy will do.
        int enemyAction;

        //Use the same algorithm to pick the enemies move we use for ourselves.
        same = 0;
        bestScore = -1;
        for (int i = 0; i < TOTAL_ACTIONS; i++)
        {
            if (!root->game.isValidMove(i, false))
            {
                continue;
            }

            double score = root->scoreMove(i, false);
            if (score == bestScore)
            {
                same++;
                if (random(0, same) == 0)
                {
                    enemyAction = i;
                }
            }
            else if (score > bestScore)
            {
                same = 1;
                enemyAction = i;
                bestScore = score;
            }
        }

        //If this combination of actions hasn't been explored yet, create a new subtree to explore.
        if (!(*root).subtrees[myAction][enemyAction])
        {
            return root->expand(myAction, enemyAction);
        }

        //Do these actions and explore the next subtree.
        root = (*root).subtrees[myAction][enemyAction];
    }
    return root;
}

/**
* Chooses a random move for me and my opponent and does it.
*/
Game doRandomTurn(Game &game)
{
    //Select my random move.
    int myAction;
    int validMoves = 0;

    for (int i = 0; i < TOTAL_ACTIONS; i++)
    {
        //Don't do idiotic moves.
        //Select one at random.
        if (game.isValidMove(i, true))
        {
            validMoves++;
            if (random(0, validMoves) == 0)
            {
                myAction = i;
            }
        }
    }

    //Choose random opponent action.
    int opponentAction;

    //Whether the enemy has encountered this situation before
    bool enemyEncountered = false;

    validMoves = 0;

    //Weird algorithm that works and I don't want to explain.
    //What it does:
    //If the enemy has encountered this position before,
    //then it chooses a random action weighted by how often it did that action.
    //If they haven't, makes the enemy choose a random not idiot move.
    for (int i = 0; i < TOTAL_ACTIONS; i++)
    {
        if (game.isValidMove(i, false))
        {
            validMoves++;
            if (random(0, validMoves) == 0)
            {
                opponentAction = i;
            }
        }
    }

    return game.doTurn(myAction, opponentAction);
}


/**
* Randomly simulates the given game.
* Has me do random moves that are not stupid.
* Has opponent do random moves.
*
* Returns 1 for win. 0 for loss. -1 for draw.
*/
int simulate(Game game)
{
    while (!game.atEnd())
    {
        game = doRandomTurn(game);
    }

    if (game.alive > game.opponentAlive)
    {
        return 1;
    }
    else if (game.opponentAlive > game.alive)
    {
        return 0;
    }
    else //Draw
    {
        return -1;
    }
}


/**
* Propagates the score up the MonteTree from the leaf.
*/
void update(MonteTree *leaf, int score)
{
    while (true)
    {
        MonteTree *parent = leaf->parent;
        if (parent)
        {
            //-1 = draw, 1 = win for me, 0 = win for opponent
            if (score != -1)
            {
                parent->myStats[leaf->myAction].wins += score;
                parent->opponentStats[leaf->opponentAction].wins += 1 - score;
            }
            parent->myStats[leaf->myAction].attempts++;
            parent->opponentStats[leaf->opponentAction].attempts++;
            parent->totalPlays++;
            leaf = parent;
        }
        else
        {
            break;
        }
    }
}

int main(int argc, char* argv[])
{
    Game game(atoi(argv[1]), atoi(argv[2]), atoi(argv[3]), atoi(argv[4]), atoi(argv[5]), atoi(argv[6]));

    MonteTree current(game);

    for (int i = 0; i < MONTE_ROUNDS; i++)
    {
        //Go down the tree until we find a leaf we haven't visites yet.
        MonteTree *leaf = selection(&current);

        //Randomly simulate the game at the leaf and get the result.
        int score = simulate(leaf->game);

        //Propagate the scores back up the root.
        update(leaf, score);
    }

    int move = current.bestMove();

    std::cout << move << std::endl;

    return 0;
}

Instrucciones de compilación para Linux:

Guardar en MonteBot.cpp.
Ejecutar g++ -o -std=c++11 MonteBot MonteBot.cpp.

Comando para ejecutar: ./MonteBot <args>


1

El procrastinador - Python 3

El procrastinador postergará jugando salvando los primeros turnos. De repente, el monstruo de pánico quiere evitar perder la guerra de recursos al contrarrestar el movimiento más utilizado por los oponentes.

import sys

turn, snowballs, opponent_snowballs, ducks, opponent_ducks, max_snowballs = map(int, sys.argv[1:])

max_ducks = 25
times_opponent_ducked = max_ducks - ducks 
times_opponent_thrown = (turn - times_opponent_ducked - opponent_snowballs) / 2
times_opponent_reloaded = times_opponent_thrown + opponent_snowballs


## return a different action, if the disiered one is not possible
def throw():
    if snowballs:
        return 1
    else:
        return duck()

def duck():
    if ducks:
        return 2
    else:
        return reload()

def reload():
    return 0





def instant_gratification_monkey():
    ## throw, if you still have a ball left afterwards
    if snowballs >= 2 or opponent_ducks == 0:
        return throw()
    ## duck, if opponent can throw
    elif opponent_snowballs > 0:
        return duck()
    ## reload, if opponent has no balls and you have only one
    else:
        return reload()

def panic_monster():
    ## throw while possible, else reload
    if times_opponent_reloaded > times_opponent_ducked: 
        if snowballs > 0:
            return throw() 
        else:
            return reload()
    ## alternating reload and duck
    else: 
        if turn % 2 == 1:
            return reload() 
        else:
            return duck()

def procrastinator():     
    if turn < 13 or (snowballs + ducks > opponent_snowballs + opponent_ducks):
        return instant_gratification_monkey()
    else:
        return panic_monster()


print(procrastinator())

"El procrastinador". Entonces, ¿todos en PPCG que realmente deben hacer la tarea? (No lo nieguen, las personas que leen esto. Y yo)
HyperNeutrino

1
"Mono de gratificación instantánea" ¿También has visto ese TEDTalk? :)
HyperNeutrino


0

ParanoidBot y PanicBot - ActionScript3 ( RedTamarin )

Desde un lenguaje de nicho poco adecuado (con extensiones para proporcionar argumentos de línea de comandos), saltea ParanoidBot asustadizo y su aliado aburrido, PanicBot.

ParanoicoBot

ParanoidBot está perdiendo la cabeza y tiene una estrategia innecesariamente específica de la que depender. Primero, lanza bolas de nieve hasta que se alcanza un umbral, manteniendo algunas en reserva. Luego, después de tres patos de advertencia, se produce la paranoia y el robot intenta acumular más bolas de nieve entre patos al azar. Después de reponer su suministro, ParanoidBot vuelve a lanzar a ciegas. Debido a las voces en su cabeza, ParanoidBot puede decir si está garantizado para ganar o perder, y "creará una estrategia" en consecuencia.

import shell.Program;
import shell;

var TURN:int = Program.argv[0];
var SB:int = Program.argv[1];
var OPSB:int = Program.argv[2];
var DC:int = Program.argv[3];
var OPDC:int = Program.argv[4];
var MAXSB:int = Program.argv[5];
var usedDucks:int = 0;

if (!FileSystem.exists("data"))
    FileSystem.write("data", 0);
else
    usedDucks = FileSystem.read("data");

if (SB > OPSB + OPDC)
{ trace(1); Program.abort(); }
if (SB + DC < OPSB) {
if (DC > 0)
    trace(2);
else if (SB > 0)
    trace(1);
else
    trace(0);
Program.abort(); }

if (usedDucks >= 3) {
    if (SB > MAXSB / 3) {
        usedDucks = 0;
        FileSystem.write("data", usedDucks);
        trace(1);
        Program.abort();
    }
    else {
        if (Number.random() > 0.5 && DC > 0)
            trace(2);
        else
            trace(0);
    }
}
else {
    if (SB > (MAXSB / 6) && SB >= 3)
    { trace(1); Program.abort(); }
    else {
        usedDucks++;
        FileSystem.write("data", usedDucks);
        if (DC > 0)
            trace(2);
        else if (SB > 0)
            trace(1);
        else
            trace(0);
        Program.abort();
    }
}

Los frenos son un poco torpes para ayudar a condensar el tamaño

PanicBot

Habiéndose vuelto loco, PanicBot reacciona por miedo instintivo. Después de quedarse sin patos por el miedo, PanicBot arroja ciegamente todas sus bolas de nieve, luego hace y lanza desesperadamente más bolas de nieve hasta que (probablemente) sea derrotado.

import shell.Program;

var SB:int = Program.argv[1];
var DC:int = Program.argv[3];

if (DC > 0)
{ trace(2); Program.abort(); }
if (SB > 0)
{ trace(1); Program.abort(); }
else
{ trace(0); Program.abort(); }



Esta es una de las menos de 15 entradas que usan AS3 aquí en PPCG. Algún día, quizás este lenguaje posiblemente exótico encuentre un rompecabezas para dominar.


¿Se puede ejecutar esto desde bash en Linux?
HyperNeutrino

No lo he probado, pero sí, debería. El ejecutable RedTamarin (redshell) está creado para Windows, Mac y Linux: http://redtamarin.com/tools/redshell . Si uno de los bots anteriores se guarda en un archivo llamado snow.as, lo siguiente debería funcionar en bash:$ ./redshell snow.as -- 0 50 50 25 25

Me da un error de permiso denegado cuando intento ejecutar esto.
HyperNeutrino

@HyperNeutrino chmod +x redshelles tu amigo aquí ...
Erik the Outgolfer

Tal vez chmod 777 todo? También puede haber alguna solución de problemas en el sitio web de

0

Defensor, Python

Se recarga cuando ninguno de los jugadores tiene bolas de nieve. Si tiene bolas de nieve, arroja. Si no tiene bolas de nieve, pero el oponente las tiene, se agacha si puede, de lo contrario se recarga.

def get_move(turn, snowballs, opponent_snowballs, ducks, opponent_ducks, max_snowballs):
    if snowballs == opponent_snowballs == 0:
        return 0 #Reload
    elif snowballs > 0:
        return 1 # Throw
    elif ducks > 0:
        return 2 # Duck
    else:
        return 0 # Reload

if __name__ == "__main__": # if this is the main program
    import sys
    print(main(*[int(arg) for arg in sys.argv[1:]]))

Nota: aún no probado

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.