단어를 추측 (일명 Lingo)


13

이 도전의 목표는 가능한 한 적은 시도로 단어를 추측 할 수있는 프로그램을 작성하는 것입니다. Lingo TV 쇼 ( http://en.wikipedia.org/wiki/Lingo_(US_game_show) ) 의 개념을 기반으로합니다 .

규칙

명령 행에서 첫 번째 인수로 전달 된 단어 길이가 주어지면, 플레이어 프로그램 은 표준 출력 다음에 단일 문자 로 추측을 작성하여 단어를 추측하려는 5 가지 시도를 처리 \n합니다.

추측이 이루어지면 프로그램은 표준 입력에서 문자열을 수신 한 후 단일 \n문자를받습니다.

문자열은 추측 할 단어와 길이가 같으며 다음 문자 시퀀스로 구성됩니다.

  • X: 이것은 주어진 글자가 추측 할 단어에 없음을 의미합니다.
  • ?: 이것은 주어진 글자가 추측하기 위해 단어에 있지만 다른 위치에 있음을 의미합니다.
  • O:이 위치의 문자가 올바르게 추측되었음을 의미합니다.

추측 단어 인 경우, 예를 들어 dents, 프로그램은 단어를 전송 dozes, 그 수신처 OXX?O때문에 d그리고 s올, e잘못하여, o그리고 z본 아니다.

편지 추측 할 수있는 단어보다 추측 시도에 더의 시대 인 경우,이 것을주의 하지 로 표시 ?하고 O추측 할 수있는 단어 문자의 발행 수의 수보다 더 많은 시간. 예를 들어, 추측 할 단어가 cozies있고 프로그램이를 보내면 찾을 단어 가 하나만 있기 때문에 tosses수신됩니다 .XOXXOOs

단어는 영어 단어 목록에서 선택됩니다. 프로그램에서 전송 한 단어가 올바른 길이의 유효한 단어가 아닌 경우 시도는 자동 실패로 간주되며의 단어 만 X반환됩니다.
플레이어 프로그램은 wordlist.txt한 줄에 한 단어 씩 이름을 가진 파일 이 현재 작업 디렉토리에 있으며 필요에 따라 읽을 수 있다고 가정해야 합니다.
게제는 알파벳 소문자 ( [a-z]) 로만 구성되어야합니다 .
프로그램에 다른 네트워크 또는 파일 작업이 허용되지 않습니다.

구성된 문자열 만 O반환되거나 프로그램이 5 번 시도하여 단어를 추측 할 수없는 경우 게임이 종료됩니다 .

채점

게임의 점수는 주어진 공식에 의해 주어진다 :

score = 100 * (6 - number_of_attempts)

따라서 첫 번째 시도에서 단어가 올바르게 추측되면 500 점이 제공됩니다. 마지막 시도는 100 포인트의 가치가 있습니다.

단어를 추측하지 못하면 0 점이 부여됩니다.

구덩이

플레이어 프로그램은 각 단어 길이에 대해 4 ~ 13 자 사이의 임의의 단어 100 개를 추측하도록하여 평가됩니다 .
무작위 단어 선택은 사전에 수행되므로 모든 항목은 동일한 단어를 추측해야합니다.

이기는 프로그램과 정답은 가장 높은 점수에 도달하는 것입니다.

프로그램은 https://github.com/noirotm/lingo 의 코드를 사용하여 Ubuntu 가상 머신에서 실행됩니다 . 컴파일 및 / 또는 실행에 대한 합리적인 지침이 제공되는 한 모든 언어로 구현할 수 있습니다.

git 저장소에서 루비로 몇 가지 테스트 구현을 제공하고 있습니다.

이 질문은 정기적으로 게시 된 답변의 순위로 업데이트되어 도전자가 참가를 개선 할 수 있습니다.

공식적인 최종 평가는 7 월 1 일 에 이루어질 것 입니다.

최신 정보

항목은 이제 wordlistN.txt파일이 있다고 가정하여 4에서 13 사이의 N에 대한 현재 단어 길이에 대한 단어 목록을 빠르게 읽을 수 있습니다.

예를 들어, wordlist4.txt4 개의 모든 단어를 wordlist10.txt포함하고 10 개의 모든 단어를 포함 하는 파일 등이 있습니다.

1 라운드 결과

2014-07-01의 날짜에 다음 세 가지 항목이 제출되었습니다.

                        4       5       6       7       8       9       10      11      12      13      Total
./chinese-perl-goth.pl  8100    12400   15700   19100   22100   25800   27900   30600   31300   33600   226600
java Lingo              10600   14600   19500   22200   25500   28100   29000   31600   32700   33500   247300
./edc65                 10900   15800   22300   24300   27200   29600   31300   33900   33400   33900   262600

** Rankings **
1: ./edc65 (262600)
2: java Lingo (247300)
3: ./chinese-perl-goth.pl (226600)

모든 참가작은 @ edc65의 C ++의 출품작으로 당첨되었습니다.

모든 참가자는 정말 대단합니다. 나는 지금까지 @ chinese-perl-goth를 이길 수 없었습니다.
더 많은 출품작이 제출되면 다른 평가가 이루어집니다. 더 잘할 수 있다고 생각되면 현재 항목을 개선 할 수도 있습니다.


1
명확히하기 위해 : 프로그램이 6 번 이상 단어를 추측하려고하면 부정적인 점을 얻습니까 아니면 제로를 얻습니까? 다시 말해, 6 번 부정적인 점을 피하려고 시도한 후에 프로그램을 종료하려면 논리가 필요합니까? (프로그램이 단어를 추측하지 못하면 규칙은 0 점이라고 함)
DankMemes

1
다섯 번의 시도 후 @ZoveGames, 프로그램을 종료해야하지만, 게임 엔진은 거부하면 강제로 종료합니다 :)
SirDarius

1
@RichardA 예, 파이썬에 대해 걱정하지 마십시오. 일류 시민이므로 파이썬 코드를 실행하는 데 아무런 문제가 없습니다 :)
SirDarius

1
@justhalf 감사합니다! 마침내 계속할 수 있습니다!
MisterBla

1
@justhalf 좋은 아이디어, 실제로 구현하려고합니다
SirDarius

답변:


5

C ++ 267700 포인트

오래된 MasterMind 엔진에서 포팅.
MasterMind와의 차이점 :

  • 더 많은 슬롯
  • 더 많은 기호
  • 더 큰 솔루션 공간 (그러나 모든 기호 조합이 허용되지 않기 때문에 그다지 많지 않음)
  • 답변은 많은 정보를 제공하므로 각 추측 후에 더 많은 정보를 얻을 수 있습니다
  • 응답이 생성 속도가 느리고 알고리즘이 많이 수행해야하기 때문에 유감입니다.

기본 아이디어는 솔루션 공간을 최소화하는 단어를 선택하는 것입니다. 알고리즘은 첫 번째 추측 ( '일'을 의미)에 대해 실제로 느리지 만 가장 좋은 첫 번째 추측은 len이라는 단어에만 의존하므로 소스에서 하드 코딩됩니다. 다른 추측은 몇 초 만에 이루어집니다.

코드

(g ++ -O3과 컴파일)

#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <ctime>
#include <cstdlib>

using namespace std;

class LRTimer
{
private:
    time_t start;
public:
    void startTimer(void)
    {
        time(&start);
    }

    double stopTimer(void)
    {
        return difftime(time(NULL),start);
    } 

};

#define MAX_WORD_LEN 15
#define BIT_QM 0x8000

LRTimer timer;
int size, valid, wordLen;

string firstGuess[] = { "", "a", "as", "iao", "ares", 
    "raise", "sailer", "saltier", "costlier", "clarities", 
    "anthelices", "petulancies", "incarcerates", "allergenicity" };

class Pattern
{
public:
    char letters[MAX_WORD_LEN];
    char flag;
    int mask;

    Pattern() 
        : letters(), mask(), flag()
    {
    }

    Pattern(string word) 
        : letters(), mask(), flag()
    {
        init(word);
    }

    void init(string word)
    {
        const char *wdata = word.data();
        for(int i = 0; i < wordLen; i++) {
            letters[i] = wdata[i];
            mask |= 1 << (wdata[i]-'a');
        }
    }

    string dump()
    {
        return string(letters);
    }

    int check(Pattern &secret)
    {
        if ((mask & secret.mask) == 0)
            return 0;

        char g[MAX_WORD_LEN], s[MAX_WORD_LEN];
        int r = 0, q = 0, i, j, k=99;
        for (i = 0; i < wordLen; i++)
        {
            g[i] = (letters[i] ^ secret.letters[i]);
            if (g[i])
            {
                r += r;
                k = 0;
                g[i] ^= s[i] = secret.letters[i];
            }
            else
            {
                r += r + 1;
                s[i] = 0;
            }
        }
        for (; k < wordLen; k++)
        {
            q += q;
            if (g[k]) 
            {
                for (j = 0; j < wordLen; j++)
                    if (g[k] == s[j])
                    {
                        q |= BIT_QM;
                        s[j] = 0;
                        break;
                    }
            }
        }
        return r|q;
    }

    int count(int ck, int limit);

    int propcheck(int limit);

    void filter(int ck);
};

string dumpScore(int ck)
{
    string result(wordLen, 'X');
    for (int i = wordLen; i--;)
    {
        result[i] = ck & 1 ? 'O' : ck & BIT_QM ? '?' : 'X';
        ck >>= 1;
    }
    return result;
}

int parseScore(string ck)
{
    int result = 0;
    for (int i = 0; i < wordLen; i++)
    {
        result += result + (
            ck[i] == 'O' ? 1 : ck[i] == '?' ? BIT_QM: 0
        );
    }
    return result;
}

Pattern space[100000];

void Pattern::filter(int ck)
{
    int limit = valid, i = limit;
//  cerr << "Filter IN Valid " << setbase(10) << valid << " This " << dump() << "\n"; 

    while (i--)
    {
        int cck = check(space[i]);
//      cerr << setbase(10) << setw(8) << i << ' ' << space[i].dump() 
//          << setbase(16) << setw(8) << cck << " (" << Pattern::dumpScore(cck) << ") ";

        if ( ck != cck )
        {
//          cerr << " FAIL\r" ;
            --limit;
            if (i != limit) 
            {
                Pattern t = space[i];
                space[i] = space[limit];
                space[limit] = t;
            }
        }
        else
        {
//          cerr << " PASS\n" ;
        }
    }
    valid = limit;
//  cerr << "\nFilter EX Valid " << setbase(10) << valid << "\n"; 
};

int Pattern::count(int ck, int limit)
{
    int i, num=0;
    for (i = 0; i < valid; ++i)
    {
        if (ck == check(space[i]))
            if (++num >= limit) return num;
    }
    return num;
}

int Pattern::propcheck(int limit)
{
    int k, mv, nv;

    for (k = mv = 0; k < valid; ++k)
    {
        int ck = check(space[k]);
        nv = count(ck, limit);
        if (nv >= limit)
        {
            return 99999;
        }
        if (nv > mv) mv = nv;
    }
    return mv;
}

int proposal(bool last)
{
    int i, minnv = 999999, mv, result;

    for (i = 0; i < valid; i++) 
    {
        Pattern& guess = space[i];
//      cerr << '\r' << setw(6) << i << ' ' << guess.dump();
        if ((mv = guess.propcheck(minnv)) < minnv)
        {
//          cerr << setw(6) << mv << ' ' << setw(7) << setiosflags(ios::fixed) << setprecision(0) << timer.stopTimer() << " s\n";
            minnv = mv;
            result = i;
        }
    }   
    if (last) 
        return result;
    minnv *= 0.75;
    for (; i<size; i++) 
    {
        Pattern& guess = space[i];
//      cerr << '\r' << setw(6) << i << ' ' << guess.dump();
        if ((mv = guess.propcheck(minnv)) < minnv)
        {
//          cerr << setw(6) << mv << ' ' << setw(7) << setiosflags(ios::fixed) << setprecision(0) << timer.stopTimer() << " s\n";
            minnv = mv;
            result = i;
        }
    }   
    return result;
}

void setup(string wordfile)
{
    int i = 0; 
    string word;
    ifstream infile(wordfile.data());
    while(infile >> word)
    {
        if (word.length() == wordLen) {
            space[i++].init(word);
        }
    }
    infile.close(); 
    size = valid = i;
}

int main(int argc, char* argv[])
{
    if (argc < 2) 
    {
        cerr << "Specify word length";
        return 1;
    }

    wordLen = atoi(argv[1]);

    timer.startTimer();
    setup("wordlist.txt");
    //cerr << "Words " << size 
    //  << setiosflags(ios::fixed) << setprecision(2)
    //  << " " << timer.stopTimer() << " s\n";

    valid = size;
    Pattern Guess = firstGuess[wordLen];
    for (int t = 0; t < 5; t++)
    {
        cout << Guess.dump() << '\n' << flush;
        string score;
        cin >> score;
        int ck = parseScore(score);
        //cerr << "\nV" << setw(8) << valid << " #" 
        //  << setw(3) << t << " : " << Guess.dump()
        //  << " : " << score << "\n";
        if (ck == ~(-1 << wordLen))
        {
            break;
        }
        Guess.filter(ck); 
        Guess = space[proposal(t == 3)];
    }
    // cerr << "\n";

    double time = timer.stopTimer();
    //cerr << setiosflags(ios::fixed) << setprecision(2)
    //   << timer.stopTimer() << " s\n";

    return 0;
}

내 점수

링고를 사용한 평가, 100 라운드 :

4   9000
5   17700
6   22000
7   25900
8   28600
9   29700
10  31000
11  32800
12  33500
13  34900

총 265'100

자체 평가 점수

전체 단어 목록에서 내 평균 점수는 다음과 같습니다. 테스트 중에 알고리즘의 일부 세부 사항이 변경되었으므로 완전히 신뢰할 수 없습니다.

 4 # words  6728 PT AVG   100.98 87170.41 s
 5 # words 14847 PT AVG   164.44 42295.38 s
 6 # words 28127 PT AVG   212.27 46550.00 s 
 7 # words 39694 PT AVG   246.16 61505.54 s
 8 # words 49004 PT AVG   273.23 63567.45 s
 9 # words 50655 PT AVG   289.00 45438.70 s
10 # words 43420 PT AVG   302.13 2952.23 s
11 # words 35612 PT AVG   323.62 3835.00 s
12 # words 27669 PT AVG   330.19 5882.98 s
13 # words 19971 PT AVG   339.60 2712.98 s

이 수치에 따르면 평균 점수는 257 ~ 800에 가까워 야합니다

피트 스코어

마지막으로 Ruby를 설치 했으므로 이제 '공식'점수를 얻었습니다.

    4       5       6       7       8       9      10      11      12      13   TOTAL
10700   16300   22000   25700   27400   30300   32000   33800   34700   34800   267700

내 의도는 이와 같은 것을 만드는 것이 었습니다. 아아 나는 솔루션 공간을 실제로 최소화하는 방법을 찾지 못했기 때문에 근사했다. 그리고 내 파이썬에는 파이썬이 있으므로 더 느립니다. 또한 첫 번째 추측을 하드 코딩했습니다. 당신은 짧은 줄을 위해 내 것보다 확실히 낫습니다. 동일한 입력 세트에서 구현을 테스트하여 비교할 수 있습니까? 또한 우리는 첫 번째 추측과는 상당히 다릅니다.
justhalf 2016 년

@ justhalf lingo.go로 라운드를 시도했습니다. 구덩이를 확인하지 않았습니다 (Ruby가 설치되어 있지 않습니다). 우리의 점수는 가깝습니다. 운이 좋을 것 같아요.
edc65

보고 된 평균이 내가보고 한 점수보다 낫기 때문에 귀하의 생각이 낫습니다. 시간이 많이 걸리는 것 같습니다.
justhalf

지금까지 가장 강한 선수 인 것 같습니다. 오늘 공식 결과를 오늘 발표 할 예정입니다.
SirDarius

죄송합니다. 위의 의견을 수정하여 제출 한 내용이 Java로되어 있음을 잊었습니다.
justhalf

5

Java, 249700 포인트 (테스트에서 Chinese Perl Goth보다 우수)

업데이트 된 순위 목록 :

                        4 5 6 8 9 10 11 12 13 합계
perl chinese_perl_goth.pl 6700 12300 16900 19200 23000 26100 28500 29600 32100 33900 228300
자바 Lingo 9400 14700 18900 21000 26300 28700 30300 32400 33800 34200 249700

다음은 다음을 사용하는 이전 순위 목록입니다 pit.rb.

                        4 5 6 8 9 10 11 12 13 합계
ruby player-example.rb 200400400500 1800 1400 1700 1600 3200 4400 15600
루비 player-example2.rb 2700 3200 2500 4300 7300 6300 8200 10400 13300 15000 73200
루비 player-example3.rb 4500 7400 9900 13700 15400 19000 19600 22300 24600 27300 163700
perl chinese_perl_goth.pl 6400 14600 16500 21000 22500 26000 27200 30600 32500 33800 231100
자바 Lingo 4800 13100 16500 21400 27200 29200 30600 32400 33700 36100 245000

** 순위 **
1 : 자바 링고 (245000)
2 : 펄 chinese_perl_goth.pl (231100)
3 : 루비 player-example3.rb (163700)
4 : 루비 player-example2.rb (73200)
5 : 루비 player-example.rb (15600)

@chineseperlgoth와 비교하여 짧은 단어 (<6 문자)로 잃어 버렸지 만 긴 단어 (> = 6 문자)로 이깁니다.

이 아이디어는 @chineseperlgoth와 유사합니다. 내 주요 아이디어는 다음 추측에 가장 많은 정보를 제공하는 추측 (나머지 가능성 중 하나가 아닌 동일한 길이의 단어 일 수 있음)을 찾는 것입니다.

현재 나는 여전히 공식을 가지고 놀고 있지만 위의 스코어 보드의 경우 최소값을 산출하는 단어를 선택합니다.

-num_confusion * 엔트로피

최신 버전은 다른 점수를 사용하여 다음 최상의 추측을 찾습니다. 현재 추측 후 "단일 가능성"의 수를 최대화합니다. 이것은 가능한 모든 후보에 대해 정리 된 단어 목록의 모든 단어를 시도하여 (시간을 절약하기 위해) 수행되며, 어떤 추측이 "단일 가능성"(즉,이 추측 후에 하나의 가능한 대답 만 있음)을 생성 할 가능성이 높은지 확인합니다. 다음 추측.

예를 들어이 실행 :

새로운 라운드를 시작하면 단어가 유익합니다
있어 : seora
보냄 :? XOXX
있어 : topsl
보냄 : XOX? X
있어 : 승려
보냄 : XO? XO
잡았다
보냄 : OXXXX
있어 : boons
보냄 : OOOOO
100 점을 얻은 라운드

처음 세 가지 추측에서, 우리는 어딘가에 "n"을 가진 "* oo * s"를 얻었고 여전히 하나의 글자를 더 찾아야합니다. 이제이 알고리즘의 장점은 그 형태와 유사한 단어를 추측하는 대신 이전 추측과 전혀 관련이없는 단어를 추측하고 더 많은 문자를 제공하려고하며 누락 된 문자를 표시한다는 것입니다. 이 경우 누락 된 "b"의 위치를 ​​올바르게 가져오고 올바른 최종 추측 "boons"로 끝납니다.

코드는 다음과 같습니다.

import java.util.*;
import java.io.*;

class Lingo{
    public static String[] guessBestList = new String[]{
                                "",
                                "a",
                                "sa",
                                "tea",
                                "orae",
                                "seora", // 5
                                "ariose",
                                "erasion",
                                "serotina",
                                "tensorial",
                                "psalterion", // 10
                                "ulcerations",
                                "culteranismo",
                                "persecutional"};
    public static HashMap<Integer, ArrayList<String>> wordlist = new HashMap<Integer, ArrayList<String>>();

    public static void main(String[] args){
        readWordlist("wordlist.txt");
        Scanner scanner = new Scanner(System.in);
        int wordlen = Integer.parseInt(args[0]);
        int roundNum = 5;
        ArrayList<String> candidates = new ArrayList<String>();
        candidates.addAll(wordlist.get(wordlen));
        String guess = "";
        while(roundNum-- > 0){
            guess = guessBest(candidates, roundNum==4, roundNum==0);
            System.out.println(guess);
            String response = scanner.nextLine();
            if(isAllO(response)){
                break;
            }
            updateCandidates(candidates, guess, response);
            //print(candidates);
        }
    }

    public static void print(ArrayList<String> candidates){
        for(String str: candidates){
            System.err.println(str);
        }
        System.err.println();
    }

    public static void readWordlist(String path){
        try{
            BufferedReader reader = new BufferedReader(new FileReader(path));
            while(reader.ready()){
                String word = reader.readLine();
                if(!wordlist.containsKey(word.length())){
                    wordlist.put(word.length(), new ArrayList<String>());
                }
                wordlist.get(word.length()).add(word);
            }
        } catch (Exception e){
            System.exit(1);
        }
    }

    public static boolean isAllO(String response){
        for(int i=0; i<response.length(); i++){
            if(response.charAt(i) != 'O') return false;
        }
        return true;
    }

    public static String getResponse(String word, String guess){
        char[] wordChar = word.toCharArray();
        char[] result = new char[word.length()];
        Arrays.fill(result, 'X');
        for(int i=0; i<guess.length(); i++){
            if(guess.charAt(i) == wordChar[i]){
                result[i] = 'O';
                wordChar[i] = '_';
            }
        }
        for(int i=0; i<guess.length(); i++){
            if(result[i] == 'O') continue;
            for(int j=0; j<wordChar.length; j++){
                if(result[j] == 'O') continue;
                if(wordChar[j] == guess.charAt(i)){
                    result[i] = '?';
                    wordChar[j] = '_';
                    break;
                }
            }
        }
        return String.valueOf(result);
    }

    public static void updateCandidates(ArrayList<String> candidates, String guess, String response){
        for(int i=candidates.size()-1; i>=0; i--){
            String candidate = candidates.get(i);
            if(!response.equals(getResponse(candidate, guess))){
                candidates.remove(i);
            }
        }
    }

    public static int countMatchingCandidates(ArrayList<String> candidates, String guess, String response){
        int result = 0;
        for(String candidate: candidates){
            if(response.equals(getResponse(candidate, guess))){
                result++;
            }
        }
        return result;
    }

    public static String[] getSample(ArrayList<String> words, int size){
        String[] result = new String[size];
        int[] indices = new int[words.size()];
        for(int i=0; i<words.size(); i++){
            indices[i] = i;
        }
        Random rand = new Random(System.currentTimeMillis());
        for(int i=0; i<size; i++){
            int take = rand.nextInt(indices.length-i);
            result[i] = words.get(indices[take]);
            indices[take] = indices[indices.length-i-1];
        }
        return result;
    }

    public static String guessBest(ArrayList<String> candidates, boolean firstGuess, boolean lastGuess){
        if(candidates.size() == 1){
            return candidates.get(0);
        }
        String minGuess = candidates.get(0);
        int wordlen = minGuess.length();
        if(firstGuess && guessBestList[wordlen].length()==wordlen){
            return guessBestList[wordlen];
        }
        int minMatches = Integer.MAX_VALUE;
        String[] words;
        if(lastGuess){
            words = candidates.toArray(new String[0]);
        } else if (candidates.size()>10){
            words = bestWords(wordlist.get(wordlen), candidates, 25);
        } else {
            words = wordlist.get(wordlen).toArray(new String[0]);
        }
        for(String guess: words){
            double sumMatches = 0;
            for(String word: candidates){
                int matches = countMatchingCandidates(candidates, guess, getResponse(word, guess));
                if(matches == 0) matches = candidates.size();
                sumMatches += (matches-1)*(matches-1);
            }
            if(sumMatches < minMatches){
                minGuess = guess;
                minMatches = sumMatches;
            }
        }
        return minGuess;
    }

    public static String[] bestWords(ArrayList<String> words, ArrayList<String> candidates, int size){
        int[] charCount = new int[123];
        for(String candidate: candidates){
            for(int i=0; i<candidate.length(); i++){
                charCount[(int)candidate.charAt(i)]++;
            }
        }
        String[] tmp = (String[])words.toArray(new String[0]);
        Arrays.sort(tmp, new WordComparator(charCount));
        String[] result = new String[size+Math.min(size, candidates.size())];
        String[] sampled = getSample(candidates, Math.min(size, candidates.size()));
        for(int i=0; i<size; i++){
            result[i] = tmp[tmp.length-i-1];
            if(i < sampled.length){
                result[size+i] = sampled[i];
            }
        }
        return result;
    }

    static class WordComparator implements Comparator<String>{
        int[] charCount = null;

        public WordComparator(int[] charCount){
            this.charCount = charCount;
        }

        public Integer count(String word){
            int result = 0;
            int[] multiplier = new int[charCount.length];
            Arrays.fill(multiplier, 1);
            for(char chr: word.toCharArray()){
                result += multiplier[(int)chr]*this.charCount[(int)chr];
                multiplier[(int)chr] = 0;
            }
            return Integer.valueOf(result);
        }

        public int compare(String s1, String s2){
            return count(s1).compareTo(count(s2));
        }
    }
}

이 항목은 매우 강력합니다! 현재 단서에서 단어를 추측 할 수 없을 때 비슷한 전략을 사용하여 TV 쇼에서 인간 플레이어를 본 것을 기억합니다.
SirDarius

3

여전히 개선의 여지가 있지만 적어도 포함 된 예제 플레이어를 능가합니다 :)

단어 목록을 캐싱하기 위해 현재 디렉토리에 대한 쓰기 액세스를 가정합니다 (약간 더 빠르게 실행하도록). 를 사용하여 wordlist.lenN.stor파일을 만듭니다 Storable. 이것이 문제라면 read_cached_wordlist코드를 제거 하고 그냥 사용하도록 변경하십시오 read_wordlist.

설명

먼저 현재 단어 목록 ( build_histogram) 에있는 모든 단어에 문자 빈도 히스토그램을 작성 합니다. 그런 다음 다음 추측을 선택해야합니다 find_best_word. 스코어링 알고리즘은 히스토그램 값을 합산하여 이미 본 글자를 건너 뜁니다. 이것은 단어 목록에서 가장 빈번한 글자를 포함하는 단어를 제공합니다. 주어진 점수를 가진 단어가 두 개 이상인 경우 무작위로 하나를 선택합니다. 단어를 찾은 후 게임 엔진에 보내서 답장을 읽은 다음 유용한 정보를 시도해보십시오. :)

나는 단어의 주어진 위치에서 발생할 수있는 일련의 조건, 즉 글자를 유지합니다. 시작시 이것은 간단 (['a'..'z'] x $len)하지만 회신에 제공된 힌트를 기반으로 업데이트됩니다 (참조 update_conds). 그런 조건에서 정규 표현식을 작성하고 단어 목록을 필터링합니다.

테스트하는 동안 위에서 언급 한 필터링이 ?너무 잘 처리되지 않으므로 두 번째 필터 ( filter_wordlist_by_reply)가 발견되었습니다. 이것은 ?다른 위치의 단어에서 로 표시되는 문자 가 발생하고 그에 따라 단어 목록을 필터링 한다는 사실을 이용합니다 .

솔루션이 발견되지 않는 한 (또는 더 이상 stdin에서 읽을 수 없어, 실패를 의미하는 경우가 아니면) 주 루프가 반복 될 때마다이 단계가 반복됩니다.

암호

#!perl
use strict;
use warnings;
use v5.10;
use Storable;

$|=1;

sub read_wordlist ($) {
    my ($len) = @_;
    open my $w, '<', 'wordlist.txt' or die $!;
    my @wordlist = grep { chomp; length $_ == $len } <$w>;
    close $w;
    \@wordlist
}

sub read_cached_wordlist ($) {
    my ($len) = @_;
    my $stor = "./wordlist.len$len.stor";
    if (-e $stor) {
        retrieve $stor
    } else {
        my $wl = read_wordlist $len;
        store $wl, $stor;
        $wl
    }
}

sub build_histogram ($) {
    my ($wl) = @_;
    my %histo = ();
    for my $word (@$wl) {
        $histo{$_}++ for ($word =~ /./g);
    }
    \%histo
}

sub score_word ($$) {
    my ($word, $histo) = @_;
    my $score = 0;
    my %seen = ();
    for my $l ($word =~ /./g) {
        if (not exists $seen{$l}) {
            $score += $histo->{$l};
            $seen{$l} = 1;
        }
    }
    $score
}

sub find_best_word ($$) {
    my ($wl, $histo) = @_;
    my @found = (sort { $b->[0] <=> $a->[0] } 
                 map [ score_word($_, $histo), $_ ], @$wl);
    return undef unless @found;
    my $maxscore = $found[0]->[0];
    my @max;
    for (@found) {
        last if $_->[0] < $maxscore;
        push @max, $_->[1];
    }
    $max[rand @max]
}

sub build_conds ($) {
    my ($len) = @_;
    my @c;
    push @c, ['a'..'z'] for 1..$len;
    \@c
}

sub get_regex ($) {
    my ($cond) = @_;
    local $" = '';
    my $r = join "", map { "[@$_]" } @$cond;
    qr/^$r$/
}

sub remove_cond ($$$) {
    my ($conds, $pos, $ch) = @_;
    return if (scalar @{$conds->[$pos]} == 1);
    return unless grep { $_ eq $ch } @{$conds->[$pos]};
    $conds->[$pos] = [ grep { $_ ne $ch } @{$conds->[$pos]} ]
}

sub add_cond ($$$) {
    my ($conds, $pos, $ch) = @_;
    return if (scalar @{$conds->[$pos]} == 1);
    return if grep { $_ eq $ch } @{$conds->[$pos]};
    push @{$conds->[$pos]}, $ch
}

sub update_conds ($$$$) {
    my ($word, $reply, $conds, $len) = @_;
    my %Xes;
    %Xes = ();
    for my $pos (reverse 0..$len-1) {
        my $r = substr $reply, $pos, 1;
        my $ch = substr $word, $pos, 1;

        if ($r eq 'O') {
            $conds->[$pos] = [$ch]
        }

        elsif ($r eq '?') {
            for my $a (0..$len-1) {
                if ($a == $pos) {
                    remove_cond $conds, $a, $ch
                } else {
                    unless (exists $Xes{$a} and $Xes{$a} eq $ch) {
                        add_cond($conds, $a, $ch);
                    }
                }
            }
        }

        elsif ($r eq 'X') {
            $Xes{$pos} = $ch;
            for my $a (0..$len-1) {
                remove_cond $conds, $a, $ch
            }
        }
    }
}

sub uniq ($) {
    my ($data) = @_;
    my %seen; 
    [ grep { !$seen{$_}++ } @$data ]
}

sub filter_wordlist_by_reply ($$$) {
    my ($wl, $word, $reply) = @_;
    return $wl unless $reply =~ /\?/;
    my $newwl = [];
    my $len = length $reply;
    for my $pos (0..$len-1) {
        my $r = substr $reply, $pos, 1;
        my $ch = substr $word, $pos, 1;
        next unless $r eq '?';
        for my $a (0..$len-1) {
            if ($a != $pos) {
                if ('O' ne substr $reply, $a, 1) {
                    push @$newwl, grep { $ch eq substr $_, $a, 1 } @$wl
                }
            }
        }
    }
    uniq $newwl
}

my $len = $ARGV[0] or die "no length";
my $wl = read_cached_wordlist $len;
my $conds = build_conds $len;

my $c=0;
do {
    my $histo = build_histogram $wl;
    my $word = find_best_word $wl, $histo;
    die "no candidates" unless defined $word;
    say $word;
    my $reply = <STDIN>; 
    chomp $reply;
    exit 1 unless length $reply;
    exit 0 if $reply =~ /^O+$/;
    update_conds $word, $reply, $conds, $len;
    $wl = filter_wordlist_by_reply $wl, $word, $reply;
    $wl = [ grep { $_ =~ get_regex $conds } @$wl ]
} while 1

1
내 규칙은 원래 디스크에 쓰는 것을 금지했지만 단어 목록의 캐싱을 허용하는 예외를 만들었습니다. 내가 찾은 큰 것 하나
만으로도

이 항목은 내 자신의 (발표되지 않은) 시도보다 잘 작동합니다. 알고리즘을 조금 설명해 주시겠습니까?
다리우스

간단한 설명을 추가했습니다. 코드 형식도 약간 수정했습니다.
중국 펄 고스

@ sirDarius : 특정 테스트에서 적절한 길이의 항목 만 포함 된 단어 목록을 사용하면 손실이 없을 것이라고 생각합니다. 프로그램이 파일 내에서 길이가 지정된 길이 이외의 단어를 무시하는 것은 지나치게 어렵지 않지만 그러한 단어가 있으면 테스트 속도가 느려집니다. 또한, 제출물이 단어 목록과 N이 주어지면 가장 도움이되는 방식으로 형식화 된 단어 목록을 표준 출력으로 보낼 수있는 선택적 프로그램을 제출할 수있는 가치가 있는지 궁금합니다.
supercat

... 그리고 메인 프로그램이 원시 단어 목록 대신 그것을 사용하도록 허용하십시오 (따라서 일부 사전 분석이 필요한 경우 게임 당 한 번이 아니라 각 단어 길이마다 한 번만 수행하면됩니다).
supercat 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.