summaryrefslogtreecommitdiff
path: root/noncore/games/wordgame/wordgame.cpp
Unidiff
Diffstat (limited to 'noncore/games/wordgame/wordgame.cpp') (more/less context) (show whitespace changes)
-rw-r--r--noncore/games/wordgame/wordgame.cpp1476
1 files changed, 1476 insertions, 0 deletions
diff --git a/noncore/games/wordgame/wordgame.cpp b/noncore/games/wordgame/wordgame.cpp
new file mode 100644
index 0000000..ca4352d
--- a/dev/null
+++ b/noncore/games/wordgame/wordgame.cpp
@@ -0,0 +1,1476 @@
1/**********************************************************************
2** Copyright (C) 2000 Trolltech AS. All rights reserved.
3**
4** This file is part of Qtopia Environment.
5**
6** This file may be distributed and/or modified under the terms of the
7** GNU General Public License version 2 as published by the Free Software
8** Foundation and appearing in the file LICENSE.GPL included in the
9** packaging of this file.
10**
11** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
12** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13**
14** See http://www.trolltech.com/gpl/ for GPL licensing information.
15**
16** Contact info@trolltech.com if any conditions of this licensing are
17** not clear to you.
18**
19**********************************************************************/
20
21
22#include "wordgame.h"
23
24#include <qpe/applnk.h>
25#include <qpe/global.h>
26#include <qpe/filemanager.h>
27#include <qpe/resource.h>
28#include <qpe/config.h>
29
30#include <qapplication.h>
31#include <qmessagebox.h>
32#include <qcombobox.h>
33#include <qdatetime.h>
34#include <qfileinfo.h>
35#include <qfile.h>
36#include <qdir.h>
37#include <qiconset.h>
38#include <qlabel.h>
39#include <qlineedit.h>
40#include <qpushbutton.h>
41#include <qtextstream.h>
42#include <qtimer.h>
43#include <qpe/qpetoolbar.h>
44#include <qtoolbutton.h>
45#include <qvbox.h>
46#include <qwidgetstack.h>
47#include <qpainter.h>
48#include <qlayout.h>
49#include <qregexp.h>
50
51#include <stdlib.h>
52#include <unistd.h>
53#include <pwd.h>
54#include <sys/types.h>
55
56enum RuleEffects {
57 Multiplier=15,
58 MultiplyAll=64,
59 Start=128
60};
61
62static const int rack_tiles=7;
63
64const char* sampleWGR=
65 "wordgame_shapes\n"
66 "15 15\n"
67 "400001040100004\n"
68 "030000000000030\n"
69 "002002000200200\n"
70 "000300020003000\n"
71 "000020000020000\n"
72 "102001000100201\n"
73 "000000202000000\n"
74 "400200050002004\n"
75 "000000202000000\n"
76 "102001000100201\n"
77 "000020000020000\n"
78 "000300020003000\n"
79 "002002000200200\n"
80 "030000000000030\n"
81 "400001040100004\n"
82 "1 2 3 66 67 194 100 0\n"
83 "1 j 8\n"
84 "1 q 7\n"
85 "1 x 6\n"
86 "1 z 6\n"
87 "1 w 4\n"
88 "1 k 4\n"
89 "1 v 3\n"
90 "1 f 3\n"
91 "2 y 3\n"
92 "2 h 2\n"
93 "2 b 2\n"
94 "2 m 2\n"
95 "3 p 2\n"
96 "3 g 2\n"
97 "3 u 2\n"
98 "4 d 2\n"
99 "4 c 2\n"
100 "5 l 1\n"
101 "5 o 1\n"
102 "7 t 1\n"
103 "7 n 1\n"
104 "7 a 1\n"
105 "7 r 1\n"
106 "8 s 1\n"
107 "8 i 1\n"
108 "11 e 1\n"
109 "0\n";
110
111WordGame::WordGame( QWidget* parent, const char* name, WFlags fl ) :
112 QMainWindow(parent, name, fl)
113{
114 setIcon( Resource::loadPixmap( "wordgame" ) );
115 setCaption( tr("Word Game") );
116
117 setToolBarsMovable( FALSE );
118 vbox = new QVBox(this);
119
120 setCentralWidget(vbox);
121 toolbar = new QPEToolBar(this);
122 addToolBar(toolbar, Bottom);
123 reset = new QToolButton(Resource::loadPixmap("back"), tr("Back"), "", this, SLOT(resetTurn()), toolbar);
124 done = new QToolButton(Resource::loadPixmap("done"), tr("Done"), "", this, SLOT(endTurn()), toolbar);
125 scoreinfo = new ScoreInfo(toolbar);
126 scoreinfo->setFont(QFont("Helvetica",10));
127 new QToolButton(Resource::loadPixmap("finish"), tr("Close"), "", this, SLOT(endGame()), toolbar);
128 toolbar->setStretchableWidget(scoreinfo);
129
130 cpu = 0;
131 board = 0;
132 bag = 0;
133 racks = 0;
134
135 aiheart = new QTimer(this);
136 connect(aiheart, SIGNAL(timeout()), this, SLOT(think()));
137
138 readConfig();
139}
140
141WordGame::~WordGame()
142{
143 writeConfig();
144}
145
146void WordGame::writeConfig()
147{
148 Config cfg("WordGame");
149 cfg.setGroup("Game");
150 cfg.writeEntry("NameList",namelist,';');
151 cfg.writeEntry("CurrentPlayer",gameover ? 0 : player+1);
152 if ( !gameover ) {
153 cfg.writeEntry("Rules",rules);
154 bag->writeConfig(cfg);
155 board->writeConfig(cfg);
156 scoreinfo->writeConfig(cfg);
157 }
158 for (int p=0; p<nplayers; p++) {
159 cfg.setGroup("Player"+QString::number(p+1));
160 if ( gameover ) cfg.clearGroup(); else rack(p)->writeConfig(cfg);
161 }
162}
163
164void WordGame::readConfig()
165{
166 Config cfg("WordGame");
167 cfg.setGroup("Game");
168 int currentplayer = cfg.readNumEntry("CurrentPlayer",0);
169 QStringList pnames = cfg.readListEntry("NameList",';');
170 if ( currentplayer ) {
171 gameover = FALSE;
172 rules = cfg.readEntry("Rules");
173 if ( rules.find("x-wordgamerules") >= 0 ) {
174 // rules files moved
175 rules = "Sample.rules";
176 }
177 if ( loadRules(rules) ) {
178 startGame(pnames);
179 bag->readConfig(cfg);
180 board->readConfig(cfg);
181 scoreinfo->readConfig(cfg);
182 for (int p=0; p<nplayers; p++) {
183 cfg.setGroup("Player"+QString::number(p+1));
184 rack(p)->readConfig(cfg);
185 }
186 player=currentplayer-1;
187 readyRack(player);
188 return;
189 }
190 }
191 // fall-back
192 openGameSelector(pnames);
193}
194
195void WordGame::openGameSelector(const QStringList& initnames)
196{
197 toolbar->hide();
198 gameover = FALSE;
199
200 delete board;
201 board = 0;
202 delete racks;
203 racks = 0;
204
205 delete cpu;
206 cpu = 0;
207
208 newgame = new NewGame(vbox);
209
210 //Rules rules(this);
211 //connect(game.editrules, SIGNAL(clicked()), &rules, SLOT(editRules()));
212 //connect(&rules, SIGNAL(rulesChanged()), &game, SLOT(updateRuleSets()));
213 struct passwd* n = getpwuid(getuid());
214 QString playername = n ? n->pw_name : "";
215 if ( playername.isEmpty() ) {
216 playername = "Player";
217 }
218 newgame->player0->changeItem(playername,0);
219 newgame->player1->setCurrentItem(1);
220 newgame->updateRuleSets();
221 newgame->show();
222
223 connect(newgame->buttonOk, SIGNAL(clicked()), this, SLOT(startGame()));
224}
225
226void WordGame::startGame()
227{
228 rules = newgame->ruleslist[newgame->rules->currentItem()];
229 if ( loadRules(rules) ) {
230 QStringList names;
231 names.append(newgame->player0->currentText());
232 names.append(newgame->player1->currentText());
233 names.append(newgame->player2->currentText());
234 names.append(newgame->player3->currentText());
235 names.append(newgame->player4->currentText());
236 names.append(newgame->player5->currentText());
237 delete newgame;
238 startGame(names);
239 } else {
240 // error...
241 delete newgame;
242 close();
243 }
244}
245
246void WordGame::startGame(const QStringList& playerlist)
247{
248 toolbar->show();
249 racks = new QWidgetStack(vbox);
250 namelist.clear();
251 nplayers=0;
252 for (QStringList::ConstIterator it=playerlist.begin(); it!=playerlist.end(); ++it)
253 addPlayer(*it);
254 scoreinfo->init(namelist);
255
256 if ( nplayers ) {
257 player=0;
258 readyRack(player);
259 }
260
261 board->show();
262 racks->show();
263}
264
265bool WordGame::loadRules(const QString &name)
266{
267 QString filename = Global::applicationFileName( "wordgame", name );
268 QFile file( filename );
269 if ( !file.open( IO_ReadOnly ) )
270 return FALSE;
271
272 QTextStream ts( &file );
273
274 QString title = name;
275 title.truncate( title.length() - 6 );
276 setCaption( title );
277
278 QString shapepixmap;
279 ts >> shapepixmap;
280 int htiles,vtiles;
281 ts >> htiles >> vtiles;
282
283 if ( htiles < 3 || vtiles < 3 )
284 return FALSE;
285
286 QPixmap bgshapes = Resource::loadPixmap(shapepixmap);
287 QString rule_shapes;
288 for (int i=0; i<vtiles; i++) {
289 QString line;
290 ts >> line;
291 rule_shapes += line;
292 }
293 static int rule_effects[12];
294 int re=0,e;
295 ts >> e;
296 while ( e && re < 10 ) {
297 rule_effects[re] = e;
298 if ( re++ < 10 ) ts >> e;
299 }
300 rule_effects[re++] = 100; // default bonus
301 board = new Board(bgshapes, htiles, vtiles, vbox);
302 board->setRules(rule_shapes, rule_effects);
303 connect(board, SIGNAL(temporaryScore(int)), scoreinfo, SLOT(showTemporaryScore(int)));
304
305 bag = new Bag;
306
307 int count;
308 ts >> count;
309 while ( count ) {
310 QString text;
311 int value;
312 ts >> text >> value;
313 if ( text == "_" )
314 text = "";
315
316 Tile t(text, value);
317 for (int n=count; n--; )
318 bag->add(t);
319
320 ts >> count;
321 }
322
323 return TRUE;
324}
325
326
327NewGame::NewGame(QWidget* parent) :
328 NewGameBase(parent)
329{
330}
331
332void NewGame::updateRuleSets()
333{
334 rules->clear();
335
336 QString rulesDir = Global::applicationFileName( "wordgame", "" );
337 QDir dir( rulesDir, "*.rules" );
338 ruleslist = dir.entryList();
339 if ( ruleslist.isEmpty() ) {
340 // Provide a sample
341 QFile file( rulesDir + "Sample.rules" );
342 if ( file.open( IO_WriteOnly ) ) {
343 file.writeBlock( sampleWGR, strlen(sampleWGR) );
344 file.close();
345 updateRuleSets();
346 }
347 return;
348 }
349 int newest=0;
350 int newest_age=INT_MAX;
351 QDateTime now = QDateTime::currentDateTime();
352 QStringList::Iterator it;
353 for ( it = ruleslist.begin(); it != ruleslist.end(); ++it ) {
354 QFileInfo fi((*it));
355 int age = fi.lastModified().secsTo(now);
356 QString name = *it;
357 name.truncate( name.length()-6 ); // remove extension
358 rules->insertItem( name );
359 if ( age < newest_age ) {
360 newest_age = age;
361 newest = rules->count()-1;
362 }
363 }
364 rules->setCurrentItem(newest);
365}
366
367Rules::Rules(QWidget* parent) :
368 RulesBase(parent,0,TRUE)
369{
370}
371
372void Rules::editRules()
373{
374 if ( exec() ) {
375 // ### create a new set of rules
376 emit rulesChanged();
377 }
378}
379
380void Rules::deleteRuleSet()
381{
382 // ### delete existing rule set
383 emit rulesChanged();
384}
385
386void WordGame::addPlayer(const QString& name)
387{
388 if ( !name.isEmpty() ) {
389 int colon = name.find(':');
390 int cpu = (colon >=0 && name.left(2) == "AI") ? name.mid(2,1).toInt() : 0;
391 addPlayer(name,cpu);
392 }
393}
394
395void WordGame::addPlayer(const QString& name, int cpu)
396{
397 Rack* r = new Rack(rack_tiles,racks);
398 r->setPlayerName(name);
399 r->setComputerization(cpu);
400 racks->addWidget(r, nplayers);
401 refillRack(nplayers);
402 namelist.append(name);
403
404 ++nplayers;
405}
406
407void WordGame::nextPlayer()
408{
409 if ( !refillRack(player) ) {
410 endGame();
411 } else {
412 player = (player+1)%nplayers;
413 scoreinfo->setBoldOne(player);
414 readyRack(player);
415 }
416}
417
418bool WordGame::mayEndGame()
419{
420 int out=-1;
421 int i;
422 for (i=0; i<nplayers; i++)
423 if ( !rack(i)->count() )
424 out = i;
425 if ( out<0 ) {
426 if ( QMessageBox::warning(this,tr("End game"),
427 tr("Do you want to end the game early?"),
428 tr("Yes"), tr("No") )!=0 )
429 {
430 return FALSE;
431 }
432 }
433 return TRUE;
434}
435
436void WordGame::endGame()
437{
438 if ( gameover ) {
439 close();
440 return;
441 }
442
443 if ( !mayEndGame() )
444 return;
445 int out=-1;
446 int totalleft=0;
447 int i;
448 for (i=0; i<nplayers; i++) {
449 Rack* r = rack(i);
450 int c = r->count();
451 if ( c ) {
452 int lose=0;
453 for ( int j=0; j<c; j++ )
454 lose += r->tileRef(j)->value();
455 totalleft += lose;
456 scoreinfo->addScore(i,-lose);
457 } else {
458 out = i;
459 }
460 }
461 int highest=0;
462 int winner=0;
463 for (i=0; i<nplayers; i++) {
464 int s = scoreinfo->playerScore(i);
465 if ( s > highest ) {
466 highest = s;
467 winner = i;
468 }
469 }
470 if ( out >= 0 )
471 scoreinfo->addScore(out,totalleft);
472 scoreinfo->setBoldOne(winner);
473 gameover = TRUE;
474 done->setEnabled(TRUE);
475 reset->setEnabled(FALSE);
476}
477
478void WordGame::endTurn()
479{
480 if ( gameover ) {
481 openGameSelector(namelist);
482 } else {
483 if ( board->checkTurn() ) {
484 if ( board->turnScore() >= 0 ) {
485 scoreinfo->addScore(player,board->turnScore());
486 board->finalizeTurn();
487 } else {
488 QApplication::beep();
489 }
490 nextPlayer();
491 }
492 }
493}
494
495void WordGame::resetTurn()
496{
497 board->resetRack();
498}
499
500void WordGame::passTurn()
501{
502 // ######## trade?
503 nextPlayer();
504}
505
506bool WordGame::refillRack(int i)
507{
508 Rack* r = rack(i);
509 while ( !bag->isEmpty() && !r->isFull() ) {
510 r->addTile(bag->takeRandom());
511 }
512 return r->count() != 0;
513}
514
515void WordGame::readyRack(int i)
516{
517 Rack* r = rack(i);
518 racks->raiseWidget(i);
519 board->setCurrentRack(r);
520
521 done->setEnabled( !r->computerized() );
522 reset->setEnabled( !r->computerized() );
523
524 if ( r->computerized() ) {
525 cpu = new ComputerPlayer(board, r);
526 aiheart->start(0);
527 }
528}
529
530Rack* WordGame::rack(int i) const
531{
532 return (Rack*)racks->widget(i);
533}
534
535void WordGame::think()
536{
537 if ( !cpu->step() ) {
538 delete cpu;
539 cpu = 0;
540 aiheart->stop();
541 if ( board->turnScore() < 0 )
542 passTurn();
543 else
544 endTurn();
545 }
546}
547
548ComputerPlayer::ComputerPlayer(Board* b, Rack* r) :
549 board(b), rack(r), best(new const Tile*[rack_tiles]),
550 best_blankvalues(new Tile[rack_tiles])
551{
552 best_score = -1;
553 across=FALSE;
554 dict=0;
555}
556
557ComputerPlayer::~ComputerPlayer()
558{
559 delete [] best;
560 delete [] best_blankvalues;
561}
562
563bool ComputerPlayer::step()
564{
565 const QDawg::Node* root = dict ? Global::dawg("WordGame").root()
566 : Global::fixedDawg().root();
567 QPoint d = across ? QPoint(1,0) : QPoint(0,1);
568 const Tile* tiles[99]; // ### max board size
569 uchar nletter[4095]; // QDawg only handles 0..4095
570 memset(nletter,0,4096);
571 for (int i=0; i<rack->count(); i++) {
572 const Tile* r = rack->tileRef(i);
573 if ( r->isBlank() )
574 nletter[0]++;
575 else
576 nletter[r->text()[0].unicode()]++;
577 }
578 Tile blankvalues[99]; // ### max blanks
579 findBest(current, d, root, 0, nletter, tiles, 0, blankvalues, 0);
580 if ( ++current.rx() == board->xTiles() ) {
581 current.rx() = 0;
582 if ( ++current.ry() == board->yTiles() ) {
583 if ( across ) {
584 if ( dict == 1 ) {
585 if ( best_score >= 0 ) {
586 rack->arrangeTiles(best,best_n);
587 rack->setBlanks(best_blankvalues);
588 board->scoreTurn(best_start, best_n, best_dir);
589 board->showTurn();
590 }
591 return FALSE;
592 }
593 dict++;
594 across = FALSE;
595 current = QPoint(0,0);
596 } else {
597 across = TRUE;
598 current = QPoint(0,0);
599 }
600 }
601 }
602 return TRUE;
603}
604
605void ComputerPlayer::findBest(QPoint at, const QPoint& d, const QDawg::Node* node, ulong used, uchar* nletter, const Tile** tiles, int n, Tile* blankvalues, int blused)
606{
607 if ( !node )
608 return;
609 QChar l = node->letter();
610 const Tile* cur = board->tile(at);
611 if ( cur ) {
612 if ( cur->text()[0] == l ) {
613 bool nextok = board->contains(at+d);
614 if ( node->isWord() && n && (!nextok || !board->tile(at+d)) )
615 noteChoice(tiles,n,d,blankvalues,blused);
616 if ( nextok )
617 findBest(at+d, d, node->jump(), used, nletter, tiles, n, blankvalues, blused);
618 // #### text()[1]...
619 }
620 } else {
621 if ( nletter[l.unicode()] || nletter[0] ) {
622 int rc = rack->count();
623 ulong msk = 1;
624 for ( int x=0; x<rc; x++ ) {
625 if ( !(used&msk) ) {
626 const Tile* t = rack->tileRef(x);
627 if ( t->isBlank() || t->text() == l ) { // #### multi-char value()s
628 bool nextok = board->contains(at+d);
629 tiles[n++] = t;
630 if ( t->isBlank() )
631 blankvalues[blused++] = Tile(l,0);
632 if ( node->isWord() && (!nextok || !board->tile(at+d)) )
633 noteChoice(tiles,n,d,blankvalues,blused);
634 used |= msk; // mark
635 nletter[t->text()[0].unicode()]--;
636 if ( nextok )
637 findBest(at+d, d, node->jump(), used, nletter, tiles, n, blankvalues, blused);
638 n--;
639 nletter[t->text()[0].unicode()]++;
640 if ( t->isBlank() ) {
641 // keep looking
642 blused--;
643 used &= ~msk; // unmark
644 } else {
645 break;
646 }
647 }
648 }
649 msk <<= 1;
650 }
651 }
652 // #### text()[1]...
653 }
654 findBest(at, d, node->next(), used, nletter, tiles, n, blankvalues, blused);
655}
656
657void ComputerPlayer::noteChoice(const Tile** tiles, int n, const QPoint& d, const Tile* blankvalues, int blused)
658{
659 int s = board->score(current, tiles, n, blankvalues, d, TRUE, 0);
660/*
661if (s>0 || current==QPoint(5,1)){
662QString st;
663for ( int i=0; i<n; i++ )
664 st += tiles[i]->text();
665qDebug("%d,%d: %s (%d) for %d",current.x(),current.y(),st.latin1(),n,s);
666}
667*/
668 if ( s > best_score ) {
669 int i;
670 for ( i=0; i<n; i++ )
671 best[i] = tiles[i];
672 for ( i=0; i<blused; i++ )
673 best_blankvalues[i] = blankvalues[i];
674 best_n = n;
675 best_blused = blused;
676 best_score = s;
677 best_dir = d;
678 best_start = current;
679 }
680}
681
682int TileItem::smallWidth()
683{
684 return 16;
685}
686
687int TileItem::smallHeight()
688{
689 return 16;
690}
691
692int TileItem::bigWidth()
693{
694 return 22;
695}
696
697int TileItem::bigHeight()
698{
699 return 22;
700}
701
702void TileItem::setState( State state )
703{
704 hide();
705 s = state;
706 show(); // ### use update() in Qt 3.0
707}
708
709void TileItem::setTile(const Tile& tile)
710{
711 hide();
712 t = tile;
713 show(); // ### use update() in Qt 3.0
714}
715
716void TileItem::setBig(bool b)
717{
718 big = b;
719}
720
721void TileItem::drawShape(QPainter& p)
722{
723 static QFont value_font("heletica",8);
724 static QFont big_font("smoothtimes",17);
725 static QFont small_font("smoothtimes",10);
726
727 QRect area(x(),y(),width(),height());
728 p.setBrush(s == Floating ? yellow/*lightGray*/ : white);
729 p.drawRect(area);
730 if ( big ) {
731 p.setFont(value_font);
732 QString n = QString::number(t.value());
733 int w = p.fontMetrics().width('1');
734 int h = p.fontMetrics().height();
735 w *= n.length();
736 QRect valuearea(x()+width()-w-2,y()+height()-h+1,w,h);
737 p.drawText(valuearea,AlignCenter,n);
738 p.setFont(big_font);
739 area = QRect(x(),y(),width()-2,height()-1);
740 } else {
741 p.setFont(small_font);
742 area = QRect(x(),y()+2,width(),height()-2);
743 }
744 if ( t.value() == 0 )
745 p.setPen(darkGray);
746 p.drawText(area,AlignCenter,t.text().upper());
747}
748
749Board::Board(QPixmap bgshapes, int w, int h, QWidget* parent) :
750 QCanvasView(new QCanvas(bgshapes,w,h, TileItem::smallWidth(), TileItem::smallHeight()),
751 parent)
752{
753 grid = new TileItem*[w*h];
754 memset(grid,0,w*h*sizeof(TileItem*));
755 setFrameStyle(0);
756 setHScrollBarMode(AlwaysOff);
757 setVScrollBarMode(AlwaysOff);
758 current_rack = 0;
759 shown_n = 0;
760}
761
762Board::~Board()
763{
764 delete canvas();
765}
766
767void Board::writeConfig(Config& cfg)
768{
769 QStringList t;
770 int n=canvas()->tilesHorizontally()*canvas()->tilesVertically();
771 for (int i=0; i<n; i++)
772 t.append( grid[i] ? grid[i]->tile().key() : QString(".") );
773 cfg.writeEntry("Board",t,';');
774}
775
776void Board::readConfig(Config& cfg)
777{
778 clear();
779 QStringList t = cfg.readListEntry("Board",';');
780 int i=0;
781 int h=canvas()->tilesHorizontally();
782 for (QStringList::ConstIterator it=t.begin(); it!=t.end(); ++it) {
783 if ( *it != "." ) {
784 QPoint p(i%h,i/h);
785 setTile(p,Tile(*it));
786 }
787 i++;
788 }
789 canvas()->update();
790}
791
792void Board::clear()
793{
794 int n=canvas()->tilesHorizontally()*canvas()->tilesVertically();
795 for (int i=0; i<n; i++) {
796 delete grid[i];
797 grid[i]=0;
798 }
799}
800
801
802void Board::setCurrentRack(Rack* r)
803{
804 turn_score = -1;
805 current_rack = r;
806}
807
808void Board::resetRack()
809{
810 unshowTurn();
811 canvas()->update();
812}
813
814void Board::contentsMousePressEvent(QMouseEvent* e)
815{
816 dragstart = e->pos();
817}
818
819void Board::contentsMouseMoveEvent(QMouseEvent* e)
820{
821 if ( current_rack && !current_rack->computerized() ) {
822 QPoint d = e->pos() - dragstart;
823 if ( d.x() <= 0 && d.y() <= 0 ) {
824 // None
825 resetRack();
826 } else {
827 int n;
828 QPoint start=boardPos(dragstart);
829 QPoint end=boardPos(e->pos());
830 QPoint diff=end-start;
831 QPoint dir;
832 if ( d.x() > d.y() ) {
833 n = diff.x()+1;
834 dir = QPoint(1,0);
835 } else {
836 n = diff.y()+1;
837 dir = QPoint(0,1);
838 }
839
840 unshowTurn();
841
842 // Subtract existing tiles from n
843 QPoint t = start;
844 for ( int i=n; i--; ) {
845 if ( contains(t) && tile(t) )
846 n--;
847 t += dir;
848 }
849
850 // Move start back to real start
851 while (contains(start-dir) && tile(start-dir))
852 start -= dir;
853
854 scoreTurn(start, n, dir);
855 showTurn();
856 }
857 }
858}
859
860void Board::finalizeTurn()
861{
862 int i=0;
863 QPoint at = shown_at;
864 while ( i<shown_n && contains(at) ) {
865 if ( item(at) && item(at)->state() == TileItem::Floating ) {
866 current_rack->remove(item(at)->tile());
867 setTileState(at,TileItem::Firm);
868 i++;
869 }
870 at += shown_step;
871 }
872 canvas()->update();
873}
874
875void Board::unshowTurn()
876{
877 int i=0;
878 QPoint at = shown_at;
879 while ( i<shown_n && i<current_rack->count() && contains(at) ) {
880 if ( item(at) && item(at)->state() == TileItem::Floating ) {
881 unsetTile(at);
882 i++;
883 }
884 at += shown_step;
885 }
886}
887
888void Board::showTurn()
889{
890 unshowTurn();
891 QPoint at = shown_at;
892 int i=0;
893 while ( i<shown_n && i<current_rack->count() && contains(at) ) {
894 if ( !tile(at) ) {
895 Tile t = current_rack->tile(i);
896 setTile(at,t);
897 setTileState(at,TileItem::Floating);
898 i++;
899 }
900 at += shown_step;
901 }
902 canvas()->update();
903}
904
905int Board::bonussedValue(const QPoint& at, int base, int& all_mult) const
906{
907 int rule = rule_shape[idx(at)]-'0';
908 int effect = rule_effect[rule];
909 int mult = effect&Multiplier;
910 if ( effect & MultiplyAll ) {
911 all_mult *= mult;
912 return base;
913 } else {
914 return base * mult;
915 }
916}
917
918bool Board::isStart(const QPoint& at) const
919{
920 int rule = rule_shape[idx(at)]-'0';
921 int effect = rule_effect[rule];
922 return effect&Start;
923}
924
925bool Board::checkTurn()
926{
927 if ( current_rack->computerized() )
928 return TRUE; // computer doesn't cheat, and has already set blanks.
929
930 QPoint at = shown_at;
931 int n = shown_n;
932 QPoint d = shown_step;
933 const Tile* tiles[99];
934 Tile blankvalues[99];
935 if ( n > current_rack->count() )
936 n = current_rack->count();
937
938 QDialog check(this,0,TRUE);
939 (new QVBoxLayout(&check))->setAutoAdd(TRUE);
940
941 QHBox mw(&check);
942 new QLabel(tr("Blanks: "),&mw);
943
944 int bl=0;
945 QLineEdit* le[99];
946 for (int i=0; i<n; i++) {
947 tiles[i] = current_rack->tileRef(i);
948 if ( tiles[i]->isBlank() ) {
949 QLineEdit *l = new QLineEdit(&mw);
950 le[bl++] = l;
951 l->setMaxLength(1);
952 l->setFixedSize(l->minimumSizeHint());
953 }
954 }
955
956 QHBox btns(&check);
957 connect(new QPushButton(tr("OK"),&btns), SIGNAL(clicked()), &check, SLOT(accept()));
958 connect(new QPushButton(tr("Cancel"),&btns), SIGNAL(clicked()), &check, SLOT(reject()));
959
960 if ( bl ) {
961retry:
962 if ( !check.exec() ) {
963 unshowTurn();
964 canvas()->update();
965 return FALSE;
966 }
967
968 for (int b=0; b<bl; b++) {
969 QString v = le[b]->text();
970 blankvalues[b]=Tile(v,0);
971 if ( v.length() != 1 )
972 goto retry;
973 }
974 }
975
976 QStringList words;
977 unshowTurn();
978 turn_score = score(at,tiles,n,blankvalues,d,FALSE,&words);
979 showTurn();
980 QStringList to_add;
981 for (QStringList::Iterator it=words.begin(); it!=words.end(); ++it) {
982 if ( !Global::fixedDawg().contains(*it)
983 && !Global::dawg("WordGame").contains(*it) ) {
984 switch (QMessageBox::warning(this, tr("Unknown word"),
985 tr("<p>The word \"%1\" is not in the dictionary.").arg(*it),
986 tr("Add"), tr("Ignore"), tr("Cancel")))
987 {
988 case 0:
989 // ####### add to wordgame dictionary
990 to_add.append(*it);
991 break;
992 case 1:
993 break;
994 case 2:
995 unshowTurn();
996 canvas()->update();
997 return FALSE;
998 }
999 }
1000 }
1001 if ( to_add.count() )
1002 Global::addWords("WordGame",to_add);
1003 return TRUE;
1004}
1005
1006void Board::scoreTurn(const QPoint& at, int n, const QPoint& d)
1007{
1008 unshowTurn();
1009 shown_at = at;
1010 shown_n = n;
1011 shown_step = d;
1012 const Tile* tiles[99];
1013 if ( n > current_rack->count() )
1014 n = current_rack->count();
1015 for (int i=0; i<n; i++)
1016 tiles[i] = current_rack->tileRef(i);
1017 turn_score = score(at,tiles,n,0,d,FALSE,0);
1018 emit temporaryScore(turn_score);
1019}
1020
1021int Board::score(QPoint at, const Tile** tiles, int n, const Tile* blankvalue, const QPoint& d, bool checkdict, QStringList* words) const
1022{
1023 int total=0;
1024 int totalsidetotal=0;
1025
1026 // words gets filled with words made
1027
1028 // mainword==0 ->
1029 // Checks side words, but not main word
1030
1031 // -1 means words not in dict, or illegally positioned (eg. not connected)
1032
1033 // text is assumed to fit on board.
1034
1035 if ( words ) *words=QStringList();
1036
1037 QPoint otherd(d.y(), d.x());
1038
1039 int all_mult = 1;
1040 int bl=0;
1041
1042 bool connected = FALSE;
1043
1044 QString mainword="";
1045
1046 if ( contains(at-d) && tile(at-d) ) {
1047 return -1; // preceeding tiles
1048 }
1049
1050 const Tile* t;
1051 for (int i=0; contains(at) && ((t=tile(at)) || i<n); ) {
1052 if ( t ) {
1053 if ( checkdict || words ) mainword += t->text();
1054 total += t->value();
1055 connected = TRUE;
1056 } else {
1057 QString sideword;
1058 QString tt;
1059 if ( tiles[i]->isBlank() ) {
1060 if ( blankvalue )
1061 tt = blankvalue[bl++].text();
1062 } else {
1063 tt = tiles[i]->text();
1064 }
1065 sideword=tt;
1066 if ( checkdict || words ) mainword += tt;
1067 int side_mult = 1;
1068 int tilevalue = bonussedValue(at,tiles[i]->value(),side_mult);
1069 all_mult *= side_mult;
1070 if ( !connected && isStart(at) )
1071 connected = TRUE;
1072 total += tilevalue;
1073 int sidetotal = tilevalue;
1074 {
1075 QPoint side = at-otherd;
1076
1077 while ( contains(side) && (t=tile(side)) ) {
1078 sidetotal += t->value();
1079 sideword.prepend(t->text());
1080 side -= otherd;
1081 }
1082 }
1083 {
1084 QPoint side = at+otherd;
1085 while ( contains(side) && (t=tile(side)) ) {
1086 sidetotal += t->value();
1087 sideword.append(t->text());
1088 side += otherd;
1089 }
1090 }
1091 if ( sideword.length() > 1 ) {
1092 if ( words )
1093 words->append(sideword);
1094 if ( checkdict && !Global::fixedDawg().contains(sideword)
1095 && !Global::dawg("WordGame").contains(sideword) )
1096 return -1;
1097 totalsidetotal += sidetotal * side_mult;
1098 connected = TRUE;
1099 }
1100 i++;
1101 }
1102 at += d;
1103 }
1104
1105 if ( words )
1106 words->append(mainword);
1107 if ( checkdict && !Global::fixedDawg().contains(mainword)
1108 && !Global::dawg("WordGame").contains(mainword) )
1109 return -1;
1110
1111 if ( n == rack_tiles )
1112 totalsidetotal += rack_tiles_bonus;
1113
1114 return connected ? totalsidetotal + total * all_mult : -1;
1115}
1116
1117QPoint Board::boardPos(const QPoint& p) const
1118{
1119 return QPoint(p.x()/canvas()->tileWidth(), p.y()/canvas()->tileHeight());
1120}
1121
1122void Board::contentsMouseReleaseEvent(QMouseEvent*)
1123{
1124 if ( current_rack ) {
1125 }
1126}
1127
1128
1129void Board::setRules(const QString& shapes, const int* effects)
1130{
1131 rule_shape=shapes; rule_effect=effects;
1132 int i=0;
1133 int maxre=0;
1134 for (int y=0; y<yTiles(); y++) {
1135 for (int x=0; x<xTiles(); x++) {
1136 int re = shapes[i++]-'0';
1137 if ( re > maxre ) maxre = re;
1138 canvas()->setTile(x,y,re);
1139 }
1140 }
1141 rack_tiles_bonus=effects[maxre+1];
1142}
1143
1144void Board::unsetTile(const QPoint& p)
1145{
1146 delete item(p);
1147 grid[idx(p)] = 0;
1148}
1149
1150void Board::setTile(const QPoint& p, const Tile& t)
1151{
1152 TileItem* it=item(p);
1153 if ( !it ) {
1154 it = grid[idx(p)] = new TileItem(t,FALSE,canvas());
1155 it->move(p.x()*canvas()->tileWidth(), p.y()*canvas()->tileHeight());
1156 it->show();
1157 } else {
1158 it->setTile(t);
1159 }
1160}
1161
1162Rack::Rack(int ntiles, QWidget* parent) : QCanvasView(
1163 new QCanvas(ntiles*TileItem::bigWidth(),TileItem::bigHeight()),
1164 parent),
1165 item(ntiles)
1166{
1167 setLineWidth(1);
1168 setFixedHeight(sizeHint().height());
1169 n = 0;
1170 for (int i=0; i<ntiles; i++)
1171 item[i]=0;
1172 setHScrollBarMode(AlwaysOff);
1173 setVScrollBarMode(AlwaysOff);
1174 canvas()->setBackgroundColor(gray);
1175 dragging = 0;
1176}
1177
1178Rack::~Rack()
1179{
1180 clear();
1181 delete canvas();
1182}
1183
1184void Rack::clear()
1185{
1186 for (int i=0; i<n; i++)
1187 delete item[i];
1188 n=0;
1189}
1190
1191void Rack::writeConfig(Config& cfg)
1192{
1193 QStringList l;
1194 for (int i=0; i<n; i++)
1195 l.append(tile(i).key());
1196 cfg.writeEntry("Tiles",l,';');
1197}
1198
1199void Rack::readConfig(Config& cfg)
1200{
1201 clear();
1202 int x=0;
1203 QStringList l = cfg.readListEntry("Tiles",';');
1204 for (QStringList::ConstIterator it=l.begin(); it!=l.end(); ++it) {
1205 TileItem *i = new TileItem(Tile(*it),TRUE,canvas());
1206 i->move(x++,0);
1207 i->show();
1208 item[n++] = i;
1209 }
1210 layoutTiles();
1211}
1212
1213static int cmp_tileitem(const void *a, const void *b)
1214{
1215 const TileItem* ia = *(TileItem**)a;
1216 const TileItem* ib = *(TileItem**)b;
1217 return int(ia->x() - ib->x());
1218}
1219
1220void Rack::layoutTiles()
1221{
1222 int w = TileItem::bigWidth()+2;
1223
1224 if ( dragging ) dragging->moveBy(dragging_adj,0);
1225 qsort(item.data(), n, sizeof(TileItem*), cmp_tileitem);
1226 if ( dragging ) dragging->moveBy(-dragging_adj,0);
1227
1228 for (int i=0; i<n ;i++)
1229 if ( item[i] == dragging ) {
1230 item[i]->setZ(1);
1231 } else {
1232 item[i]->move(i*w, 0);
1233 item[i]->setZ(0);
1234 }
1235 canvas()->update();
1236}
1237
1238void Rack::setBlanks(const Tile* bv)
1239{
1240 for (int j=0; j<n; j++) {
1241 Tile tt = item[j]->tile();
1242 if ( tt.isBlank() ) {
1243 tt.setText(bv->text());
1244 item[j]->setTile(tt);
1245 bv++;
1246 }
1247 }
1248}
1249
1250bool Rack::arrangeTiles(const Tile** s, int sn)
1251{
1252 bool could = TRUE;
1253 for (int j=0; j<n; j++) {
1254 Tile tt = item[j]->tile();
1255 int f=-1;
1256 for (int i=0; i<sn && f<0; i++) {
1257 if (s[i] && *s[i] == tt ) {
1258 s[i]=0;
1259 f=i;
1260 }
1261 }
1262 if ( f >= 0 ) {
1263 item[j]->move(f-999,0);
1264 } else {
1265 could = FALSE;
1266 }
1267 }
1268 layoutTiles();
1269 return could;
1270}
1271
1272void Rack::addTile(const Tile& t)
1273{
1274 TileItem *i = new TileItem(t,TRUE,canvas());
1275 i->show();
1276 item[n++] = i;
1277 layoutTiles();
1278}
1279
1280void Rack::remove(Tile t)
1281{
1282 for (int i=0; i<n ;i++)
1283 if ( item[i]->tile() == t ) {
1284 remove(i);
1285 return;
1286 }
1287}
1288
1289void Rack::remove(int i)
1290{
1291 delete item[i];
1292 n--;
1293 for (;i<n;i++)
1294 item[i]=item[i+1];
1295 layoutTiles();
1296}
1297
1298void Rack::resizeEvent(QResizeEvent* e)
1299{
1300 canvas()->resize(width()-frameWidth()*2,height()-frameWidth()*2);
1301 QCanvasView::resizeEvent(e);
1302}
1303
1304void Rack::contentsMousePressEvent(QMouseEvent* e)
1305{
1306 if ( computerized() )
1307 return;
1308 QCanvasItemList list = canvas()->collisions(e->pos());
1309 if (list.count()) {
1310 dragging = list.first();
1311 dragstart = e->pos()-QPoint(int(dragging->x()),int(dragging->y()));
1312 } else {
1313 dragging = 0;
1314 }
1315}
1316
1317void Rack::contentsMouseMoveEvent(QMouseEvent* e)
1318{
1319 if ( computerized() )
1320 return;
1321 //int w = TileItem::bigWidth()+2;
1322 if ( dragging ) {
1323 dragging_adj = TileItem::bigWidth()/2;
1324 if ( dragging->x() > e->x()-dragstart.x() )
1325 dragging_adj = -dragging_adj;
1326 dragging->move(e->x()-dragstart.x(),0);
1327 layoutTiles();
1328 }
1329}
1330
1331void Rack::contentsMouseReleaseEvent(QMouseEvent* e)
1332{
1333 if ( computerized() )
1334 return;
1335 if ( dragging ) {
1336 dragging=0;
1337 layoutTiles();
1338 }
1339}
1340
1341Tile::Tile(const QString& key)
1342{
1343 int a=key.find('@');
1344 txt = key.left(a);
1345 val = key.mid(a+1).toInt();
1346 blank = txt.isEmpty();
1347}
1348
1349QString Tile::key() const
1350{
1351 return txt+"@"+QString::number(val);
1352}
1353
1354Bag::Bag()
1355{
1356 tiles.setAutoDelete(TRUE);
1357}
1358
1359void Bag::writeConfig(Config& cfg)
1360{
1361 QStringList t;
1362 for (QListIterator<Tile> it(tiles); it; ++it)
1363 t.append((*it)->key());
1364 cfg.writeEntry("Tiles",t,';');
1365}
1366
1367void Bag::readConfig(Config& cfg)
1368{
1369 tiles.clear();
1370 QStringList t = cfg.readListEntry("Tiles",';');
1371 for (QStringList::ConstIterator it=t.begin(); it!=t.end(); ++it )
1372 add(Tile(*it));
1373}
1374
1375void Bag::add(const Tile& t)
1376{
1377 tiles.append(new Tile(t));
1378}
1379
1380Tile Bag::takeRandom()
1381{
1382 Tile* rp = tiles.take(random()%tiles.count());
1383 Tile r=*rp;
1384 return r;
1385}
1386
1387ScoreInfo::ScoreInfo( QWidget* parent, const char* name, WFlags fl ) :
1388 QLabel("<P>",parent,name,fl)
1389{
1390 score=0;
1391 msgtimer = new QTimer(this);
1392 connect(msgtimer, SIGNAL(timeout()), this, SLOT(showScores()));
1393 setBackgroundMode( PaletteButton );
1394}
1395
1396ScoreInfo::~ScoreInfo()
1397{
1398 if ( score ) delete [] score;
1399}
1400
1401void ScoreInfo::writeConfig(Config& cfg)
1402{
1403 QStringList l;
1404 for (int i=0; i<(int)names.count(); i++)
1405 l.append(QString::number(score[i]));
1406 cfg.writeEntry("Scores",l,';');
1407}
1408
1409void ScoreInfo::readConfig(Config& cfg)
1410{
1411 QStringList l = cfg.readListEntry("Scores",';');
1412 int i=0;
1413 for (QStringList::ConstIterator it=l.begin(); it!=l.end(); ++it )
1414 score[i++]=(*it).toInt();
1415 showScores();
1416}
1417
1418
1419QSize ScoreInfo::sizeHint() const
1420{
1421 return QSize(QLabel::sizeHint().width(),fontMetrics().height());
1422}
1423
1424void ScoreInfo::init(const QStringList& namelist)
1425{
1426 names = namelist;
1427 if ( score ) delete [] score;
1428 score = new int[names.count()];
1429 memset(score,0,sizeof(int)*names.count());
1430 boldone = -1;
1431 showScores();
1432}
1433
1434void ScoreInfo::addScore(int player, int change)
1435{
1436 score[player] += change;
1437 showScores();
1438}
1439
1440void ScoreInfo::setBoldOne(int b)
1441{
1442 boldone=b;
1443 showScores();
1444}
1445
1446void ScoreInfo::showScores()
1447{
1448 QString r="<p>";
1449 int i=0;
1450 //int spl=(names.count()+1)/2; // 2 lines
1451 for (QStringList::ConstIterator it=names.begin(); it!=names.end(); ) {
1452 if ( i==boldone ) r += "<b>";
1453 QString n = *it;
1454 n.replace(QRegExp(":.*"),"");
1455 r += n;
1456 r += ":";
1457 r += QString::number(score[i]);
1458 if ( i==boldone ) r += "</b>";
1459
1460 ++i;
1461 ++it;
1462 if ( it != names.end() )
1463 r += " ";
1464 }
1465 setText(r);
1466}
1467
1468void ScoreInfo::showTemporaryScore(int amount)
1469{
1470 if ( amount < 0 )
1471 setText(tr("<P>Invalid move"));
1472 else
1473 setText(tr("<P>Score: ")+QString::number(amount));
1474 msgtimer->start(3000,TRUE);
1475}
1476