Tepenin Kralı: Speed ​​Clue AI


24

Hız ipucu

Cluedo / Clue , zorlayıcı bir kesinti oyun bileşenine sahip klasik bir tahta oyunudur. Speed ​​Clue, sadece kartları kullanarak bu öğeyi vurgulayan 3-6 oyunculu bir değişkendir. Sonuç olarak, standart Cluedo ve Speed ​​Clue arasındaki tek fark, hala oyunda olan her oyuncunun, zar atışlarının ve diğer oyuncuların tavsiyelerinin merhametinde belirli bir odaya ulaşmak için beklemek yerine sırayla istediği herhangi bir öneride bulunabilmesidir. Daha önce Cluedo'yu hiç oynamadıysanız veya iki sürüm arasındaki açık farklardan emin olmak istiyorsanız, burada ayarlanmış bir Speed ​​Clue kuralı bulabilirsiniz .


Hedef

15 Mayıs 2014 00:00 GMT'den önce Speed ​​Clue oynamak için bir AI programı yazıp gönderin. Bu saatten sonra, tüm yasal kayıtları kullanarak bir turnuva düzenleyeceğim. AI turnuvadaki en fazla oyunu kazanan katılımcı mücadeleyi kazanıyor.


AI Özellikleri

Sunucunuzla oyun oynamak için kullandığınız teknikleri kullanarak , uygulama protokolünü bir TCP / IP bağlantısı üzerinden kesinlikle kullandığı sürece AI'nızı seçtiğiniz herhangi bir dilde yazabilirsiniz . Tüm kısıtlamaların ayrıntılı bir açıklamasını burada bulabilirsiniz .


Nasıl oynanır

GitHub deposundaki yarışmadan vazgeçerek başlayın . entriesStackExchange kullanıcı adınızı kullanarak adlandırılmış dizine bir dizin ekleyin ve kodunuzu bu klasörde geliştirin. Girişinizi göndermeye hazır olduğunuzda, revizyonlarınız için bir talepte bulunun ve ardından girişinizi bu siteye duyurmak için bu talimatları izleyin .

coreBaşlamak için dizinde bazı kodlar ve JAR'ler sağladım ; malzemeler için kaba bir rehber için siteme bakın . Buna ek olarak, diğer oyuncular ayağa kalkmanıza ve koşmanıza yardımcı olmak için girişlerine ek olarak yardımcı kod gönderiyorlar. Girişleri keşfetmek için biraz zaman ayırın ve göndermeden önce girişinizi başkalarının girişleriyle test etmeyi unutmayın!


Sonuçlar

Place | User         | AI                 | Result
------+--------------+--------------------+-------------------------------------------------------
    1 | gamecoder    | SpockAI            | 55.75%
    2 | Peter Taylor | InferencePlayer    | 33.06%
    3 | jwg          | CluePaddle         | 20.19%
    4 | Peter Taylor | SimpleCluedoPlayer |  8.34%
    5 | gamecoder    | RandomPlayer       |  1.71%
 ---- | ray          | 01                 | Player "ray-01" [3] sent an invalid accuse message: ""

Yukarıdaki sonuçlar her kalifiye AI'nın katıldığı 25.200 geçerli eşleşmeden elde ettiği kazanç yüzdesini göstermektedir. Sonuçlara sayılan toplam 30.000 kibrit vardı ve 01diskalifiye edildiğinde indirilen 6.100 civarında indirim yapıldı.

Onurlu bir söz, Ray’in AI’sına gitmeli 01. İlk testim bunun en güçlü olduğunu gösterdi ve rekabeti kazanmasını bekledim. Bununla birlikte, tahmin edebileceğim kadarıyla olası tüm çözümleri ortadan kaldırmaya yönlendiren çok zaman zaman ortaya çıkan bir hata var gibi görünüyor. Turnuva, üç oyuncunun tüm maçlarını bitirdi ve dört oyuncunun maçlarına (12.000 oyun içeri girdi!) 01Böceğinin hatası ortaya çıktığında başladı. Sadece 3 oyunculu maç sıralamasını göz önünde bulundurursam, sonuçlar şöyle görünür:

Place | User         | AI                 | Result
------+--------------+--------------------+--------
    1 | ray          | 01                 | 72.10%
    2 | gamecoder    | SpockAI            | 51.28%
    3 | Peter Taylor | InferencePlayer    | 39.97%
    4 | Peter Taylor | SimpleCluedoPlayer | 17.65%
    5 | jwg          | CluePaddle         | 16.92%
    6 | gamecoder    | RandomPlayer       |  2.08%

Sonuçlara göre bazı veri madenciliği yapmayı planlamıştım ancak çok yoruldum. Yarışmanın ilerleyişini korumak için yarışma sunucusunun tamamen yeniden yazılmasını gerektiren (güç kesintileri, sistem yeniden başlatmaları) tüm yol boyunca koşma konusunda teknik zorluklar yaşadım. Herhangi birisinin hala ilgilenmesi durumunda oluşturulan tüm sonuç dosyalarıyla birlikte koddaki tüm değişiklikleri yorumlayacağım ve işleme koyacağım. Veri madenciliğini de yapmaya karar verirsem sonuçlarım havuza da eklenecek.


Oynadığınız için teşekkürler!


4
Sunucunuzun bir kopyasını sınava girebilecek katılımcılara sunabilir misiniz?
Peter Taylor

you must accept two port numbers: the first will be the port to which your program will listen, and the second will be the port to which your program will send., Neden iki bağlantı noktası?
Hasturkun

1
@PeterTaylor, yazdığım anda sunucunun bir kopyasını hazırlayacağım. Sence neden bir ay veriyorum? ;)
sadakatsu

@Hasturkun, Sunucu için planladığım mimari, gönderiminize komut satırı ile başlayacağı. Hangi programın hangisi olduğunu kolayca tanımlayabilmesi için mesajların gönderilmesi için her programın hangi bağlantı noktasını kullanacağını seçer (protokolün hiçbir tanımlayıcı içermediğine dikkat edin). Ayrıca, her programın hangi portlara mesaj göndereceğini bilmesi gerekir, böylece sunucunun gerçekte mesajları alabilmesi için. Bunlar, her gönderinin komut satırı argümanları olarak alması gereken iki porttur.
sadakatsu

1
Yazdığım tek ağ programı UDP kullanıyor. Bunun için çalışması için ihtiyaç duyduğum lock-step player güncellemelerini en iyi destekleyen teknolojiyi kullanmak için, ikisi ve (2) arasındaki farkları anlamak için TCP / IP kullanmaya karar verdim.
sadakatsu

Yanıtlar:


5

AI01 - Python 3

Bunun için daha iyi bir isim bulamıyorum:

Tanımlayıcı : ray-ai01

Teknoloji : Python 3

Seçildi : evet

Argümanlar :ai01.py identifier port

Açıklama : Çıkarımla çalışın. Sahibinin bilmediği kartların sayısı bir eşikten az olduğunda, bu AI özyinelemeli küresel çıkarımla imkansız tüm çözümleri ortadan kaldırmaya başlar. Aksi takdirde, yerel çıkarım kullanır.

#!/usr/bin/env python
import itertools

from speedclue.playerproxy import Player, main
from speedclue.cards import CARDS
from speedclue.protocol import BufMessager

# import crash_on_ipy


class Card:
    def __init__(self, name, type):
        self.name = name
        self.possible_owners = []
        self.owner = None
        self.in_solution = False
        self.disproved_to = set()
        self.type = type

    def __repr__(self):
        return self.name

    def log(self, *args, **kwargs):
        pass

    def set_owner(self, owner):
        assert self.owner is None
        assert self in owner.may_have
        for player in self.possible_owners:
            player.may_have.remove(self)
        self.possible_owners.clear()
        self.owner = owner
        owner.must_have.add(self)
        self.type.rest_count -= 1

    def set_as_solution(self):
        # import pdb; pdb.set_trace()
        assert self.owner is None
        self.type.solution = self
        self.in_solution = True
        for player in self.possible_owners:
            player.may_have.remove(self)
        self.possible_owners.clear()
        self.type.rest_count -= 1

    def __hash__(self):
        return hash(self.name)


class CardType:
    def __init__(self, type_id):
        self.type_id = type_id
        self.cards = [Card(name, self) for name in CARDS[type_id]]
        self.rest_count = len(self.cards)
        self.solution = None


class PlayerInfo:
    def __init__(self, id):
        self.id = id
        self.must_have = set()
        self.may_have = set()
        self.selection_groups = []
        self.n_cards = None

    def __hash__(self):
        return hash(self.id)

    def set_have_not_card(self, card):
        if card in self.may_have:
            self.may_have.remove(card)
            card.possible_owners.remove(self)

    def log(self, *args, **kwargs):
        pass

    def update(self):
        static = False
        updated = False
        while not static:
            static = True
            if len(self.must_have) == self.n_cards:
                if not self.may_have:
                    break
                for card in self.may_have:
                    card.possible_owners.remove(self)
                self.may_have.clear()
                static = False
                updated = True
            if len(self.must_have) + len(self.may_have) == self.n_cards:
                static = False
                updated = True
                for card in list(self.may_have):
                    card.set_owner(self)

            new_groups = []
            for group in self.selection_groups:
                group1 = []
                for card in group:
                    if card in self.must_have:
                        break
                    if card in self.may_have:
                        group1.append(card)
                else:
                    if len(group1) == 1:
                        group1[0].set_owner(self)
                        updated = True
                        static = False
                    elif group1:
                        new_groups.append(group1)
            self.selection_groups = new_groups

            if len(self.must_have) + 1 == self.n_cards:
                # There is only one card remain to be unknown, so this card must
                # be in all selection groups
                cards = self.may_have.copy()
                for group in self.selection_groups:
                    if self.must_have.isdisjoint(group):
                        cards.intersection_update(group)

                for card in self.may_have - cards:
                    static = False
                    updated = True
                    self.set_have_not_card(card)

        # assert self.must_have.isdisjoint(self.may_have)
        # assert len(self.must_have | self.may_have) >= self.n_cards
        return updated


class Suggestion:
    def __init__(self, player, cards, dplayer, dcard):
        self.player = player
        self.cards = cards
        self.dplayer = dplayer
        self.dcard = dcard
        self.disproved = dplayer is not None


class AI01(Player):
    def prepare(self):
        self.set_verbosity(0)

    def reset(self, player_count, player_id, card_names):
        self.log('reset', 'id=', player_id, card_names)
        self.fail_count = 0
        self.suggest_count = 0
        self.card_types = [CardType(i) for i in range(len(CARDS))]
        self.cards = list(itertools.chain(*(ct.cards for ct in self.card_types)))
        for card in self.cards:
            card.log = self.log
        self.card_map = {card.name: card for card in self.cards}
        self.owned_cards = [self.card_map[name] for name in card_names]
        self.players = [PlayerInfo(i) for i in range(player_count)]
        for player in self.players:
            player.log = self.log
        self.player = self.players[player_id]
        for card in self.cards:
            card.possible_owners = list(self.players)
        n_avail_cards = len(self.cards) - len(CARDS)
        for player in self.players:
            player.may_have = set(self.cards)
            player.n_cards = n_avail_cards // player_count \
                + (player.id < n_avail_cards % player_count)
        for card in self.owned_cards:
            card.set_owner(self.player)
        for card in self.cards:
            if card not in self.owned_cards:
                self.player.set_have_not_card(card)
        self.suggestions = []
        self.avail_suggestions = set(itertools.product(*CARDS))
        self.possible_solutions = {
            tuple(self.get_cards_by_names(cards)): 1
            for cards in self.avail_suggestions
        }
        self.filter_solutions()

    def filter_solutions(self):
        new_solutions = {}
        # assert self.possible_solutions
        join = next(iter(self.possible_solutions))
        for sol in self.possible_solutions:
            for card, type in zip(sol, self.card_types):
                if card.owner or type.solution and card is not type.solution:
                    # This candidate can not be a solution because it has a
                    # card that has owner or this type is solved.
                    break
            else:
                count = self.check_solution(sol)
                if count:
                    new_solutions[sol] = count
                    join = tuple(((x is y) and x) for x, y in zip(join, sol))
        self.possible_solutions = new_solutions
        updated = False
        for card in join:
            if card and not card.in_solution:
                card.set_as_solution()
                updated = True
                self.log('found new target', card, 'in', join)

        # self.dump()
        return updated

    def check_solution(self, solution):
        """
        This must be called after each player is updated.
        """
        players = self.players
        avail_cards = set(card for card in self.cards if card.possible_owners)
        avail_cards -= set(solution)
        if len(avail_cards) >= 10:
            return 1
        count = 0

        def resolve_player(i, avail_cards):
            nonlocal count
            if i == len(players):
                count += 1
                return
            player = players[i]
            n_take = player.n_cards - len(player.must_have)
            cards = avail_cards & player.may_have
            for choice in map(set, itertools.combinations(cards, n_take)):
                player_cards = player.must_have | choice
                for group in player.selection_groups:
                    if player_cards.isdisjoint(group):
                        # Invalid choice
                        break
                else:
                    resolve_player(i + 1, avail_cards - choice)

        resolve_player(0, avail_cards)
        return count

    def suggest1(self):
        choices = []
        for type in self.card_types:
            choices.append([])
            if type.solution:
                choices[-1].extend(self.player.must_have & set(type.cards))
            else:
                choices[-1].extend(sorted(
                    (card for card in type.cards if card.owner is None),
                    key=lambda card: len(card.possible_owners)))

        for sgi in sorted(itertools.product(*map(lambda x:range(len(x)), choices)),
                key=sum):
            sg = tuple(choices[i][j].name for i, j in enumerate(sgi))
            if sg in self.avail_suggestions:
                self.avail_suggestions.remove(sg)
                break
        else:
            sg = self.avail_suggestions.pop()
            self.fail_count += 1
            self.log('fail')
        self.suggest_count += 1
        return sg

    def suggest(self):
        sg = []
        for type in self.card_types:
            card = min((card for card in type.cards if card.owner is None),
                key=lambda card: len(card.possible_owners))
            sg.append(card.name)
        sg = tuple(sg)

        if sg not in self.avail_suggestions:
            sg = self.avail_suggestions.pop()
        else:
            self.avail_suggestions.remove(sg)
        return sg

    def suggestion(self, player_id, cards, disprove_player_id=None, card=None):
        sg = Suggestion(
            self.players[player_id],
            self.get_cards_by_names(cards),
            self.players[disprove_player_id] if disprove_player_id is not None else None,
            self.card_map[card] if card else None,
        )
        self.suggestions.append(sg)
        # Iter through the non-disproving players and update their may_have
        end_id = sg.dplayer.id if sg.disproved else sg.player.id
        for player in self.iter_players(sg.player.id + 1, end_id):
            if player is self.player:
                continue
            for card in sg.cards:
                player.set_have_not_card(card)
        if sg.disproved:
            # The disproving player has sg.dcard
            if sg.dcard:
                if sg.dcard.owner is None:
                    sg.dcard.set_owner(sg.dplayer)
            else:
                # Add a selection group to the disproving player
                sg.dplayer.selection_groups.append(sg.cards)
            self.possible_solutions.pop(tuple(sg.cards), None)

        self.update()

    def update(self):
        static = False
        while not static:
            static = True
            for card in self.cards:
                if card.owner is not None or card.in_solution:
                    continue
                if len(card.possible_owners) == 0 and card.type.solution is None:
                    # In solution
                    card.set_as_solution()
                    static = False

            for type in self.card_types:
                if type.solution is not None:
                    continue
                if type.rest_count == 1:
                    card = next(card for card in type.cards if card.owner is None)
                    card.set_as_solution()
                    static = False

            for player in self.players:
                if player is self.player:
                    continue
                if player.update():
                    static = False

            if self.filter_solutions():
                static = False

    def iter_players(self, start_id, end_id):
        n = len(self.players)
        for i in range(start_id, start_id + n):
            if i % n == end_id:
                break
            yield self.players[i % n]

    def accuse(self):
        if all(type.solution for type in self.card_types):
            return [type.solution.name for type in self.card_types]
        possible_solutions = self.possible_solutions
        if len(possible_solutions) == 1:
            return next(possible_solutions.values())
        # most_possible = max(self.possible_solutions, key=self.possible_solutions.get)
        # total = sum(self.possible_solutions.values())
        # # self.log('rate:', self.possible_solutions[most_possible] / total)
        # if self.possible_solutions[most_possible] > 0.7 * total:
        #     self.log('guess', most_possible)
        #     return [card.name for card in most_possible]
        return None

    def disprove(self, suggest_player_id, cards):
        cards = self.get_cards_by_names(cards)
        sg_player = self.players[suggest_player_id]
        cards = [card for card in cards if card in self.owned_cards]
        for card in cards:
            if sg_player in card.disproved_to:
                return card.name
        return max(cards, key=lambda c: len(c.disproved_to)).name

    def accusation(self, player_id, cards, is_win):
        if not is_win:
            cards = tuple(self.get_cards_by_names(cards))
            self.possible_solutions.pop(cards, None)
            # player = self.players[player_id]
            # for card in cards:
            #     player.set_have_not_card(card)
            # player.update()
        else:
            self.log('fail rate:', self.fail_count / (1e-8 + self.suggest_count))
            self.log('fail count:', self.fail_count, 'suggest count:', self.suggest_count)

    def get_cards_by_names(self, names):
        return [self.card_map[name] for name in names]

    def dump(self):
        self.log()
        for player in self.players:
            self.log('player:', player.id, player.n_cards,
                sorted(player.must_have, key=lambda x: x.name),
                sorted(player.may_have, key=lambda x: x.name),
                '\n    ',
                player.selection_groups)
        self.log('current:', [type.solution for type in self.card_types])
        self.log('possible_solutions:', len(self.possible_solutions))
        for sol, count in self.possible_solutions.items():
            self.log('  ', sol, count)
        self.log('id|', end='')

        def end():
            return ' | ' if card.name in [g[-1] for g in CARDS] else '|'

        for card in self.cards:
            self.log(card.name, end=end())
        self.log()
        for player in self.players:
            self.log(' *'[player.id == self.player.id] + str(player.id), end='|')
            for card in self.cards:
                self.log(
                    ' ' + 'xo'[player in card.possible_owners or player is card.owner],
                    end=end())
            self.log()


if __name__ == '__main__':
    main(AI01, BufMessager)

AI kodu burada bulunabilir .


AI'nızla bir çekme isteği yapabilir misiniz? Yarışma deposuna almak istiyorum.
sadakatsu

@ gamecoder Daha güçlü bir AI01 yaptım ve bir çekme isteği yolladım.
Ray,

1
Halen işler geçerli olduğu için 01'iniz en güçlüsüdür. Çalıştığım denemelerde, sürekli olarak rekabet ettiği maçların ~% 67'sini kazanıyor. Yarışma sona ermeden önce onu zorlayabilecek sağlam girişler göreceğimizi umuyorum.
sadakatsu

Şuna bak SpockAI. Karşı oldukça iyi bir performans sergiliyor 01. Rekabeti kazanıp kazanamayacağını bilmiyorum, ama kazanma sayınızı düşürdüğüme sevindim; )
sadakatsu

@ gamecoder Aslında AI'mı birkaç gün önce yeni kurallara göre güncelledim. Yeni girişini gördüğüme sevindim. İyi performans gösteriyor gibi görünüyor ama yetersiz olması nedeniyle defalarca test etmedim. Belki daha hızlı hale getirebilirsin, böylece daha kolay test edebiliriz.
Ray,

4

SimpleCluedoPlayer.java

Bu sınıf AbstractCluedoPlayer, tüm G / Ç işlemlerini yürüten ve mantığın basit bir yazılı arayüz ile çalışmasına izin veren kullanır. Her şey github'da .

Bu, yüksek olasılıkla rastgele oyuncuyu yener (en kötü durumda, 15 öneri alır, rastgele oyuncunun ortalaması ise 162 olur), ancak kolayca yenilebilir. Topun yuvarlanmasını sağlamak için öneriyorum.

package org.cheddarmonk.cluedoai;

import java.io.IOException;
import java.io.PrintStream;
import java.net.UnknownHostException;
import java.util.*;

/**
 * A simple player which doesn't try to make inferences from partial information.
 * It merely tries to maximise the information gain by always making suggestions involving cards which
 * it does not know to be possessed by a player, and to minimise information leakage by recording who
 * has seen which of its own cards.
 */
public class SimpleCluedoPlayer extends AbstractCluedoPlayer {
    private Map<CardType, Set<Card>> unseenCards;
    private Map<Card, Integer> shownBitmask;
    private Random rnd = new Random();

    public SimpleCluedoPlayer(String identifier, int serverPort) throws UnknownHostException, IOException {
        super(identifier, serverPort);
    }

    @Override
    protected void handleReset() {
        unseenCards = new HashMap<CardType, Set<Card>>();
        for (Map.Entry<CardType, Set<Card>> e : Card.byType.entrySet()) {
            unseenCards.put(e.getKey(), new HashSet<Card>(e.getValue()));
        }

        shownBitmask = new HashMap<Card, Integer>();
        for (Card myCard : myHand()) {
            shownBitmask.put(myCard, 0);
            unseenCards.get(myCard.type).remove(myCard);
        }
    }

    @Override
    protected Suggestion makeSuggestion() {
        return new Suggestion(
            selectRandomUnseen(CardType.SUSPECT),
            selectRandomUnseen(CardType.WEAPON),
            selectRandomUnseen(CardType.ROOM));
    }

    private Card selectRandomUnseen(CardType type) {
        Set<Card> candidates = unseenCards.get(type);
        Iterator<Card> it = candidates.iterator();
        for (int idx = rnd.nextInt(candidates.size()); idx > 0; idx--) {
            it.next();
        }
        return it.next();
    }

    @Override
    protected Card disproveSuggestion(int suggestingPlayerIndex, Suggestion suggestion) {
        Card[] byNumShown = new Card[playerCount()];
        Set<Card> hand = myHand();
        int bit = 1 << suggestingPlayerIndex;
        for (Card candidate : suggestion.cards()) {
            if (!hand.contains(candidate)) continue;

            int bitmask = shownBitmask.get(candidate);
            if ((bitmask & bit) == bit) return candidate;
            byNumShown[Integer.bitCount(bitmask)] = candidate;
        }

        for (int i = byNumShown.length - 1; i >= 0; i--) {
            if (byNumShown[i] != null) return byNumShown[i];
        }

        throw new IllegalStateException("Unreachable");
    }

    @Override
    protected void handleSuggestionResponse(Suggestion suggestion, int disprovingPlayerIndex, Card shown) {
        if (shown != null) unseenCards.get(shown.type).remove(shown);
        else {
            // This player never makes a suggestion with cards from its own hand, so we're ready to accuse.
            unseenCards.put(CardType.SUSPECT, Collections.singleton(suggestion.suspect));
            unseenCards.put(CardType.WEAPON, Collections.singleton(suggestion.weapon));
            unseenCards.put(CardType.ROOM, Collections.singleton(suggestion.room));
        }
    }

    @Override
    protected void recordSuggestionResponse(int suggestingPlayerIndex, Suggestion suggestion, Card shown) {
        shownBitmask.put(shown, shownBitmask.get(shown) | (1 << suggestingPlayerIndex));
    }

    @Override
    protected void recordSuggestionResponse(int suggestingPlayerIndex, Suggestion suggestion, int disprovingPlayerIndex) {
        // Do nothing.
    }

    @Override
    protected Suggestion makeAccusation() {
        Set<Card> suspects = unseenCards.get(CardType.SUSPECT);
        Set<Card> weapons = unseenCards.get(CardType.WEAPON);
        Set<Card> rooms = unseenCards.get(CardType.ROOM);
        if (suspects.size() * weapons.size() * rooms.size()  == 1) {
            return new Suggestion(suspects.iterator().next(), weapons.iterator().next(), rooms.iterator().next());
        }

        return null;
    }

    @Override
    protected void recordAccusation(int accusingPlayer, Suggestion accusation, boolean correct) {
        // Do nothing.
    }

    //*********************** Public Static Interface ************************//
    public static void main(String[] args) throws Exception {
        try {
            System.setOut(new PrintStream("/tmp/speed-cluedo-player" + args[0]+".log"));
            new SimpleCluedoPlayer(args[0], Integer.parseInt(args[1])).run();
        } catch (Throwable th) {
            th.printStackTrace(System.out);
        }
    }
}

Çok güzel, temiz kod. Test sunucusuna veya rastgele bir oyuncuya bakan birinin böyle hissettiğinden şüpheliyim. Ayrıca bundan daha basit bir AI olamayacağınızı düşünüyorum ^ _ ^
sadakatsu

4

SpockAI

Tanımlayıcı: gamecoder-SpockAI

Repo Girişi: buraya tıklayın

Seçildi: Evet

Teknoloji: Java 7 tabanlıcom.sadakatsu.clue.jar

Argümanlar: {identifier} portNumber [logOutput: true|false]

Açıklama:

SpockAIKnowledgeyazdığım bir sınıf üzerine kurulmuş bir Speed ​​Clue oyuncusu . KnowledgeSınıf şimdiye kadar ne oldu oyun verdik ki tüm olası durumları temsil eder. Oyunun çözümlerini ve oyuncuların muhtemel ellerini setler olarak temsil eder ve bu setleri bir şey öğrenildiğinde mümkün olduğunca azaltmak için yinelemeli indirimler kullanır. SpockAIEn yararlı en kötü sonuçların alınabileceği konusunda hangi önerilerin sağlanacağını belirlemek için bu sınıfı kullanır ve sırayla bu önerilerden birini rastgele seçer. Bir öneriyi reddetmesi gerektiğinde, ya öneren AI'yı gösterdiği bir kartı göstermeye ya da olasılıkları en az azalttığı kategoriden bir kart göstermeye çalışır. Sadece çözümü bildiği zaman suçlamada bulunur.

En iyi öneriyi belirlemek için kullandığım sezgisel çözüm aşağıdaki gibidir. Bir öneriden gelen tüm bilgiler öğrenildikten sonra, olası çözüm ve olası oyuncu el setleri azaltılmış olacaktır (öneri yeni bir bilgi vermediyse). Teorik olarak, en iyi öneri, olası çözümlerin sayısını en çok azaltan önermedir. Beraberlik durumunda, oyuncular için olası el sayısını en çok azaltan bir önerinin daha iyi olduğunu varsayarım. Bu yüzden, her öneri için, bilgide bir çelişki yaratmayan her olası sonucu denerim. Hangi sonuç, çözüm / el sayısındaki en az iyileşmeye sahipse, önerinin alacağı sonuç olarak kabul edilir. Sonra tüm önerilerin sonuçlarını karşılaştırdım ve hangisinin en iyi sonucu verdiğini seçiyorum. Bu şekilde, en kötü durumdan en iyi bilgi kazanımını garanti ederim.

Daha SpockAIda güçlü hale getirmek için olası çözümlerin ve olası oyuncu ellerinin kaba kuvvet birleşimini analiz etmeyi düşünüyorum , ancak SpockAIzaten en yavaş, en çok kaynak yoğun giriş olduğundan, muhtemelen bunu atlayacağım.

Yasal Uyarı:

Bu yarışmaya haftalar önce bir AI yayınlamayı planlamıştım. Halen, AI'mı geçen hafta Cuma'ya kadar yazmaya başlayamadım ve kodumda saçma hatalar bulmaya devam ettim. Bu nedenle SpockAI, son başvuru tarihinden önce çalışabilmemin tek yolu büyük bir iplik havuzu kullanmaktı. Sonuçta (şu anda) SpockAI +% 90 CPU kullanımı ve 2GB + bellek kullanımı (bunun için çöp toplayıcıyı suçlamam rağmen) vurabilir. SpockAIYarışmaya katılmayı düşünüyorum , ancak diğerleri bunun kurallara aykırı olduğunu düşünürlerse , ikinci sırayı "kazanan" unvanını SpockAIkazanacağım. Bu şekilde hissediyorsanız, lütfen bu cevap üzerindeki etkisine yorum yapın.


3

InferencePlayer.java

Github'daki tam kod (not: bu AbstractCluedoPlayerbenim öncekilerle aynı SimpleCluedoPlayer).

Bu oyuncunun asıl özü PlayerInformationsınıfıdır (burada hafifçe kesilmiş):

private static class PlayerInformation {
    final PlayerInformation[] context;
    final int playerId;
    final int handSize;
    Set<Integer> clauses = new HashSet<Integer>();
    Set<Card> knownHand = new HashSet<Card>();
    int possibleCards;
    boolean needsUpdate = false;

    public PlayerInformation(PlayerInformation[] context, int playerId, boolean isMe, int handSize, Set<Card> myHand) {
        this.context = context;
        this.playerId = playerId;
        this.handSize = handSize;
        if (isMe) {
            knownHand.addAll(myHand);
            possibleCards = 0;
            for (Card card : knownHand) {
                int cardMask = idsByCard.get(card);
                clauses.add(cardMask);
                possibleCards |= cardMask;
            }
        }
        else {
            possibleCards = allCardsMask;
            for (Card card : myHand) {
                possibleCards &= ~idsByCard.get(card);
            }

            if (playerId == -1) {
                // Not really a player: this represents knowledge about the solution.
                // The solution contains one of each type of card.
                clauses.add(suspectsMask & possibleCards);
                clauses.add(weaponsMask & possibleCards);
                clauses.add(roomsMask & possibleCards);
            }
        }
    }

    public void hasCard(Card card) {
        if (knownHand.add(card)) {
            // This is new information.
            needsUpdate = true;
            clauses.add(idsByCard.get(card));

            // Inform the other PlayerInformation instances that their player doesn't have the card.
            int mask = idsByCard.get(card);
            for (PlayerInformation pi : context) {
                if (pi != this) pi.excludeMask(mask);
            }

            if (knownHand.size() == handSize) {
                possibleCards = mask(knownHand);
            }
        }
    }

    public void excludeMask(int mask) {
        if (knownHand.size() == handSize) return; // We can't benefit from any new information.

        if ((mask & possibleCards) != 0) {
            // The fact that we have none of the cards in the mask contains some new information.
            needsUpdate = true;
            possibleCards &= ~mask;
        }
    }

    public void disprovedSuggestion(Suggestion suggestion) {
        if (knownHand.size() == handSize) return; // We can't benefit from any new information.

        // Exclude cards which we know the player doesn't have.
        needsUpdate = clauses.add(mask(suggestion.cards()) & possibleCards);
    }

    public void passedSuggestion(Suggestion suggestion) {
        if (knownHand.size() == handSize) return; // We can't benefit from any new information.

        excludeMask(mask(suggestion.cards()));
    }

    public boolean update() {
        if (!needsUpdate) return false;

        needsUpdate = false;

        // Minimise the clauses, step 1: exclude cards which the player definitely doesn't have.
        Set<Integer> newClauses = new HashSet<Integer>();
        for (int clause : clauses) {
            newClauses.add(clause & possibleCards);
        }
        clauses = newClauses;

        if (clauses.contains(0)) throw new IllegalStateException();

        // Minimise the clauses, step 2: where one clause is a superset of another, discard the less specific one.
        Set<Integer> toEliminate = new HashSet<Integer>();
        for (int clause1 : clauses) {
            for (int clause2 : clauses) {
                if (clause1 != clause2 && (clause1 & clause2) == clause1) {
                    toEliminate.add(clause2);
                }
            }
        }
        clauses.removeAll(toEliminate);

        // Every single-card clause is a known card: update knownHand if necessary.
        for (int clause : clauses) {
            if (((clause - 1) & clause) == 0) {
                Card singleCard = cardsById.get(clause);
                hasCard(cardsById.get(clause));
            }
        }

        // Every disjoint set of clauses of size equal to handSize excludes all cards not in the union of that set.
        Set<Integer> disjoint = new HashSet<Integer>(clauses);
        for (int n = 2; n <= handSize; n++) {
            Set<Integer> nextDisjoint = new HashSet<Integer>();
            for (int clause : clauses) {
                for (int set : disjoint) {
                    if ((set & clause) == 0) nextDisjoint.add(set | clause);
                }
            }
            disjoint = nextDisjoint;
        }

        for (int set : disjoint) excludeMask(~set);

        return true;
    }
}

Müzikçaların onaylamadığı önerileri (bu kartlardan hiçbirine sahip olmadıklarını gösterir), önermediği önerileri (bu kartlardan en az birini tuttuklarını belirtir) ve konumu kesin olan kartları birleştirir. Daha sonra, bu bilgiyi özüne sıkıştırmak için bazı temel kuralları tekrar eder.

Bir şeyleri gözden kaçırmış olmama rağmen, elde edilebilecek daha belirleyici bir bilgi olduğunu sanmıyorum (yanlış suçlamalar, bunlarla uğraşmayacak kadar nadir olduğunu tahmin ediyorum). Orada olan oyuncu X kartı Y sahip olduğunu tahmin olasılıkları daha gelişmiş bir oyuncu için potansiyel ...

Muhtemelen önemli bir iyileşme sağlayan diğer alan, hangi öneride bulunacağına karar vermektir. Oldukça sarhoş ağır kuvvet yaklaşımı kullanarak bilgi kazancını maksimize etmeye çalışıyorum, ancak farklı varsayımsal engellemeden edinilen bilgilerin göreceli değerlerini değerlendirmede çok haklı çıkarımlı bir buluş var. Ancak, bir başkası layık bir rakip gönderinceye kadar sezgisel taramayı denemeyeceğim.


SimpleCluedoPlayer'ınızı veya InferencePlayer'ınızı çalıştıramazsınız. "SpeedClueContest / input / peter_taylor /" dizininde karınca koştum ve JAR'ları başarıyla oluşturdum. Bu JAR'lara olan göreceli ve mutlak yolları denedim, bu sırada onları "tanımlayıcı" ve "portNumber" olarak geçirdim, ancak TestServer bunların her biri için "canlı tanımlayıcı" mesajını bekliyor. Aradım ve "/tmp/speed-cluedo-player"+identifier+".log" dosyasını bulamıyorum. Bir şekilde süreci batırdım mı?
sadakatsu

@ gamecoder, muhtemelen kod yazmamalıyım /tmp. Basit bir yama olmalı; Kısa süre içinde inceleyeceğim.
Peter Taylor

1
Düzeltmeniz işe yarıyor; Depoya dahil ettim. Şimdi, InferencePlayer ile dikkatlice okudum ve onunla arasında çalışmaya başladığım LogicalAI ile yeterince fark olduğundan emin olmak için> ___ <
sadakatsu

İşte rakibiniz :-) geliyor
Ray

@Ray, mükemmel. AI'nızı parçalara ayırmaya çalışacağım ve bir noktada benimkinden nasıl farklılaştığını göreceğim: bir bakışta, benzer bir analiz kullanıyor gibi görünüyor.
Peter Taylor

2

CluePaddle (ClueStick / ClueBat / ClueByFour) - C #

AI'leri uygulamanın kolay olduğu bir C # istemcisi olan ClueBot ve CluePaddle adı verilen en ciddi girişim dahil olmak üzere çeşitli AI'lar yazdım. Kod, https://github.com/jwg4/SpeedClueContest/tree/clue_paddle adresinde bir çekme isteği ile kodları yukarı akışta birleştirmeye başladı.

ClueStick, temelde olanların çoğunu sadece tahmin eden ve görmezden gelen bir kavram kanıtıdır. ClueBat, sahte suçlamalar yapmaya zorlamak için ClueStick'teki bir kusuru kullanmaya çalışmasının dışında bir başka aptal AI. ClueByFour, makul önerilerde bulunur ve makul önerilerde bulunur ve başkaları tarafından gösterilen kartları hatırlar.

CluePaddle en zekidir. Sadece hangi tekliflere dayanarak teklif edildiğine değil, hangi oyuncuların belirli bir öneri almama teklifine dayanmadığına dayanarak kimin kime sahip olduğunu bulmaya çalışır. Her oyuncunun atm kartı olduğu dikkate alınmaz, ancak bu düzeltilmelidir. Birkaç epeyce ders içeriyor, bu yüzden tüm kodu buraya göndermeyeceğim, ancak aşağıdaki yöntem bir lezzet veriyor.

public void Suggestion(int suggester, MurderSet suggestion, int? disprover, Card disproof)
{
  List<int> nonDisprovers = NonDisprovers(suggester, disprover).ToList();

  foreach (var player in nonDisprovers)
  {
    m_cardTracker.DoesntHaveAnyOf(player, suggestion);
  }

  if (disprover != null && disproof == null)
  {
    // We know who disproved it but not what they showed.
    Debug.Assert(disprover != m_i, "The disprover should see the disproof");
    Debug.Assert(suggester != m_i, "The suggester should see the disproof");
    m_cardTracker.DoesntHaveAllOf(suggester, suggestion);
    m_cardTracker.HasOneOf((int)disprover, suggestion);
  }

  if (disproof != null)
  {
    // We know who disproved it and what they showed.
    Debug.Assert(disprover != null, "disproof is not null but disprover is null");
    m_cardTracker.DoesHave((int)disprover, disproof.Value);
  }
}

4 birbirlerine karşı oynarsa, CluePaddle en fazla oyunu kazanır, ClueByFour ikinci ve diğer hiçbir yerde.

Sadece CluePaddle (şimdiye kadar) rekabetçi bir giriştir. Kullanımı:

CluePaddle.exe identifier port

Bir başkası bir C # AI yapmak istiyorsa, sadece çözüm yolunda bir ConsoleApplication projesi oluşturun, IClueAIarayüzü bir sınıfta uygulayın ve sonra diğer projeler için ne yaptığınızı Programtüretip ProgramTemplatekopyalayın Main(). Tek bağımlılık birim testi için NUnit'tir ve tüm testleri koddan kolayca kaldırabilirsiniz (ancak yapma, NUnit'i yüklemeyin).


AI'larınızı derlemeye ve bunları ContestServer'da test etmeye çalıştım (yakında yayınlanacak). CluePaddleProje iddia ederek, derleme değil NUnitdiğer projeler derleme yapmak halde yüklü değildir. Derleme yapanlar test sırasında durur ve Java bir bağlantı sıfırlama hatası rapor eder. Yanlış bir şey yapıp yapmadığımı belirlememe yardım eder misin?
sadakatsu

Düzeltme: ClueStickBaşlatmaya çalıştığımda durduran tek AI. Diğer ikisi deneme turnuvasında yarışır ve sonunda aynı ihlallerden diskalifiye edilir. ClueByFourKartlardan herhangi birini tutmadığında suçlama olarak yaptığı kanıtlanmamış bir öneriyi tekrarlamamaktan dolayı diskalifiye olur. ClueBatgösterdiği veya elindeki kartları olan suçlamaları yapmaktan diskalifiye olur. Lütfen uyumluluğu sağlamak için revize edilmiş AI kısıtlamalarına göz atın.
sadakatsu

@ gamecoder NUnit kurulu mu? Yükleyemezseniz şartlı olarak birim test kodunu kaldırabilirim. CluePaddle benim gerçek girişim - Diğer ikisi tarafından yapılan ihlallerden endişe duymuyorum çünkü gerçekten kazanmak için oynamadıkları için.
jwg

Ayrıca bazı güncellemeler var CluePaddle. Bunlar için daha sonra talepte bulunacağım.
jwg

NUnit'i yükledim. Diğer projelerinizin referanslarını kullanarak MSVS'deki ad alanlarını keşfedebildim. Çekme isteğinizi dört gözle bekliyorum.
sadakatsu
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.