CodinGameに新しく登場したWordleマスターマインドの文字列版
固定文字数の文字列を推測とそれによって与えられるヒントで
単手数で答えたほうが勝ち
旧バージョン
現在はオプティマイズゲームになっていますが、最初は対戦型ゲーム
対戦型の時はルーチンをいくつか作り1位を保ってました
答えは事前に与えられる単語集の中にありました
新バージョン
単語集が与えられず
SUBMIT後は問題すらわからず、エラー時のログもだせません
最初これではゲームにならないかなと放置、
正直なところWordleの投稿か承認されない可能性もありそうと思っていました
機械学習
承認されたタイミングで再開発してみました
今回は少し機械学習も使ってみることにします...(CharGPTさん活用)
aからzまでの文字列を使われる頻度の高い順にならべかえられますか?と質問してその解答
これらの文字を含む文字列を頻度の高い順に並べると、以下のようになります。
e, a, t, o, i, n, s, h, r, d, l, u, c, m, f, w, y, g, p, b, v, k, x, j, q, z
これを単語の生成ルーチンと初期投入に導入してみました
(本来の機械学習とは意味が違うような...)
序盤解答
初期投入はこの文字列を頭から順番に答え、必要文字を絞り込みます
初期文字列生成
文字列の生成は再帰で作成
頻度順に文字を並べていき必要文字列が揃った段階で
過去、全解答とヒントに突き合わせ、矛盾のない最初のものを解答
文字生成のが多くなるパターンでタイムアウトを発生
暫定的に定時で最後に作ったものを提示
SUBMITしてバグ
全問解けていない 105人中96位
SUBMIT後が検証する方法がなく、まっくらな洞窟で手探り開始の気分
シミュレーション作成
最終的にローカルで問題生成から解答まで検証するシミュレーションを作成しました
このゲームのサーバー側の作業は少ないのでなんとかなりました
何度か動かして、ソースと にらめっこした結果
抜け落ちるケースが判明 もれを塞いだ所なんとなく直ります(一個余分なreturn)
再帰系のプログラムだと直接の原因はわかりにくい
バグが取れて 18位ぐらい
文字生成をバラつかせる
次にタイムアウトをなくしたい
文字生成で解答の頻度が少ないものを後回しにできないか、一日ぐらい頭の中で検討
複数回、同じ英字が発生している時、その検索段階の文字列を一時保管して
生成文字を初期段階でバラつかせる
他にもシミュレーションの回数を複数回して平均回数を測定
解答ターン数は平均9.5回から9.1回に改善しました
結果
初期解答のルーチンも検討してみましたが、
これだと思ったルーチンは平均11.5回で 妄想通りにはいかない
ということで 10位まであがる 寝て起きて 106人中11位
追加案検討
あるていど単語に意味があるなら、
単語集をこちらでつくって、そこから答えるという案はある
ロジックはそんなに大変ではないけど、単語集作成が大変
(追記 8文字の英単語606個 追加、これで1位いただき!
とか妄想したけど1点もあがらず、サーバー側の文字列ランダムっぽい)
シミュレーション版のソースを最後に残しときます
import sys
import math
import time
import random
def get_guess(words,states):
global az,answer,same3
if len(az)>=word_length:
rw=az[:word_length]
az=az[word_length:]
answer+=[rw]
return
r=""
word_check_rec("",True)
if len(answer)>0:return
for w in same3:
word_check_rec(w,False)
if len(answer)>0:return
return
def check_same3(word):
return len(word)-len(set(list(word)))>=1
def word_check_rec(word,same3_sw):
global answer,same3
if answer!=[]:return
if same3_sw and check_same3(word):
same3+=[word]
#ep("same3 打ち切り",word)
return
if len(set(list(word)) | s_use)>word_length:
ep("打ち切り")
return
l=len(word)
if l==word_length:return
#ep(word,hit,l,hit[l])
if hit[l]!="*":
if l+1==word_length:
if check(word+hit[l]):answer+=[word+hit[l]]
elif l+1<word_length:
word_check_rec(word+hit[l],same3_sw)
else:
for a in ab:
if a not in no_use_line[l]:
if l+1==word_length:
if check(word+a):answer+=[word+a]
elif l+1<word_length:
word_check_rec(word+a,same3_sw)
return
def check(word):
global check_count,u_count
if len(set(list(word)) | s_use)>word_length:
u_count+=1
return False
check_count+=1
if check_count%100==0:
ep(check_count,word,set(list(word)) | s_use)
if time.time()-stime>0.040:
ep("無理やり")
return True
for w,st in zip(words,states):
if get_state(word,w)!=st:return False
return True
def get_state(answer_word,word):
r=[]
for ca,c in zip(answer_word,word):
if ca==c:r+=[3]
elif answer_word.find(c)>=0:r+=[2]
else:r+=[1]
return r
def ep(*t):print(t,file=sys.stderr)
def ret_guess(guess):
if guess=="":
return " ".join(list("0"*word_length))
else:
return " ".join(str(i) for i in get_state(q_word,guess))
def random_word(n):
a=list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
r=""
for i in range(n):
random.shuffle(a)
r+=a[0]
return r
def last1(hit):
for uc in use:
line=[]
for i,c in enumerate(hit):
if c==uc:line+=[-1]
if c!="*":continue
if uc not in no_use_line[i]:
line+=[i]
if len(line)==1:
p=line[0]
if p!=-1:
nhit=hit[:p]+uc+hit[p+1:]
ep("@@@ nhit,hit,q_word @@@",nhit,hit,q_word)
hit=nhit
return hit
time_list=[]
for world in range(100):
ep("============ NEW GAME ==============")
q_word=random_word(8)
word_length = len(q_word)
az="".join("e, a, t, o, i, n, s, h, r, d, l, u, c, m, f, w, y, g, p, b, v, k, x, j, q, z".upper().split(", "))
print("AIUEOSBCDFGHJKLMNPQRTVWXYZ",file=sys.stderr)
ab=az
print(az,file=sys.stderr)
# game loop
words=[]
states=[]
no_use_line=[]
for i in range(word_length):
no_use_line+=[["#"]]
use=[]
hit="*"*word_length
check_out=[]
guess=""
times=0
while True:
times+=1
check_count=0
#line=[int(i) for i in input().split()]
line=[int(i) for i in ret_guess(guess).split()]
if line.count(3)==word_length:
time_list+=[times]
break
if line[0]!=0:
words+=[guess]
states+=[line]
stime=time.time()
ep("hit,words,states",hit,words,states)
ep("use",use)
ep("no_use_line",no_use_line)
for i,(c,state) in enumerate(zip(guess,line)):
if state==1:
ab=ab.replace(c,"")
if state==3:
hit=hit[:i]+c+hit[i+1:]
if c not in use:use+=[c]
if state==2:
if c not in use:use+=[c]
if c not in no_use_line[i]:
ep(no_use_line[i])
no_use_line[i]+=[c]
ep(no_use_line[i])
hit=last1(hit)
hit=last1(hit)
if len(use)==word_length:
ab="".join(use)
ep("ab",ab)
s_use=set(use)
u_count=0
answer=[]
same3=[]
get_guess(words,states)
ep("check_count,u_count",check_count,u_count)
guess=answer[0]
print(answer[0],"---------------")
print("平均",sum(time_list)/len(time_list))