00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "wordgame.h"
00023
00024 #include <opie2/oresource.h>
00025
00026 #include <qpe/global.h>
00027 #include <qpe/config.h>
00028
00029 #include <qapplication.h>
00030 #include <qmessagebox.h>
00031 #include <qcombobox.h>
00032 #include <qdir.h>
00033 #include <qlineedit.h>
00034 #include <qpushbutton.h>
00035 #include <qtextstream.h>
00036 #include <qtimer.h>
00037 #include <qtoolbar.h>
00038 #include <qtoolbutton.h>
00039 #include <qvbox.h>
00040 #include <qwidgetstack.h>
00041 #include <qlayout.h>
00042
00043 #include <stdlib.h>
00044 #include <unistd.h>
00045 #include <pwd.h>
00046 #include <sys/types.h>
00047
00048 enum RuleEffects {
00049 Multiplier=15,
00050 MultiplyAll=64,
00051 Start=128
00052 };
00053
00054 static int tile_smallw = 16;
00055 static int tile_smallh = 16;
00056 static int tile_bigw = 22;
00057 static int tile_bigh = 22;
00058 static int tile_stweak = -2;
00059 static int tile_btweak = -1;
00060
00061 static const int rack_tiles=7;
00062
00063 const char* sampleWGR=
00064 "wordgame_shapes\n"
00065 "15 15\n"
00066 "400001040100004\n"
00067 "030000000000030\n"
00068 "002002000200200\n"
00069 "000300020003000\n"
00070 "000020000020000\n"
00071 "102001000100201\n"
00072 "000000202000000\n"
00073 "400200050002004\n"
00074 "000000202000000\n"
00075 "102001000100201\n"
00076 "000020000020000\n"
00077 "000300020003000\n"
00078 "002002000200200\n"
00079 "030000000000030\n"
00080 "400001040100004\n"
00081 "1 2 3 66 67 194 100 0\n"
00082 "1 j 8\n"
00083 "1 q 7\n"
00084 "1 x 6\n"
00085 "1 z 6\n"
00086 "1 w 4\n"
00087 "1 k 4\n"
00088 "1 v 3\n"
00089 "1 f 3\n"
00090 "2 y 3\n"
00091 "2 h 2\n"
00092 "2 b 2\n"
00093 "2 m 2\n"
00094 "3 p 2\n"
00095 "3 g 2\n"
00096 "3 u 2\n"
00097 "4 d 2\n"
00098 "4 c 2\n"
00099 "5 l 1\n"
00100 "5 o 1\n"
00101 "7 t 1\n"
00102 "7 n 1\n"
00103 "7 a 1\n"
00104 "7 r 1\n"
00105 "8 s 1\n"
00106 "8 i 1\n"
00107 "11 e 1\n"
00108 "0\n";
00109
00110 WordGame::WordGame( QWidget* parent, const char* name, WFlags fl ) :
00111 QMainWindow(parent, name, fl)
00112 {
00113 if ( qApp->desktop()->width() < 240 ) {
00114 tile_smallw = 10;
00115 tile_smallh = 10;
00116 tile_bigw = 16;
00117 tile_bigh = 16;
00118 tile_stweak = 0;
00119 tile_btweak = 0;
00120 }
00121
00122 setIcon( Opie::Core::OResource::loadPixmap( "wordgame/WordGame" ) );
00123 setCaption( tr("Word Game") );
00124
00125 setToolBarsMovable( FALSE );
00126 vbox = new QVBox(this);
00127
00128 setCentralWidget(vbox);
00129 toolbar = new QToolBar(this);
00130 addToolBar(toolbar, Bottom);
00131 bool useBigIcon = qApp->desktop()->size().width() > 330;
00132 reset = new QToolButton(Opie::Core::OResource::loadPixmap("back", Opie::Core::OResource::SmallIcon),
00133 tr("Back"), "", this, SLOT(resetTurn()), toolbar);
00134 reset->setUsesBigPixmap( useBigIcon );
00135 done = new QToolButton(Opie::Core::OResource::loadPixmap("done", Opie::Core::OResource::SmallIcon),
00136 tr("Done"), "", this, SLOT(endTurn()), toolbar);
00137 done->setUsesBigPixmap( useBigIcon );
00138 scoreinfo = new ScoreInfo(toolbar);
00139 scoreinfo->setFont(QFont("Helvetica",10));
00140 QToolButton *btn = new QToolButton(Opie::Core::OResource::loadPixmap("finish", Opie::Core::OResource::SmallIcon),
00141 tr("Close"), "", this, SLOT(endGame()), toolbar);
00142 btn->setUsesBigPixmap( useBigIcon );
00143 toolbar->setStretchableWidget(scoreinfo);
00144
00145 cpu = 0;
00146 board = 0;
00147 bag = 0;
00148 racks = 0;
00149
00150 aiheart = new QTimer(this);
00151 connect(aiheart, SIGNAL(timeout()), this, SLOT(think()));
00152
00153 readConfig();
00154 }
00155
00156 WordGame::~WordGame()
00157 {
00158 writeConfig();
00159 }
00160
00161 void WordGame::writeConfig()
00162 {
00163 Config cfg("WordGame");
00164 cfg.setGroup("Game");
00165 cfg.writeEntry("NameList",namelist,';');
00166 cfg.writeEntry("CurrentPlayer",gameover ? 0 : player+1);
00167 if ( !gameover ) {
00168 cfg.writeEntry("Rules",rules);
00169 bag->writeConfig(cfg);
00170 board->writeConfig(cfg);
00171 scoreinfo->writeConfig(cfg);
00172 }
00173 for (int p=0; p<nplayers; p++) {
00174 cfg.setGroup("Player"+QString::number(p+1));
00175 if ( gameover ) cfg.clearGroup(); else rack(p)->writeConfig(cfg);
00176 }
00177 }
00178
00179 void WordGame::readConfig()
00180 {
00181 Config cfg("WordGame");
00182 cfg.setGroup("Game");
00183 int currentplayer = cfg.readNumEntry("CurrentPlayer",0);
00184 QStringList pnames = cfg.readListEntry("NameList",';');
00185 if ( currentplayer ) {
00186 gameover = FALSE;
00187 rules = cfg.readEntry("Rules");
00188 if ( rules.find("x-wordgamerules") >= 0 ) {
00189
00190 rules = "Sample.rules";
00191 }
00192 if ( loadRules(rules) ) {
00193 startGame(pnames);
00194 bag->readConfig(cfg);
00195 board->readConfig(cfg);
00196 scoreinfo->readConfig(cfg);
00197 for (int p=0; p<nplayers; p++) {
00198 cfg.setGroup("Player"+QString::number(p+1));
00199 rack(p)->readConfig(cfg);
00200 }
00201 player=currentplayer-1;
00202 readyRack(player);
00203 return;
00204 }
00205 }
00206
00207 openGameSelector(pnames);
00208 }
00209
00210 void WordGame::openGameSelector(const QStringList& initnames)
00211 {
00212 toolbar->hide();
00213 gameover = FALSE;
00214
00215 delete board;
00216 board = 0;
00217 delete racks;
00218 racks = 0;
00219
00220 delete cpu;
00221 cpu = 0;
00222
00223 newgame = new NewGame(vbox);
00224
00225
00226
00227
00228 struct passwd* n = getpwuid(getuid());
00229 QString playername = n ? n->pw_name : "";
00230 if ( playername.isEmpty() ) {
00231 playername = "Player";
00232 }
00233 newgame->player0->changeItem(playername,0);
00234 newgame->player1->setCurrentItem(1);
00235 newgame->updateRuleSets();
00236 newgame->show();
00237
00238 connect(newgame->buttonOk, SIGNAL(clicked()), this, SLOT(startGame()));
00239 }
00240
00241 void WordGame::startGame()
00242 {
00243 rules = newgame->ruleslist[newgame->rules->currentItem()];
00244 if ( loadRules(rules) ) {
00245 QStringList names;
00246 names.append(newgame->player0->currentText());
00247 names.append(newgame->player1->currentText());
00248 names.append(newgame->player2->currentText());
00249 names.append(newgame->player3->currentText());
00250 names.append(newgame->player4->currentText());
00251 names.append(newgame->player5->currentText());
00252 delete newgame;
00253 startGame(names);
00254 } else {
00255
00256 delete newgame;
00257 close();
00258 }
00259 }
00260
00261 void WordGame::startGame(const QStringList& playerlist)
00262 {
00263 toolbar->show();
00264 racks = new QWidgetStack(vbox);
00265 racks->setFixedHeight(TileItem::bigHeight()+2);
00266 namelist.clear();
00267 nplayers=0;
00268 for (QStringList::ConstIterator it=playerlist.begin(); it!=playerlist.end(); ++it)
00269 addPlayer(*it);
00270 scoreinfo->init(namelist);
00271
00272 if ( nplayers ) {
00273 player=0;
00274 readyRack(player);
00275 }
00276
00277 board->show();
00278 racks->show();
00279 }
00280
00281 bool WordGame::loadRules(const QString &name)
00282 {
00283 QString filename = Global::applicationFileName( "wordgame", name );
00284 QFile file( filename );
00285 if ( !file.open( IO_ReadOnly ) )
00286 return FALSE;
00287
00288 QTextStream ts( &file );
00289
00290 QString title = name;
00291 title.truncate( title.length() - 6 );
00292
00293
00294 QString shapepixmap;
00295 ts >> shapepixmap;
00296 int htiles,vtiles;
00297 ts >> htiles >> vtiles;
00298
00299 if ( htiles < 3 || vtiles < 3 )
00300 return FALSE;
00301
00302 QString rule_shapes;
00303 for (int i=0; i<vtiles; i++) {
00304 QString line;
00305 ts >> line;
00306 rule_shapes += line;
00307 }
00308 static int rule_effects[12];
00309 int re=0,e;
00310 ts >> e;
00311 while ( e && re < 10 ) {
00312 rule_effects[re] = e;
00313 if ( re++ < 10 ) ts >> e;
00314 }
00315
00316 QImage shim = Opie::Core::OResource::loadImage("wordgame/wordgame_shapes");
00317 shim = shim.smoothScale((re-1)*TileItem::smallWidth(),TileItem::smallHeight());
00318 QPixmap bgshapes;
00319 bgshapes.convertFromImage(shim);
00320
00321 rule_effects[re++] = 100;
00322 board = new Board(bgshapes, htiles, vtiles, vbox);
00323 board->setRules(rule_shapes, rule_effects);
00324 connect(board, SIGNAL(temporaryScore(int)), scoreinfo, SLOT(showTemporaryScore(int)));
00325
00326 bag = new Bag;
00327
00328 int count;
00329 ts >> count;
00330 while ( count ) {
00331 QString text;
00332 int value;
00333 ts >> text >> value;
00334 if ( text == "_" )
00335 text = "";
00336
00337 Tile t(text, value);
00338 for (int n=count; n--; )
00339 bag->add(t);
00340
00341 ts >> count;
00342 }
00343
00344 return TRUE;
00345 }
00346
00347
00348 NewGame::NewGame(QWidget* parent) :
00349 NewGameBase(parent)
00350 {
00351 }
00352
00353 void NewGame::updateRuleSets()
00354 {
00355 rules->clear();
00356
00357 QString rulesDir = Global::applicationFileName( "wordgame", "" );
00358 QDir dir( rulesDir, "*.rules" );
00359 ruleslist = dir.entryList();
00360 if ( ruleslist.isEmpty() ) {
00361
00362 QFile file( rulesDir + "Sample.rules" );
00363 if ( file.open( IO_WriteOnly ) ) {
00364 file.writeBlock( sampleWGR, strlen(sampleWGR) );
00365 file.close();
00366 updateRuleSets();
00367 }
00368 return;
00369 }
00370 int newest=0;
00371 int newest_age=INT_MAX;
00372 QDateTime now = QDateTime::currentDateTime();
00373 QStringList::Iterator it;
00374 for ( it = ruleslist.begin(); it != ruleslist.end(); ++it ) {
00375 QFileInfo fi((*it));
00376 int age = fi.lastModified().secsTo(now);
00377 QString name = *it;
00378 name.truncate( name.length()-6 );
00379 rules->insertItem( name );
00380 if ( age < newest_age ) {
00381 newest_age = age;
00382 newest = rules->count()-1;
00383 }
00384 }
00385 rules->setCurrentItem(newest);
00386 }
00387
00388 Rules::Rules(QWidget* parent) :
00389 RulesBase(parent,0,TRUE)
00390 {
00391 }
00392
00393 void Rules::editRules()
00394 {
00395 if ( exec() ) {
00396
00397 emit rulesChanged();
00398 }
00399 }
00400
00401 void Rules::deleteRuleSet()
00402 {
00403
00404 emit rulesChanged();
00405 }
00406
00407 void WordGame::addPlayer(const QString& name)
00408 {
00409 if ( !name.isEmpty() ) {
00410 int colon = name.find(':');
00411 int cpu = (colon >=0 && name.left(2) == "AI") ? name.mid(2,1).toInt() : 0;
00412 addPlayer(name,cpu);
00413 }
00414 }
00415
00416 void WordGame::addPlayer(const QString& name, int cpu)
00417 {
00418 Rack* r = new Rack(rack_tiles,racks);
00419 r->setPlayerName(name);
00420 r->setComputerization(cpu);
00421 racks->addWidget(r, nplayers);
00422 refillRack(nplayers);
00423 namelist.append(name);
00424
00425 ++nplayers;
00426 }
00427
00428 void WordGame::nextPlayer()
00429 {
00430 if ( !refillRack(player) ) {
00431 endGame();
00432 } else {
00433 player = (player+1)%nplayers;
00434 scoreinfo->setBoldOne(player);
00435 readyRack(player);
00436 }
00437 }
00438
00439 bool WordGame::mayEndGame()
00440 {
00441 int out=-1;
00442 int i;
00443 for (i=0; i<nplayers; i++)
00444 if ( !rack(i)->count() )
00445 out = i;
00446 if ( out<0 ) {
00447 if ( QMessageBox::warning(this,tr("End game"),
00448 tr("Do you want to end the game early?"),
00449 tr("Yes"), tr("No") )!=0 )
00450 {
00451 return FALSE;
00452 }
00453 }
00454 return TRUE;
00455 }
00456
00457 void WordGame::endGame()
00458 {
00459 if ( gameover ) {
00460 close();
00461 return;
00462 }
00463
00464 if ( !mayEndGame() )
00465 return;
00466 int out=-1;
00467 int totalleft=0;
00468 int i;
00469 for (i=0; i<nplayers; i++) {
00470 Rack* r = rack(i);
00471 int c = r->count();
00472 if ( c ) {
00473 int lose=0;
00474 for ( int j=0; j<c; j++ )
00475 lose += r->tileRef(j)->value();
00476 totalleft += lose;
00477 scoreinfo->addScore(i,-lose);
00478 } else {
00479 out = i;
00480 }
00481 }
00482 int highest=0;
00483 int winner=0;
00484 for (i=0; i<nplayers; i++) {
00485 int s = scoreinfo->playerScore(i);
00486 if ( s > highest ) {
00487 highest = s;
00488 winner = i;
00489 }
00490 }
00491 if ( out >= 0 )
00492 scoreinfo->addScore(out,totalleft);
00493 scoreinfo->setBoldOne(winner);
00494 gameover = TRUE;
00495 done->setEnabled(TRUE);
00496 reset->setEnabled(FALSE);
00497 }
00498
00499 void WordGame::endTurn()
00500 {
00501 if ( gameover ) {
00502 openGameSelector(namelist);
00503 } else {
00504 if ( board->checkTurn() ) {
00505 if ( board->turnScore() >= 0 ) {
00506 scoreinfo->addScore(player,board->turnScore());
00507 board->finalizeTurn();
00508 } else {
00509 QApplication::beep();
00510 }
00511 nextPlayer();
00512 }
00513 }
00514 }
00515
00516 void WordGame::resetTurn()
00517 {
00518 board->resetRack();
00519 }
00520
00521 void WordGame::passTurn()
00522 {
00523
00524 nextPlayer();
00525 }
00526
00527 bool WordGame::refillRack(int i)
00528 {
00529 Rack* r = rack(i);
00530 while ( !bag->isEmpty() && !r->isFull() ) {
00531 r->addTile(bag->takeRandom());
00532 }
00533 return r->count() != 0;
00534 }
00535
00536 void WordGame::readyRack(int i)
00537 {
00538 Rack* r = rack(i);
00539 racks->raiseWidget(i);
00540 board->setCurrentRack(r);
00541
00542 done->setEnabled( !r->computerized() );
00543 reset->setEnabled( !r->computerized() );
00544
00545 if ( r->computerized() ) {
00546 cpu = new ComputerPlayer(board, r);
00547 aiheart->start(0);
00548 }
00549 }
00550
00551 Rack* WordGame::rack(int i) const
00552 {
00553 return (Rack*)racks->widget(i);
00554 }
00555
00556 void WordGame::think()
00557 {
00558 if ( !cpu->step() ) {
00559 delete cpu;
00560 cpu = 0;
00561 aiheart->stop();
00562 if ( board->turnScore() < 0 )
00563 passTurn();
00564 else
00565 endTurn();
00566 }
00567 }
00568
00569 ComputerPlayer::ComputerPlayer(Board* b, Rack* r) :
00570 board(b), rack(r), best(new const Tile*[rack_tiles]),
00571 best_blankvalues(new Tile[rack_tiles])
00572 {
00573 best_score = -1;
00574 across=FALSE;
00575 dict=0;
00576 }
00577
00578 ComputerPlayer::~ComputerPlayer()
00579 {
00580 delete [] best;
00581 delete [] best_blankvalues;
00582 }
00583
00584 bool ComputerPlayer::step()
00585 {
00586 const QDawg::Node* root = dict ? Global::dawg("WordGame").root()
00587 : Global::fixedDawg().root();
00588 QPoint d = across ? QPoint(1,0) : QPoint(0,1);
00589 const Tile* tiles[99];
00590 uchar nletter[4095];
00591 memset(nletter,0,4096);
00592 for (int i=0; i<rack->count(); i++) {
00593 const Tile* r = rack->tileRef(i);
00594 if ( r->isBlank() )
00595 nletter[0]++;
00596 else
00597 nletter[r->text()[0].unicode()]++;
00598 }
00599 Tile blankvalues[99];
00600 findBest(current, d, root, 0, nletter, tiles, 0, blankvalues, 0);
00601 if ( ++current.rx() == board->xTiles() ) {
00602 current.rx() = 0;
00603 if ( ++current.ry() == board->yTiles() ) {
00604 if ( across ) {
00605 if ( dict == 1 ) {
00606 if ( best_score >= 0 ) {
00607 rack->arrangeTiles(best,best_n);
00608 rack->setBlanks(best_blankvalues);
00609 board->scoreTurn(best_start, best_n, best_dir);
00610 board->showTurn();
00611 }
00612 return FALSE;
00613 }
00614 dict++;
00615 across = FALSE;
00616 current = QPoint(0,0);
00617 } else {
00618 across = TRUE;
00619 current = QPoint(0,0);
00620 }
00621 }
00622 }
00623 return TRUE;
00624 }
00625
00626 void ComputerPlayer::findBest(QPoint at, const QPoint& d, const QDawg::Node* node, ulong used, uchar* nletter, const Tile** tiles, int n, Tile* blankvalues, int blused)
00627 {
00628 if ( !node )
00629 return;
00630 QChar l = node->letter();
00631 const Tile* cur = board->tile(at);
00632 if ( cur ) {
00633 if ( cur->text()[0] == l ) {
00634 bool nextok = board->contains(at+d);
00635 if ( node->isWord() && n && (!nextok || !board->tile(at+d)) )
00636 noteChoice(tiles,n,d,blankvalues,blused);
00637 if ( nextok )
00638 findBest(at+d, d, node->jump(), used, nletter, tiles, n, blankvalues, blused);
00639
00640 }
00641 } else {
00642 if ( nletter[l.unicode()] || nletter[0] ) {
00643 int rc = rack->count();
00644 ulong msk = 1;
00645 for ( int x=0; x<rc; x++ ) {
00646 if ( !(used&msk) ) {
00647 const Tile* t = rack->tileRef(x);
00648 if ( t->isBlank() || t->text() == l ) {
00649 bool nextok = board->contains(at+d);
00650 tiles[n++] = t;
00651 if ( t->isBlank() )
00652 blankvalues[blused++] = Tile(l,0);
00653 if ( node->isWord() && (!nextok || !board->tile(at+d)) )
00654 noteChoice(tiles,n,d,blankvalues,blused);
00655 used |= msk;
00656 nletter[t->text()[0].unicode()]--;
00657 if ( nextok )
00658 findBest(at+d, d, node->jump(), used, nletter, tiles, n, blankvalues, blused);
00659 n--;
00660 nletter[t->text()[0].unicode()]++;
00661 if ( t->isBlank() ) {
00662
00663 blused--;
00664 used &= ~msk;
00665 } else {
00666 break;
00667 }
00668 }
00669 }
00670 msk <<= 1;
00671 }
00672 }
00673
00674 }
00675 findBest(at, d, node->next(), used, nletter, tiles, n, blankvalues, blused);
00676 }
00677
00678 void ComputerPlayer::noteChoice(const Tile** tiles, int n, const QPoint& d, const Tile* blankvalues, int blused)
00679 {
00680 int s = board->score(current, tiles, n, blankvalues, d, TRUE, 0);
00681
00682
00683
00684
00685
00686
00687
00688
00689 if ( s > best_score ) {
00690 int i;
00691 for ( i=0; i<n; i++ )
00692 best[i] = tiles[i];
00693 for ( i=0; i<blused; i++ )
00694 best_blankvalues[i] = blankvalues[i];
00695 best_n = n;
00696 best_blused = blused;
00697 best_score = s;
00698 best_dir = d;
00699 best_start = current;
00700 }
00701 }
00702
00703 int TileItem::smallWidth()
00704 {
00705 return tile_smallw;
00706 }
00707
00708 int TileItem::smallHeight()
00709 {
00710 return tile_smallh;
00711 }
00712
00713 int TileItem::bigWidth()
00714 {
00715 return tile_bigw;
00716 }
00717
00718 int TileItem::bigHeight()
00719 {
00720 return tile_bigh;
00721 }
00722
00723 void TileItem::setState( State state )
00724 {
00725 hide();
00726 s = state;
00727 show();
00728 }
00729
00730 void TileItem::setTile(const Tile& tile)
00731 {
00732 hide();
00733 t = tile;
00734 show();
00735 }
00736
00737 void TileItem::setBig(bool b)
00738 {
00739 big = b;
00740 }
00741
00742 void TileItem::drawShape(QPainter& p)
00743 {
00744 static QFont *value_font=0;
00745 static QFont *big_font=0;
00746 static QFont *small_font=0;
00747 if ( !value_font ) {
00748 value_font = new QFont("helvetica",8);
00749 if ( TileItem::bigWidth() < 20 ) {
00750 big_font = new QFont("helvetica",12);
00751 small_font = new QFont("helvetica",8);
00752 } else {
00753 big_font = new QFont("smoothtimes",17);
00754 small_font = new QFont("smoothtimes",10);
00755 }
00756 }
00757
00758 QRect area(x(),y(),width(),height());
00759 p.setBrush(s == Floating ? yellow : white);
00760 p.drawRect(area);
00761 if ( big ) {
00762 p.setFont(*value_font);
00763 QString n = QString::number(t.value());
00764 int w = p.fontMetrics().width('1');
00765 int h = p.fontMetrics().height();
00766 w *= n.length();
00767 QRect valuearea(x()+width()-w-1,y()+height()-h,w,h);
00768 p.drawText(valuearea,AlignCenter,n);
00769 p.setFont(*big_font);
00770 area = QRect(x(),y()+tile_btweak,width()-4,height()-1);
00771 } else {
00772 p.setFont(*small_font);
00773 area = QRect(x()+1+tile_stweak,y()+1,width(),height()-3);
00774 }
00775 if ( t.value() == 0 )
00776 p.setPen(darkGray);
00777 p.drawText(area,AlignCenter,t.text().upper());
00778 }
00779
00780 Board::Board(QPixmap bgshapes, int w, int h, QWidget* parent) :
00781 QCanvasView(new QCanvas(bgshapes,w,h, TileItem::smallWidth(), TileItem::smallHeight()),
00782 parent)
00783 {
00784 setFixedSize(w*TileItem::smallWidth(),h*TileItem::smallHeight());
00785 grid = new TileItem*[w*h];
00786 memset(grid,0,w*h*sizeof(TileItem*));
00787 setFrameStyle(0);
00788 setHScrollBarMode(AlwaysOff);
00789 setVScrollBarMode(AlwaysOff);
00790 current_rack = 0;
00791 shown_n = 0;
00792 }
00793
00794 Board::~Board()
00795 {
00796 delete canvas();
00797 }
00798
00799 QSize Board::sizeHint() const
00800 {
00801 return QSize(canvas()->width(),canvas()->height());
00802 }
00803
00804 void Board::writeConfig(Config& cfg)
00805 {
00806 QStringList t;
00807 int n=canvas()->tilesHorizontally()*canvas()->tilesVertically();
00808 for (int i=0; i<n; i++)
00809 t.append( grid[i] ? grid[i]->tile().key() : QString(".") );
00810 cfg.writeEntry("Board",t,';');
00811 }
00812
00813 void Board::readConfig(Config& cfg)
00814 {
00815 clear();
00816 QStringList t = cfg.readListEntry("Board",';');
00817 int i=0;
00818 int h=canvas()->tilesHorizontally();
00819 for (QStringList::ConstIterator it=t.begin(); it!=t.end(); ++it) {
00820 if ( *it != "." ) {
00821 QPoint p(i%h,i/h);
00822 setTile(p,Tile(*it));
00823 }
00824 i++;
00825 }
00826 canvas()->update();
00827 }
00828
00829 void Board::clear()
00830 {
00831 int n=canvas()->tilesHorizontally()*canvas()->tilesVertically();
00832 for (int i=0; i<n; i++) {
00833 delete grid[i];
00834 grid[i]=0;
00835 }
00836 }
00837
00838
00839 void Board::setCurrentRack(Rack* r)
00840 {
00841 turn_score = -1;
00842 current_rack = r;
00843 }
00844
00845 void Board::resetRack()
00846 {
00847 unshowTurn();
00848 canvas()->update();
00849 }
00850
00851 void Board::contentsMousePressEvent(QMouseEvent* e)
00852 {
00853 dragstart = e->pos();
00854 }
00855
00856 void Board::contentsMouseMoveEvent(QMouseEvent* e)
00857 {
00858 if ( current_rack && !current_rack->computerized() ) {
00859 QPoint d = e->pos() - dragstart;
00860 if ( d.x() <= 0 && d.y() <= 0 ) {
00861
00862 resetRack();
00863 } else {
00864 int n;
00865 QPoint start=boardPos(dragstart);
00866 QPoint end=boardPos(e->pos());
00867 QPoint diff=end-start;
00868 QPoint dir;
00869 if ( d.x() > d.y() ) {
00870 n = diff.x()+1;
00871 dir = QPoint(1,0);
00872 } else {
00873 n = diff.y()+1;
00874 dir = QPoint(0,1);
00875 }
00876
00877 unshowTurn();
00878
00879
00880 QPoint t = start;
00881 for ( int i=n; i--; ) {
00882 if ( contains(t) && tile(t) )
00883 n--;
00884 t += dir;
00885 }
00886
00887
00888 while (contains(start-dir) && tile(start-dir))
00889 start -= dir;
00890
00891 scoreTurn(start, n, dir);
00892 showTurn();
00893 }
00894 }
00895 }
00896
00897 void Board::finalizeTurn()
00898 {
00899 int i=0;
00900 QPoint at = shown_at;
00901 while ( i<shown_n && contains(at) ) {
00902 if ( item(at) && item(at)->state() == TileItem::Floating ) {
00903 current_rack->remove(item(at)->tile());
00904 setTileState(at,TileItem::Firm);
00905 i++;
00906 }
00907 at += shown_step;
00908 }
00909 canvas()->update();
00910 }
00911
00912 void Board::unshowTurn()
00913 {
00914 int i=0;
00915 QPoint at = shown_at;
00916 while ( i<shown_n && i<current_rack->count() && contains(at) ) {
00917 if ( item(at) && item(at)->state() == TileItem::Floating ) {
00918 unsetTile(at);
00919 i++;
00920 }
00921 at += shown_step;
00922 }
00923 }
00924
00925 void Board::showTurn()
00926 {
00927 unshowTurn();
00928 QPoint at = shown_at;
00929 int i=0;
00930 while ( i<shown_n && i<current_rack->count() && contains(at) ) {
00931 if ( !tile(at) ) {
00932 Tile t = current_rack->tile(i);
00933 setTile(at,t);
00934 setTileState(at,TileItem::Floating);
00935 i++;
00936 }
00937 at += shown_step;
00938 }
00939 canvas()->update();
00940 }
00941
00942 int Board::bonussedValue(const QPoint& at, int base, int& all_mult) const
00943 {
00944 int rule = rule_shape[idx(at)]-'0';
00945 int effect = rule_effect[rule];
00946 int mult = effect&Multiplier;
00947 if ( effect & MultiplyAll ) {
00948 all_mult *= mult;
00949 return base;
00950 } else {
00951 return base * mult;
00952 }
00953 }
00954
00955 bool Board::isStart(const QPoint& at) const
00956 {
00957 int rule = rule_shape[idx(at)]-'0';
00958 int effect = rule_effect[rule];
00959 return effect&Start;
00960 }
00961
00962 bool Board::checkTurn()
00963 {
00964 if ( current_rack->computerized() )
00965 return TRUE;
00966
00967 QPoint at = shown_at;
00968 int n = shown_n;
00969 QPoint d = shown_step;
00970 const Tile* tiles[99];
00971 Tile blankvalues[99];
00972 if ( n > current_rack->count() )
00973 n = current_rack->count();
00974
00975 QDialog check(this,0,TRUE);
00976 (new QVBoxLayout(&check))->setAutoAdd(TRUE);
00977
00978 QHBox mw(&check);
00979 new QLabel(tr("Blanks: "),&mw);
00980
00981 int bl=0;
00982 QLineEdit* le[99];
00983 for (int i=0; i<n; i++) {
00984 tiles[i] = current_rack->tileRef(i);
00985 if ( tiles[i]->isBlank() ) {
00986 QLineEdit *l = new QLineEdit(&mw);
00987 le[bl++] = l;
00988 l->setMaxLength(1);
00989 l->setFixedSize(l->minimumSizeHint());
00990 }
00991 }
00992
00993 QHBox btns(&check);
00994 connect(new QPushButton(tr("OK"),&btns), SIGNAL(clicked()), &check, SLOT(accept()));
00995 connect(new QPushButton(tr("Cancel"),&btns), SIGNAL(clicked()), &check, SLOT(reject()));
00996
00997 if ( bl ) {
00998 retry:
00999 if ( !check.exec() ) {
01000 unshowTurn();
01001 canvas()->update();
01002 return FALSE;
01003 }
01004
01005 for (int b=0; b<bl; b++) {
01006 QString v = le[b]->text();
01007 blankvalues[b]=Tile(v,0);
01008 if ( v.length() != 1 )
01009 goto retry;
01010 }
01011 }
01012
01013 QStringList words;
01014 unshowTurn();
01015 turn_score = score(at,tiles,n,blankvalues,d,FALSE,&words);
01016 showTurn();
01017 QStringList to_add;
01018 for (QStringList::Iterator it=words.begin(); it!=words.end(); ++it) {
01019 if ( !Global::fixedDawg().contains(*it)
01020 && !Global::dawg("WordGame").contains(*it) ) {
01021 switch (QMessageBox::warning(this, tr("Unknown word"),
01022 tr("<p>The word \"%1\" is not in the dictionary.").arg(*it),
01023 tr("Add"), tr("Ignore"), tr("Cancel")))
01024 {
01025 case 0:
01026
01027 to_add.append(*it);
01028 break;
01029 case 1:
01030 break;
01031 case 2:
01032 unshowTurn();
01033 canvas()->update();
01034 return FALSE;
01035 }
01036 }
01037 }
01038 if ( to_add.count() )
01039 Global::addWords("WordGame",to_add);
01040 return TRUE;
01041 }
01042
01043 void Board::scoreTurn(const QPoint& at, int n, const QPoint& d)
01044 {
01045 unshowTurn();
01046 shown_at = at;
01047 shown_n = n;
01048 shown_step = d;
01049 const Tile* tiles[99];
01050 if ( n > current_rack->count() )
01051 n = current_rack->count();
01052 for (int i=0; i<n; i++)
01053 tiles[i] = current_rack->tileRef(i);
01054 turn_score = score(at,tiles,n,0,d,FALSE,0);
01055 emit temporaryScore(turn_score);
01056 }
01057
01058 int Board::score(QPoint at, const Tile** tiles, int n, const Tile* blankvalue, const QPoint& d, bool checkdict, QStringList* words) const
01059 {
01060 int total=0;
01061 int totalsidetotal=0;
01062
01063
01064
01065
01066
01067
01068
01069
01070
01071
01072 if ( words ) *words=QStringList();
01073
01074 QPoint otherd(d.y(), d.x());
01075
01076 int all_mult = 1;
01077 int bl=0;
01078
01079 bool connected = FALSE;
01080
01081 QString mainword="";
01082
01083 if ( contains(at-d) && tile(at-d) ) {
01084 return -1;
01085 }
01086
01087 const Tile* t;
01088 for (int i=0; contains(at) && ((t=tile(at)) || i<n); ) {
01089 if ( t ) {
01090 if ( checkdict || words ) mainword += t->text();
01091 total += t->value();
01092 connected = TRUE;
01093 } else {
01094 QString sideword;
01095 QString tt;
01096 if ( tiles[i]->isBlank() ) {
01097 if ( blankvalue )
01098 tt = blankvalue[bl++].text();
01099 } else {
01100 tt = tiles[i]->text();
01101 }
01102 sideword=tt;
01103 if ( checkdict || words ) mainword += tt;
01104 int side_mult = 1;
01105 int tilevalue = bonussedValue(at,tiles[i]->value(),side_mult);
01106 all_mult *= side_mult;
01107 if ( !connected && isStart(at) )
01108 connected = TRUE;
01109 total += tilevalue;
01110 int sidetotal = tilevalue;
01111 {
01112 QPoint side = at-otherd;
01113
01114 while ( contains(side) && (t=tile(side)) ) {
01115 sidetotal += t->value();
01116 sideword.prepend(t->text());
01117 side -= otherd;
01118 }
01119 }
01120 {
01121 QPoint side = at+otherd;
01122 while ( contains(side) && (t=tile(side)) ) {
01123 sidetotal += t->value();
01124 sideword.append(t->text());
01125 side += otherd;
01126 }
01127 }
01128 if ( sideword.length() > 1 ) {
01129 if ( words )
01130 words->append(sideword);
01131 if ( checkdict && !Global::fixedDawg().contains(sideword)
01132 && !Global::dawg("WordGame").contains(sideword) )
01133 return -1;
01134 totalsidetotal += sidetotal * side_mult;
01135 connected = TRUE;
01136 }
01137 i++;
01138 }
01139 at += d;
01140 }
01141
01142 if ( words )
01143 words->append(mainword);
01144 if ( checkdict && !Global::fixedDawg().contains(mainword)
01145 && !Global::dawg("WordGame").contains(mainword) )
01146 return -1;
01147
01148 if ( n == rack_tiles )
01149 totalsidetotal += rack_tiles_bonus;
01150
01151 return connected ? totalsidetotal + total * all_mult : -1;
01152 }
01153
01154 QPoint Board::boardPos(const QPoint& p) const
01155 {
01156 return QPoint(p.x()/canvas()->tileWidth(), p.y()/canvas()->tileHeight());
01157 }
01158
01159 void Board::contentsMouseReleaseEvent(QMouseEvent*)
01160 {
01161 if ( current_rack ) {
01162 }
01163 }
01164
01165
01166 void Board::setRules(const QString& shapes, const int* effects)
01167 {
01168 rule_shape=shapes; rule_effect=effects;
01169 int i=0;
01170 int maxre=0;
01171 for (int y=0; y<yTiles(); y++) {
01172 for (int x=0; x<xTiles(); x++) {
01173 int re = shapes[i++]-'0';
01174 if ( re > maxre ) maxre = re;
01175 canvas()->setTile(x,y,re);
01176 }
01177 }
01178 rack_tiles_bonus=effects[maxre+1];
01179 }
01180
01181 void Board::unsetTile(const QPoint& p)
01182 {
01183 delete item(p);
01184 grid[idx(p)] = 0;
01185 }
01186
01187 void Board::setTile(const QPoint& p, const Tile& t)
01188 {
01189 TileItem* it=item(p);
01190 if ( !it ) {
01191 it = grid[idx(p)] = new TileItem(t,FALSE,canvas());
01192 it->move(p.x()*canvas()->tileWidth(), p.y()*canvas()->tileHeight());
01193 it->show();
01194 } else {
01195 it->setTile(t);
01196 }
01197 }
01198
01199 Rack::Rack(int ntiles, QWidget* parent) : QCanvasView(
01200 new QCanvas(ntiles*TileItem::bigWidth(),TileItem::bigHeight()),
01201 parent),
01202 item(ntiles)
01203 {
01204 setLineWidth(1);
01205 setFixedHeight(sizeHint().height());
01206 n = 0;
01207 for (int i=0; i<ntiles; i++)
01208 item[i]=0;
01209 setHScrollBarMode(AlwaysOff);
01210 setVScrollBarMode(AlwaysOff);
01211 canvas()->setBackgroundColor(gray);
01212 dragging = 0;
01213 }
01214
01215 Rack::~Rack()
01216 {
01217 clear();
01218 delete canvas();
01219 }
01220
01221 QSize Rack::sizeHint() const
01222 {
01223 return QSize(-1,TileItem::bigHeight()+2);
01224 }
01225
01226 void Rack::clear()
01227 {
01228 for (int i=0; i<n; i++)
01229 delete item[i];
01230 n=0;
01231 }
01232
01233 void Rack::writeConfig(Config& cfg)
01234 {
01235 QStringList l;
01236 for (int i=0; i<n; i++)
01237 l.append(tile(i).key());
01238 cfg.writeEntry("Tiles",l,';');
01239 }
01240
01241 void Rack::readConfig(Config& cfg)
01242 {
01243 clear();
01244 int x=0;
01245 QStringList l = cfg.readListEntry("Tiles",';');
01246 for (QStringList::ConstIterator it=l.begin(); it!=l.end(); ++it) {
01247 TileItem *i = new TileItem(Tile(*it),TRUE,canvas());
01248 i->move(x++,0);
01249 i->show();
01250 item[n++] = i;
01251 }
01252 layoutTiles();
01253 }
01254
01255 static int cmp_tileitem(const void *a, const void *b)
01256 {
01257 const TileItem* ia = *(TileItem**)a;
01258 const TileItem* ib = *(TileItem**)b;
01259 return int(ia->x() - ib->x());
01260 }
01261
01262 void Rack::layoutTiles()
01263 {
01264 int w = TileItem::bigWidth()+2;
01265
01266 if ( dragging ) dragging->moveBy(dragging_adj,0);
01267 qsort(item.data(), n, sizeof(TileItem*), cmp_tileitem);
01268 if ( dragging ) dragging->moveBy(-dragging_adj,0);
01269
01270 for (int i=0; i<n ;i++)
01271 if ( item[i] == dragging ) {
01272 item[i]->setZ(1);
01273 } else {
01274 item[i]->move(i*w, 0);
01275 item[i]->setZ(0);
01276 }
01277 canvas()->update();
01278 }
01279
01280 void Rack::setBlanks(const Tile* bv)
01281 {
01282 for (int j=0; j<n; j++) {
01283 Tile tt = item[j]->tile();
01284 if ( tt.isBlank() ) {
01285 tt.setText(bv->text());
01286 item[j]->setTile(tt);
01287 bv++;
01288 }
01289 }
01290 }
01291
01292 bool Rack::arrangeTiles(const Tile** s, int sn)
01293 {
01294 bool could = TRUE;
01295 for (int j=0; j<n; j++) {
01296 Tile tt = item[j]->tile();
01297 int f=-1;
01298 for (int i=0; i<sn && f<0; i++) {
01299 if (s[i] && *s[i] == tt ) {
01300 s[i]=0;
01301 f=i;
01302 }
01303 }
01304 if ( f >= 0 ) {
01305 item[j]->move(f-999,0);
01306 } else {
01307 could = FALSE;
01308 }
01309 }
01310 layoutTiles();
01311 return could;
01312 }
01313
01314 void Rack::addTile(const Tile& t)
01315 {
01316 TileItem *i = new TileItem(t,TRUE,canvas());
01317 i->show();
01318 item[n++] = i;
01319 layoutTiles();
01320 }
01321
01322 void Rack::remove(Tile t)
01323 {
01324 for (int i=0; i<n ;i++)
01325 if ( item[i]->tile() == t ) {
01326 remove(i);
01327 return;
01328 }
01329 }
01330
01331 void Rack::remove(int i)
01332 {
01333 delete item[i];
01334 n--;
01335 for (;i<n;i++)
01336 item[i]=item[i+1];
01337 layoutTiles();
01338 }
01339
01340 void Rack::resizeEvent(QResizeEvent* e)
01341 {
01342 canvas()->resize(width()-frameWidth()*2,height()-frameWidth()*2);
01343 QCanvasView::resizeEvent(e);
01344 }
01345
01346 void Rack::contentsMousePressEvent(QMouseEvent* e)
01347 {
01348 if ( computerized() )
01349 return;
01350 QCanvasItemList list = canvas()->collisions(e->pos());
01351 if (list.count()) {
01352 dragging = list.first();
01353 dragstart = e->pos()-QPoint(int(dragging->x()),int(dragging->y()));
01354 } else {
01355 dragging = 0;
01356 }
01357 }
01358
01359 void Rack::contentsMouseMoveEvent(QMouseEvent* e)
01360 {
01361 if ( computerized() )
01362 return;
01363
01364 if ( dragging ) {
01365 dragging_adj = TileItem::bigWidth()/2;
01366 if ( dragging->x() > e->x()-dragstart.x() )
01367 dragging_adj = -dragging_adj;
01368 dragging->move(e->x()-dragstart.x(),0);
01369 layoutTiles();
01370 }
01371 }
01372
01373 void Rack::contentsMouseReleaseEvent(QMouseEvent* )
01374 {
01375 if ( computerized() )
01376 return;
01377 if ( dragging ) {
01378 dragging=0;
01379 layoutTiles();
01380 }
01381 }
01382
01383 Tile::Tile(const QString& key)
01384 {
01385 int a=key.find('@');
01386 txt = key.left(a);
01387 val = key.mid(a+1).toInt();
01388 blank = txt.isEmpty();
01389 }
01390
01391 QString Tile::key() const
01392 {
01393 return txt+"@"+QString::number(val);
01394 }
01395
01396 Bag::Bag()
01397 {
01398 tiles.setAutoDelete(TRUE);
01399 }
01400
01401 void Bag::writeConfig(Config& cfg)
01402 {
01403 QStringList t;
01404 for (QListIterator<Tile> it(tiles); it; ++it)
01405 t.append((*it)->key());
01406 cfg.writeEntry("Tiles",t,';');
01407 }
01408
01409 void Bag::readConfig(Config& cfg)
01410 {
01411 tiles.clear();
01412 QStringList t = cfg.readListEntry("Tiles",';');
01413 for (QStringList::ConstIterator it=t.begin(); it!=t.end(); ++it )
01414 add(Tile(*it));
01415 }
01416
01417 void Bag::add(const Tile& t)
01418 {
01419 tiles.append(new Tile(t));
01420 }
01421
01422 Tile Bag::takeRandom()
01423 {
01424 Tile* rp = tiles.take(random()%tiles.count());
01425 Tile r=*rp;
01426 return r;
01427 }
01428
01429 ScoreInfo::ScoreInfo( QWidget* parent, const char* name, WFlags fl ) :
01430 QLabel("<P>",parent,name,fl)
01431 {
01432 score=0;
01433 msgtimer = new QTimer(this);
01434 connect(msgtimer, SIGNAL(timeout()), this, SLOT(showScores()));
01435 setBackgroundMode( PaletteButton );
01436 }
01437
01438 ScoreInfo::~ScoreInfo()
01439 {
01440 if ( score ) delete [] score;
01441 }
01442
01443 void ScoreInfo::writeConfig(Config& cfg)
01444 {
01445 QStringList l;
01446 for (int i=0; i<(int)names.count(); i++)
01447 l.append(QString::number(score[i]));
01448 cfg.writeEntry("Scores",l,';');
01449 }
01450
01451 void ScoreInfo::readConfig(Config& cfg)
01452 {
01453 QStringList l = cfg.readListEntry("Scores",';');
01454 int i=0;
01455 for (QStringList::ConstIterator it=l.begin(); it!=l.end(); ++it )
01456 score[i++]=(*it).toInt();
01457 showScores();
01458 }
01459
01460
01461 QSize ScoreInfo::sizeHint() const
01462 {
01463 return QSize(QLabel::sizeHint().width(),fontMetrics().height());
01464 }
01465
01466 void ScoreInfo::init(const QStringList& namelist)
01467 {
01468 names = namelist;
01469 if ( score ) delete [] score;
01470 score = new int[names.count()];
01471 memset(score,0,sizeof(int)*names.count());
01472 boldone = -1;
01473 showScores();
01474 }
01475
01476 void ScoreInfo::addScore(int player, int change)
01477 {
01478 score[player] += change;
01479 showScores();
01480 }
01481
01482 void ScoreInfo::setBoldOne(int b)
01483 {
01484 boldone=b;
01485 showScores();
01486 }
01487
01488 void ScoreInfo::showScores()
01489 {
01490 QString r="<p>";
01491 int i=0;
01492
01493 for (QStringList::ConstIterator it=names.begin(); it!=names.end(); ) {
01494 if ( i==boldone ) r += "<b>";
01495 QString n = *it;
01496 n.replace(QRegExp(":.*"),"");
01497 r += n;
01498 r += ":";
01499 r += QString::number(score[i]);
01500 if ( i==boldone ) r += "</b>";
01501
01502 ++i;
01503 ++it;
01504 if ( it != names.end() )
01505 r += " ";
01506 }
01507 setText(r);
01508 }
01509
01510 void ScoreInfo::showTemporaryScore(int amount)
01511 {
01512 if ( amount < 0 )
01513 setText(tr("<P>Invalid move"));
01514 else
01515 setText(tr("<P>Score: ")+QString::number(amount));
01516 msgtimer->start(3000,TRUE);
01517 }
01518