C, ortalama 500+ 1500 1750 puan
Bu, sürüm 2'ye göre nispeten küçük bir gelişmedir (önceki sürümlerle ilgili notlar için aşağıya bakın). İki bölüm var. Birincisi: Havuzdan tahtaları rastgele seçmek yerine, havuzdaki her tahta üzerinde tekrarlar, havuzun tepesine dönmeden ve tekrar etmeden önce her birini sırayla kullanır. (Bu yineleme gerçekleşirken havuz değiştirildiği için, üst üste iki kez veya daha kötüsü seçilen tahtalar olacaktır, ancak bu ciddi bir endişe değildir.) İkinci değişiklik, havuzun değiştiğinde programın şimdi izlemesidir. ve program havuz içeriğini iyileştirmeden çok uzun bir süre devam ederse, aramanın "durduğunu", havuzu boşalttığını ve yeni bir arama ile başladığını belirler. İki dakika dolana kadar bunu yapmaya devam eder.
Başlangıçta 1500 nokta aralığının ötesine geçmek için bir çeşit sezgisel arama yapacağımı düşünmüştüm. @ mellamokb'un 4527 puanlık bir tahta hakkındaki yorumu beni geliştirmek için bolca yer olduğunu varsaymamı sağladı. Ancak, nispeten küçük bir kelime listesi kullanıyoruz. 4527 puanlık tahta, en kapsayıcı kelime listesi olan YAWL kullanarak puan aldı - resmi ABD Scrabble kelime listesinden bile daha büyük. Bunu göz önünde bulundurarak, programımın bulduğu panoları yeniden inceledim ve 1700'ün üzerinde sınırlı sayıda pano olduğunu gördüm. Örneğin, 1726 puan alan bir tahtayı keşfetmiş olan birden fazla koşum vardı, ancak her zaman bulunan aynı tahtaydı (dönüşleri ve yansımaları görmezden gelmek).
Başka bir test olarak, programımı sözlük olarak YAWL kullanarak çalıştırdım ve yaklaşık bir düzine çalışmadan sonra 4527 puanlık kurulu buldu. Bu göz önüne alındığında, programımın zaten arama alanının üst sınırında olduğunu varsayıyorum ve bu nedenle planladığım yeniden yazma çok az kazanç için ekstra karmaşıklık getirecektir.
Programımın english.0
kelime listesini kullanarak bulduğu en yüksek puan alan beş tahtanın listesi:
1735 : D C L P E I A E R N T R S E G S
1738 : B E L S R A D G T I N E S E R S
1747 : D C L P E I A E N T R D G S E R
1766 : M P L S S A I E N T R N D E S G
1772: G R E P T N A L E S I T D R E S
İnancım, 531 kelimeyle 1772 "grep board" (onu aramaya başladığım gibi), bu kelime listesiyle mümkün olan en yüksek puanlama kurulu olduğu. Programımın iki dakikalık çalışmasının% 50'den fazlası bu anakartla bitiyor. Ayrıca daha iyi bir şey bulamadan programımı bir gecede bıraktım. Bu nedenle, daha yüksek puan alan bir tahta varsa, muhtemelen programın arama tekniğini yenen bir yönünün olması gerekir. Örneğin, mizanpajda olası her küçük değişikliğin toplam puanda büyük bir düşüşe neden olduğu bir tahta, programım tarafından asla bulunamayabilir. Benim önsezim, böyle bir tahtanın var olma ihtimalinin düşük olması.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#define WORDLISTFILE "./english.0"
#define XSIZE 4
#define YSIZE 4
#define BOARDSIZE (XSIZE * YSIZE)
#define DIEFACES 6
#define WORDBUFSIZE 256
#define MAXPOOLSIZE 32
#define STALLPOINT 64
#define RUNTIME 120
/* Generate a random int from 0 to N-1.
*/
#define random(N) ((int)(((double)(N) * rand()) / (RAND_MAX + 1.0)))
static char const dice[BOARDSIZE][DIEFACES] = {
"aaeegn", "elrtty", "aoottw", "abbjoo",
"ehrtvw", "cimotu", "distty", "eiosst",
"delrvy", "achops", "himnqu", "eeinsu",
"eeghnw", "affkps", "hlnnrz", "deilrx"
};
/* The dictionary is represented in memory as a tree. The tree is
* represented by its arcs; the nodes are implicit. All of the arcs
* emanating from a single node are stored as a linked list in
* alphabetical order.
*/
typedef struct {
int letter:8; /* the letter this arc is labelled with */
int arc:24; /* the node this arc points to (i.e. its first arc) */
int next:24; /* the next sibling arc emanating from this node */
int final:1; /* true if this arc is the end of a valid word */
} treearc;
/* Each of the slots that make up the playing board is represented
* by the die it contains.
*/
typedef struct {
unsigned char die; /* which die is in this slot */
unsigned char face; /* which face of the die is showing */
} slot;
/* The following information defines a game.
*/
typedef struct {
slot board[BOARDSIZE]; /* the contents of the board */
int score; /* how many points the board is worth */
} game;
/* The wordlist is stored as a binary search tree.
*/
typedef struct {
int item: 24; /* the identifier of a word in the list */
int left: 16; /* the branch with smaller identifiers */
int right: 16; /* the branch with larger identifiers */
} listnode;
/* The dictionary.
*/
static treearc *dictionary;
static int heapalloc;
static int heapsize;
/* Every slot's immediate neighbors.
*/
static int neighbors[BOARDSIZE][9];
/* The wordlist, used while scoring a board.
*/
static listnode *wordlist;
static int listalloc;
static int listsize;
static int xcursor;
/* The game that is currently being examined.
*/
static game G;
/* The highest-scoring game seen so far.
*/
static game bestgame;
/* Variables to time the program and display stats.
*/
static time_t start;
static int boardcount;
static int allscores;
/* The pool contains the N highest-scoring games seen so far.
*/
static game pool[MAXPOOLSIZE];
static int poolsize;
static int cutoffscore;
static int stallcounter;
/* Some buffers shared by recursive functions.
*/
static char wordbuf[WORDBUFSIZE];
static char gridbuf[BOARDSIZE];
/*
* The dictionary is stored as a tree. It is created during
* initialization and remains unmodified afterwards. When moving
* through the tree, the program tracks the arc that points to the
* current node. (The first arc in the heap is a dummy that points to
* the root node, which otherwise would have no arc.)
*/
static void initdictionary(void)
{
heapalloc = 256;
dictionary = malloc(256 * sizeof *dictionary);
heapsize = 1;
dictionary->arc = 0;
dictionary->letter = 0;
dictionary->next = 0;
dictionary->final = 0;
}
static int addarc(int arc, char ch)
{
int prev, a;
prev = arc;
a = dictionary[arc].arc;
for (;;) {
if (dictionary[a].letter == ch)
return a;
if (!dictionary[a].letter || dictionary[a].letter > ch)
break;
prev = a;
a = dictionary[a].next;
}
if (heapsize >= heapalloc) {
heapalloc *= 2;
dictionary = realloc(dictionary, heapalloc * sizeof *dictionary);
}
a = heapsize++;
dictionary[a].letter = ch;
dictionary[a].final = 0;
dictionary[a].arc = 0;
if (prev == arc) {
dictionary[a].next = dictionary[prev].arc;
dictionary[prev].arc = a;
} else {
dictionary[a].next = dictionary[prev].next;
dictionary[prev].next = a;
}
return a;
}
static int validateword(char *word)
{
int i;
for (i = 0 ; word[i] != '\0' && word[i] != '\n' ; ++i)
if (word[i] < 'a' || word[i] > 'z')
return 0;
if (word[i] == '\n')
word[i] = '\0';
if (i < 3)
return 0;
for ( ; *word ; ++word, --i) {
if (*word == 'q') {
if (word[1] != 'u')
return 0;
memmove(word + 1, word + 2, --i);
}
}
return 1;
}
static void createdictionary(char const *filename)
{
FILE *fp;
int arc, i;
initdictionary();
fp = fopen(filename, "r");
while (fgets(wordbuf, sizeof wordbuf, fp)) {
if (!validateword(wordbuf))
continue;
arc = 0;
for (i = 0 ; wordbuf[i] ; ++i)
arc = addarc(arc, wordbuf[i]);
dictionary[arc].final = 1;
}
fclose(fp);
}
/*
* The wordlist is stored as a binary search tree. It is only added
* to, searched, and erased. Instead of storing the actual word, it
* only retains the word's final arc in the dictionary. Thus, the
* dictionary needs to be walked in order to print out the wordlist.
*/
static void initwordlist(void)
{
listalloc = 16;
wordlist = malloc(listalloc * sizeof *wordlist);
listsize = 0;
}
static int iswordinlist(int word)
{
int node, n;
n = 0;
for (;;) {
node = n;
if (wordlist[node].item == word)
return 1;
if (wordlist[node].item > word)
n = wordlist[node].left;
else
n = wordlist[node].right;
if (!n)
return 0;
}
}
static int insertword(int word)
{
int node, n;
if (!listsize) {
wordlist->item = word;
wordlist->left = 0;
wordlist->right = 0;
++listsize;
return 1;
}
n = 0;
for (;;) {
node = n;
if (wordlist[node].item == word)
return 0;
if (wordlist[node].item > word)
n = wordlist[node].left;
else
n = wordlist[node].right;
if (!n)
break;
}
if (listsize >= listalloc) {
listalloc *= 2;
wordlist = realloc(wordlist, listalloc * sizeof *wordlist);
}
n = listsize++;
wordlist[n].item = word;
wordlist[n].left = 0;
wordlist[n].right = 0;
if (wordlist[node].item > word)
wordlist[node].left = n;
else
wordlist[node].right = n;
return 1;
}
static void clearwordlist(void)
{
listsize = 0;
G.score = 0;
}
static void scoreword(char const *word)
{
int const scoring[] = { 0, 0, 0, 1, 1, 2, 3, 5 };
int n, u;
for (n = u = 0 ; word[n] ; ++n)
if (word[n] == 'q')
++u;
n += u;
G.score += n > 7 ? 11 : scoring[n];
}
static void addwordtolist(char const *word, int id)
{
if (insertword(id))
scoreword(word);
}
static void _printwords(int arc, int len)
{
int a;
while (arc) {
a = len + 1;
wordbuf[len] = dictionary[arc].letter;
if (wordbuf[len] == 'q')
wordbuf[a++] = 'u';
if (dictionary[arc].final) {
if (iswordinlist(arc)) {
wordbuf[a] = '\0';
if (xcursor == 4) {
printf("%s\n", wordbuf);
xcursor = 0;
} else {
printf("%-16s", wordbuf);
++xcursor;
}
}
}
_printwords(dictionary[arc].arc, a);
arc = dictionary[arc].next;
}
}
static void printwordlist(void)
{
xcursor = 0;
_printwords(1, 0);
if (xcursor)
putchar('\n');
}
/*
* The board is stored as an array of oriented dice. To score a game,
* the program looks at each slot on the board in turn, and tries to
* find a path along the dictionary tree that matches the letters on
* adjacent dice.
*/
static void initneighbors(void)
{
int i, j, n;
for (i = 0 ; i < BOARDSIZE ; ++i) {
n = 0;
for (j = 0 ; j < BOARDSIZE ; ++j)
if (i != j && abs(i / XSIZE - j / XSIZE) <= 1
&& abs(i % XSIZE - j % XSIZE) <= 1)
neighbors[i][n++] = j;
neighbors[i][n] = -1;
}
}
static void printboard(void)
{
int i;
for (i = 0 ; i < BOARDSIZE ; ++i) {
printf(" %c", toupper(dice[G.board[i].die][G.board[i].face]));
if (i % XSIZE == XSIZE - 1)
putchar('\n');
}
}
static void _findwords(int pos, int arc, int len)
{
int ch, i, p;
for (;;) {
ch = dictionary[arc].letter;
if (ch == gridbuf[pos])
break;
if (ch > gridbuf[pos] || !dictionary[arc].next)
return;
arc = dictionary[arc].next;
}
wordbuf[len++] = ch;
if (dictionary[arc].final) {
wordbuf[len] = '\0';
addwordtolist(wordbuf, arc);
}
gridbuf[pos] = '.';
for (i = 0 ; (p = neighbors[pos][i]) >= 0 ; ++i)
if (gridbuf[p] != '.')
_findwords(p, dictionary[arc].arc, len);
gridbuf[pos] = ch;
}
static void findwordsingrid(void)
{
int i;
clearwordlist();
for (i = 0 ; i < BOARDSIZE ; ++i)
gridbuf[i] = dice[G.board[i].die][G.board[i].face];
for (i = 0 ; i < BOARDSIZE ; ++i)
_findwords(i, 1, 0);
}
static void shuffleboard(void)
{
int die[BOARDSIZE];
int i, n;
for (i = 0 ; i < BOARDSIZE ; ++i)
die[i] = i;
for (i = BOARDSIZE ; i-- ; ) {
n = random(i);
G.board[i].die = die[n];
G.board[i].face = random(DIEFACES);
die[n] = die[i];
}
}
/*
* The pool contains the N highest-scoring games found so far. (This
* would typically be done using a priority queue, but it represents
* far too little of the runtime. Brute force is just as good and
* simpler.) Note that the pool will only ever contain one board with
* a particular score: This is a cheap way to discourage the pool from
* filling up with almost-identical high-scoring boards.
*/
static void addgametopool(void)
{
int i;
if (G.score < cutoffscore)
return;
for (i = 0 ; i < poolsize ; ++i) {
if (G.score == pool[i].score) {
pool[i] = G;
return;
}
if (G.score > pool[i].score)
break;
}
if (poolsize < MAXPOOLSIZE)
++poolsize;
if (i < poolsize) {
memmove(pool + i + 1, pool + i, (poolsize - i - 1) * sizeof *pool);
pool[i] = G;
}
cutoffscore = pool[poolsize - 1].score;
stallcounter = 0;
}
static void selectpoolmember(int n)
{
G = pool[n];
}
static void emptypool(void)
{
poolsize = 0;
cutoffscore = 0;
stallcounter = 0;
}
/*
* The program examines as many boards as it can in the given time,
* and retains the one with the highest score. If the program is out
* of time, then it reports the best-seen game and immediately exits.
*/
static void report(void)
{
findwordsingrid();
printboard();
printwordlist();
printf("score = %d\n", G.score);
fprintf(stderr, "// score: %d points (%d words)\n", G.score, listsize);
fprintf(stderr, "// %d boards examined\n", boardcount);
fprintf(stderr, "// avg score: %.1f\n", (double)allscores / boardcount);
fprintf(stderr, "// runtime: %ld s\n", time(0) - start);
}
static void scoreboard(void)
{
findwordsingrid();
++boardcount;
allscores += G.score;
addgametopool();
if (bestgame.score < G.score) {
bestgame = G;
fprintf(stderr, "// %ld s: board %d scoring %d\n",
time(0) - start, boardcount, G.score);
}
if (time(0) - start >= RUNTIME) {
G = bestgame;
report();
exit(0);
}
}
static void restartpool(void)
{
emptypool();
while (poolsize < MAXPOOLSIZE) {
shuffleboard();
scoreboard();
}
}
/*
* Making small modifications to a board.
*/
static void turndie(void)
{
int i, j;
i = random(BOARDSIZE);
j = random(DIEFACES - 1) + 1;
G.board[i].face = (G.board[i].face + j) % DIEFACES;
}
static void swapdice(void)
{
slot t;
int p, q;
p = random(BOARDSIZE);
q = random(BOARDSIZE - 1);
if (q >= p)
++q;
t = G.board[p];
G.board[p] = G.board[q];
G.board[q] = t;
}
/*
*
*/
int main(void)
{
int i;
start = time(0);
srand((unsigned int)start);
createdictionary(WORDLISTFILE);
initwordlist();
initneighbors();
restartpool();
for (;;) {
for (i = 0 ; i < poolsize ; ++i) {
selectpoolmember(i);
turndie();
scoreboard();
selectpoolmember(i);
swapdice();
scoreboard();
}
++stallcounter;
if (stallcounter >= STALLPOINT) {
fprintf(stderr, "// stalled; restarting search\n");
restartpool();
}
}
return 0;
}
Sürüm 2 için notlar (9 Haziran)
İşte kodumun ilk sürümünü bir atlama noktası olarak kullanmanın bir yolu. Bu versiyondaki değişiklikler 100 satırdan az, ancak ortalama oyun skorunu üçe katladı.
Bu versiyonda program, programın bugüne kadar oluşturduğu en yüksek puan alan N kuruldan oluşan adaylardan oluşan bir “havuz” tutuyor. Her yeni tahta üretildiğinde, havuza eklenir ve havuzdaki en düşük puanlı tahta kaldırılır (eğer puanı zaten mevcut olandan daha düşükse, yeni eklenen tahta çok iyi olabilir). Havuz başlangıçta rastgele oluşturulan panolarla doldurulur, daha sonra programın çalışması boyunca sabit bir boyut tutar.
Programın ana döngüsü havuzdan rastgele bir tahta seçmek ve değiştirmek, bu yeni kurulun puanını belirlemek ve daha sonra havuza koymaktır (yeterince iyi ise). Bu şekilde, program yüksek puan tablolarını sürekli olarak geliştirmektedir. Ana faaliyet adım adım, artımlı iyileştirmeler yapmaktır, ancak havuzun boyutu da programın daha iyi hale getirmeden önce bir tahtanın puanını geçici olarak daha da kötüleştiren çok adımlı iyileştirmeler bulmasına izin verir.
Tipik olarak bu program oldukça hızlı bir şekilde iyi bir yerel maksimum bulur, bundan sonra daha iyi maksimumlar bulunamayacak kadar uzaktır. Ve bir kez daha programı 10 saniyeden daha uzun süre çalıştırmanın pek bir anlamı yok. Bu, örneğin programın bu durumu algılaması ve yeni bir aday havuzuyla yeni bir arama başlatmasıyla geliştirilebilir. Ancak, bu sadece marjinal bir artış sağlayacaktır. Uygun bir sezgisel arama tekniği muhtemelen daha iyi bir keşif yolu olacaktır.
(Yan not: Bu sürümün yaklaşık 5k pano / sn ürettiğini gördüm. İlk sürüm tipik olarak 20k pano / sn yaptı, başlangıçta endişelendim. Profilleme üzerine, kelime listesini yönetmek için fazladan zaman harcadığını keşfettim. Başka bir deyişle, bu tamamen program başına çok daha fazla kelime bulmaktan kaynaklanıyordu.Bunun ışığında, kelime listesini yönetmek için kodu değiştirmeyi düşündüm, ancak bu program sadece tahsis edilen 120 saniyenin 10'unu kullanıyor, bir optimizasyon çok erken olacaktır.)
Sürüm 1 için notlar (2 Haziran)
Bu çok ama çok basit bir çözüm. Tüm yaptığı rastgele tahtalar oluşturur ve daha sonra 10 saniye sonra en yüksek puanı alan olanı verir. (Varsayılan olarak 10 saniyeye ayarladım çünkü problem spesifikasyonunun izin verdiği ekstra 110 saniye genellikle beklemeye değecek kadar bulunan nihai çözümü geliştirmiyor.) Bu yüzden son derece aptal. Bununla birlikte, daha akıllı bir arama için iyi bir başlangıç noktası yapmak için tüm altyapıya sahiptir ve eğer herhangi biri son tarihten önce kullanmak isterse, bunu yapmaya teşvik ederim.
Program sözlüğü bir ağaç yapısına okuyarak başlar. (Form olabildiğince optimize edilmemiştir, ancak bu amaçlar için yeterince iyi değildir.) Başka bir temel başlatma işleminden sonra, pano oluşturmaya ve puanlamaya başlar. Program makinemde saniyede yaklaşık 20k pano inceliyor ve yaklaşık 200k panodan sonra rastgele yaklaşım kurumaya başlıyor.
Herhangi bir zamanda sadece bir kart değerlendirildiğinden, puanlama verileri global değişkenlerde saklanır. Bu, özyinelemeli işlevlere bağımsız değişken olarak iletilmesi gereken sabit veri miktarını en aza indirmeme olanak tanır. (Eminim bu bazı insanlara kovan verir ve onlardan özür dilerim.) Kelime listesi ikili bir arama ağacı olarak saklanır. Bulunan her kelimenin kelime listesinde aranması gerekir, böylece yinelenen kelimeler iki kez sayılmaz. Kelime listesi sadece değerlendirme sürecinde gereklidir, bu yüzden skor bulunduktan sonra atılır. Bu nedenle, programın sonunda, seçilen kartın tekrar tekrar puanlanması gerekir, böylece kelime listesi yazdırılabilir.
İlginç gerçek: Rastgele oluşturulmuş Boggle tahtasının skoru english.0
, 61.7 puan.
4527
(1414
toplam kelime), burada bulundu: ai.stanford.edu/~chuongdo/boggle/index.html