#define NDEBUG // GLOBAL.H // // some global macros, just for my convenience #ifndef GLOBAL_H #define GLOBAL_H #include #include #include #include #include #include #include #include #include using namespace std; inline int max(int a,int b) { return (a>b)? a : b;}; inline int min(int a,int b) { return (a>b)? b : a;}; inline int sqr(int x){return x*x;}; //inline bool isLetter(char c) {return c>='A' /*&& c <='Z'*/;}; // using a macro is much faster because of the missing optimizer on the target machine #define isLetter(c) ((c)>='A') inline double ClockSec() {return (double)clock()/CLOCKS_PER_SEC;}; typedef unsigned long ULONG; typedef unsigned short USHORT; typedef unsigned char UBYTE; typedef signed long LONG; typedef signed short SHORT; typedef signed char BYTE; // ******************************************************************* // display error and stop program void Error(char *); // global indicator for DebugOutput extern bool DebugMode; // maximum word length/grid size #define MAXWORDLENGTH 16 #define MAXWORDS 512 #define ROWS 16 #define COLUMNS 32 #define MAXTIME 59 #endif #ifndef INSERTPOSITION_H #define INSERTPOSITION_H // struct containg information where to insert a word into the grid struct InsertPosition { char x,y; bool horizontal; }; // an insert position with a score struct ScoredInsPos : public InsertPosition { int poolIdx; int score; int gridIdx; // number of the grid to which this position was applied }; // a priority queue for ScoredInsPos, top scores first class PQScoredInsPos { // vector _heap; // make memory allocation "by hand", because its faster without optimization ScoredInsPos *_heap; int _capacity; int _size; public: PQScoredInsPos() :_capacity(100000),_size(0) { // use malloc because we need realloc later _heap=(ScoredInsPos *)malloc(sizeof(ScoredInsPos)*_capacity); }; ~PQScoredInsPos() { free(_heap); } // insert a new object into the queue void Insert(const ScoredInsPos &newInsPos); // get a reference to the topmost element with the highest score (or NULL, if empty) const ScoredInsPos *Top() const { if(_size>0) return &_heap[0]; else return NULL; } // remove the top element from the heap void Pop(); int Size() const {return _size;} }; #endif // WORDPOOL.H // // static pool of all words in the wordlist // // (base class for all word lists) #ifndef WORDPOOL_H #define WORDPOOL_H // a word together with its length struct Word { char *word; // the characters of the word int length; // the word length }; // pair of a word index and a character position struct WordCharPosPair { short poolIdx; short charPos; // character position in this word }; typedef vector WordIndexVector; class WordPool { private: static int _poolsize; static Word _wordList[MAXWORDS]; // foreach letter of the alphabet: vector containing all words having this letter static WordIndexVector _wordsPerChar[26]; // foreach letter pair: vector containing all words having this letter combination static WordIndexVector _wordsPer2Chars[26][26]; // foreach letter triple: vector containing all words having this letter combination static WordIndexVector _wordsPer3Chars[26][26][26]; // foreach letter pair or letter triple: marker, if the pair/triple exists in the pool static bool _pairExists[26][26]; static bool _tripleExists[26][26][26]; private: // no constructor is allowed outside: everything is static here WordPool(){}; // Initialize the lookup table '_wordsPerChar' and '_wordsPer2Chars' static void InitLookupTables(); // sort the _wordList static void SortWords(); // helper functions for sorting static void CalcLetterScore(int *letterScore,const Word *wordList, int listsize); static void CalcWordScore(int *wordscore,const int *letterScore,const Word *wordList,int listsize); static void SordWordsByScore(Word *wordList,int listsize ,int *wordScore); public: static void ReadPool(const char *filename); static int GetPoolSize() {return _poolsize;}; static int GetWordLength(int nr) { assert(nr>=0 && nr < _poolsize); return _wordList[nr].length; } // get word and length in one step static const Word *GetPoolWord(int nr) { assert(nr>=0 && nr <_poolsize); return &_wordList[nr]; } static WordIndexVector &GetWordsPerChar(char c) { assert(isLetter(c)); return _wordsPerChar[c-'A']; } static WordIndexVector &GetWordsPer2Chars(char c1,char c2) { assert(isLetter(c1)); assert(isLetter(c2)); return _wordsPer2Chars[c1-'A'][c2-'A']; } static WordIndexVector &GetWordsPer3Chars(char c1,char c2,char c3) { assert(isLetter(c1)); assert(isLetter(c2)); assert(isLetter(c3)); return _wordsPer3Chars[c1-'A'][c2-'A'][c3-'A']; } static WordIndexVector &GetWordsPer12Chars(char c,char cAfter) { if(cAfter!=0) return GetWordsPer2Chars(c,cAfter); else return GetWordsPerChar(c); } static WordIndexVector &GetWordsPer123Chars(char c1,char c2,char c3) { if(c1!=0 && c3 !=0) return GetWordsPer3Chars(c1,c2,c3); else if(c1!=0) return GetWordsPer2Chars(c1,c2); else if(c3!=0) return GetWordsPer2Chars(c2,c3); else return GetWordsPerChar(c2); } static bool PairExists(char c1,char c2) { assert(isLetter(c1)); assert(isLetter(c2)); return _pairExists[c1-'A'][c2-'A']; } static bool TripleExists(char c1,char c2,char c3) { assert(isLetter(c1)); assert(isLetter(c2)); assert(isLetter(c3)); return _tripleExists[c1-'A'][c2-'A'][c3-'A']; } static bool PairOrTripleExists(char c1,char c2,char c3) { if(c1!=0 && c3!=0) return TripleExists(c1,c2,c3); if(c1!=0) return PairExists(c1,c2); if(c3!=0) return PairExists(c2,c3); return true; } // return index of word in "pool", or -1 if word not in pool static int FindWordIndex(const char *word) { // ** slow version, to be optimized for(int i=0;i<_poolsize;++i) if(strcmp(word,_wordList[i].word)==0) return i; return -1; } }; #endif // class for crossword grids; // every grid knows the words already stored in it // words can be inserted, but never removed #ifndef GRID_H #define GRID_H // blocking character #define BLOCK_CHAR '#' // guessed coverage percentage for the empty cells #ifndef COVERAGE #define COVERAGE 87 #endif // State of a character in the grid enum State_e { ST_EMPTY=0,ST_HORIZONTAL=1,ST_VERTICAL=2,ST_HORVERT=3,ST_BLOCKED=4 }; // ****************************************************************** // the grid class class Grid { // ********************************************************************** // member variables // the grid _cells char _cells[COLUMNS][ROWS]; UBYTE _cellState[COLUMNS][ROWS]; // indicator whether the flipper was used bool _hasFlipper; // sum of the number of characters of all words int _charCount; // the first column where new characters are expected for score guessing BYTE _firstFreeColumnEdge[ROWS]; // analogue to _firstFreeColumnEdge BYTE _firstFreeRowEdge[COLUMNS]; BYTE _firstFreeRow[COLUMNS]; // the number of empty cells outside the maximum row or maximum column int _emptyCellsHoriz, _emptyCellsVertic; // the position of the first incomplete word (x=y=-1, if there is no incomplete word) InsertPosition _firstIncompleteWord; bool _firstIncompleteWordValid; // indicator if _firstIncompleteWord has to be calculated // ********************************************************************** // put letter c into the grid, direction information included void PutLetter(char c, int x,int y,State_e dir) { assert(isLetter(c)); assert(x>=0 && y>=0 && x=0 && y>=0 && x=0 && y>=0 && x=0 && y>=0 && x=0 && y>=0 && x0 && isLetter(*(pCell-ROWS)) && (*(pState-ROWS) & dir)!=0) return; if(x0 && isLetter(*(pCell-1)) && (*(pState-1) & dir)!=0) return; if(y=0 && yCOLUMNS) newFreeCol = COLUMNS; if(newFreeCol>_firstFreeColumnEdge[y]) { _emptyCellsHoriz -= newFreeCol - _firstFreeColumnEdge[y]; _firstFreeColumnEdge[y]=newFreeCol; } } // adjust _firstFreeColumnEdge[y-1,y,y+1] void AdjustFreeColumns(int y,int x) { int col = x+1; IncreaseFirstFreeColumnEdge(y,col+1); if(y>0) IncreaseFirstFreeColumnEdge(y-1,col); if(y=0 && xROWS) newFreeRow = ROWS; if(newFreeRow>_firstFreeRowEdge[x]) { _emptyCellsVertic -= newFreeRow - _firstFreeRowEdge[x]; _firstFreeRowEdge[x]=newFreeRow; } } // adjust _firstFreeRow and related arrays after inserting a letter at (x,y) void AdjustFreeRows(int x,int y) { int row = y+1; if(row>_firstFreeRow[x]) _firstFreeRow[x]=row; // adjust _firstFreeRowEdge IncreaseFirstFreeRowEdge(x,row+1); if(x>0) IncreaseFirstFreeRowEdge(x-1,row); if(x0) { cBefore = *pCell; if(!isLetter(cBefore)) cBefore = 0; } if(x0) { cBefore = *pCell; if(!isLetter(cBefore)) cBefore = 0; } if(y0) { if(*pState & ST_BLOCKED) return true; } if(y0) { if(*pState & ST_BLOCKED) return true; } if(x=0 && *pCell==' ') { ++count; --x; pCell-=ROWS; } return count; } // test how many cells are empty before the current cell (vertically) int CountEmptyCellsBeforeVertic(int x,int yStart) const { register int count=0; register int y=yStart-1; register const char *pCell = &_cells[x][y]; while(y>=0 && *pCell==' ') { ++count; --y; --pCell; } return count; } private: // find the first column where a newly inserted word can start // if it has to cross (x,y) int FindStarterColumn(int xStart,int y) const { register int x=xStart; register int result = x; register const char *pCell = &_cells[x-1][y]; register const UBYTE *pState = &_cellState[x-1][y]; while(x>0) { if(*pCell == BLOCK_CHAR) return x; if (*pCell ==' ') result = x; if((*pState&ST_BLOCKED)!=0) return result; pCell-=ROWS; pState-=ROWS; --x; } return 0; } // find the first row where a newly inserted word can start // if it has to cross (x,y) int FindStarterRow(int x,int yStart) const { register int y=yStart; register int result = y; register const char *pCell = &_cells[x][y-1]; register const UBYTE *pState = &_cellState[x][y-1]; while(y>0) { if(*pCell == BLOCK_CHAR) return y; if(*pCell ==' ') result = y; if((*pState&ST_BLOCKED)!=0) return result; --pCell; --pState; --y; } return 0; } // unblock all cells that are not used in both directions void UnblockPossibleCells() { register UBYTE state; register UBYTE *pState=&_cellState[0][0]; register int x,y; for(x=0;x0) return ClockSec() / Score() * ExpectedScore(); else return 54; } // calculate the score after inserting, without actually doing the insert int ScoreAfterInsert(int poolIdx,int x,int y,bool horizontalDir) const { return _charCount+WordPool::GetWordLength(poolIdx); } // the guessed score when about x% of the empty space will be used // (corresponding to ScoreAfterInsert) int ExpectedScoreAfterInsert(int poolIdx,int x,int y,bool horizontalDir) const; // look for an incomplete word in the current grid // returns true, if there is one, and in insPos the result bool FindIncompleteWordPos(InsertPosition &insPos); // remove word starting at x,y in horizontal or vertical direction // (very slow, since this class is optimized for inserting and copying, but not for removal) void RemoveWord(int x,int y,bool horizontalDir); // test if there is a removable word at position x,y bool IsThereRemoveableWord(int x, int y,bool horizontalDir) const { assert(0<=x && x < COLUMNS); assert(0<=y && y < ROWS); if(!isLetter(_cells[x][y])) return false; if(horizontalDir) { if((_cellState[x][y] & ST_HORIZONTAL)==0) return false; if(x>0 && _cells[x-1][y]!=BLOCK_CHAR) return false; } else { if((_cellState[x][y] & ST_VERTICAL)==0) return false; if(y>0 && _cells[x][y-1]!=BLOCK_CHAR) return false; } return true; } // test if the words are connected (needed after removal) bool IsConnected() const; bool HasFlipper() const { return _hasFlipper; } }; // print the grid ostream& operator<< (ostream &os,const Grid &grid); #endif #ifndef HASHTABLE_H #define HASHTABLE_H // class for ULONG hash sets // implemented for closed hashing class Hashtable { int _size; ULONG *_tableMem; public: Hashtable(int size):_size(size) { _tableMem = new ULONG[size]; memset(&_tableMem[0],0,size*sizeof(ULONG)); } ~Hashtable() { delete[] _tableMem; } void Insert(ULONG value) { ++value; // make sure value is not zero int i=value%_size; while(_tableMem[i]!=0) { ++i; if(i==_size) i=1; } _tableMem[i]=value; } bool Test(ULONG testValue) { ++testValue; int i=testValue%_size; register ULONG value; while((value=_tableMem[i])!=0) { if(value==testValue) return true; ++i; if(i==_size) i=1; } return false; } }; #endif #ifndef SOLVER_H #define SOLVER_H class Grid; // the main problem solving class class Solver { static int _LevelSize; static void FindInitialGrids(vector &gridVec); static void FindNextGridLevel(vector &gridVec,vector &nextGridVec); static void AddWordsThroughCell(Grid &grid,int gridIdx,int x,int y,PQScoredInsPos &posQueue); static void AddAllGridsWithOneWordLess(const Grid &grid,vector *gridVec); static void AddAllGridsWithTwoWordLess(const Grid &grid,vector *gridVec); static void GetMatchingWordsWithFlipper(Grid &theGrid,int gridIdx, int xStart,int yStart, bool horizontalDir, vector &result); static void FindAllWordsWithFlipper(vector &gridVec,vector &nextGridVec); // find all positions where a word can be removed from the grid (without // checking for connectedness) static void FindRemovePositions(const Grid &grid,vector &posVec); public: static Grid *FindBestGrid(); }; #endif // insert a new object into the priority queue void PQScoredInsPos::Insert(const ScoredInsPos &newInsPos) { int pos = _size; ++_size; if(_size>=_capacity) { _capacity*=2; _heap=(ScoredInsPos *)realloc(_heap,sizeof(ScoredInsPos)*_capacity); } while(pos>0) { int posAbove = (pos-1)>>1; if(_heap[posAbove].score< newInsPos.score) { _heap[pos]=_heap[posAbove]; pos=posAbove; } else break; } _heap[pos] = newInsPos; } // remove the top element from the heap (and re-heap) void PQScoredInsPos::Pop() { #ifdef _DEBUG if(_size<=0) Error("calling Pop for an empty heap"); #endif ScoredInsPos insPos = _heap[_size-1]; // remember last element --_size; int pos=0; // Position where to place insPos int posBelow = 1; while(posBelow<_size) { // set posBelow to the sucessor element with the bigger score if(posBelow+1 < _size && (_heap[posBelow].score < _heap[posBelow+1].score) ) ++posBelow; if(insPos.score<_heap[posBelow].score) { _heap[pos]=_heap[posBelow]; pos = posBelow; posBelow=pos+pos +1; } else break; } _heap[pos] = insPos; } // WordPool.cpp // // pool of all words in the wordlist // int WordPool::_poolsize=0; Word WordPool::_wordList[MAXWORDS]; WordIndexVector WordPool::_wordsPerChar[26]; WordIndexVector WordPool::_wordsPer2Chars[26][26]; WordIndexVector WordPool::_wordsPer3Chars[26][26][26]; bool WordPool::_pairExists[26][26]; bool WordPool::_tripleExists[26][26][26]; // ******************************************************************** // helper function bool TestIfContainsIdx(const WordIndexVector &wiVec,int poolIdx) { int size = (int)wiVec.size(); for(int k=0;klength;++j) { ++letterScore[word->word[j]-'A']; } } } void WordPool::CalcWordScore(int *wordscore,const int *letterScore,const Word *wordList,int listsize) { int i,j; // foreach word: for(i=0;ilength;++j) sum+=letterScore[word->word[j]-'A']; // subtract the score for the letters of the word itself sum-=word->length; // remember the score wordscore[i]=sum / word->length; } } // sort words by score, higher score first void WordPool::SordWordsByScore(Word *wordList,int listsize ,int *wordScore) { int i,j; for(i=0;imaxScore) { maxScore = wordScore[j]; idx=j; } } if(idx==i) continue; // bring the found word to the front of the list Word tmpWord=wordList[idx]; wordList[idx]=wordList[i]; wordList[i]=tmpWord; int tmpScore = wordScore[idx]; wordScore[idx]=wordScore[i]; wordScore[i]=tmpScore; } } void WordPool::InitLookupTables() { int i,j,length; const char *word; char c; WordCharPosPair newPair; memset(_pairExists,0,sizeof(bool)*26*26); memset(_tripleExists,0,sizeof(bool)*26*26*26); for(i=0;i<_poolsize;++i) { length=_wordList[i].length; word=_wordList[i].word; for(j=0;j=length) continue; char c2=word[j+1]; _pairExists[c-'A'][c2-'A']=true; WordIndexVector &wiVec2 = _wordsPer2Chars[c-'A'][c2-'A']; newPair.poolIdx = (short)i; newPair.charPos = j; wiVec2.push_back(newPair); // and the last one is for three letter sequences if(j+2>=length) continue; char c3=word[j+2]; _tripleExists[c-'A'][c2-'A'][c3-'A']=true; WordIndexVector &wiVec3 = _wordsPer3Chars[c-'A'][c2-'A'][c3-'A']; newPair.poolIdx = (short)i; newPair.charPos = j; wiVec3.push_back(newPair); } } } // read a list of words into the word pool from the input stream 'is' void WordPool::ReadPool(const char *filename) { char buf[MAXWORDLENGTH+2]; int i,j,length; FILE *file; file=fopen(filename,"r"); if(!file) Error("file not found"); _poolsize=0; for(i=0;i0 && !isalpha(buf[length-1])) buf[--length]=0; // remove eol characters if(length<=0) // ignore empty lines continue; // convert buffer to upper case for(j=0;jgrid._charCount) return false; return memcmp(_cells,grid._cells,ROWS*COLUMNS*sizeof(UBYTE)) < 0; } // ***************************************************************** // calculation of a hash code. // important: blocked or filled cells are treated equal! // so two equally shaped grids with same insertion points cells give the same hash code ULONG Grid::HashCode() { register ULONG result=0; register ULONG mult = 1; register ULONG cell; register UBYTE *pState; register char *pCell; register const ULONG pi=314159; register int count; result += _charCount * mult; mult *= pi; pState = &_cellState[0][0]; pCell = &_cells[0][0]; // unrolled loop to speed up calculation for(count=0;countlength ; _charCount+= length; // put the characters into the grid int xend=xstart + length; const char *pWord = word->word; for(int x=xstart;x0) PutBlockChar(xstart-1,ystart,ST_HORIZONTAL); if(xendlength; _charCount+= length; // put the word into the grid int yend = ystart + length; const char *pWord = word->word; for(int y=ystart;y0) PutBlockChar(xstart,ystart-1,ST_VERTICAL); if(yendlength; if(horizontalDir) CheckIncompleteAfterHorizInsert(x,y,length); else CheckIncompleteAfterVerticInsert(x,y,length); } else _firstIncompleteWordValid=false; } // try to insert a word into the grid horizontally at (xstart,ystart) void Grid::InsertWordHoriz(int poolIdx,int xstart,int ystart) { const Word *word=WordPool::GetPoolWord(poolIdx); #ifdef _DEBUG // does the the word match? if(!DoesWordFitHoriz(word,xstart,ystart)) Error("InsertWordHoriz, word does not fit"); #endif // get the word out of the word pool int length = word->length ; _charCount+= length; // put the characters into the grid int xend=xstart + length; const char *pWord = word->word; for(int x=xstart;x0) PutBlockChar(xstart-1,ystart,ST_HORIZONTAL); if(xendlength; _charCount+= length; // put the word into the grid int yend = ystart + length; const char *pWord = word->word; for(int y=ystart;y0) PutBlockChar(xstart,ystart-1,ST_VERTICAL); if(yendlength; if(horizontalDir) CheckIncompleteAfterHorizInsert(x,y,length); else CheckIncompleteAfterVerticInsert(x,y,length); } else _firstIncompleteWordValid=false; } void Grid::RemoveWordHoriz(int x,int y,char *word) { // extract word characters, and remove them char c; register char *pWord = word; int xl = x; while(xl0) { assert(_cells[x-1][y]==BLOCK_CHAR); RemoveBlockChar(x-1,y,ST_HORIZONTAL); } if(xl0) { assert(_cells[x][y-1]==BLOCK_CHAR); RemoveBlockChar(x,y-1,ST_VERTICAL); } if(ylpoolIdx; charPos=itWord->charPos; // position where cellChar occurs const Word *word = WordPool::GetPoolWord(poolIdx); assert(charPos >=0 && charPoslength); assert(word->word[charPos]==cellChar); // if there is a word crossing "(x,y)": the cell cannot be blocked if(horizontalDir) { xstart = x-charPos; ystart = y; if(DoesWordFitHoriz(word,xstart,ystart )) return; } else { xstart = x; ystart = y-charPos; if(DoesWordFitVertic(word,xstart,ystart)) return; } } BlockCell(x,y); } // ********************************************************************** bool Grid::DoCharsMatchHoriz (const Word &word,int xstart,int ystart) const { int length = word.length; // (1) first try whether the word fits into the grid by its size if(xstart<0 || ystart<0 || ystart>=ROWS || xstart+length > COLUMNS) return false; // (2) now check if the word matches the other characters in the grid const char *pWord=word.word; const char *pCellStart = &_cells[xstart][ystart]; const char *pCell = pCellStart; const char *pCellEnd = pCell + ROWS*length; register char c; while(pCell!=pCellEnd) { c = *pCell; if(c != ' ' && c != *pWord) // speed enhanced return false; ++pWord; pCell+=ROWS; // goto next column } if(xstart>0) { pCellStart-=ROWS; c= *pCellStart; if(c != ' ' && c != BLOCK_CHAR) return false; } if(xstart+length=COLUMNS || ystart<0 || ystart+length>ROWS) return false; // (2) now check if the word matches the other characters in the grid const char *pWord=word.word; const char *pCellStart = &_cells[xstart][ystart]; const char *pCell = pCellStart; const char *pCellEnd = pCell + length; register char c; while(pCell != pCellEnd) { c = *pCell; if(c != ' ' && c != *pWord) // speed enhanced return false; ++pWord; ++pCell; // goto next row } if(ystart>0) { --pCellStart; c= *pCellStart; if(c != ' ' && c != BLOCK_CHAR) return false; } if(ystart+length=ROWS || xstart+length > COLUMNS) return false; // count non-matches; stop when count reaches 2 const char *pWord=word.word; const char *pCellStart = &_cells[xstart][ystart]; const char *pCell = pCellStart; const char *pCellEnd = pCell + ROWS*length; register char c; int missCount=0; while(pCell!=pCellEnd) { c = *pCell; if(c == BLOCK_CHAR) return false; if(c != ' ' && c != *pWord) // speed enhanced { ++missCount; if(missCount>1) return false; } ++pWord; pCell+=ROWS; // goto next column } if(missCount!=1) return false; if(xstart>0) { pCellStart-=ROWS; c= *pCellStart; if(c != ' ' && c != BLOCK_CHAR) return false; } if(xstart+length=COLUMNS || ystart<0 || ystart+length>ROWS) return false; // (2) now check if the word matches the other characters in the grid const char *pWord=word.word; const char *pCellStart = &_cells[xstart][ystart]; const char *pCell = pCellStart; const char *pCellEnd = pCell + length; register char c; int missCount=0; while(pCell != pCellEnd) { c = *pCell; if(c == BLOCK_CHAR) return false; if(c != ' ' && c != *pWord) { ++missCount; if(missCount>1) return false; } ++pWord; ++pCell; // goto next row } if(missCount!=1) return false; if(ystart>0) { --pCellStart; c= *pCellStart; if(c != ' ' && c != BLOCK_CHAR) return false; } if(ystart+lengthend()); for(const WordCharPosPair* it = &(*wiVec->begin()); it!= wiVecEnd; ++it) { poolIdx = it->poolIdx; y2 = ystart - it->charPos; if(y2end()); for(const WordCharPosPair* it = &(*wiVec->begin()); it!= wiVecEnd; ++it) { poolIdx = it->poolIdx; x2 = xstart - it->charPos; if(x20) { y=yStart-1; if(isLetter(_cells[x][y]) && (_cellState[x][y] & ST_VERTICAL)==0) { _firstIncompleteWord.x=x; _firstIncompleteWord.y=y; _firstIncompleteWord.horizontal=false; _firstIncompleteWordValid=true; return; } } if(yStart0) { x=xStart-1; if(isLetter(_cells[x][y]) && (_cellState[x][y] & ST_HORIZONTAL)==0) { _firstIncompleteWord.x=x; _firstIncompleteWord.y=y; _firstIncompleteWord.horizontal=true; _firstIncompleteWordValid=true; return; } } if(xStart=0) { insPos = _firstIncompleteWord; return true; } else return false; } // ********************************************************************** bool Grid::DoesWordFitHoriz(const Word *word,int xstart,int ystart) { // (1) make sure there is no word in horizontal direction starting there // (testing the direction of the first character should be sufficient, // otherwise the next test will fail) if(_cellState[xstart][ystart]&ST_HORIZONTAL) return false; // (2) test if the characters match if(!DoCharsMatchHoriz(*word,xstart,ystart)) return false; // (3) now check the neighbour characters above and below if there // are words in the pool that might fit to them int xend = xstart + word->length; register const char *pWord=word->word; for(register int x=xstart;xlength; register const char *pWord = word->word; for(register int y=ystart;ylength; register const char *pWord=word->word; for(register int x=xstart;xlength; register const char *pWord = word->word; for(register int y=ystart;y0) InsertNextPosPair(posQueue,cells,p.x-1,p.y,queueSize); if(p.x0) InsertNextPosPair(posQueue,cells,p.x,p.y-1,queueSize); if(p.y1) // if there is more than one area, the grid is not connected return false; FloodFill(cells,x,y); } } } return true; } // ********************************************************************* // calculate the score one can gain at all when inserting this word int Grid::ExpectedScoreAfterInsert(int poolIdx,int x,int y,bool horizontalDir) const { int length = WordPool::GetWordLength(poolIdx); int charcount = _charCount+length; // calculate the new values for the emptyCells counters register int emptyCellsHoriz = _emptyCellsHoriz; register int emptyCellsVertic = _emptyCellsVertic; register int i; int iStart,iEnd; register int freeRowCol,newFreeRowCol; if(horizontalDir) { iStart= ((x>0)?-1:0); iEnd= ((x+length < COLUMNS) ?length+1:length); for(i=iStart;ilength-1) newFreeRowCol--; if(newFreeRowCol>ROWS) newFreeRowCol= ROWS; if(freeRowCol < newFreeRowCol) emptyCellsVertic -= (newFreeRowCol - freeRowCol); } iStart= ((y>0)?-1:0); iEnd= ((y< ROWS-1) ? 2:1); for(i=iStart;i0) newFreeRowCol--; if(newFreeRowCol>COLUMNS) newFreeRowCol= COLUMNS; if( freeRowCol < newFreeRowCol) emptyCellsHoriz -= newFreeRowCol - freeRowCol; } } else { iStart= ((y>0)?-1:0); iEnd= ((y+length < ROWS) ?length+1:length); for(i=iStart;ilength-1) newFreeRowCol--; if(newFreeRowCol>COLUMNS) newFreeRowCol= COLUMNS; if(freeRowCol0)?-1:0); iEnd= ((x< COLUMNS-1) ? 2:1); for(i=iStart;i0) newFreeRowCol--; if(newFreeRowCol>ROWS) newFreeRowCol= ROWS; if(freeRowCol < newFreeRowCol) emptyCellsVertic -= newFreeRowCol - freeRowCol; } } int emptyCells = emptyCellsHoriz+emptyCellsVertic; return charcount + emptyCells * charcount*COVERAGE/(2*ROWS*COLUMNS-emptyCells )/100; } // ********************************************************************** // put the cross word grid into the output stream ostream& operator<< (ostream &os,const Grid &grid) { int x,y; char c; for(y=0;y &gridVec) { int i; int size = WordPool::GetPoolSize(); for(i=0;i &gridVec,vector &nextGridVec) { int i; ULONG hash; // foreach grid int gvsize= (int)gridVec.size(); InsertPosition insPos; register int x,y; PQScoredInsPos posQueue; for(i=0;i='A') AddWordsThroughCell(grid,i,x, y,posQueue); \ if(posQueue.Size()>maxSize) break; \ ++y; y=0; switch(firstFreeRow) { case 16: ADDWORDS_ORSTOP case 15: ADDWORDS_ORSTOP case 14: ADDWORDS_ORSTOP case 13: ADDWORDS_ORSTOP case 12: ADDWORDS_ORSTOP case 11: ADDWORDS_ORSTOP case 10: ADDWORDS_ORSTOP case 9: ADDWORDS_ORSTOP case 8: ADDWORDS_ORSTOP case 7: ADDWORDS_ORSTOP case 6: ADDWORDS_ORSTOP case 5: ADDWORDS_ORSTOP case 4: ADDWORDS_ORSTOP case 3: ADDWORDS_ORSTOP case 2: ADDWORDS_ORSTOP case 1: ADDWORDS_ORSTOP default: ; } if(posQueue.Size()>maxSize) break; } #ifndef BENCHMARK if(ClockSec()>MAXUPPER_TIMELIMIT) return; #endif } } if(DebugMode) { cerr << "Level size: " << posQueue.Size() << endl; #ifdef XXX if(posQueue.Size()==0) { for(i=0;ipoolIdx; charPos=itWord->charPos; // position where cellChar occurs const Word *word = WordPool::GetPoolWord(poolIdx); assert(charPos >=0 && charPoslength); assert(word->word[charPos]==cellChar); if(horizontalDir) { xstart = x-charPos; ystart = y; if(!theGrid.DoesWordFitHoriz(word,xstart,ystart )) continue; } else { xstart = x; ystart = y-charPos; if(!theGrid.DoesWordFitVertic(word,xstart,ystart)) continue; } // there is a word crossing "(x,y)": remember that found = true; // do not insert words that have been inserted by a previous call of AddWordsThroughCell if(charPos > maxStartBefore) continue; insPos.x = (char)xstart; insPos.y = (char)ystart; insPos.poolIdx = poolIdx; insPos.horizontal = horizontalDir; insPos.gridIdx = gridIdx; // get the score, but do not insert the word into the grid insPos.score = theGrid.ExpectedScoreAfterInsert(poolIdx,xstart,ystart,horizontalDir); posQueue.Insert(insPos); } // do not test the same cell if not necessary if(!found) theGrid.BlockCell(x,y); } // get all insert positions starting at (x,y) which may be possible with 1 non-matching character void Solver::GetMatchingWordsWithFlipper(Grid &theGrid,int gridIdx, int xStart,int yStart, bool horizontalDir, vector &result) { int x,y; ScoredInsPos insPos; int patternLength; char pattern[MAXWORDLENGTH]; // get the characters in the grid up to the next BLOCK_CHAR patternLength = theGrid.GetCharPattern(xStart,yStart,horizontalDir,pattern); if(patternLength < 2) return; // the word has to reach the first character and can stop only at an empty space int minlength=0; while(minlength=patternLength) // stop if there are only spaces in the pattern return; while(minlengthlength; if(lengthpatternLength) continue; // process every position between x-length+1 and x x=xStart; y=yStart; for(int count=0;count &gridVec,vector &nextGridVec) { int i,x,y; vector insPosVec; insPosVec.reserve(100000); if(DebugMode) { cerr << "gridVec.size()=" << (int)gridVec.size() << "\n"; } for(i=0;i<(int)gridVec.size();++i) { Grid &grid = gridVec[i]; int maxsize=(int)insPosVec.size()+MAXSIBLINGS; for(x=0;xmaxsize) break; } if((int)insPosVec.size()>maxsize) break; #ifndef BENCHMARK if(ClockSec()>MAXUPPER_TIMELIMIT) return; #endif } } if(DebugMode) { cerr << "insPosVec.size()=" << (int)insPosVec.size() << "\n"; } for(i=0;i<(int)insPosVec.size();++i) { ScoredInsPos insPos = insPosVec[i]; nextGridVec.push_back(gridVec[insPos.gridIdx]); // make a copy nextGridVec.back().InsertWordWithFlipper(insPos.poolIdx,insPos.x,insPos.y,insPos.horizontal); } if(DebugMode) { cerr << "algorithm restarted with " << (int)nextGridVec.size() << " grids\n\n"; } } // fill the grid vector with all grids having one word less than the given grid void Solver::AddAllGridsWithOneWordLess(const Grid &grid,vector *gridVec) { int x,y; for(x=0;xpush_back(grid); // make a copy! gridVec->back().RemoveWord(x,y,dir==0); if(!gridVec->back().IsConnected()) gridVec->pop_back(); } } } } if(DebugMode) { cerr << "algorithm restarted with " << (int)gridVec->size() << " grids\n\n"; } } // find all positions where a word can be removed from the grid (without // checking for connectedness) void Solver::FindRemovePositions(const Grid &grid,vector &posVec) { int x,y; InsertPosition insPos; for(x=0;x *gridVec) { vector posVec; posVec.reserve(ROWS*COLUMNS*2); FindRemovePositions(grid,posVec); int size = (int)posVec.size(); for(int i=0;ipush_back(grid); // makes a copy! gridVec->back().RemoveWord(posVec[i].x,posVec[i].y,posVec[i].horizontal); gridVec->back().RemoveWord(posVec[j].x,posVec[j].y,posVec[j].horizontal); if(!gridVec->back().IsConnected()) gridVec->pop_back(); } } if(DebugMode) { cerr << "algorithm restarted with " << (int)gridVec->size() << " grids\n\n"; } } Grid *Solver::FindBestGrid() { vector *gridVec = new vector; vector *nextGridVec = new vector; vector *tmpVec; InsertPosition tmp; _LevelSize = STARTLEVELSIZE; FindInitialGrids(*gridVec); Grid bestGrid; int bestScore = -1; // repeat until no more words could be inserted int level=1; // when no more words can be inserted, there are two strategies for continuation: // 3 == insert best grid so far alone // 2 == insert all grids with one word less // 1 == use flipper square // 0 == stop the algorithm int continueStrategyCount=3; while(gridVec->size()>0 || continueStrategyCount>0) { // now, choose the grid with the highest score // but no grids with incomplete words! Grid *gridVecEnd=&(*gridVec->end()); for(Grid *it = &(*gridVec->begin());it!=gridVecEnd;++it) { if(it->Score()>bestScore && !it->FindIncompleteWordPos(tmp)) { bestGrid= *it; bestScore = bestGrid.Score(); continueStrategyCount = 3; } } double currentTime = ClockSec(); #ifndef BENCHMARK // play it safe, except we make a benchmark if(currentTime>MAXUPPER_TIMELIMIT) break; #endif double expectedTime = bestGrid.ExpectedTime(); if(expectedTime < LOWER_TIMELIMIT && _LevelSize < MAXLEVELSIZE && level>4) _LevelSize += LEVELSIZESTEP; else if ((currentTime > LOWER_TIMELIMIT || expectedTime > UPPER_TIMELIMIT) && _LevelSize > MINLEVELSIZE) { _LevelSize -= 2*LEVELSIZESTEP; if (expectedTime > MAXUPPER_TIMELIMIT) _LevelSize -= 6*LEVELSIZESTEP; if(_LevelSize < MINLEVELSIZE) _LevelSize=MINLEVELSIZE; } if(DebugMode) { cerr << bestGrid; cerr << "best grid score: " << bestScore << endl; cerr << "expected score : " << bestGrid.ExpectedScore() << endl; cerr << "current time : " << currentTime << endl; cerr << "estimated time : " << expectedTime << endl; cerr << "current level : " << level << endl; cerr << "current level size: " << _LevelSize << endl; } // basic algorithm: // create all grids with one word more, choose a subset among them nextGridVec->clear(); FindNextGridLevel(*gridVec,*nextGridVec); // if level is empty, try different continuation strategies if(nextGridVec->size()==0) { if(DebugMode) cerr << "continue strategy=" << continueStrategyCount << endl; if(continueStrategyCount == 0) // stop if there was no improvement after last continuation break; // make sure the algo does not stop though there are obvious continuations gridVec->clear(); if(continueStrategyCount==3) { if(DebugMode) cerr << "continue with best grid alone ...\n"; gridVec->push_back(bestGrid); FindNextGridLevel(*gridVec,*nextGridVec); } else if(continueStrategyCount==2 && !bestGrid.HasFlipper()) { AddAllGridsWithOneWordLess(bestGrid,gridVec); FindNextGridLevel(*gridVec,*nextGridVec); } else if(continueStrategyCount==1 && !bestGrid.HasFlipper()) { // gridVec->push_back(bestGrid); AddAllGridsWithOneWordLess(bestGrid,gridVec); FindAllWordsWithFlipper(*gridVec,*nextGridVec); } --continueStrategyCount; } tmpVec = gridVec; // switch both vectors quickly gridVec = nextGridVec; nextGridVec = tmpVec; level++; } delete gridVec; delete nextGridVec; // return copy of the best grid return new Grid(bestGrid); } // WordOMatic.cpp : Definiert den Einstiegspunkt für die Konsolenanwendung. // bool DebugMode=false; bool ShowTimeResult = false; int main(int argc, char* argv[]) { int i; if(argc<2) Error("missing file name"); // check additional arguments for(i=2;i