King of the Hill: Speed ​​Clue AI


24

Indizio di velocità

Cluedo / Clue è un classico gioco da tavolo con un avvincente componente di gioco a deduzione. Speed ​​Clue è una variante per 3-6 giocatori che enfatizza questo componente usando solo le carte. Il risultato è che l'unica differenza tra Cluedo standard e Speed ​​Clue è che ogni giocatore ancora in gioco può dare qualsiasi suggerimento gli piaccia nel suo turno invece di aspettare di raggiungere una stanza specifica in balia dei tiri di dado e dei suggerimenti di altri giocatori. Se non hai mai giocato a Cluedo prima o vuoi essere certo delle differenze esplicite tra le due versioni, potresti trovare qui un set completo di regole di Speed ​​Clue .


Obbiettivo

Scrivi e invia un programma AI per giocare a Speed ​​Clue prima del 15 maggio 2014 00:00 GMT. Dopo quel tempo, gestirò un torneo usando tutte le voci legali. Il concorrente la cui AI vince il maggior numero di partite nel torneo vince la sfida.


Specifiche AI

Puoi scrivere la tua IA praticamente in qualsiasi lingua tu scelga, usando qualunque tecnica tu usi, purché usi rigorosamente il protocollo dell'applicazione su una connessione TCP / IP per giocare con il server. Una spiegazione dettagliata di tutte le restrizioni è disponibile qui .


Come giocare

Inizia biforcando il repository GitHub del concorso . Aggiungi una directory nella entriesdirectory denominata usando il tuo nome utente StackExchange e sviluppa il tuo codice in quella cartella. Quando sei pronto per inviare la tua iscrizione, fai una richiesta pull con le tue revisioni, quindi segui queste istruzioni per annunciare la tua iscrizione su questo sito.

Ho fornito un po 'di codice e JAR nella coredirectory per iniziare; consultare il mio sito per una guida approssimativa per i materiali. Inoltre, altri giocatori stanno inviando un codice di supporto oltre alle loro voci per aiutarti a metterti in funzione. Prenditi un po 'di tempo per esplorare le voci e non dimenticare di testare la tua voce rispetto alle voci di altri prima di inviare!


risultati

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: ""

I risultati sopra mostrano la percentuale di vincita che ogni AI qualificato aveva delle 25.200 partite valide a cui ha partecipato. Ci sono state 30.000 partite in totale che hanno contato per i risultati e 6.100 o giù di lì che sono state scontate quando è 01stato squalificato.

Una menzione d'onore deve andare all'intelligenza 01artificiale di ray . I miei test iniziali hanno dimostrato che era il più forte e mi aspettavo che vincesse la competizione. Tuttavia, sembra avere un bug molto intermittente che, per quanto posso immaginare, lo porta ad eliminare tutte le possibili soluzioni. Il torneo aveva terminato tutte le partite a tre giocatori e aveva iniziato le partite a quattro giocatori (12.000 partite in!) Quando 01è stato rivelato il bug. Se considero solo la classifica delle partite per 3 giocatori, i risultati sembrano così:

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%

Avevo programmato di fare un po 'di data mining sui risultati, ma sono esausto. Ho avuto difficoltà tecniche nel far correre la competizione fino in fondo (interruzioni di corrente, riavvii di sistema) che hanno reso necessario riscrivere completamente il server del concorso per salvare i suoi progressi mentre procedeva. Commenterò e commetterò tutte le modifiche al codice con tutti i file dei risultati che sono stati generati nel caso qualcuno fosse ancora interessato. Se decido di eseguire anche il data mining, i miei risultati verranno aggiunti anche al repository.


Grazie per aver giocato!


4
Puoi mettere a disposizione una copia del tuo server per la prova da parte dei partecipanti?
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., Perché due porte?
Hasturkun,

1
@PeterTaylor, renderò disponibile una copia del server non appena l'ho scritta. Perché pensi che stia dando un mese? ;)
sadakatsu,

@Hasturkun, L'architettura che ho pianificato per il server è che inizierà i tuoi invii tramite riga di comando. Sceglierà quale porta verrà utilizzata da ciascun programma per inviargli messaggi in modo che possa facilmente identificare quale programma è quale (notare che il protocollo non include alcun identificatore). Inoltre, ogni programma deve sapere a quale porta inviare i messaggi in modo che il server possa effettivamente ricevere messaggi. Queste sono le due porte che ogni invio deve ricevere come argomenti della riga di comando.
Sadakatsu,

1
L'unico programma di rete che ho scritto utilizza UDP. Ho deciso di utilizzare TCP / IP per (1) comprendere le differenze tra i due e (2) per utilizzare la tecnologia che supporta al meglio gli aggiornamenti del lettore passo-passo di cui ho bisogno per farlo funzionare.
Sadakatsu,

Risposte:


5

AI01 - Python 3

Non riesco ancora a trovare un nome migliore per questo :-P.

Identificatore : ray-ai01

Tecnologia : Python 3

Selezionato : si

Argomenti :ai01.py identifier port

Descrizione : lavoro per inferenza. Quando il numero di carte che il proprietario non è noto è inferiore a una soglia, questa IA inizia a eliminare tutte le soluzioni impossibili da un'inferenza globale ricorsiva. Altrimenti, usa l'inferenza locale.

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

Il codice AI può essere trovato qui .


Potresti fare una richiesta pull con la tua IA? Vorrei inserirlo nel repository del contest.
Sadakatsu,

@gamecoder Ho realizzato un AI01 più forte e ho inviato una richiesta pull.
Ray,

1
Allo stato attuale, il tuo 01 è il più forte. Nelle prove che corro, vince costantemente ~ 67% delle partite in cui compete. Spero che vedremo alcune voci solide prima della fine del concorso che possano sfidarlo.
Sadakatsu,

Dai un'occhiata al mio SpockAI. Si comporta abbastanza bene contro 01. Non so se vincerà la competizione, ma sono contento di vedere il tuo conteggio delle vittorie ridotto; )
sadakatsu,

@gamecoder In realtà, ho aggiornato la mia IA diversi giorni fa secondo le nuove regole. Sono felice di vedere la tua nuova voce. Sembra funzionare bene ma non l'ho provato molte volte a causa dell'inefficienza. Forse puoi renderlo più veloce in modo che siamo più facili da testare.
Ray,

4

SimpleCluedoPlayer.java

Questa classe utilizza AbstractCluedoPlayer, che gestisce tutti gli I / O e consente alla logica di funzionare con una semplice interfaccia tipizzata. Il tutto è su Github .

Questo batte il giocatore casuale con alta probabilità (nel peggiore dei casi prende 15 suggerimenti, mentre il giocatore casuale prende una media di 162), ma sarà facilmente battuto. Lo offro per far rotolare la palla.

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

Codice molto bello e pulito. Dubito che chiunque guardi il server di prova o il giocatore casuale si senta così. Penso anche che non puoi avere un'intelligenza artificiale più semplice di questa ^ _ ^
sadakatsu,

4

SpockAI

Identifier: gamecoder-SpockAI

Voce Repo: clicca qui

Selezionato:

Tecnologia: Java 7 basato sucom.sadakatsu.clue.jar

Argomenti: {identifier} portNumber [logOutput: true|false]

Descrizione:

SpockAIè un giocatore di Speed ​​Clue costruito su una classe chiamata Knowledgeche ho scritto. La Knowledgeclasse rappresenta tutti i possibili stati che il gioco avrebbe potuto dare ciò che è successo finora. Rappresenta le soluzioni del gioco e le possibili mani dei giocatori come set e utilizza deduzioni iterative per ridurre questi set il più possibile ogni volta che qualcosa viene appreso. SpockAIusa questa classe per determinare quali suggerimenti sono certi di avere i risultati peggiori più utili e seleziona casualmente uno di quei suggerimenti a sua volta. Quando deve confutare un suggerimento, tenta di mostrare una carta che ha già mostrato l'intelligenza artificiale suggerente o di mostrarle una carta della categoria per la quale ha ridotto al minimo le possibilità. Accusa solo quando conosce la soluzione.

L'euristica che ho usato per determinare il miglior suggerimento è la seguente. Dopo che tutte le informazioni sono state apprese da un suggerimento, la possibile soluzione e i possibili set di mani del giocatore saranno stati ridotti (a meno che il suggerimento non riveli nuove informazioni). Teoricamente, il miglior suggerimento è quello che più riduce il numero di possibili soluzioni. Nel caso di un pareggio, suppongo che un suggerimento che riduce maggiormente il numero di mani possibili per i giocatori sia migliore. Quindi, per ogni suggerimento, provo ogni possibile risultato che non porti a una contraddizione nella conoscenza. Qualunque risultato abbia il minor miglioramento nel numero di soluzioni / mani, si presume che sia il risultato che il suggerimento avrà. Quindi confronto tutti i risultati dei suggerimenti e scelgo quale ha il risultato migliore. In questo modo, garantisco un guadagno ottimale delle informazioni nel caso peggiore.

Sto pensando di aggiungere un'analisi della combinazione della forza bruta delle possibili soluzioni e delle possibili mani del giocatore per renderla SpockAIancora più forte, ma poiché SpockAIè già la voce più lenta e ad alta intensità di risorse, probabilmente la salterò.

Disclaimer:

Avevo intenzione di rilasciare un'intelligenza artificiale per questo concorso settimane fa. Allo stato attuale, non sono stato in grado di iniziare a scrivere la mia IA fino a venerdì della scorsa settimana e ho continuato a trovare bug ridicoli nel mio codice. Per questo motivo, l'unico modo in cui sono riuscito a mettermi SpockAIal lavoro prima della scadenza era usare un grande pool di thread. Il risultato finale è che (attualmente) SpockAI può colpire + 90% di utilizzo della CPU e 2 GB + di utilizzo della memoria (anche se per questo biasimo il garbage collector). Ho intenzione di partecipare SpockAIal concorso, ma se altri ritengono che sia una violazione delle regole , assegnerò il titolo di "vincitore" al secondo posto dovrebbe SpockAIvincere. Se la pensi così, lascia un commento in tal senso su questa risposta.


3

InferencePlayer.java

Codice completo su Github (nota: utilizza lo stesso del AbstractCluedoPlayermio precedente SimpleCluedoPlayer).

Il vero nucleo di questo giocatore è la sua PlayerInformationclasse (qui leggermente ritagliata):

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

Unifica le informazioni sui suggerimenti che il giocatore non ha confutato (indicando che non hanno nessuna di quelle carte), i suggerimenti che hanno smentito (che indicano che possiedono almeno una di quelle carte) e le carte la cui posizione è certa. Quindi applica in modo iterativo alcune regole di base per compattare tali informazioni nella sua essenza.

Non penso che ci siano altre informazioni deterministiche da ottenere (tranne che per via di false accuse, che presumo siano troppo rare per preoccuparmi), anche se potrei aver trascurato qualcosa. V'è il potenziale per un giocatore più sofisticato per le probabilità di stima che il giocatore X ha carta di Y ...

L'altra area che probabilmente ammette un miglioramento significativo è nel decidere quale suggerimento dare. Cerco di massimizzare il guadagno di informazioni usando un approccio di forza pesante piuttosto ingombrante, ma c'è molta euristica scarsamente giustificata nella valutazione dei meriti relativi delle conoscenze acquisite da diverse ipotesi disproof. Tuttavia, non proverò a mettere a punto l'euristica fino a quando qualcun altro non pubblicherà un degno avversario.


Non riesco a far funzionare né SimpleCluedoPlayer né InferencePlayer. Ho eseguito la formica nella directory "SpeedClueContest / entry / peter_taylor /" e ho generato correttamente i JAR. Ho provato percorsi relativi e assoluti a questi JAR, passando loro "identificatore" e "portNumber" in quell'ordine, ma il TestServer si blocca in attesa del messaggio "identificatore in vita" per ciascuno di essi. Ho cercato e non riesco a trovare "/tmp/speed-cluedo-player"+identifier+".log". Ho incasinato il processo in qualche modo?
Sadakatsu,

@gamecoder, probabilmente non dovrei codificare /tmp. Dovrebbe essere una semplice patch; Ci penserò tra poco.
Peter Taylor,

1
La tua correzione funziona; L'ho unito nel repository. Ora riesco a leggere attentamente InferencePlayer per assicurarmi che ci siano abbastanza differenze tra esso e LogicalAI su cui avevo iniziato a lavorare> ___ <
sadakatsu il

Arriva il tuo avversario :-)
Ray,

@Ray, eccellente. Proverò a dissezionare la tua IA e vedrò come differisce dalla mia ad un certo punto: a prima vista, sembra usare un'analisi simile.
Peter Taylor,

2

CluePaddle (ClueStick / ClueBat / ClueByFour) - C #

Ho scritto ClueBot, un client C # per il quale è semplice implementare AI e vari AI, incluso il tentativo più serio chiamato CluePaddle. Il codice è disponibile all'indirizzo https://github.com/jwg4/SpeedClueContest/tree/clue_paddle con una richiesta pull avviata per unirlo in upstream.

ClueStick è una dimostrazione di concetto che sostanzialmente indovina e ignora la maggior parte di ciò che accade. ClueBat è un'altra stupida IA, tranne per il fatto che cerca di sfruttare un difetto in ClueStick per costringerlo a fare false accuse. ClueByFour è un'intelligenza artificiale ragionevole in quanto fornisce suggerimenti ragionevoli e ricorda le carte mostrate da altri.

CluePaddle è il più intelligente. Cerca di capire chi ha ciò che si basa non solo su quali disproof sono state offerte, ma anche in base a quali giocatori non hanno offerto una confutazione di un determinato suggerimento. Non tiene conto di quante carte ha ogni giocatore, ma questo deve essere risolto. Include un paio di classi piuttosto lunghe, quindi non pubblicherò l'intero codice qui, ma il seguente metodo dà un sapore.

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

Se i 4 giocano l'uno contro l'altro, CluePaddle vince di gran lunga il maggior numero di partite, con ClueByFour secondo e gli altri due da nessuna parte.

Solo CluePaddle è una voce competitiva (finora). Uso:

CluePaddle.exe identifier port

Se qualcun altro vuole fare una C # AI, basta creare un progetto ConsoleApplication nel soltution, implementare l' IClueAIinterfaccia in una classe, e quindi rendere il vostro Programderivano da ProgramTemplatee copiare quello che gli altri fanno per progetti Main(). L'unica dipendenza è NUnit per il unit testing e potresti facilmente rimuovere tutti i test dal codice (ma non installarlo, basta installarlo).


Ho provato a compilare i tuoi AI e testarli nel ContestServer (che presto sarà pubblicato). Il CluePaddleprogetto non viene compilato, sostenendo che NUnitnon è installato anche se gli altri progetti vengono compilati. Quelli che eseguono la compilazione finiscono per arrestarsi durante il test e Java segnala un errore di reimpostazione della connessione. Potresti aiutarmi a determinare se potrei aver fatto qualcosa di sbagliato?
Sadakatsu,

Correzione: ClueStickè l'unica IA che si blocca quando provo ad avviarla. Gli altri due competono nel torneo di prova e alla fine vengono squalificati per le stesse violazioni. ClueByFourviene squalificato per non aver ripetuto un suggerimento non approvato che fa come accusa quando non possiede nessuna delle carte. ClueBatviene squalificato per aver fatto accuse che hanno carte che sono state mostrate o che hanno in mano. Controlla le restrizioni AI modificate per garantire la conformità.
Sadakatsu,

@gamecoder Hai installato NUnit? Se non è possibile installarlo, posso rimuovere in modo condizionale il codice di test dell'unità. CluePaddle è la mia vera entrata - non sono troppo preoccupato per le violazioni da parte degli altri due poiché non stanno davvero giocando per vincere.
Jwg

Ho anche alcuni aggiornamenti per CluePaddle. Farò una richiesta pull per questi più tardi.
Jwg

Ho installato NUnit. Sono stato in grado di esplorare i suoi spazi dei nomi in MSVS usando i riferimenti dei tuoi altri progetti. Non vedo l'ora della tua richiesta pull.
Sadakatsu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.