King of the Hill: Speed ​​Clue AI


24

Đầu mối tốc độ

Cluedo / Clue là một trò chơi bảng cổ điển với thành phần chơi trò chơi khấu trừ hấp dẫn. Speed ​​Clue là biến thể 3-6 người chơi nhấn mạnh thành phần này bằng cách chỉ sử dụng các thẻ. Kết quả là sự khác biệt duy nhất giữa Cluedo tiêu chuẩn và Clue tốc độ là mỗi người chơi vẫn còn trong trò chơi có thể đưa ra bất kỳ đề nghị nào mà anh ta vui lòng đến lượt mình thay vì chờ đến một phòng cụ thể trong sự xót xa của những viên xúc xắc và những gợi ý của người chơi khác. Nếu bạn chưa bao giờ chơi Cluedo trước đây hoặc muốn chắc chắn về sự khác biệt rõ ràng giữa hai phiên bản, bạn có thể tìm thấy quy tắc Clue Tốc độ hoàn chỉnh được đặt ở đây .


Mục tiêu

Viết và gửi chương trình AI để chơi Speed ​​Clue trước ngày 15 tháng 5 năm 2014 00:00 GMT. Sau thời gian đó, tôi sẽ điều hành một giải đấu bằng cách sử dụng tất cả các mục hợp pháp. Người tham gia có AI chiến thắng hầu hết các trò chơi trong giải đấu sẽ chiến thắng thử thách.


Thông số kỹ thuật AI

Bạn có thể viết AI của mình bằng bất kỳ ngôn ngữ nào bạn chọn, sử dụng bất kỳ kỹ thuật nào bạn sử dụng, miễn là nó sử dụng nghiêm ngặt giao thức ứng dụng qua kết nối TCP / IP để chơi trò chơi với máy chủ. Một lời giải thích chi tiết về tất cả các hạn chế có thể được tìm thấy ở đây .


Cách chơi

Bắt đầu bằng cách bỏ qua kho lưu trữ GitHub của cuộc thi . Thêm một thư mục trong entriesthư mục có tên bằng tên người dùng StackExchange của bạn và phát triển mã của bạn trong thư mục đó. Khi bạn đã sẵn sàng để gửi mục nhập của bạn, hãy thực hiện một yêu cầu kéo với bản sửa đổi của bạn, sau đó làm theo các hướng dẫn sau để thông báo mục nhập của bạn trên trang web này.

Tôi đã cung cấp một số mã và JAR trong corethư mục để giúp bạn bắt đầu; xem trang web của tôi để được hướng dẫn sơ bộ cho các tài liệu. Ngoài ra, những người chơi khác đang gửi mã người trợ giúp ngoài các mục nhập của họ để giúp bạn đứng dậy và chạy. Hãy dành chút thời gian để khám phá các mục và đừng quên kiểm tra mục của bạn so với mục của người khác trước khi gửi!


Các kết quả

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

Các kết quả ở trên cho thấy tỷ lệ phần trăm chiến thắng mà mỗi AI đủ điều kiện có trong số 25.200 trận đấu hợp lệ mà nó tham gia. Tổng cộng có 30.000 trận đấu được tính vào kết quả và 6.100 hoặc hơn thế được giảm giá khi 01bị loại.

Một đề cập danh dự cần phải đi đến 01AI của ray . Thử nghiệm ban đầu của tôi cho thấy nó là mạnh nhất và tôi hy vọng nó sẽ chiến thắng trong cuộc thi. Tuy nhiên, nó dường như có một lỗi rất gián đoạn, theo như tôi có thể đoán, dẫn đến việc loại bỏ tất cả các giải pháp có thể. Giải đấu đã kết thúc tất cả các trận đấu ba người chơi và đã bắt đầu bốn trận đấu của người chơi (12.000 trận đấu!) Khi 01lỗi của chúng được tiết lộ. Nếu tôi chỉ xem xét bảng xếp hạng trận đấu 3 người, kết quả sẽ như sau:

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%

Tôi đã lên kế hoạch thực hiện một số khai thác dữ liệu về kết quả, nhưng tôi đã kiệt sức. Tôi gặp khó khăn về kỹ thuật trong việc khiến cuộc thi phải chạy suốt (sự cố mất điện, khởi động lại hệ thống) đòi hỏi phải viết lại hoàn toàn máy chủ cuộc thi để lưu lại tiến trình của nó. Tôi sẽ bình luận và cam kết tất cả các thay đổi đối với mã với tất cả các tệp kết quả đã được tạo trong trường hợp bất kỳ ai vẫn quan tâm. Nếu tôi quyết định thực hiện khai thác dữ liệu, kết quả của tôi cũng sẽ được thêm vào kho lưu trữ.


Cảm ơn đã chơi cùng!


4
Bạn có thể tạo một bản sao của máy chủ của bạn để người đăng ký kiểm tra không?
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., Tại sao hai cổng?
Hasturkun

1
@PeterTaylor, tôi sẽ tạo một bản sao của máy chủ ngay khi tôi viết nó. Tại sao bạn nghĩ rằng tôi đang cho một tháng? ;)
sadakatsu

@Hasturkun, Kiến trúc tôi đã lên kế hoạch cho máy chủ là nó sẽ bắt đầu đệ trình của bạn thông qua dòng lệnh. Nó sẽ chọn cổng mà mỗi chương trình sẽ sử dụng để gửi tin nhắn để nó có thể dễ dàng xác định chương trình nào (lưu ý rằng giao thức không bao gồm bất kỳ định danh nào). Ngoài ra, mỗi chương trình cần biết cổng nào để gửi tin nhắn để máy chủ thực sự có thể nhận tin nhắn. Đây là hai cổng mà mỗi lần gửi phải nhận làm đối số dòng lệnh.
sadakatsu

1
Chương trình mạng duy nhất tôi đã viết sử dụng UDP. Tôi quyết định sử dụng TCP / IP để (1) hiểu được bất kỳ sự khác biệt nào giữa hai và (2) để sử dụng công nghệ hỗ trợ tốt nhất cho các cập nhật trình phát khóa mà tôi cần để làm việc này.
sadakatsu

Câu trả lời:


5

AI01 - Python 3

Tôi chưa thể tìm thấy một tên tốt hơn cho nó :-P.

Định danh : ray-ai01

Công nghệ : Python 3

Đã chọn : có

Luận điểm :ai01.py identifier port

Sự miêu tả : Làm việc bằng suy luận. Khi số lượng thẻ mà chủ sở hữu không biết là ít hơn một ngưỡng, AI này bắt đầu loại bỏ tất cả các giải pháp không thể bằng cách suy luận toàn cầu đệ quy. Mặt khác, nó sử dụng suy luận cục bộ.

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

Mã AI có thể được tìm thấy ở đây .


Bạn có thể thực hiện một yêu cầu kéo với AI của bạn? Tôi muốn có được nó vào repo cuộc thi.
sadakatsu

@gamecoder Tôi đã tạo AI01 mạnh hơn và đã gửi yêu cầu kéo.
Ray

1
Như mọi thứ hiện tại, 01 của bạn là mạnh nhất. Trong các thử nghiệm tôi chạy, nó liên tục thắng ~ 67% số trận mà nó thi đấu. Tôi hy vọng chúng ta sẽ thấy một số mục chắc chắn trước khi cuộc thi kết thúc có thể thách thức nó.
sadakatsu

Kiểm tra của tôi SpockAI. Nó thực hiện khá tốt chống lại 01. Tôi không biết liệu nó có chiến thắng trong cuộc thi hay không, nhưng tôi rất vui khi thấy số tiền thắng của bạn giảm đi; )
sadakatsu

@gamecoder Thực tế, tôi đã cập nhật AI của mình vài ngày trước theo các quy tắc mới. Tôi rất vui khi thấy mục mới của bạn. Nó dường như hoạt động tốt nhưng tôi đã không kiểm tra nó nhiều lần do nó không hiệu quả. Có lẽ bạn có thể làm cho nó nhanh hơn để chúng tôi dễ kiểm tra hơn.
Ray

4

SimpleCluedoPlayer.java

Lớp này sử dụng AbstractCluedoPlayer, xử lý tất cả I / O và cho phép logic hoạt động với giao diện gõ đơn giản. Toàn bộ điều là trên github .

Điều này đánh bại người chơi ngẫu nhiên với xác suất cao (trong trường hợp xấu nhất phải mất 15 đề xuất, trong khi người chơi ngẫu nhiên mất trung bình 162), nhưng nó sẽ dễ dàng bị đánh bại. Tôi cung cấp cho nó để có được quả bóng lăn.

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

Rất đẹp, sạch mã. Tôi nghi ngờ bất cứ ai nhìn vào máy chủ thử nghiệm hoặc người chơi ngẫu nhiên đều cảm thấy như vậy. Tôi cũng nghĩ rằng bạn không thể có AI đơn giản hơn thế này ^ _ ^
sadakatsu

4

SpockAI

Định danh: gamecoder-SpockAI

Mục nhập lại: bấm vào đây

Đã chọn:

Công nghệ: Java 7 dựa trêncom.sadakatsu.clue.jar

Lập luận: {identifier} portNumber [logOutput: true|false]

Sự miêu tả:

SpockAIlà một người chơi Speed ​​Clue được xây dựng dựa trên một lớp được gọi là Knowledgetôi đã viết. CácKnowledge lớp đại diện cho tất cả các trạng thái có thể rằng trò chơi có thể được những gì đã xảy ra cho đến nay. Nó đại diện cho các giải pháp của trò chơi và bàn tay khả thi của người chơi dưới dạng các bộ và sử dụng các khoản khấu trừ lặp để giảm các bộ này càng nhiều càng tốt mỗi khi có thứ gì đó được học. SpockAIsử dụng lớp này để xác định những đề xuất nào được đảm bảo để có kết quả trong trường hợp xấu nhất hữu ích nhất và chọn ngẫu nhiên một trong những gợi ý đó trong lượt của nó. Khi cần từ chối một đề xuất, nó cố gắng hiển thị một thẻ mà nó đã hiển thị AI gợi ý hoặc hiển thị cho nó một thẻ từ danh mục mà nó đã giảm khả năng ít nhất. Nó chỉ đưa ra lời buộc tội khi biết giải pháp.

Các heuristic tôi sử dụng để xác định gợi ý tốt nhất là như sau. Sau khi tất cả thông tin đã được học từ một gợi ý, giải pháp khả thi và các bộ tay người chơi có thể sẽ bị giảm (trừ khi đề xuất không tiết lộ thông tin mới). Về mặt lý thuyết, gợi ý tốt nhất là một trong những cách làm giảm nhiều nhất các giải pháp có thể. Trong trường hợp hòa, tôi cho rằng một gợi ý rằng hầu hết giảm số tay có thể cho người chơi là tốt hơn. Vì vậy, đối với mỗi đề xuất, tôi thử mọi kết quả có thể không dẫn đến mâu thuẫn trong kiến ​​thức. Bất kỳ kết quả nào có sự cải thiện ít nhất về số lượng giải pháp / số lần trao tay đều được coi là kết quả mà đề xuất sẽ có. Sau đó, tôi so sánh tất cả các kết quả của các đề xuất và chọn kết quả nào có kết quả tốt nhất. Bằng cách này, tôi đảm bảo đạt được thông tin trường hợp xấu nhất tối ưu.

Tôi đang xem xét thêm một phân tích kết hợp vũ lực của các giải pháp khả thi và bàn tay người chơi có thể để làm cho SpockAImạnh mẽ hơn, nhưng kể từ đóSpockAI đã là mục nhập chậm nhất, tốn nhiều tài nguyên nhất, tôi có thể sẽ bỏ qua điều đó.

Tuyên bố miễn trừ trách nhiệm:

Tôi đã có ý định phát hành AI cho cuộc thi này vài tuần trước. Vì thế, tôi đã không thể bắt đầu viết AI cho đến thứ Sáu tuần trước và tôi liên tục tìm thấy những lỗi vô lý trong mã của mình. Bởi vì điều này, cách duy nhất tôi có thể SpockAIlàm việc trước thời hạn là sử dụng một nhóm luồng lớn. Kết quả cuối cùng là (hiện tại) SpockAI có thể đạt mức sử dụng CPU + 90% và sử dụng bộ nhớ 2GB + (mặc dù tôi đổ lỗi cho trình thu gom rác vì điều này). Tôi dự định chạy SpockAItrong cuộc thi, nhưng nếu những người khác cảm thấy đó là vi phạm các quy tắc , tôi sẽ trao danh hiệu "người chiến thắng" cho vị trí thứ hai sẽ SpockAIgiành chiến thắng. Nếu bạn cảm thấy như vậy, xin vui lòng để lại nhận xét về tác động đó đối với câu trả lời này.


3

Suy luậnPlayer.java

Mã đầy đủ trên Github (lưu ý: điều này sử dụng giống AbstractCluedoPlayernhư trước đây của tôi SimpleCluedoPlayer).

Cốt lõi thực sự của người chơi này là PlayerInformationlớp của nó (ở đây hơi bị cắt xén):

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

Nó thống nhất thông tin về các đề xuất mà người chơi không từ chối (chỉ ra rằng họ không giữ bất kỳ thẻ nào trong số đó), các đề xuất mà họ đã từ chối (chỉ ra rằng họ giữ ít nhất một trong số các thẻ đó) và thẻ có vị trí chắc chắn. Sau đó, nó lặp đi lặp lại áp dụng một số quy tắc cơ bản để thu gọn thông tin đó vào bản chất của nó.

Tôi không nghĩ sẽ có thêm thông tin xác định nào (ngoại trừ thông qua các cáo buộc sai, mà tôi cho là quá hiếm để bận tâm), mặc dù tôi có thể đã bỏ qua điều gì đó. Có tiềm năng cho một cầu thủ tinh vi hơn để xác suất ước tính rằng người chơi X có thẻ Y ...

Các lĩnh vực khác có thể thừa nhận sự cải thiện đáng kể là trong việc quyết định đề xuất nào. Tôi cố gắng tối đa hóa việc đạt được thông tin bằng cách sử dụng một cách tiếp cận lực lượng khá nặng nề, nhưng có rất nhiều phương pháp phỏng đoán kém hợp lý trong việc đánh giá giá trị tương đối của kiến ​​thức thu được từ các giả định khác nhau. Tuy nhiên, tôi sẽ không thử điều chỉnh các heuristic cho đến khi có người khác đăng một đối thủ xứng đáng.


Tôi không thể chạy SimpleCluedoPlayer hoặc InferencePlayer của bạn để chạy. Tôi đã chạy ant trong thư mục "SpeedClueContest / entry / peter_taylor /" và tạo thành công các JAR. Tôi đã thử các đường dẫn tương đối và tuyệt đối đến các JAR này, chuyển cho chúng "định danh" và "portNumber" theo thứ tự đó, nhưng TestServer bị treo chờ thông báo "định danh còn sống" cho mỗi chúng. Tôi đã tìm và không thể tìm thấy "/tmp/speed-cluedo-player"+identifier+".log". Tôi đã làm xáo trộn quá trình bằng cách nào đó?
sadakatsu

@gamecoder, có lẽ tôi không nên dùng mã cứng /tmp. Nó nên là một bản vá đơn giản; Tôi sẽ xem xét nó trong thời gian ngắn.
Peter Taylor

1
Sửa chữa của bạn hoạt động; Tôi đã hợp nhất nó vào repo. Bây giờ tôi có thể đọc kỹ InferencePlayer để đảm bảo có đủ sự khác biệt giữa nó và LogicalAI tôi đã bắt đầu làm việc trên> ___ <
sadakatsu

Đây là đối thủ của bạn :-)
Ray

@Ray, xuất sắc. Tôi sẽ cố gắng mổ xẻ AI của bạn và xem nó khác với tôi như thế nào tại một thời điểm nào đó: nhìn thoáng qua, nó dường như sử dụng một phân tích tương tự.
Peter Taylor

2

CluePbag (ClueStick / ClueBat / ClueByFour) - C #

Tôi đã viết ClueBot, một ứng dụng khách C # mà việc triển khai AI đơn giản và nhiều AI khác nhau, bao gồm cả nỗ lực nghiêm túc nhất được gọi là CluePadd. Mã này có tại https://github.com/jwg4/SpeedClueContest/tree/clue_paddle với yêu cầu kéo bắt đầu hợp nhất nó thành ngược dòng.

ClueStick là một bằng chứng về khái niệm mà về cơ bản chỉ là đoán và bỏ qua hầu hết những gì xảy ra. ClueBat là một AI ngu ngốc khác, ngoại trừ việc nó cố gắng khai thác lỗ hổng trong ClueStick để buộc nó thực hiện các cáo buộc sai. ClueByFour là một AI hợp lý ở chỗ nó đưa ra các đề xuất hợp lý và ghi nhớ các thẻ mà nó được hiển thị bởi những người khác.

CluePbag là thông minh nhất. Nó cố gắng tìm ra ai có những gì không chỉ dựa trên những gì không được cung cấp, mà còn dựa trên những người chơi không đưa ra sự không chắc chắn về một đề nghị nhất định. Nó không tính đến việc mỗi người chơi có bao nhiêu thẻ nhưng điều này sẽ được sửa. Nó bao gồm một vài lớp khá dài nên tôi sẽ không đăng toàn bộ mã ở đây, nhưng phương pháp sau đây mang lại hương vị.

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

Nếu cả 4 thi đấu với nhau, CluePadd sẽ giành chiến thắng trong hầu hết các trò chơi, với ClueByFour thứ hai và hai người còn lại.

Chỉ CluePadd là một mục cạnh tranh (cho đến nay). Sử dụng:

CluePaddle.exe identifier port

Nếu bất cứ ai khác muốn tạo C # AI, chỉ cần tạo dự án ConsoleApplication trong giải pháp, triển khai IClueAIgiao diện trong một lớp, sau đó tạo Programra nguồn gốc của bạn ProgramTemplatevà sao chép những gì các dự án khác làm Main(). Sự phụ thuộc duy nhất là NUnit cho thử nghiệm đơn vị và bạn có thể dễ dàng xóa tất cả thử nghiệm khỏi mã (nhưng không, chỉ cần cài đặt NUnit).


Tôi đã cố gắng biên dịch AI của bạn và kiểm tra chúng trong Máy chủ cuộc thi (sắp được đăng). Các CluePaddledự án không biên dịch, tuyên bố rằng NUnitkhông được cài đặt mặc dù các dự án khác làm biên dịch. Những trình biên dịch cuối cùng bị đình trệ trong quá trình thử nghiệm và Java báo cáo lỗi thiết lập lại kết nối. Bạn có thể giúp tôi xác định liệu tôi có thể làm điều gì sai không?
sadakatsu

Sửa chữa: ClueSticklà AI duy nhất ngăn cản khi tôi cố gắng khởi động nó. Hai người còn lại cạnh tranh trong giải đấu thử nghiệm và cuối cùng bị loại vì vi phạm tương tự. ClueByFourbị loại vì không lặp lại đề nghị không được chấp thuận mà nó đưa ra như một lời buộc tội khi nó không giữ bất kỳ thẻ nào. ClueBatbị loại vì đưa ra các cáo buộc có thẻ mà nó đã được hiển thị hoặc có trong tay. Vui lòng kiểm tra các hạn chế AI sửa đổi để đảm bảo tuân thủ.
sadakatsu

@gamecoder Bạn đã cài đặt NUnit chưa? Nếu bạn không thể cài đặt nó, tôi có thể loại bỏ mã kiểm tra đơn vị một cách có điều kiện. CluePadd là mục thực tế của tôi - Tôi không quá lo lắng về những vi phạm của hai người kia vì họ không thực sự chơi để giành chiến thắng.
jwg

Tôi cũng có một số cập nhật CluePaddle. Tôi sẽ làm một yêu cầu kéo cho những điều này sau.
jwg

Tôi đã cài đặt NUnit. Tôi đã có thể khám phá các không gian tên của nó trong MSVS bằng cách sử dụng các tham chiếu của các dự án khác của bạn. Tôi sẽ mong đợi yêu cầu kéo của bạn.
sadakatsu
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.