King of the Hill: Speed ​​Clue AI


24

เงื่อนงำความเร็ว

Cluedo / Clueเป็นเกมกระดานคลาสสิกที่มีองค์ประกอบการเล่นเกมที่หักที่น่าสนใจ Speed ​​Clue เป็นผู้เล่น 3-6 คนที่เน้นองค์ประกอบนี้โดยใช้การ์ดเท่านั้น ผลที่ได้คือความแตกต่างเพียงอย่างเดียวระหว่าง Cluedo มาตรฐานและ Speed ​​Clue คือผู้เล่นแต่ละคนยังคงอยู่ในเกมอาจให้คำแนะนำใด ๆ ที่เขาพอใจในตาของเขาแทนที่จะรอไปถึงห้องเฉพาะที่เมตตาลูกเต๋าและคำแนะนำของผู้เล่นคนอื่น ถ้าคุณไม่เคยเล่น Cluedo ก่อนหรือต้องการเพื่อให้แน่ใจในความแตกต่างอย่างชัดเจนระหว่างสองรุ่นคุณอาจพบที่สมบูรณ์ความเร็วแย้มชุดกฎที่นี่


เป้าหมาย

เขียนและส่งโปรแกรม AI เพื่อเล่น Speed ​​Clue ก่อนวันที่ 15 พฤษภาคม 2014 00:00 GMT หลังจากนั้นฉันก็จะจัดการแข่งขันโดยใช้ข้อมูลที่ถูกต้องทั้งหมด ผู้เข้าร่วมที่ AI ชนะเกมมากที่สุดในการแข่งขันชนะการแข่งขัน


ข้อมูลจำเพาะ AI

คุณสามารถเขียน AI ของคุณในภาษาใดก็ได้ที่คุณเลือกใช้เทคนิคใดก็ได้ที่คุณใช้ตราบใดที่มันใช้โปรโตคอลแอปพลิเคชัน ผ่านการเชื่อมต่อ TCP / IP อย่างเคร่งครัดเพื่อเล่นเกมกับเซิร์ฟเวอร์ คำอธิบายรายละเอียดของข้อ จำกัด ทั้งหมดที่สามารถพบได้ที่นี่


วิธีการเล่น

เริ่มต้นด้วยการฟอร์กประกวดพื้นที่เก็บข้อมูล GitHub เพิ่มไดเรกทอรีภายใต้ entriesไดเรกทอรีชื่อโดยใช้ชื่อผู้ใช้ StackExchange ของคุณและพัฒนารหัสของคุณในโฟลเดอร์นั้น เมื่อคุณพร้อมที่จะส่งผลงานของคุณทำคำขอดึงพร้อมกับการแก้ไขของคุณแล้วทำตามคำแนะนำเหล่านี้สำหรับการประกาศรายการของคุณบนเว็บไซต์นี้

ฉันได้ให้รหัสและขวดบางอย่างในcoreไดเรกทอรีเพื่อให้คุณเริ่มต้น; ดูเว็บไซต์ของฉันสำหรับคำแนะนำคร่าวๆสำหรับวัสดุ นอกจากนี้ผู้เล่นคนอื่นกำลังส่งรหัสผู้ช่วยนอกเหนือจากรายการของพวกเขาเพื่อช่วยให้คุณเริ่มต้นใช้งานได้ ใช้เวลาสำรวจรายการและอย่าลืมทดสอบรายการของคุณกับรายการของผู้อื่นก่อนส่ง!


ผล

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

ผลลัพธ์ข้างต้นแสดงเปอร์เซ็นต์การชนะแต่ละ AI ที่ผ่านการรับรองมีการแข่งขันที่ถูกต้อง 25,200 ครั้งที่เข้าร่วม มีการแข่งขันทั้งหมด 30,000 นัดที่นับรวมในผลการแข่งขันและ 6,100 คะแนนหรือมากกว่านั้นจึงถูกลดราคาเมื่อ01ถูกตัดสิทธิ์

การกล่าวถึงเกียรติต้องไปที่01AI ของรังสี การทดสอบครั้งแรกของฉันแสดงให้เห็นว่ามันแข็งแกร่งที่สุดและฉันคาดหวังให้ชนะการแข่งขัน อย่างไรก็ตามดูเหมือนว่าจะมีข้อผิดพลาดเป็นระยะ ๆ ซึ่งเท่าที่ฉันเดาได้นำไปสู่การกำจัดวิธีแก้ปัญหาที่เป็นไปได้ทั้งหมด ทัวร์นาเมนต์สิ้นสุดการแข่งขันสามผู้เล่นทั้งหมดและเริ่มการแข่งขันผู้เล่นสี่คน (12,000 เกมใน!) เมื่อ01มีการเปิดเผยบั๊ก หากฉันพิจารณาอันดับการแข่งขันของผู้เล่น 3 คนผลลัพธ์จะเป็นดังนี้:

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%

ฉันวางแผนที่จะทำ data mining จากผลลัพธ์ แต่ฉันหมดแรง ฉันมีปัญหาทางเทคนิคในการทำให้การแข่งขันวิ่งผ่าน (ความล้มเหลวของระบบการบูตระบบใหม่) ซึ่งจำเป็นต้องเขียนเซิร์ฟเวอร์การแข่งขันใหม่ทั้งหมดเพื่อบันทึกความคืบหน้าในขณะดำเนินการ ฉันจะแสดงความคิดเห็นและคอมมิชชันการเปลี่ยนแปลงทั้งหมดของรหัสกับไฟล์ผลลัพธ์ทั้งหมดที่สร้างขึ้นในกรณีที่ทุกคนยังคงสนใจ ถ้าฉันตัดสินใจทำ data mining เช่นกันผลลัพธ์ของฉันจะถูกเพิ่มไปยังที่เก็บ


ขอบคุณสำหรับการเล่น!


4
คุณสามารถทำสำเนาเซิร์ฟเวอร์ของคุณเพื่อให้ผู้เข้าร่วมทดสอบได้หรือไม่
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.ทำไมสองพอร์ต
Hasturkun

1
@PeterTaylor ฉันจะทำสำเนาเซิร์ฟเวอร์พร้อมใช้งานทันทีที่ฉันเขียน ทำไมคุณถึงคิดว่าฉันให้หนึ่งเดือน? ;)
sadakatsu

@Hasturkun สถาปัตยกรรมที่ฉันได้วางแผนไว้สำหรับเซิร์ฟเวอร์ก็คือมันจะเริ่มส่งผลงานของคุณผ่านทางบรรทัดคำสั่ง มันจะเลือกพอร์ตที่แต่ละโปรแกรมจะใช้ในการส่งข้อความเพื่อให้สามารถระบุได้อย่างง่ายดายว่าโปรแกรมใดคือ (โปรดทราบว่าโปรโตคอลไม่มีตัวระบุใด ๆ ) นอกจากนี้แต่ละโปรแกรมจำเป็นต้องรู้ว่าพอร์ตใดที่จะส่งข้อความเพื่อให้เซิร์ฟเวอร์สามารถรับข้อความได้จริง นี่เป็นสองพอร์ตที่การส่งแต่ละครั้งจะต้องได้รับเป็นอาร์กิวเมนต์บรรทัดคำสั่ง
sadakatsu

1
โปรแกรมเครือข่ายเดียวที่ฉันเขียนใช้ UDP ฉันตัดสินใจที่จะใช้ TCP / IP เพื่อ (1) ทำความเข้าใจความแตกต่างระหว่างสองและ (2) เพื่อใช้เทคโนโลยีที่รองรับการอัพเดตเพลย์เยอร์ขั้นตอนการล็อคที่ดีที่สุดที่ฉันต้องการเพื่อใช้งานได้
sadakatsu

คำตอบ:


5

AI01 - Python 3

ฉันหาชื่อที่ดีกว่าไม่ได้แล้ว :-P

ตัวระบุ : ray-ai01

เทคโนโลยี : Python 3

เลือก : ใช่

อาร์กิวเมนต์ :ai01.py identifier port

คำอธิบาย : ทำงานโดยอนุมาน เมื่อจำนวนของการ์ดที่ไม่เป็นที่รู้จักของเจ้าของนั้นน้อยกว่าขีด จำกัด AI นี้จะเริ่มกำจัดการแก้ปัญหาที่เป็นไปไม่ได้ทั้งหมดด้วยการอนุมานแบบเรียกซ้ำทั่วโลก มิฉะนั้นจะใช้การอนุมานในท้องถิ่น

#!/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 สามารถพบได้ที่นี่


คุณสามารถส่งคำขอดึง AI ของคุณได้หรือไม่? ฉันต้องการที่จะเข้าร่วมการแข่งขันคืน
sadakatsu

@gamecoder ฉันทำ AI01 ให้แข็งแกร่งขึ้นและส่งคำขอการดึง
เรย์

1
เมื่อสิ่งต่าง ๆ ยังคงอยู่ 01 ของคุณแข็งแกร่งที่สุด ในการทดสอบที่ฉันวิ่งมันชนะอย่างต่อเนื่อง ~ 67% ของการแข่งขันที่เข้าแข่งขัน ฉันหวังว่าเราจะเห็นรายการที่เป็นของแข็งก่อนที่การแข่งขันจะสิ้นสุดลงซึ่งสามารถท้าทายได้
sadakatsu

SpockAIตรวจสอบของฉัน มันทำงานได้ดี01ทีเดียว ฉันไม่รู้ว่ามันจะชนะการแข่งขันหรือไม่ แต่ฉันดีใจที่คุณเห็นจำนวนการชนะลดลง )
sadakatsu

@gamecoder จริง ๆ แล้วฉันได้อัพเดท AI เมื่อหลายวันก่อนตามกฎใหม่ ฉันดีใจที่เห็นรายการใหม่ของคุณ ดูเหมือนว่าจะทำงานได้ดี แต่ฉันไม่ได้ทดสอบหลายครั้งเนื่องจากไม่มีประสิทธิภาพ บางทีคุณสามารถทำให้เร็วขึ้นเพื่อให้เราทดสอบได้ง่ายขึ้น
เรย์

4

SimpleCluedoPlayer.java

คลาสนี้ใช้AbstractCluedoPlayerซึ่งจัดการ I / O ทั้งหมดและให้ตรรกะทำงานกับอินเตอร์เฟสที่พิมพ์ได้ง่าย สิ่งที่ทั้งเป็นบน GitHub

ผู้เล่นที่สุ่มจะมีโอกาสสูง (ในกรณีที่แย่ที่สุดจะใช้เวลา 15 ข้อเสนอแนะในขณะที่ผู้เล่นสุ่มใช้ค่าเฉลี่ย 162) แต่จะพ่ายแพ้ได้ง่าย ฉันเสนอให้ลูกบอลกลิ้ง

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

ดีมาก ๆ รหัสสะอาด ฉันสงสัยว่าใครก็ตามที่ดูเซิร์ฟเวอร์ทดสอบหรือผู้เล่นแบบสุ่มรู้สึกอย่างนั้น ฉันคิดว่าคุณคงไม่มี AI ที่ง่ายกว่านี้อีก ^ _ ^
sadakatsu

4

SpockAI

ตัวบ่งชี้: gamecoder-SpockAI

รายการ Repo: คลิกที่นี่

เลือก:ใช่

เทคโนโลยี: Java 7 ขึ้นอยู่กับcom.sadakatsu.clue.jar

อาร์กิวเมนต์: {identifier} portNumber [logOutput: true|false]

รายละเอียด:

SpockAIเป็นผู้เล่นเบาะแสความเร็วที่สร้างขึ้นในชั้นเรียนที่เรียกKnowledgeว่าฉันเขียน Knowledgeชั้นหมายถึงทั้งหมดที่รัฐเป็นไปได้ว่าเกมนี้จะได้รับสิ่งที่เกิดขึ้นป่านนี้ มันแสดงให้เห็นถึงวิธีการแก้ปัญหาของเกมและมือที่เป็นไปได้ของผู้เล่นเป็นชุดและใช้การหักซ้ำเพื่อลดชุดเหล่านี้เท่าที่จะทำได้ทุกครั้งที่มีการเรียนรู้ SpockAIใช้คลาสนี้เพื่อพิจารณาว่าข้อเสนอแนะใดที่มั่นใจได้ว่าจะได้ผลลัพธ์ที่เลวร้ายที่สุดและเป็นประโยชน์มากที่สุดและสุ่มเลือกหนึ่งในคำแนะนำเหล่านั้นเมื่อถึงคราว เมื่อต้องการพิสูจน์หักล้างข้อเสนอแนะจะพยายามแสดงการ์ดที่แสดง AI ที่แนะนำแล้วหรือแสดงการ์ดจากหมวดหมู่ที่ได้ลดความเป็นไปได้น้อยที่สุด มันทำให้เกิดข้อกล่าวหาเมื่อรู้วิธีแก้ปัญหาเท่านั้น

ฮิวริสติกที่ฉันใช้ในการพิจารณาคำแนะนำที่ดีที่สุดมีดังนี้ หลังจากข้อมูลทั้งหมดได้รับการเรียนรู้จากข้อเสนอแนะวิธีแก้ปัญหาที่เป็นไปได้และชุดมือผู้เล่นที่เป็นไปได้จะลดลง (เว้นแต่คำแนะนำจะไม่มีข้อมูลใหม่) ในทางทฤษฎีคำแนะนำที่ดีที่สุดคือคำตอบที่ช่วยลดจำนวนโซลูชันที่เป็นไปได้มากที่สุด ในกรณีของการเสมอกันฉันคิดว่าคำแนะนำที่ลดจำนวนมือที่เป็นไปได้สำหรับผู้เล่นจะดีกว่า ดังนั้นสำหรับข้อเสนอแนะแต่ละข้อฉันลองใช้ทุกผลลัพธ์ที่เป็นไปได้ซึ่งไม่นำไปสู่ความขัดแย้งในความรู้ ผลลัพธ์ใดที่มีการปรับปรุงน้อยที่สุดในการแก้ปัญหา / การนับด้วยมือจะถือว่าเป็นผลลัพธ์ที่คำแนะนำจะมี จากนั้นฉันจะเปรียบเทียบผลลัพธ์ของข้อเสนอแนะทั้งหมดแล้วเลือกว่าข้อใดมีผลลัพธ์ที่ดีที่สุด ด้วยวิธีนี้ฉันรับประกันการได้รับข้อมูลกรณีที่เลวร้ายที่สุด

ฉันกำลังพิจารณาที่จะเพิ่มการวิเคราะห์การรวมกันที่ดุร้ายของโซลูชันที่เป็นไปได้ SpockAIแข็งแกร่งยิ่งขึ้น แต่เนื่องจากSpockAIเป็นรายการที่ใช้ทรัพยากรมากที่สุดช้าที่สุด

Disclaimer:

ฉันตั้งใจจะปล่อย AI สำหรับการแข่งขันนี้เมื่อหลายสัปดาห์ก่อน ฉันไม่สามารถเริ่มต้นเขียน AI จนกระทั่งวันศุกร์ที่แล้วและฉันก็พบข้อบกพร่องที่น่าหัวเราะในรหัสของฉัน ด้วยเหตุนี้วิธีเดียวที่ฉันสามารถSpockAIทำงานก่อนกำหนดได้คือใช้กลุ่มเธรดขนาดใหญ่ ผลลัพธ์สุดท้ายก็คือ (ปัจจุบัน) SpockAI สามารถใช้งาน CPU ได้ถึง 90% และใช้หน่วยความจำ 2GB + (แม้ว่าฉันจะตำหนิตัวเก็บขยะสำหรับเรื่องนี้) ฉันตั้งใจจะวิ่งSpockAIในการแข่งขัน แต่ถ้าคนอื่นรู้สึกว่าเป็นการละเมิดกฎฉันจะให้รางวัลชื่อของ "ผู้ชนะ" ไปยังที่สองที่ควรได้SpockAIรับรางวัล หากคุณรู้สึกแบบนี้โปรดแสดงความคิดเห็นกับคำตอบนั้น


3

InferencePlayer.java

รหัสเต็มบน Github (หมายเหตุ: นี่ใช้เหมือนกับAbstractCluedoPlayerของฉันก่อนหน้านี้SimpleCluedoPlayer )

แกนกลางที่แท้จริงของผู้เล่นนี้คือPlayerInformationระดับของมัน(ตัดออกเล็กน้อยที่นี่):

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

มันรวมข้อมูลเกี่ยวกับข้อเสนอแนะที่ผู้เล่นไม่ได้หักล้าง (แสดงว่าพวกเขาไม่มีบัตรเหล่านั้น) คำแนะนำที่พวกเขาได้หักล้าง (แสดงว่าพวกเขาถือไพ่อย่างน้อยหนึ่งใบ) และการ์ดที่มีตำแหน่งแน่นอน จากนั้นจึงนำกฎพื้นฐานบางอย่างมาใช้ในการกระชับข้อมูลให้เป็นสาระสำคัญ

ฉันไม่คิดว่าจะมีข้อมูลที่แน่นอนอีกต่อไปที่จะได้รับ (ยกเว้นจากการกล่าวหาที่ผิดซึ่งฉันคิดว่าหายากเกินกว่าจะรบกวน) แม้ว่าฉันจะมองข้ามบางสิ่งก็ตาม นั่นคือที่มีศักยภาพสำหรับผู้เล่นที่มีความซับซ้อนมากขึ้นเพื่อความน่าจะเป็นประมาณการว่าผู้เล่นมีการ์ด X Y ...

ส่วนอื่น ๆ ที่อาจยอมรับการปรับปรุงที่สำคัญคือการตัดสินใจว่าควรทำอย่างไร ฉันพยายามที่จะเพิ่มพูนข้อมูลให้มากที่สุดโดยใช้วิธีการที่ใช้กำลังแรงมาก แต่ก็มีวิธีแก้ปัญหาที่ไม่ดีพอในการประเมินข้อดีของความรู้ที่ได้จากการแยกสมมุติฐานที่แตกต่างกัน อย่างไรก็ตามฉันจะไม่ลองปรับฮิวริสติกจนกว่าจะมีคนอื่นโพสท์คู่ต่อสู้ที่คู่ควร


ฉันไม่สามารถรับ SimpleCluedoPlayer หรือ InferencePlayer ของคุณให้ทำงานได้ ฉันรัน ant ในไดเร็กทอรี "SpeedClueContest / รายการ / peter_taylor /" และสร้าง JAR ได้สำเร็จ ฉันได้ลองเส้นทางสัมพัทธ์และเส้นทางสัมบูรณ์ไปยัง JARs เหล่านี้โดยส่ง "ตัวระบุ" และ "พอร์ตหมายเลข" ตามลำดับ แต่ TestServer หยุดรอข้อความ "ตัวระบุมีชีวิตอยู่" สำหรับพวกเขาแต่ละคน ฉันค้นหาและไม่พบ "/tmp/speed-cluedo-player"+identifier+".log" ฉันได้ทำกระบวนการอย่างใดอย่างหนึ่งหรือไม่
sadakatsu

@gamecoder /tmpผมอาจจะไม่ยากรหัส มันควรจะเป็นแผ่นแปะง่ายๆ ฉันจะดูมันในไม่ช้า
Peter Taylor

1
การแก้ไขของคุณใช้งานได้; ฉันได้รวมมันเป็น repo ตอนนี้ฉันได้อ่าน InferencePlayer อย่างรอบคอบเพื่อให้แน่ใจว่ามีความแตกต่างระหว่างมันกับ LogicalAI ที่ฉันเริ่มทำงาน> ___ <
sadakatsu

ฝ่ายตรงข้ามของคุณมาที่นี่ :-)
เรย์

@ เรย์ยอดเยี่ยม ฉันจะพยายามตัด AI ของคุณและดูว่ามันแตกต่างจากของฉันในบางจุด: ในคร่าวๆคร่าว ๆ ดูเหมือนว่าจะใช้การวิเคราะห์ที่คล้ายกัน
Peter Taylor

2

CluePaddle (ClueStick / ClueBat / ClueByFour) - C #

ฉันได้เขียน ClueBot ซึ่งเป็นไคลเอนต์ C # ซึ่งตรงไปตรงมาเพื่อใช้งาน AIs และ AIs ต่างๆรวมถึงความพยายามที่ร้ายแรงที่สุดที่เรียกว่า CluePaddle รหัสอยู่ที่https://github.com/jwg4/SpeedClueContest/tree/clue_paddleโดยมีคำขอดึงเริ่มที่จะรวมเข้ากับอัปสตรีม

ClueStick เป็นหลักฐานพิสูจน์แนวคิดซึ่งโดยทั่วไปแล้วเพียงแค่คาดเดาและไม่สนใจสิ่งที่เกิดขึ้นส่วนใหญ่ ClueBat เป็น AI ที่โง่อีกคนหนึ่งยกเว้นว่ามันพยายามที่จะใช้ประโยชน์จากข้อบกพร่องใน ClueStick เพื่อบังคับให้ทำข้อกล่าวหาที่ผิดพลาด ClueByFour เป็น AI ที่สมเหตุสมผลในการให้คำแนะนำที่สมเหตุสมผลและจดจำการ์ดที่ผู้อื่นแสดง

CluePaddle เป็นคนที่ฉลาดที่สุด มันพยายามที่จะคิดออกว่าใครมีอะไรที่ไม่เพียง แต่อิงตามสิ่งที่มีการลดราคา แต่ยังขึ้นอยู่กับว่าผู้เล่นคนใดที่ไม่ได้เสนอข้อเสนอแนะที่ไม่เหมาะสม ไม่คำนึงถึงจำนวนไพ่ที่ผู้เล่นแต่ละคนมี atm แต่จะต้องแก้ไข มันมีคลาสค่อนข้างยาวสองชั้นดังนั้นฉันจะไม่โพสต์รหัสทั้งหมดที่นี่ แต่วิธีการต่อไปนี้ให้รสชาติ

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 ครั้งต่อกัน CluePaddle ชนะโดยเกมส่วนใหญ่ด้วย ClueByFour วินาทีและอีกสองไม่มีที่ไหนเลย

CluePaddle เท่านั้นเป็นรายการแข่งขัน (จนถึง) การใช้งาน:

CluePaddle.exe identifier port

ถ้าคนอื่นต้องการที่จะทำให้ C # AI เพียงสร้างโครงการ ConsoleApplication ใน Soltution ที่ใช้IClueAIอินเตอร์เฟซในชั้นเรียนและจากนั้นให้คุณProgramมาจากProgramTemplateและคัดลอกสิ่งที่โครงการอื่น ๆ Main()ทำเพื่อ การพึ่งพาเพียงอย่างเดียวคือ NUnit สำหรับการทดสอบหน่วยและคุณสามารถลบการทดสอบทั้งหมดออกจากรหัสได้อย่างง่ายดาย (แต่ไม่ต้องติดตั้งเพียงแค่ NUnit)


ฉันได้พยายามรวบรวม AIs ของคุณและทดสอบใน ContestServer (เร็ว ๆ นี้จะโพสต์) CluePaddleโครงการไม่ได้รวบรวมอ้างว่าNUnitไม่ได้ติดตั้งแม้ว่าโครงการอื่น ๆ ทำรวบรวม ผู้ที่คอมไพล์จนจบในระหว่างการทดสอบและ Java รายงานข้อผิดพลาดการรีเซ็ตการเชื่อมต่อ คุณช่วยฉันตัดสินได้ไหมว่าฉันทำอะไรผิดหรือเปล่า?
sadakatsu

การแก้ไข: ClueStickเป็น AI เดียวที่ถ่วงเวลาเมื่อฉันพยายามเริ่มต้น อีกสองคนเข้าแข่งขันในทัวร์นาเมนต์ทดลองและในที่สุดก็ถูกตัดสิทธิ์เนื่องจากการละเมิดเดียวกัน ClueByFourได้รับการตัดสิทธิ์สำหรับความล้มเหลวในการทำซ้ำคำแนะนำ undisproved มันทำให้เป็นข้อกล่าวหาเมื่อมันไม่ได้ถือบัตรใด ๆ ClueBatถูกตัดสิทธิ์เนื่องจากมีการกล่าวหาว่ามีบัตรที่แสดงหรืออยู่ในมือ โปรดตรวจสอบข้อ จำกัด AI ที่ได้รับการแก้ไขเพื่อให้แน่ใจว่าสอดคล้อง
sadakatsu

@gamecoder คุณติดตั้ง NUnit หรือไม่ หากคุณไม่สามารถติดตั้งได้ฉันสามารถลบรหัสทดสอบหน่วยได้ตามเงื่อนไข CluePaddle เป็นรายการที่แท้จริงของฉัน - ฉันไม่กังวลเกี่ยวกับการละเมิดอีกสองรายการเนื่องจากพวกเขาไม่ได้เล่นเพื่อชนะจริงๆ
jwg

CluePaddleฉันยังมีการปรับปรุงบางอย่างไป ฉันจะทำคำขอดึงในภายหลัง
jwg

ฉันติดตั้ง NUnit แล้ว ฉันสามารถสำรวจเนมสเปซใน MSVS โดยใช้การอ้างอิงโครงการอื่นของคุณ ฉันจะรอคำขอดึงของคุณ
sadakatsu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.