Snowball Fight KoTH!


35

Risultati (22 maggio 2017 21:40:37 UTC)

Masterha vinto 18 round, ha perso 2 round e ha segnato 0 round ha
Save Onevinto 15 round, ha perso 3 round e ha pareggiato 2 round ha
Machine Gunvinto 14 round, ha perso 3 round e ha segnato 3 round ha
Monte Botvinto 14 round, ha perso 3 round e ha segnato 3 round ha
Amb Botvinto 12 round round, persi 8 round, e pareggiati 0 round ha
Cowardvinto 11 round, ha perso 3 round e ha segnato 6 round ha
Pain in the Nashvinto 11 round, ha perso 9 round e ha segnato 0 round ha
Nece Botvinto 10 round, ha perso 7 round e ha segnato 3 round ha
Naming Things is Hardvinto 10 round, ha perso 7 round e ha segnato 3 round ha
The Procrastinatorvinto 10 round, ha perso 8 round e ha segnato 2 round ha
Yggdrasilvinto 10 round, ha perso 10 round e ha segnato 0 round ha
Simple Botvinto 9 round, ha perso 4 round e 7 round ha
Table Botvinto 9 round, ha perso 6 round round e 5 round vincenti ha
Prioritized Random Botvinto 8 round, ha perso 7 round e 5 round
Upper Hand Botha vinto 7 round, ha perso 13 round e ha segnato 0 round ha
Aggressorvinto 6 round, ha perso 10 round e ha segnato 4 round ha
Insanevinto 5 round, ha perso 15 round e ha segnato 0 round ha
The Ugly Ducklingvinto 4 round, ha perso 16 round e ha segnato 0 round ha
Know Botvinto 3 round round, ha perso 14 round e ha pareggiato 3 round ha
Paranoid Botvinto 0 round, ha perso 19 round e ha segnato 1 round ha
Panic Botvinto 0 round, ha perso 19 round e ha segnato 1 round

Sfortunatamente non ho potuto testare The Crazy X-Code Randomess perché non riesco a farlo funzionare da bash su Linux. Lo includerò se riesco a farlo funzionare.

Uscita controller completa


Il gioco

Questo è un gioco KoTH molto semplice. È una lotta a palle di neve uno contro uno. Hai un contenitore inizialmente vuoto che può contenere kpalle di neve. Puoi schivare i jtempi. Ad ogni turno, ad entrambi i giocatori viene chiesto di scegliere contemporaneamente quale mossa fare. Ci sono tre mosse:

  • ricarica: ti dà un'altra palla di neve (fino a k)
  • lancio: lancia una palla di neve, che ucciderà l'altro giocatore se decidono di ricaricare. Se entrambi i giocatori lanciano una palla di neve, nessuno muore (hanno un obiettivo così buono da colpire l'un l'altro le palle di neve)
  • anatra: non fa nulla ed evita di essere colpito se l'altro giocatore lancia una palla di neve. Se non hai più anatre rimaste, allora non succede nulla e se l'altro giocatore lancia una palla di neve muori.

Obbiettivo

Non morire.

Specifiche di sfida

Il tuo programma può essere scritto in qualsiasi lingua. Devi prendere ciascuna di queste variabili come argomento per ogni esecuzione:

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

turn- quanti turni sono trascorsi ( 0alla prima iterazione)
snowballs- quante palle di neve hai
opponent_snowballs- quante palle di neve ha l'avversario
ducks- quante altre volte puoi schivare
opponent_ducks- quante altre volte l'avversario può schivare
max_snowballs- il numero massimo di palle di neve che puoi store ( k)

L'output della funzione chiave dovrebbe essere 0per ricaricare, 1lanciare e 2anatra. È necessario produrre la tua mossa, newline terminato. Si prega di non produrre mosse non valide, ma il controller è molto resiliente e non si romperà se si producono mosse non valide (anche se la mossa non è nemmeno un numero intero). Essa deve essere newline terminato però. Se la mossa non è presente [0, 1, 2], verrà automaticamente spostata su 0. Il vincitore verrà deciso come il giocatore con il maggior numero di vittorie in un torneo round-robin completo.

Regole

È possibile leggere / scrivere da / su un file per l'archiviazione della memoria tra le iterazioni. Il bot verrà inserito nella propria directory in modo che non si verifichino conflitti di nome file. Non è possibile modificare le funzioni integrate (come il generatore casuale). È stato abbastanza divertente la prima volta , ma non lo sarà più. Al tuo programma non è permesso fare cose che sono solo palese esecuzione di stallo. Si applicano scappatoie standard .

analisi

Il codice sorgente per il controller è disponibile qui . Esempio di esecuzione: java Controller "python program1/test1.py" "python program2/test2.py" 10 5per 10 palle di neve e 5 anatre.

A giudicare

Il vincitore verrà deciso selezionando la persona con il maggior numero di vittorie dopo un round robin completo. Mentre questo è un pareggio, rimuovere tutte le persone che non hanno più vittorie. Quindi, ripetere fino a quando una persona vince. Lo standard di valutazione sarà di 50 palle di neve e 25 anatre.

Happy KoTHing!

EDIT : il gioco verrà dichiarato pareggio se passano 1000 round. Il tuo bot potrebbe supporre che turn < 1000.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Dennis,

@HyperNeutrino Altre domande: ho pensato che lo "standard di valutazione" sarebbe 50 palle di neve e 25 anatre? E perché c'è un pareggio a volte dopo ~ 18 round?
CommonGuy

@Manu Ehh merda Ho dimenticato di cambiare le impostazioni nei miei argomenti VM. E inoltre, perché se entrano in un ciclo infinito di collisioni con la palla di neve, lo termina dopo 10 round di ripetizione di un ciclo periodo 1 o periodo 2.
HyperNeutrino,

1
Quindi, ci sarà un altro round? Perché voglio caricare il mio bot e sarei curioso di sapere come funzionerebbe.
Erbsenhirn,

@erbsenhirn Se carichi un bot e esegui il ping in chat o in The Nineteenth Byte , eseguirò un'altra corsa.
HyperNeutrino,

Risposte:


13

Master, C #

Ho addestrato una piccola rete neurale (usando Sharpneat ). Sembra come raccogliere palle di neve e schivare ...

In una versione precedente del controller, ha persino trovato un bug. È passato dallo 0% vincendo al 100% quando ha scoperto come vincere imbrogliare.

Modifica: ho dimenticato di ripristinare lo stato interale delle reti e ho sbagliato la rete. La rete appena addestrata è molto più piccola.

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
}

Apparentemente il ripristino dello stato della rete ha migliorato notevolmente le prestazioni :)
HyperNeutrino,

Contro cosa hai addestrato la rete neurale? Contro altri robot pubblicati qui?
JAD,

@JarkoDubbeldam Sì, ne ho portato diversi su C # e ho addestrato la rete contro di loro. Ecco perché probabilmente perderà contro i nuovi robot.
CommonGuy,

O semplicemente addestrare un'altra rete contro i robot e questa: p
JAD

Wat. 8 voti per una rete neurale!
Christopher,

6

Salva uno, Python

Lancia immediatamente la maggior parte delle sue palle di neve, ma ne salva sempre una nel caso l'avversario stia guardando per mancanza di munizioni. Quindi, si anatra il più a lungo possibile (di nuovo, salvando 1) prima di ricaricare a meno che non ci sia una ricarica sicura garantita o uccisione garantita.

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
se hai 0 palle di neve, proverà a lanciare 1
Carl Bosch

@CarlBosch che dovrebbe essere impossibile raggiungere lo stato (a parte iniziare con 0), ma farò comunque una modifica per coprire quel caso
SnoringFrog

2
@SnoringFrog per chiarire le regole, inizi con 0 palle di neve
PhiNotPi

@PhiNotPi Devo averlo completamente ignorato. Grazie per il chiarimento
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);
        }
    }
}

Questo bot seleziona un numero intero casuale nell'intervallo 0da os + od, quindi sceglie di lanciare, schivare o ricaricare, con le soglie determinate dal suo attuale numero di palle di neve e anatre.

Una cosa che è importante rendersi conto è che una volta che un bot ha più palle di neve rispetto all'altro bot ha palle di neve + anatre, quindi puoi forzare una vittoria. Da questo, possiamo escogitare il concetto di "punti":

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

Se uno di questi numeri diventa positivo, quel giocatore è in grado di forzare una vittoria.

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 tabella "differenza in punti" costituisce la base della teoria dei giochi per questa competizione. Non cattura completamente tutte le informazioni, ma mostra come le palle di neve siano fondamentalmente più preziose delle anatre (poiché le palle di neve sono sia offesa che difesa). Se l'avversario lancia una palla di neve e ti schiva con successo, allora sei un passo avanti verso una vittoria forzata, dal momento che il tuo avversario ha utilizzato una risorsa più preziosa. Questa tabella descrive anche cosa dovresti fare in molti casi speciali, ad esempio quando alcune opzioni di spostamento non sono disponibili.

La tabella "somma dei punti" mostra come, nel tempo, la somma dei punti si avvicina allo zero (quando entrambi i giocatori finiscono le anatre), a quel punto il primo giocatore a fare un errore (ricaricare quando non è stato necessario) immediatamente perde.

Ora, proviamo ad estendere questa strategia di forzatura a casi in cui non è effettivamente forzata (come in, stiamo vincendo con un ampio margine ma la lettura della mente da parte dell'avversario ci batterà). Fondamentalmente, abbiamo le spalle di neve ma dobbiamo far nevicare il nostro avversario s+1(os+2 vincere il tempo del , ecc.) Consecutivamente per vincere. In questo caso, vogliamo eseguire alcune anatre o alcune ricariche per guadagnare un po 'di tempo.

In questo momento, questo bot cerca sempre di intrufolarsi in alcune anatre, semplicemente perché non rischia una perdita immediata: supponiamo che l'avversario stia seguendo una strategia simile per lanciare quante più palle di neve possibile, quindi tentare di ricaricare è davvero pericoloso. Inoltre, al fine di prevenire la prevedibilità, vogliamo intrufolarli nel seguire una distribuzione uniformemente casuale: la probabilità di anatra è correlata al numero di anatre che dobbiamo eseguire in relazione al numero di palle di neve che dobbiamo lanciare.

Se stiamo perdendo molto, nel qual caso s + d < os + odallora dobbiamo sgattaiolare in alcune ricariche oltre a usare tutte le nostre anatre, in questo caso, vogliamo ricaricare in modo casuale, ma solo tutte le volte di cui abbiamo bisogno.

Questo è il motivo per cui i nostri robot hanno la priorità nell'ordine di lancio, anatra e ricarica, e usano os + odper generare il numero casuale, poiché quello è il numero di soglia delle mosse che dobbiamo fare.

C'è un caso limite e altri due casi speciali che il bot attualmente gestisce. Il caso limite è quando l'avversario non ha né palle di neve né anatre, quindi la randomizzazione non funziona, quindi lanciamo se possibile, altrimenti ricarichiamo. Un altro caso speciale è quando l'avversario non può ricaricare, e quindi non vi è alcun vantaggio nel lancio (poiché l'avversario si schianterà o lancerà), quindi ci schiviamo sempre (poiché salvare le nostre palle di neve è più prezioso che salvare le nostre anatre). L'ultimo caso speciale è se l'avversario non ha palle di neve, nel qual caso lo giochiamo in sicurezza e ricarichiamo, se possibile.


Questo potrebbe finire per stampare più numeri che potrebbero non funzionare correttamente.
HyperNeutrino,

@HyperNeutrino Ho dimenticato di aggiungere un blocco "else" quando ho riscritto questo bot dall'uso delle dichiarazioni di ritorno a stampa.
PhiNotPi

1
@HyperNeutrino Lo ha fatto per me, e l'ho considerato infastidito ...
Erik the Outgolfer

Ah. Sì, scusami per aver incasinato il tuo codice: P Ma bello, primo programma che usa la casualità!
HyperNeutrino,

6

NeceBot - Python

Ecco la tabella della teoria dei giochi per il gioco:

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

Laddove ~non si ottiene alcun vantaggio, Wsi vince, Lsi perde, +-Ssi ottiene / si perde una palla di neve sull'avversario e +-Dsi ottiene / si perde un'anatra sull'avversario. Questo è un gioco completamente simmetrico.

Nota che la mia soluzione non tiene conto di quella tabella. Perché sono cattivo in matematica.

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

Si chiama NeceBot perché cerca di ridurre ciò che è necessario prima. Ha poi alcune strategie arbitrarie, che spero funzionino.


4
Whee so many <3s lol. +1 per avere un tavolo da gioco e non usarlo: P Ma bella soluzione :)
HyperNeutrino,

3 + opponent_snowballs <3questo potrebbe essere un errore?
PhiNotPi

@PhiNotPi Yup. Intendeva essere 2. Risolto ora, grazie!
Artyer,

Sfortunatamente, il gran numero di <3s rende il codice abbastanza difficile da capire :(
CalculatorFeline

5

Vigliacco - Scala

Lancia, se l'avversario non ha munizioni, altrimenti (in ordine di priorità) anatre, lancia o ricarica.

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

Sembra che questo blocchi il mio bot ...
Erik the Outgolfer,

5

TheUglyDuckling - Python

Si abbasserà sempre fino a quando non potrà quindi provare a lanciare se l'avversario è vuoto o ricaricare se entrambi sono vuoti. Utilizzerà ricaricare come ultima risorsa.

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

Roba semplice.

  • Se l'avversario ha le palle di neve e tu hai le anatre, allora ti chiedi.
  • Se l'avversario non ha palle di neve e tu ce l'hai, allora lanci.
  • In qualsiasi altro caso, ricarichi.

5

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

Nominare le cose è difficile e non sono sicuro di avere una strategia coerente per nominarle.

Cerca di giocare i primi round per ottenere una vittoria anticipata. Dopodiché, gioca più sicuro per il resto del tempo, cercando di vincere per logoramento.

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

Cerca di salvare le palle di neve fino a quando non è garantito che uccida l'avversario, o fino a quando non esce dalle anatre (nel qual caso, inizia a sparare alla cieca tutte le sue palle di neve, come una mitragliatrice)

Anatra anche quando l'avversario ha una palla di neve, perché non vuole morire.

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

Tiene traccia della frequenza delle mosse precedenti, presume che l'avversario effettuerà di nuovo la più frequente, difende da ciò.

** Aggiornato per non aspettarsi mosse che l'avversario non può fare **

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

Non sono sicuro di come funzioni, ma se memorizza i dati tra i round (al contrario dei turni), sfortunatamente tutti i dati vengono eliminati tra i round. Non invalida la tua soluzione, ma tienilo a mente :)
HyperNeutrino

Non si aspetta di conservare i dati tra i round, solo per aspettarsi che l'attuale avversario sia coerente.
AShelly,

Tutto apposto. Va bene. Volevo solo assicurarmi che non ci fossero idee sbagliate. :)
HyperNeutrino,

4

Braingolf , The Aggressor

<<?1:0

L'aggressore non è un codardo! Se ha una palla di neve, lancerà! Se non ha palle di neve, ne farà di più!

Braingolf , The Insane

Questo non è in realtà un robot, è solo un programmatore che ho rapito e costretto a trasferire su Braingolf ogni progetto che abbia mai realizzato. Non ha più un briciolo di sanità mentale.

<3r!?:1+|%

Genera un numero casuale inferiore a 3 e le uscite in t % rcui t è il turno corrente e r è il numero casuale

Per eseguirli, devi scaricare braingolf.pyda github, quindi salvare il codice braingolf in un file ed eseguire

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

o semplicemente inserisci il codice direttamente in questo modo

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

Gli input sono abbastanza irrilevanti fintanto che il secondo argomento dopo il codice / nome file è la quantità di palle di neve che ha l'aggressore.

Nota: l'aggressore si comporta in modo identico al TestBot, volevo solo entrare in Braingolf

Braingolf , The Brainy [Broken now now]

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

Ovviamente qualcuno doveva farlo: D Bello, e persino giocare a golf! : D
HyperNeutrino,

Oh aspetta, questo è uguale al mio tranne Gofier. lol
HyperNeutrino,

@HyperNeutrino sì, adesso sto lavorando a uno vero in una lingua vera. Userei Braingolf per uno vero, ma non può fare condizionali nidificati, quindi questo rende le cose difficili
Skidsdev

2
Penso che dovresti pubblicare "The Brainy" come risposta separata. Inoltre, penso che sia errato.
Erik the Outgolfer,

"The Insane" non è un bot stabile, quindi non sono sicuro di come lo verrebbe verificato da @HyperNeutrino.
Erik the Outgolfer,

3

TestBot - Python

Questa è una presentazione di prova per mostrarti come potrebbe essere una presentazione valida. La strategia: ricarica e lancio alternativi. Piuttosto una cattiva strategia ma ti dà un'idea di come dovrebbe funzionare il tuo programma.

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

Sarebbero _, turn, snowballs, opponent_snowballs, ducks, opponent_ducks, max_snowballs = sys.argvgli argomenti?
Artyer,

@Artyer Sì. Si scopre che il primo argomento ha il nome file.
HyperNeutrino,

Puoi usarlo solo sys.argv[1:]se non vuoi _
scherzare

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)

Questo robot cerca di raccogliere più palle di neve del suo avversario, e a quel punto inizia a lanciare. Se in qualsiasi momento UHB non ha più palle di neve del suo avversario, dovrà:

  • Anatra, se l'avversario ha le palle di neve e le anatre rimangono
  • Altrimenti, ricarica (a meno che UHB non sia al massimo, quindi getta invece, anche se non penso che questa situazione potrebbe mai sorgere)

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

Ho chiamato questo bot "Yggdrasil" perché in realtà guarda avanti l'albero di gioco ed esegue una valutazione dello stato, da cui può calcolare una strategia mista approssimativamente ideale. Poiché si basa su strategie miste, è molto non deterministico. Non so quanto bene farà questa cosa in una vera competizione.

Alcune cose su questo bot:

  • Il nucleo è una funzione ricorsiva che calcola il valore e la strategia mista quasi ideale per ogni particolare stato del gioco. In questo momento ho impostato per guardare 4 passi avanti.
  • Gioca in modo estremamente accattivante, poiché in molti casi questo robot equivale a "scegliere una mossa casuale con le forbici di carta da roccia". Mantiene la sua posizione e spera che il suo avversario gli dia un vantaggio statistico. Se questo bot fosse perfetto (cosa che non lo è), il meglio che potresti fare contro di esso sarebbe il 50% di vittorie e il 50% di perdite. Di conseguenza, non c'è nessun avversario che batte costantemente, ma anche nessuno a cui perde costantemente.

Ancora non capisco il nome ...: P
HyperNeutrino,

@HyperNeutrino Yggdrasil è un albero mitologico, e in questo caso mi riferisco all'albero del gioco.
PhiNotPi

Ohhhh, mi sento come se avessi dovuto ricordare questo. : P Bello!
HyperNeutrino,

2

Pain in the Nash (C ++)

Così chiamato perché il fatto che dovevo scrivere il mio risolutore di equilibrio di Nash era un vero dolore. Sono sorpreso che non ci siano librerie Nash-solving prontamente disponibili!

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

Compilare come C ++ 11 o superiore. Per prestazioni, è bene compilare con il supporto OpenMP (ma questo è solo per la velocità; non è necessario)

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

Questo utilizza gli equilibri di Nash per decidere cosa fare in ogni turno, il che significa che in teoria vincerà o pareggerà sempre nel lungo periodo (in molti giochi), indipendentemente dalla strategia utilizzata dall'avversario. Se questo è il caso in pratica dipende dal fatto che io abbia commesso degli errori nell'implementazione. Tuttavia, poiché questa competizione KoTH ha un solo round contro ogni avversario, probabilmente non andrà molto bene in classifica.

La mia idea originale era quella di avere una semplice funzione di valutazione per ogni stato del gioco (ad esempio ogni palla vale + b, ogni anatra è + d), ma questo porta a ovvi problemi a capire quali dovrebbero essere quelle valutazioni e significa che non può agire sui rendimenti decrescenti della raccolta di sempre più palle, ecc. Quindi, invece, questo analizzerà l' intero albero di gioco , lavorando all'indietro dal turno 1000, e compilando le valutazioni effettive in base a come ogni gioco potrebbe essere lanciato.

Il risultato è che non ho assolutamente idea di quale strategia utilizzi, ad eccezione di un paio di comportamenti "ovvi" codificati (lanciare palle di neve se si hanno più palle di quante l'avversario abbia palle + anatre e ricaricare se si è entrambi fuori di palle di neve). Se qualcuno vuole analizzare il set di dati che produce immagino che ci sia un comportamento interessante da scoprire!

Provare questo contro "Save One" mostra che vince davvero nel lungo periodo, ma solo con un piccolo margine (514 vittorie, 486 sconfitte, 0 pareggi nel primo lotto di 1000 partite e 509 vittorie, 491 sconfitte, 0 disegna nel secondo).


Importante!

Funzionerà immediatamente, ma non è una grande idea. Il mio laptop con specifiche per sviluppatori moderatamente impiega circa 9 minuti per generare l'albero di gioco completo. Ma salverà le probabilità finali in un file una volta che sono state generate, dopodiché ogni turno sta solo generando un numero casuale e confrontandolo con 2 byte, quindi è superveloce.

Per scorciatoia, scarica questo file (3,5 MB) e inseriscilo nella directory con l'eseguibile.

Oppure puoi generarlo tu stesso eseguendo:

./pain_in_the_nash 1000 50 25

Che salverà un file per turno, fino alla convergenza. Si noti che ogni file è 3,5 MB e converge alla svolta 720 (ovvero 280 file, ~ 1 GB) e poiché la maggior parte dei giochi non arriva da nessuna parte alla svolta 720, i file pre-convergenza hanno un'importanza molto bassa.


È possibile fare in modo che il programma produca solo il risultato finale? Grazie!
HyperNeutrino,

@HyperNeutrino tutti gli altri output dovrebbero essere stderr, quindi non dovrebbero avere alcun impatto, ma l'ho aggiornato per mostrare i progressi solo quando si esegue in modalità di preelaborazione. Ora scriverà su stdout solo quando funziona normalmente. Suggerisco di seguire il suggerimento "importante", poiché altrimenti rimarrà solo al primo turno per diversi minuti (almeno con la preelaborazione è possibile vedere l'avanzamento).
Dave,

Oh ok. Seguirò quel suggerimento, grazie!
HyperNeutrino,

Gradirei se potessi caricare i file di dati perché ci vuole un'eternità per generarli tutti. Se potessi farlo sarebbe fantastico :)
HyperNeutrino,

@HyperNeutrino OK, ci sono voluti anche un'eternità per caricare sulla mia terribile Internet, ma il file convergente da 3,5 MB è disponibile qui: github.com/davidje13/snowball_koth_pitn/blob/master/… (basta inserirlo nella stessa directory).
Dave,

1

Swift - TheCrazy_XcodeRandomness

Purtroppo, questo può essere eseguito solo localmente, in Xcode, perché contiene il Foundationmodulo e la sua funzione arc4random_uniform(),. Tuttavia, puoi praticamente dire cos'è l'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
}

Questo può essere eseguito da bash su Linux?
HyperNeutrino,

@HyperNeutrino So che può farlo su macOS, ma non so se lo fa su Linux. Se riesci a verificarlo, sarebbe fantastico. Prova il swiftcomando e poi controlla se funziona
Mr. Xcoder

Sembra non esistere; c'è un pacchetto con esso ma non è Swift la lingua. Quindi non posso provare questo finché non riesco a far funzionare qualcosa, mi dispiace.
HyperNeutrino,

gli unici possibili compositori sono Xcode e IntelliJ, ma non possono essere Foundation
eseguiti

riposa in pace. Dovrei essere in grado di eseguirlo dalla riga di comando per eseguire il controller con esso, ma se ho tempo, potrei eseguirlo manualmente di nuovo tutti gli altri robot.
HyperNeutrino,

1

TableBot, Python 2

Chiamato TableBot perché è stato creato implementando questa tabella:

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 rappresenta avere 1 o più, uno 0 rappresenta non averne.

Il 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

Provalo online!


1

AmbBot - Schema della racchetta

Per lo più volevo provare a usarlo amb, perché è bello. Questo bot ordina in modo casuale le opzioni (ricarica, lancia e anatra), filtra quelle che non hanno senso e sceglie la prima opzione. Ma con amb, possiamo usare continuazioni e backtracking!

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

Ho anche fatto un piccolo programma di test per eseguire due di questi robot uno contro l'altro. Sembra che il secondo bot vinca più spesso, quindi potrei aver fatto un errore da qualche 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 ++

Praticamente ho preso il codice da questo koth e l' ho modificato per questa sfida. Utilizza l'algoritmo di Disaccoppiamento UCT Monte Carlo Tree Search. Dovrebbe essere abbastanza vicino all'equilibrio di 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;
}

Compilare le istruzioni per Linux:

Salva in MonteBot.cpp.
Correreg++ -o -std=c++11 MonteBot MonteBot.cpp .

Comando da eseguire: ./MonteBot <args>


1

The Procrastinator - Python 3

Il procrastinatore procrastinerà giocando salvo il primo paio di turni. Improvvisamente il mostro panico vuole evitare di perdere la guerra delle risorse contrastando la mossa più usata dagli avversari.

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

"Il procrastinatore". Quindi, tutti su PPCG che hanno effettivamente intenzione di fare i compiti? (Non negarlo, le persone che leggono questo e io)
HyperNeutrino,

1
"Instant Gratification Monkey" Hai visto anche TEDTalk? :)
HyperNeutrino,


0

ParanoidBot e PanicBot - ActionScript3 ( RedTamarin )

Da un linguaggio di nicchia inconsapevole (con estensioni per fornire argomenti da riga di comando) saluta lo scaltro ParanoidBot e il suo noioso alleato PanicBot.

ParanoidBot

ParanoidBot sta perdendo la testa e ha una strategia inutilmente specifica su cui contare. In primo luogo, lancia palle di neve fino al raggiungimento di una soglia, mantenendo alcuni in riserva. Quindi, dopo tre anatre ammonitrici, inizia la paranoia e il robot tenta di accumulare più palle di neve tra anatre casuali. Dopo aver rifornito la sua riserva, ParanoidBot torna al lancio alla cieca. A causa delle voci nella sua testa, ParanoidBot può dire se è garantito per vincere o perdere e "strategia" di conseguenza.

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

Le parentesi graffe sono un po 'traballanti per aiutare a condensare le dimensioni

PanicBot

Essendo già impazzito, PanicBot reagisce per paura istintiva. Dopo aver esaurito le anatre per il rannicchiarsi nella paura, PanicBot lancia alla cieca tutte le sue palle di neve, quindi crea e lancia disperatamente più palle di neve fino a quando (probabilmente) viene sconfitto.

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



Questa è una delle meno di 15 altre voci che usano AS3 qui su PPCG. Un giorno, forse questo linguaggio probabilmente esotico troverà un enigma da dominare.


Questo può essere eseguito da bash su Linux?
HyperNeutrino,

Non l'ho provato, ma sì, dovrebbe. L'eseguibile RedTamarin (redshell) è realizzato per Windows, Mac e Linux: http://redtamarin.com/tools/redshell . Se uno dei robot sopra è salvato in un file chiamato snow.as, il seguente dovrebbe funzionare in bash:$ ./redshell snow.as -- 0 50 50 25 25

Mi dà un errore di autorizzazione negata quando provo a eseguire questo.
HyperNeutrino,

@HyperNeutrino chmod +x redshellè il tuo amico qui ...
Erik the Outgolfer

Forse chmod 777 tutto? È possibile che sul sito Web di RedTamarin ci sia anche qualche risoluzione dei problemi

0

Difensore, Python

Ricarica quando nessuno dei due giocatori ha palle di neve. Se ha palle di neve, lancia. Se non ha palle di neve, ma l'avversario lo fa, si anatra se può, altrimenti si ricarica.

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: non ancora testato

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.