Snowball Fight KoTH!


35

Kết quả (ngày 22 tháng 5 năm 2017 21:40:37 UTC)

Masterthắng 18 vòng, thua 2 vòng, và buộc 0 vòng
Save Onethắng 15 vòng, thua 3 vòng, và 2 vòng
Machine Gunthắng 14 vòng, thua 3 vòng, và
Monte Botthắng 3 vòng thắng 14 vòng, thua 3 vòng, và
Amb Botthắng 3 vòng thắng 12 vòng Vòng đấu, thua 8 vòng, và buộc 0 vòng
Cowardthắng 11 vòng, thua 3 vòng, và buộc 6 vòng
Pain in the Nashthắng 11 vòng, thua 9 vòng, và buộc 0 vòng
Nece Botthắng 10 vòng, thua 7 vòng, và buộc 3 vòng
Naming Things is Hardthắng 10 vòng, thua 7 vòng, và buộc 3 vòng
The Procrastinatorthắng 10 vòng, thua 8 vòng, và buộc 2 vòng
Yggdrasilthắng 10 vòng, thua 10 vòng, và buộc 0 vòng
Simple Botthắng 9 vòng, thua 4 vòng, và 7 vòng
Table Botthắng 9 vòng, thua 6 vòng vòng đấu, và buộc 5 vòng
Prioritized Random Botthắng 8 vòng, thua 7 vòng và buộc 5 vòng
Upper Hand Botthắng 7 vòng, thua 13 vòng và buộc 0 vòng
Aggressorthắng 6 vòng, thua 10 vòng, và
Insanethắng 4 vòng thắng 5 vòng, thua 15 vòng và trói 0 vòng
The Ugly Ducklingthắng 4 vòng, thua 16 vòng và buộc 0 vòng
Know Botthắng 3 vòng Vòng đấu, thua 14 vòng, và buộc 3 vòng
Paranoid Botthắng 0 vòng, thua 19 vòng và trói 1 vòng
Panic Botthắng 0 vòng, thua 19 vòng, và buộc 1 vòng

Thật không may, tôi không thể kiểm tra Crazy X-Code Randomess vì tôi không thể chạy nó từ bash trên Linux. Tôi sẽ bao gồm nó nếu tôi có thể làm cho nó hoạt động.

Đầu ra bộ điều khiển đầy đủ


Tro choi

Đây là một trò chơi KoTH rất đơn giản. Đó là một trận đấu bóng tuyết một chọi một. Bạn có một thùng chứa ban đầu trống có thể chứa kbóng tuyết. Bạn có thể vịt lên đến jlần. Mỗi lượt, cả hai người chơi được yêu cầu đồng thời đưa ra lựa chọn cho việc di chuyển để thực hiện. Có ba động thái:

  • tải lại: cung cấp cho bạn một quả cầu tuyết khác (tối đa k)
  • throw: ném một quả cầu tuyết, thứ sẽ giết người chơi khác nếu họ quyết định tải lại. Nếu cả hai người chơi ném một quả cầu tuyết, không ai chết (họ có mục tiêu tốt đến mức họ sẽ đánh những quả bóng tuyết của nhau)
  • Vịt: không làm gì cả và tránh bị đánh nếu người chơi khác ném quả cầu tuyết. Nếu bạn không còn vịt nữa, thì không có gì xảy ra và nếu người chơi khác ném quả cầu tuyết, bạn sẽ chết.

Mục tiêu

Đừng chết.

Thông số kỹ thuật của Challlenge

Chương trình của bạn có thể được viết bằng bất kỳ ngôn ngữ nào. Bạn phải lấy từng biến này làm đối số cho mỗi lần thực hiện:

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

turn- đã trôi qua bao nhiêu lượt ( 0trong lần lặp đầu tiên)
snowballs- bạn có
opponent_snowballsbao nhiêu quả bóng tuyết - đối thủ có bao nhiêu quả bóng tuyết
ducks- bạn có thể đạp thêm
opponent_ducksbao nhiêu lần - đối thủ có thể đạp thêm bao nhiêu lần
max_snowballs- số lần ném tuyết tối đa bạn có thể cửa hàng ( k)

Đầu ra của chức năng chính phải là 0để tải lại, 1ném và 2cho vịt. Bạn phải xuất di chuyển của bạn, dòng mới chấm dứt. Vui lòng không xuất các di chuyển không hợp lệ, nhưng bộ điều khiển rất linh hoạt và sẽ không bị hỏng nếu bạn xuất các di chuyển không hợp lệ (ngay cả khi di chuyển của bạn thậm chí không phải là số nguyên). Nó phải được chấm dứt dòng mới mặc dù. Nếu di chuyển không vào [0, 1, 2], nó sẽ mặc định di chuyển của bạn đến 0. Người chiến thắng sẽ được quyết định là người chơi có nhiều chiến thắng nhất từ ​​một giải đấu vòng tròn đầy đủ.

Quy tắc

Bạn có thể đọc / ghi từ / đến một tệp để lưu trữ bộ nhớ giữa các lần lặp. Bot của bạn sẽ được đặt trong thư mục riêng của nó để xung đột tên tệp sẽ không xảy ra. Bạn không thể thay đổi các hàm tích hợp (như trình tạo ngẫu nhiên). Lần đầu tiên nó được thực hiện khá buồn cười , nhưng nó sẽ không còn nữa. Chương trình của bạn không được phép làm những việc chỉ bị đình trệ trắng trợn. Áp dụng sơ hở tiêu chuẩn .

Kiểm tra

Mã nguồn cho bộ điều khiển có thể được tìm thấy ở đây . Ví dụ về việc chạy nó: java Controller "python program1/test1.py" "python program2/test2.py" 10 5cho 10 quả cầu tuyết và 5 con vịt.

Đánh giá

Người chiến thắng sẽ được quyết định bằng cách chọn người có nhiều chiến thắng nhất sau một vòng đấu đầy đủ. Trong khi đây là hòa, hãy loại bỏ tất cả những người không có nhiều chiến thắng nhất. Sau đó, lặp lại cho đến khi một người chiến thắng. Tiêu chuẩn đánh giá sẽ là 50 quả cầu tuyết và 25 con vịt.

Chúc bạn vui vẻ!

EDIT : Trò chơi sẽ được tuyên bố hòa nếu 1000 vòng đấu trôi qua. Bot của bạn có thể cho rằng turn < 1000.


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Dennis

@HyperNeutrino Thêm câu hỏi: Tôi nghĩ "tiêu chuẩn đánh giá" sẽ là 50 quả cầu tuyết và 25 con vịt? Và tại sao đôi khi có một trận hòa sau ~ 18 vòng?
CommonGuy

@Manu Ehh tào lao Tôi quên thay đổi cài đặt trong các đối số VM của tôi. Ngoài ra, đó là bởi vì nếu chúng đi vào một vòng lặp vô tận của các vụ va chạm bóng tuyết, nó sẽ kết thúc nó sau 10 vòng lặp lại một vòng lặp giai đoạn 1 hoặc giai đoạn 2.
HyperNeutrino

1
Vì vậy, sẽ có một vòng khác? Vì tôi muốn tải lên bot của mình và sẽ tò mò anh ấy sẽ làm việc tốt như thế nào.
erbsenhirn

@erbsenhirn Nếu bạn tải lên bot và ping tôi trong trò chuyện hoặc trên Byte thứ mười chín , và tôi sẽ chạy một lần chạy khác.
HyperNeutrino

Câu trả lời:


13

Thạc sĩ, C #

Tôi đã đào tạo một mạng lưới thần kinh nhỏ (sử dụng Sharpneat ). Nó có vẻ thích nhặt bóng tuyết và vịt ...

Trong phiên bản trước của bộ điều khiển, nó thậm chí còn tìm thấy một lỗi. Nó đã đi từ chiến thắng 0% đến 100% khi phát hiện ra cách chiến thắng bằng cách gian lận.

Chỉnh sửa: Tôi quên đặt lại trạng thái liên mạng và đào tạo mạng sai. Mạng mới được đào tạo nhỏ hơn nhiều.

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
}

Rõ ràng việc đặt lại trạng thái mạng làm cho hiệu suất tốt hơn rất nhiều :)
HyperNeutrino

Chống lại những gì bạn đã đào tạo mạng lưới thần kinh? Chống lại các bot khác được đăng ở đây?
JAD

@JarkoDubbeldam Vâng, tôi đã chuyển một vài người trong số họ sang C # và huấn luyện mạng chống lại họ. Đó là lý do tại sao nó có thể sẽ chống lại các bot mới.
CommonGuy

Hoặc chỉ cần đào tạo một mạng khác chống lại các bot và mạng này: p
JAD

Wat. 8 phiếu cho một mạng lưới thần kinh!
Christopher

6

Lưu một, Python

Ném hầu hết các quả cầu tuyết của nó ngay lập tức, nhưng luôn cứu một người trong trường hợp đối thủ đang xem thiếu đạn. Sau đó, nó vịt càng lâu càng tốt (một lần nữa, tiết kiệm 1) trước khi tải lại trừ khi có tải lại an toàn được bảo đảm hoặc giết chết được bảo đảm.

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
nếu bạn có 0 quả cầu tuyết, nó sẽ cố ném 1
Carl Bosch

@CarlBosch không thể đạt được trạng thái (ngoài bắt đầu bằng 0), nhưng tôi sẽ thực hiện chỉnh sửa để khắc phục trường hợp đó
SnoringFrog

2
@SnoringFrog để làm rõ các quy tắc, bạn bắt đầu với 0 quả cầu tuyết
PhiNotPi

@PhiNotPi Tôi đã hoàn toàn bỏ qua điều đó. Cảm ơn đã làm rõ
SnoringFrog

6

Ưu tiênRandomBot, 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);
        }
    }
}

Bot này sẽ chọn một số nguyên ngẫu nhiên trong phạm vi 0tới os + od, và sau đó chọn một trong hai ném, vịt, hoặc tải lại, với sự ngưỡng xác định bằng số lượng hiện tại của snowballs và vịt.

Một điều quan trọng cần nhận ra, là một khi một bot có nhiều bóng tuyết hơn các bot khác có bóng tuyết + vịt, thì bạn có thể buộc một chiến thắng. Từ đó, chúng ta có thể đưa ra khái niệm "điểm":

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

Nếu một trong hai số này trở nên tích cực, thì người chơi đó có thể buộc một chiến thắng.

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

Bảng "khác biệt về điểm" tạo thành nền tảng của lý thuyết trò chơi cho cuộc thi này. Nó không hoàn toàn nắm bắt được tất cả thông tin, nhưng nó cho thấy những quả bóng tuyết về cơ bản có giá trị hơn vịt (vì những quả bóng tuyết vừa tấn công vừa phòng thủ). Nếu đối thủ ném một quả bóng tuyết và bạn vịt thành công, thì bạn đã tiến một bước gần hơn đến một chiến thắng cưỡng bức, vì đối thủ của bạn đã sử dụng hết một nguồn tài nguyên quý giá hơn. Bảng này cũng mô tả những gì bạn nên làm trong rất nhiều trường hợp đặc biệt, chẳng hạn như khi không có tùy chọn di chuyển nhất định.

Bảng "tổng số điểm" cho thấy, theo thời gian, tổng số điểm gần bằng 0 (vì cả hai người chơi hết vịt), tại đó, người chơi đầu tiên mắc lỗi (tải lại khi họ không cần) ngay lập tức thua cuộc

Bây giờ, chúng ta hãy cố gắng mở rộng chiến lược buộc này sang các trường hợp không thực sự bắt buộc (như trong, chúng ta đang chiến thắng với một tỷ lệ lớn nhưng đọc được suy nghĩ về phía đối thủ sẽ đánh bại chúng ta). Về cơ bản, chúng ta có những squả bóng tuyết nhưng cần phải ném tuyết đối thủ s+1(hoặc s+2, v.v.) liên tục để giành chiến thắng. Trong trường hợp này, chúng tôi muốn thực hiện một vài con vịt hoặc một vài lần tải lại để mua cho mình một chút thời gian.

Ngay bây giờ, bot này luôn cố gắng lẻn vào một số con vịt, đơn giản là vì sau đó nó không có nguy cơ thua lỗ ngay lập tức: chúng tôi cho rằng đối thủ đang theo một chiến lược tương tự như ném bóng tuyết nhiều nhất có thể, vì vậy cố gắng tải lại là thực sự nguy hiểm. Ngoài ra, để ngăn chặn khả năng dự đoán, chúng tôi muốn lén theo những phân phối ngẫu nhiên đồng đều: xác suất vịt có liên quan đến số lượng vịt chúng tôi cần thực hiện so với số lượng bóng tuyết chúng tôi cần ném.

Nếu chúng ta thua lỗ nặng, trong trường hợp s + d < os + odđó, chúng ta cần phải lén lút trong một số lần tải lại ngoài việc sử dụng tất cả các con vịt của chúng ta, trong trường hợp này, chúng ta muốn tải lại ngẫu nhiên, nhưng chỉ cần nhiều lần chúng ta cần.

Đây là lý do tại sao các bot của chúng tôi ưu tiên theo thứ tự ném, vịt và tải lại và sử dụng os + odđể tạo số ngẫu nhiên, vì đó là số lần di chuyển mà chúng tôi cần thực hiện.

Có một trường hợp cạnh và hai trường hợp đặc biệt khác, bot hiện đang xử lý. Trường hợp cạnh là khi đối thủ không có bóng tuyết hay vịt, và do đó ngẫu nhiên hóa không hoạt động, vì vậy chúng tôi ném nếu có thể, nếu không chúng tôi tải lại. Một trường hợp đặc biệt khác là khi đối thủ không thể tải lại, và do đó không có lợi ích gì khi ném (vì đối thủ sẽ là vịt hoặc ném), vì vậy chúng tôi luôn vịt (vì cứu bóng tuyết của chúng tôi có giá trị hơn cứu vịt của chúng tôi). Trường hợp đặc biệt cuối cùng là nếu đối thủ không có bóng tuyết, trong trường hợp đó chúng tôi chơi nó an toàn và tải lại nếu có thể.


Điều này có thể kết thúc in nhiều số có thể không hoạt động đúng.
HyperNeutrino

@HyperNeutrino Tôi đã quên thêm một khối "khác" khi tôi viết lại bot này bằng cách sử dụng trả về để in báo cáo.
PhiNotPi

1
@HyperNeutrino Nó đã làm cho tôi, và tôi coi nó đã bị lỗi ...
Erik the Outgolfer

À. Vâng, xin lỗi vì đã làm rối mã của bạn: P Nhưng thật tuyệt, chương trình đầu tiên sử dụng tính ngẫu nhiên!
HyperNeutrino

6

NeceBot - Python

Đây là bảng lý thuyết trò chơi cho trò chơi:

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

Trường hợp ~có nghĩa là không có lợi thế, Wlà thắng, Lthua, +-Scó nghĩa là một quả bóng tuyết được / mất trên đối thủ và +-Dcó nghĩa là một con vịt được / mất trên đối thủ. Đây là một trò chơi hoàn toàn đối xứng.

Lưu ý rằng giải pháp của tôi không đưa bảng đó vào tài khoản. Bởi vì tôi kém môn toán.

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

Nó được gọi là NeceBot vì nó cố gắng giảm những gì cần thiết trước tiên. Nó có một số chiến lược tùy ý sau đó, mà tôi hy vọng sẽ làm việc.


4
Whee rất nhiều <3s lol. +1 để có một bảng trò chơi và sau đó không sử dụng nó: P Nhưng giải pháp hay :)
HyperNeutrino

3 + opponent_snowballs <3đây có thể là một sai lầm?
PhiNotPi

@PhiNotPi Yup. Đồng nghĩa với việc 2. Đã sửa bây giờ, cảm ơn!
Artyer

Thật không may, số lượng lớn <3làm cho mã khá khó hiểu :(
CalculatorFeline

5

Hèn nhát - Scala

Ném, nếu đối thủ không có đạn, nếu không (theo thứ tự ưu tiên) vịt, ném hoặc tải lại.

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

Có vẻ như điều này làm ùn tắc bot của tôi ...
Erik the Outgolfer

5

TheUglyDuckling - Python

Sẽ luôn luôn vịt cho đến khi nó không thể cố gắng ném nếu đối thủ trống hoặc tải lại nếu cả hai đều trống. Sẽ sử dụng tải lại như là một phương sách cuối cùng.

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

Thứ đơn giản.

  • Nếu đối thủ có bóng tuyết và bạn có vịt, thì bạn vịt.
  • Nếu đối thủ không có bóng tuyết và bạn có, thì bạn ném.
  • Trong mọi trường hợp khác, bạn tải lại.

5

Bot đặt tên-vật-cứng-cứng - VB.NET

Đặt tên mọi thứ thật khó khăn và tôi không chắc mình có một chiến lược gắn kết để đặt tên cho nó.

Cố gắng đánh bạc trong vài vòng đầu tiên để có được chiến thắng sớm. Sau đó, chơi an toàn hơn trong thời gian còn lại, cố gắng giành chiến thắng bằng sự tiêu hao.

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

MáyGun, Python 3

Cố gắng cứu những quả bóng tuyết cho đến khi nó được bảo đảm để giết đối thủ hoặc cho đến khi hết vịt (Trong trường hợp đó, nó bắt đầu bắn một cách mù quáng tất cả những quả bóng tuyết của mình, giống như một khẩu súng máy)

Nó cũng vịt bất cứ khi nào đối thủ có một quả cầu tuyết, bởi vì nó không muốn chết.

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

Kiến thức, Python3

Theo dõi tần suất của các động tác trước đó, giả sử đối thủ sẽ thực hiện thường xuyên nhất một lần nữa, bảo vệ chống lại điều đó.

** Cập nhật để không mong đợi di chuyển đối thủ không thể thực hiện **

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

Tôi không chắc chắn chính xác cách thức hoạt động của nó, nhưng nếu nó lưu trữ dữ liệu giữa các vòng (trái ngược với lượt), thật không may, tất cả dữ liệu sẽ bị xóa giữa các vòng. Nó không làm mất hiệu lực giải pháp của bạn mà chỉ cần ghi nhớ điều đó :)
HyperNeutrino

Nó không mong đợi giữ lại dữ liệu giữa các vòng, chỉ để mong đối thủ hiện tại là nhất quán.
AShelly

Ổn thỏa. Đuợc. Tôi chỉ muốn chắc chắn rằng không có quan niệm sai lầm. :)
HyperNeutrino

4

Braingolf , Kẻ xâm lược

<<?1:0

Kẻ xâm lược là không hèn nhát! Nếu anh ta có một quả cầu tuyết, anh ta sẽ ném! Nếu anh ta không có bóng tuyết, anh ta sẽ làm thêm!

Braingolf , Người điên

Đây thực sự không phải là một bot, nó chỉ là một lập trình viên mà tôi đã bắt cóc và buộc phải chuyển mọi dự án mà anh ấy từng làm cho braingolf. Anh ta không còn có một chút tỉnh táo.

<3r!?:1+|%

Tạo số ngẫu nhiên nhỏ hơn 3 và đầu ra t % rtrong đó t là lượt hiện tại và r là số ngẫu nhiên

Để chạy chúng, bạn sẽ cần tải xuống braingolf.pytừ github, sau đó lưu mã braingolf vào một tệp và chạy

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

hoặc chỉ cần chèn mã trực tiếp như thế này

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

Các đầu vào khá không liên quan, miễn là đối số thứ 2 sau mã / tên tệp là số lượng quả cầu tuyết mà Kẻ xâm lược có.

Lưu ý: Kẻ xâm lược thực sự hành xử giống hệt với TestBot, tôi chỉ muốn thực hiện một mục trong braingolf

Braingolf , The Brainy [Hỏng ngay bây giờ]

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

Tất nhiên ai đó đã phải làm điều này: D Nice, và thậm chí là chơi gôn! : D
HyperNeutrino

Oh chờ đợi điều này giống như của tôi ngoại trừ gofier. lol
HyperNeutrino

@HyperNeutrino yup, bây giờ tôi đang làm việc với một ngôn ngữ thực sự bằng ngôn ngữ thực sự. Tôi sẽ sử dụng Braingolf cho một người thực sự, nhưng nó không thể tạo ra các điều kiện lồng nhau, vì vậy điều đó gây khó khăn
Skidsdev

2
Tôi nghĩ bạn nên đăng "The Brainy" như một câu trả lời riêng biệt. Ngoài ra, tôi nghĩ rằng nó sai.
Erik the Outgolfer

"The Insane" không phải là một bot ổn định, vì vậy tôi không chắc @HyperNeutrino sẽ kiểm tra nó như thế nào.
Erik the Outgolfer

3

TestBot - Python

Đây là một bài kiểm tra để cho bạn thấy một bài nộp hợp lệ có thể trông như thế nào. Chiến lược: Thay thế tải lại và ném. Khá là một chiến lược tồi nhưng nó cho bạn ý tưởng về cách chương trình của bạn nên hoạt động.

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

Sẽ _, turn, snowballs, opponent_snowballs, ducks, opponent_ducks, max_snowballs = sys.argvlà đối số?
Artyer

@Artyer Có. Nó chỉ ra rằng đối số đầu tiên có tên tệp.
HyperNeutrino

Bạn chỉ có thể sử dụng sys.argv[1:]nếu bạn không muốn gây rối với điều đó_
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)

Bot này cố gắng thu thập nhiều quả cầu tuyết hơn đối thủ của mình và tại thời điểm đó bắt đầu ném. Nếu tại bất kỳ thời điểm nào, UHB không có nhiều quả cầu tuyết hơn đối thủ của mình, nó sẽ:

  • Vịt, nếu đối thủ có bóng tuyết và nó còn vịt
  • Mặt khác, tải lại (trừ khi UHB ở mức tối đa, sau đó nó sẽ ném, mặc dù tôi không nghĩ rằng tình huống này sẽ xảy ra)

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

Tôi đặt tên cho bot này là "Yggdrasil" bởi vì nó thực sự nhìn xuống cây trò chơi và thực hiện định giá trạng thái, từ đó nó có thể tính toán một chiến lược hỗn hợp xấp xỉ lý tưởng. Bởi vì nó dựa trên các chiến lược hỗn hợp, nó rất không mang tính quyết định. Tôi không biết điều này sẽ làm tốt như thế nào trong cạnh tranh thực sự.

Một vài điều về bot này:

  • Cốt lõi là một hàm đệ quy tính toán giá trị và chiến lược hỗn hợp gần như lý tưởng cho bất kỳ trạng thái trò chơi cụ thể nào. Ngay bây giờ tôi đã thiết lập nó để nhìn về phía trước 4 bước.
  • Nó chơi cực kỳ lôi cuốn, vì trong rất nhiều trường hợp, bot này tương đương với "chọn một bước di chuyển ngẫu nhiên trong cái kéo giấy bằng đá". Nó giữ vững lập trường và hy vọng rằng đối thủ của nó mang lại cho nó một lợi thế thống kê. Nếu bot này hoàn hảo (không phải vậy), điều tốt nhất bạn có thể làm để chống lại nó sẽ là 50% chiến thắng và 50% thua lỗ. Kết quả là, không có đối thủ mà nó liên tục đánh bại, nhưng cũng không có đối thủ nào liên tục thua.

Tôi vẫn không hiểu tên ...: P
HyperNeutrino

@HyperNeutrino Yggdrasil là một cây thần thoại, và trong trường hợp này tôi đang đề cập đến cây trò chơi.
PhiNotPi

Ohhhh phải tôi cảm thấy như tôi nên nhớ điều này. : P Đẹp!
HyperNeutrino

2

Đau ở Nash (C ++)

Gọi như vậy bởi vì việc tôi phải viết bộ giải cân bằng Nash của riêng tôi là một nỗi đau thực sự. Tôi ngạc nhiên rằng không có bất kỳ thư viện giải quyết Nash nào có sẵn!

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

Biên dịch thành C ++ 11 hoặc tốt hơn. Về hiệu năng, thật tốt khi biên dịch với hỗ trợ OpenMP (nhưng đây chỉ là tốc độ; không bắt buộc)

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

Điều này sử dụng cân bằng Nash để quyết định những việc cần làm trong mỗi lượt, điều đó có nghĩa là về mặt lý thuyết, nó sẽ luôn thắng hoặc hòa trong thời gian dài (qua nhiều trò chơi), bất kể đối thủ sử dụng chiến lược nào. Cho dù đó là trường hợp trong thực tế phụ thuộc vào việc tôi có mắc lỗi nào trong quá trình thực hiện hay không. Tuy nhiên, vì cuộc thi KoTH này chỉ có một vòng đấu với mỗi đối thủ, nên có lẽ nó sẽ không làm tốt trên bảng xếp hạng.

Ý tưởng ban đầu của tôi là có một chức năng định giá đơn giản cho từng trạng thái trò chơi (ví dụ: mỗi quả bóng có giá trị + b, mỗi con vịt là + d), nhưng điều này dẫn đến những vấn đề rõ ràng để tìm ra những định giá đó nên là gì và có nghĩa là nó không thể hành động dựa trên lợi nhuận giảm dần của việc thu thập ngày càng nhiều quả bóng, v.v. Vì vậy, thay vào đó, điều này sẽ phân tích toàn bộ cây trò chơi , hoạt động ngược từ lượt 1000 và điền vào các giá trị thực tế dựa trên cách mỗi trò chơi có thể xoay ra.

Kết quả là tôi hoàn toàn không biết chiến lược này sử dụng chiến lược nào, ngoại trừ một vài hành vi "rõ ràng" được mã hóa cứng (ném bóng tuyết nếu bạn có nhiều bóng hơn đối thủ có bóng + vịt và tải lại nếu cả hai bạn đều ra ngoài của quả cầu tuyết). Nếu bất cứ ai muốn phân tích dữ liệu mà nó tạo ra, tôi tưởng tượng có một số hành vi thú vị để khám phá!

Thử nghiệm điều này với "Save One" cho thấy rằng nó thực sự chiến thắng trong thời gian dài, nhưng chỉ bằng một tỷ lệ nhỏ (514 trận thắng, 486 trận thua, 0 trận hòa trong đợt 1000 trận đầu tiên và 509 trận thắng, 491 trận thua, 0 vẽ trong lần thứ hai).


Quan trọng!

Điều này sẽ hoạt động tốt, nhưng đó không phải là một ý tưởng tuyệt vời. Mất khoảng 9 phút trên máy tính xách tay dành cho nhà phát triển vừa phải của tôi để tạo cây trò chơi đầy đủ. Nhưng nó sẽ lưu các xác suất cuối cùng vào một tệp khi chúng được tạo và sau đó mỗi lượt chỉ tạo một số ngẫu nhiên và so sánh nó với 2 byte, vì vậy nó cực nhanh.

Để tắt tất cả những thứ đó, chỉ cần tải xuống tệp này (3,5 MB) và đặt nó vào thư mục với tệp thực thi.

Hoặc bạn có thể tự tạo nó bằng cách chạy:

./pain_in_the_nash 1000 50 25

Mà sẽ lưu một tệp mỗi lượt, cho đến khi hội tụ. Lưu ý rằng mỗi tệp là 3,5 MB và nó sẽ hội tụ ở lượt 720 (tức là 280 tệp, ~ 1 GB) và vì hầu hết các trò chơi không nhận được bất kỳ nơi nào gần lượt 720, các tệp hội tụ trước có tầm quan trọng rất thấp.


Có thể làm cho chương trình chỉ xuất kết quả cuối cùng? Cảm ơn!
HyperNeutrino

@HyperNeutrino tất cả các đầu ra khác phải là stderr, vì vậy không nên có bất kỳ tác động nào, nhưng tôi đã cập nhật nó để chỉ hiển thị tiến trình khi chạy ở chế độ tiền xử lý. Bây giờ nó sẽ chỉ ghi vào thiết bị xuất chuẩn khi chạy bình thường. Tuy nhiên, tôi khuyên bạn nên làm theo đề xuất "quan trọng", vì nếu không, nó sẽ chỉ dừng lại ở lượt đầu tiên trong vài phút (ít nhất là với tiền xử lý, bạn có thể thấy tiến trình).
Dave

Ờ được rồi. Tôi sẽ làm theo gợi ý đó, cảm ơn!
HyperNeutrino

Tôi sẽ đánh giá cao nếu bạn có thể tải lên các tệp dữ liệu vì phải mất tất cả để tạo ra tất cả. Nếu bạn có thể làm điều đó sẽ rất tuyệt :)
HyperNeutrino

@HyperNeutrino OK, nó cũng mất mãi mãi để tải lên trên mạng internet khủng khiếp của tôi, nhưng tệp hội tụ 3,5 MB có sẵn ở đây: github.com/davidje13/snowball_koth_pitn/blob/master/ tựa (chỉ cần đặt nó vào cùng thư mục).
Dave

1

Swift - TheCrazy_XcodeRandomness

Đáng buồn thay, điều này chỉ có thể được chạy cục bộ, trong Xcode, bởi vì nó chứa Foundationmô-đun và chức năng của nó , arc4random_uniform(). Tuy nhiên, bạn có thể nói khá nhiều thuật toán là gì:

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
}

Điều này có thể được chạy từ bash trên Linux không?
HyperNeutrino

@HyperNeutrino Tôi biết nó có thể trên macOS, nhưng tôi không biết nếu nó có trên Linux. Nếu bạn có thể kiểm tra điều đó, nó sẽ rất tuyệt. Hãy thử swiftlệnh và sau đó kiểm tra xem nó có hoạt động không
Ông Xcoder

Nó dường như không tồn tại; Có một gói với nó nhưng nó không phải là ngôn ngữ Swift. Vì vậy, tôi có thể không kiểm tra điều này cho đến khi tôi có thể làm cho một cái gì đó hoạt động, xin lỗi.
HyperNeutrino

trình biên dịch duy nhất có thể là Xcode và IntelliJ, nhưng nó không thể chạy trực tuyến vì Foundation, xin lỗi: /
Ông Xcoder

Yên nghỉ. Tôi cần có khả năng chạy nó từ dòng lệnh để chạy bộ điều khiển với nó, nhưng nếu có thời gian, tôi có thể tự chạy lại tất cả các bot khác.
HyperNeutrino

1

BảngBot, Python 2

Được gọi là TableBot vì nó được tạo bằng cách thực hiện bảng này:

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

1 đại diện cho có 1 hoặc nhiều hơn, 0 đại diện cho không có.

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

Hãy thử trực tuyến!


1

AmbBot - Lược đồ vợt

Tôi chủ yếu muốn thử sử dụng amb, bởi vì nó mát mẻ. Bot này sắp xếp ngẫu nhiên các tùy chọn (tải lại, ném và vịt), lọc ra những tùy chọn không có ý nghĩa và chọn tùy chọn đầu tiên. Nhưng với amb, chúng tôi có thể sử dụng tiếp tục và quay lui!

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

Tôi cũng đã thực hiện một chương trình thử nghiệm nhỏ để chạy hai trong số các bot này với nhau. Cảm giác như bot thứ hai thắng thường xuyên hơn, vì vậy tôi có thể đã mắc lỗi ở đâu đó.

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

Về cơ bản tôi đã lấy mã từ koth này và sửa đổi nó cho thử thách này. Nó sử dụng thuật toán Tìm kiếm UCT Monte Carlo Tree Decoupling. Nó sẽ khá gần với trạng thái cân bằng 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;
}

Hướng dẫn biên dịch cho linux:

Lưu vào MonteBot.cpp.
Chạy đi g++ -o -std=c++11 MonteBot MonteBot.cpp.

Lệnh chạy: ./MonteBot <args>


1

Người trì hoãn - Python 3

Người trì hoãn sẽ trì hoãn bằng cách chơi lưu vài lượt đầu tiên. Đột nhiên, con quái vật hoảng loạn muốn tránh thua cuộc chiến tài nguyên bằng cách chống lại các đối thủ được sử dụng nhiều nhất.

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

"Người trì hoãn". Vì vậy, tất cả mọi người trên PPCG, những người thực sự có nghĩa là làm bài tập về nhà? (Đừng phủ nhận nó, những người đọc nó và tôi)
HyperNeutrino

1
"Khỉ hài lòng tức thì" Bạn cũng đã thấy TEDTalk chưa? :)
HyperNeutrino


0

ParanoidBot và PanicBot - ActionScript3 ( RedTamarin )

Từ một ngôn ngữ không phù hợp, thích hợp (có phần mở rộng để cung cấp các đối số dòng lệnh) ca ngợi ParanoidBot và đồng minh ngu ngốc của anh ta, PanicBot.

Bệnh hoang tưởng

ParanoidBot đang mất trí, và có một chiến lược cụ thể không cần thiết để phụ thuộc vào. Đầu tiên, nó phóng những quả bóng tuyết cho đến khi đạt đến ngưỡng, giữ một số dự trữ. Sau đó, sau ba con vịt cảnh báo, hoang tưởng bắt đầu và bot cố gắng dự trữ nhiều quả cầu tuyết ở giữa những con vịt ngẫu nhiên. Sau khi bổ sung nguồn cung cấp, ParanoidBot quay trở lại ném một cách mù quáng. Do những tiếng nói trong đầu, ParanoidBot có thể biết liệu nó được đảm bảo thắng hay thua, và sẽ "chiến lược hóa" theo đó.

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

Niềng răng có một chút rườm rà để giúp ngưng tụ kích thước

PanicBot

Khi đã phát điên, PanicBot phản ứng vì sợ hãi theo bản năng. Sau khi hết vịt vì sợ hãi, PanicBot mù quáng ném tất cả những quả bóng tuyết của nó, sau đó tuyệt vọng tạo ra và ném thêm những quả bóng tuyết cho đến khi (có thể) bị đánh bại.

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



Đây là một trong số ít hơn 15 mục khác sử dụng AS3 tại đây trên PPCG. Một ngày nào đó, có lẽ ngôn ngữ kỳ lạ được cho là này sẽ tìm ra một câu đố để thống trị.


Điều này có thể được chạy từ bash trên Linux?
HyperNeutrino

Tôi đã không kiểm tra điều đó, nhưng vâng, nó nên. Tệp thực thi RedTamarin (redshell) được xây dựng cho Windows, Mac và Linux: http://redtamarin.com/tools/redshell . Nếu một trong các bot ở trên được lưu vào một tệp có tên snow.as, thì các công việc sau phải hoạt động trong bash:$ ./redshell snow.as -- 0 50 50 25 25

Nó cho phép tôi từ chối lỗi khi tôi cố chạy nó.
HyperNeutrino

@HyperNeutrino chmod +x redshelllà bạn của bạn ở đây ...
Erik the Outgolfer

Có lẽ chmod 777 mọi thứ? Có thể có một số khắc phục sự cố trên trang web

0

Hậu vệ, Python

Tải lại khi không có người chơi có quả cầu tuyết. Nếu nó có quả cầu tuyết, nó ném. Nếu nó không có quả cầu tuyết, nhưng đối thủ thì có, nó sẽ nhảy nếu có thể, nếu không thì tải lại.

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

Lưu ý: chưa được thử nghiệm

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.