diff options
Diffstat (limited to 'kblackbox/kbbgame.cpp')
-rw-r--r-- | kblackbox/kbbgame.cpp | 740 |
1 files changed, 740 insertions, 0 deletions
diff --git a/kblackbox/kbbgame.cpp b/kblackbox/kbbgame.cpp new file mode 100644 index 00000000..82028871 --- /dev/null +++ b/kblackbox/kbbgame.cpp @@ -0,0 +1,740 @@ +// +// KBlackbox +// +// A simple game inspired by an emacs module +// +// File: kbbgame.cpp +// +// The implementation of the KBBGame widget +// + +#include <config.h> + +#include <qpopupmenu.h> +#include <qkeycode.h> +#include <qlabel.h> +#include <qpushbutton.h> +#include <qtooltip.h> +#include <qstring.h> + +#include <kmessagebox.h> +#include <kdebug.h> +#include <kapplication.h> +#include <klocale.h> +#include <kconfig.h> +#include <kglobal.h> +#include <kmenubar.h> +#include <kiconloader.h> +#include <khelpmenu.h> +#include <kaction.h> +#include <kstdaction.h> +#include <kstatusbar.h> +#include <kstdgameaction.h> + +#include "kbbgame.h" +#include "util.h" +#include "version.h" + +/* + Names of pixmap files. +*/ + +const char *pFNames[NROFTYPES] = { + "white", + "gray", + "green", + "red", + "blue", + "cyan", + "brown", + "green2" +}; + +/* + Creates the KBBGame widget and sets saved options (if any). +*/ + +KBBGame::KBBGame() +{ + int i; + + QPixmap **pix = new QPixmap * [NROFTYPES]; + pix[0] = new QPixmap(); + *pix[0] = BarIcon( pFNames[0] ); + if (!pix[0]->isNull()) { + kdDebug(12009) << "Pixmap \"" << pFNames[0] << "\" loaded." << endl; + for (i = 1; i < NROFTYPES; i++) { + pix[i] = new QPixmap; + *pix[i] = BarIcon( pFNames[i] ); + if (!pix[i]->isNull()) { + kdDebug(12009) << "Pixmap \"" << pFNames[i] << "\" loaded." << endl; + } else { + pix[i] = pix[i-1]; + pix[i]->detach(); + kdDebug(12009) << "Cannot find pixmap \"" << pFNames[i] << "\". Using previous one." << endl; + } + } + } else { + kdDebug(12009) << "Cannot find pixmap \"" << pFNames[0] << "\". Pixmaps will not be loaded." << endl; + delete pix[0]; + delete pix; + pix = NULL; + } + gr = new KBBGraphic( pix, this, "KBBGraphic" ); + + statusBar()->insertItem(i18n("Score: 0000"), SSCORE); + statusBar()->insertItem(i18n("Placed: 00 / 00"), SBALLS); + statusBar()->insertItem(i18n("Run: yesno"), SRUN); + statusBar()->insertItem(i18n("Size: 00 x 00"), SSIZE); + + initKAction(); + + connect( gr, SIGNAL(inputAt(int,int,int)), + this, SLOT(gotInputAt(int,int,int)) ); + connect( this, SIGNAL(gameRuns(bool)), + gr, SLOT(setInputAccepted(bool)) ); + connect( gr, SIGNAL(endMouseClicked()), + this, SLOT(gameFinished()) ); + + /* + QToolTip::add( doneButton, i18n( + "Click here when you think you placed all the balls.") ); + */ + + /* + Game initializations + */ + running = FALSE; + gameBoard = NULL; + + KConfig *kConf; + int j; + kConf = kapp->config(); + kConf->setGroup( "KBlackBox Setup" ); + if (kConf->hasKey( "Balls" )) { + i = kConf->readNumEntry( "Balls" ); + balls = i; + switch (i) { + case 4: ballsAction->setCurrentItem(0); break; + case 6: ballsAction->setCurrentItem(1); break; + case 8: ballsAction->setCurrentItem(2); break; + } + } else { + balls = 4; + ballsAction->setCurrentItem(0); + } + if ((kConf->hasKey( "Width" )) && + (kConf->hasKey( "Balls" ))) { + i = kConf->readNumEntry( "Width" ); + j = kConf->readNumEntry( "Height" ); + gr->setSize( i+4, j+4 ); // +4 is the space for "lasers" and an edge... + gameBoard = new RectOnArray( gr->numC(), gr->numR() ); + switch (i) { + case 8: sizeAction->setCurrentItem(0); break; + case 10: sizeAction->setCurrentItem(1); break; + case 12: sizeAction->setCurrentItem(2); break; + } + } else { + gr->setSize( 8+4, 8+4 ); // +4 is the space for "lasers" and an edge... + gameBoard = new RectOnArray( gr->numC(), gr->numR() ); + sizeAction->setCurrentItem(0); + } + if (kConf->hasKey( "tutorial" )) { + tutorial = (bool) kConf->readNumEntry( "tutorial" ); + } else tutorial = FALSE; + tutorialAction->setChecked(tutorial); + + setCentralWidget( gr ); + + setScore( 0 ); + ballsPlaced = 0; + + updateStats(); + + newGame(); + setMinSize(); + + setupGUI(); +} + +/* + Saves the options and destroys the KBBGame widget. +*/ + +KBBGame::~KBBGame() +{ + KConfig *kConf; + QString s; + + kConf = kapp->config(); + kConf->setGroup( "KBlackBox Setup" ); + kConf->writeEntry( "Balls", balls ); + kConf->writeEntry( "Width", gr->numC() - 4); + kConf->writeEntry( "Height", gr->numR() - 4); + kConf->writeEntry( "tutorial", (int) tutorial ); + + delete gameBoard; + // All the rest has "this" for parent so it doesn't need to be deleted. +} + + +/* + Resizes yourself to fit the contents perfectly, from menu. +*/ +void KBBGame::gameResize() +{ + resize( gr->wHint(), gr->hHint() + menuBar()->height() + statusBar()->height() + + toolBar()->height() ); +} + +void KBBGame::setMinSize() +{ + setMinimumSize( gr->wHint(), gr->hHint() + menuBar()->height() + statusBar()->height() + + toolBar()->height() ); +} + +/* + Settings of various options. +*/ +void KBBGame::slotSize() +{ + int i = sizeAction->currentItem(); + bool ok = false; + switch (i) { + case 0: + ok = setSize( 8, 8 ); + break; + case 1: + ok = setSize( 10, 10 ); + break; + case 2: + ok = setSize( 12, 12 ); + break; + } + + if (!ok) { + switch(gr->numR() - 4) { + case 8: + sizeAction->setCurrentItem(0); break; + case 10: + sizeAction->setCurrentItem(1); break; + case 12: + sizeAction->setCurrentItem(2); break; + } + } +} + +void KBBGame::slotBalls() +{ + int i = ballsAction->currentItem(); + bool ok = false; + switch (i) { + case 0: + ok = setBalls( 4 ); + break; + case 1: + ok = setBalls( 6 ); + break; + case 2: + ok = setBalls( 8 ); + break; + } + if (!ok) { + switch (balls) { + case 4: + ballsAction->setCurrentItem(0); break; + case 6: + ballsAction->setCurrentItem(1); break; + case 8: + ballsAction->setCurrentItem(2); break; + } + } +} + +void KBBGame::tutorialSwitch() +{ + tutorial = !tutorial; +} + +/* + Creates a new game. +*/ +void KBBGame::newGame() +{ + int i, j; + + if (running) { + bool cancel; + cancel = KMessageBox::warningContinueCancel(0, + i18n("Do you really want to give up this game?"),QString::null,i18n("Give Up")) + == KMessageBox::Cancel; + if (cancel) + return; + + abortGame(); + } + + gameBoard->fill( INNERBBT ); + for (j = 0; j < (gr->numR()); j++) { + gameBoard->set( 0, j, OUTERBBT ); + gameBoard->set( gr->numC()-1, j, OUTERBBT ); + } + for (i = 0; i < (gr->numC()); i++) { + gameBoard->set( i, 0, OUTERBBT ); + gameBoard->set( i, gr->numR()-1, OUTERBBT ); + } + for (j = 2; j < (gr->numR()-2); j++) { + gameBoard->set( 1, j, LASERBBT ); + gameBoard->set( gr->numC()-2, j, LASERBBT ); + } + for (i = 2; i < (gr->numC()-2); i++) { + gameBoard->set( i, 1, LASERBBT ); + gameBoard->set( i, gr->numR()-2, LASERBBT ); + } + gameBoard->set( 1, 1, OUTERBBT ); + gameBoard->set( 1, gr->numR()-2, OUTERBBT ); + gameBoard->set( gr->numC()-2, 1, OUTERBBT ); + gameBoard->set( gr->numC()-2, gr->numR()-2, OUTERBBT ); + + randomBalls( balls ); + remap( gameBoard, gr->getGraphicBoard() ); + gr->repaint( TRUE ); + setScore( 0 ); + detourCounter = -1; + ballsPlaced = 0; + running = TRUE; + updateStats(); + emit gameRuns( running ); +} + +/* + Ends the current game. +*/ + +void KBBGame::gameFinished() +{ + if (running) { + QString s; + if (ballsPlaced == balls) { + getResults(); + abortGame(); + if (score <= (balls*3)) + s = i18n("Your final score is: %1\n" + "You did really well!"); + else + s = i18n("Your final score is: %1\n" + "I guess you need more practice."); + + KMessageBox::information(this, + s.arg(KGlobal::locale()->formatNumber(score, 0))); + } else { + s = i18n( "You should place %1 balls!\n" + "You have placed %2.") + .arg(KGlobal::locale()->formatNumber(balls, 0)) + .arg(KGlobal::locale()->formatNumber(ballsPlaced, 0)); + + KMessageBox::sorry(this, s); + } + } +} + +/* + Computes the final score and indicate errors. +*/ + +void KBBGame::getResults() +{ + int i, j, tgam, tgra; + RectOnArray *r = gr->getGraphicBoard(); + for (j = 0; j < (gr->numR()); j++) { + for (i = 0; i < (gr->numC()); i++) { + tgam = gameBoard->get( i, j ); + tgra = r->get( i, j ); + if ((tgam == BALLBBT) && (tgra != TBALLBBG)) { + setScore( score+5 ); + r->set( i, j, WBALLBBG ); + gr->updateElement( i, j ); + } + if ((tgam != BALLBBT) && (tgra == TBALLBBG)) { + r->set( i, j, FBALLBBG ); + gr->updateElement( i, j ); + } + } + } +} + +/* + Aborts the current game. +*/ + +void KBBGame::abortGame() +{ + if (running) { + running = FALSE; + ballsPlaced = 0; + updateStats(); + gr->clearFocus(); + emit gameRuns( running ); + } +} + +/* + Gives the game up. +*/ + +void KBBGame::giveUp() +{ + if (running) { + bool stop; + stop = KMessageBox::warningContinueCancel(0, + i18n( + "Do you really want to give up this game?"),QString::null,i18n("Give Up")) + == KMessageBox::Continue; + + if (stop) { + getResults(); + abortGame(); + } + } +} + +/* + Displays game statistics. +*/ + +void KBBGame::updateStats() +{ + QString tmp; + QString s = i18n("Run: "); + if (running) + s += i18n("Yes"); + else + s += i18n("No"); + statusBar()->changeItem( s, SRUN ); + s = i18n( "Size: " ); + s += tmp.sprintf( "%2d x %2d", + gr->numC()-4, gr->numR()-4 ); + statusBar()->changeItem( s, SSIZE ); + s = i18n( "Placed: " ); + s += tmp.sprintf( "%2d / %2d", + ballsPlaced, balls ); + statusBar()->changeItem( s, SBALLS ); +} + +/* + Sets the score value to n. +*/ + +void KBBGame::setScore( int n ) +{ + score = n; + statusBar()->changeItem( i18n("Score: %1").arg(n), SSCORE ); +} + +/* + Sets the size of the black box. +*/ + +bool KBBGame::setSize( int w, int h ) +{ + bool ok = FALSE; + if (((w+4) != gr->numC()) || ((h+4) != gr->numR())) { + if (running) { + ok = KMessageBox::warningContinueCancel(0, + i18n( + "This will be the end of the current game!"),QString::null,i18n("End Game")) + == KMessageBox::Continue; + + } else ok = TRUE; + if (ok) { + gr->setSize( w+4, h+4 ); // +4 is the space for "lasers" and an edge... + setMinSize(); + gameResize(); + delete gameBoard; + gameBoard = new RectOnArray( gr->numC(), gr->numR() ); + if (running) abortGame(); + newGame(); + // gr->repaint( TRUE ); + } + } + return ok; +} + +/* + Sets the number of balls in the black box to n. +*/ + +bool KBBGame::setBalls( int n ) +{ + bool ok = FALSE; + if (balls != n) { + if (running) { + ok = KMessageBox::warningContinueCancel(0, + i18n("This will be the end of the current game!"),QString::null,i18n("End Game")) + == KMessageBox::Continue; + } else ok = TRUE; + if (ok) { + balls = n; + if (running) abortGame(); + newGame(); + } + } + return ok; +} + +/* + Puts n balls in the black box on random positions. +*/ + +void KBBGame::randomBalls( int n ) +{ + int i; + random.setSeed(0); + for (i = 0; i < n; i++) { + int x=0, y=0; // there is OUTERBBT... + while (gameBoard->get( x, y ) != INNERBBT ) { + x = 2 + random.getLong(gameBoard->width()-4); + y = 2 + random.getLong(gameBoard->height()-4); + } + gameBoard->set( x, y, BALLBBT ); + } +} + +/* + This is, in fact, the whole game... +*/ + +int KBBGame::traceRay( int startX, int startY, int *endX, int *endY ) +{ + int type, x, y, d, refl; + int slx, scx, srx, sly, scy, sry; + bool directionChanged; + *endX = x = startX; + *endY = y = startY; + /* + Just to avoid compiler warnings + */ + type = slx = scx = srx = sly = scy = sry = 0; + /* + Get the initial direction d. + 0 .. up, 1 .. right, 2 .. down, 3 .. left + (0,0) is the upper-left corner. + */ + if ((gameBoard->get( x, y-1 ) == INNERBBT) || + (gameBoard->get( x, y-1 ) == BALLBBT)) { d = 0; } + else if ((gameBoard->get( x+1, y ) == INNERBBT) || + (gameBoard->get( x+1, y ) == BALLBBT)) { d = 1; } + else if ((gameBoard->get( x, y+1 ) == INNERBBT) || + (gameBoard->get( x, y+1 ) == BALLBBT)) { d = 2; } + else if ((gameBoard->get( x-1, y ) == INNERBBT) || + (gameBoard->get( x-1, y ) == BALLBBT)) { d = 3; } + else return WRONGSTART; + /* + And now trace the ray. + */ + while (1) { + switch (d) { + case 0: + slx = -1; scx = 0; srx = 1; + sly = -1; scy = -1; sry = -1; + break; + case 1: + slx = 1; scx = 1; srx = 1; + sly = -1; scy = 0; sry = 1; + break; + case 2: + slx = 1; scx = 0; srx = -1; + sly = 1; scy = 1; sry = 1; + break; + case 3: + slx = -1; scx = -1; srx = -1; + sly = 1; scy = 0; sry = -1; + break; + } + directionChanged = FALSE; + if (gameBoard->get( x+scx, y+scy ) == LASERBBT) { + type = DETOUR; + *endX = x+scx; + *endY = y+scy; + break; + } + if (gameBoard->get( x+scx, y+scy ) == BALLBBT) { + type = HIT; + break; + } + refl = 0; + if (gameBoard->get( x+slx, y+sly ) == BALLBBT) { + type = REFLECTION; + if (gameBoard->get( x, y ) == LASERBBT) break; + directionChanged = TRUE; + refl += 1; + } + if (gameBoard->get( x+srx, y+sry ) == BALLBBT) { + type = REFLECTION; + if (gameBoard->get( x, y ) == LASERBBT) break; + directionChanged = TRUE; + refl +=2; + } + // turn to the right + if (refl == 1) d = (d + 1) % 4; + // turn to the left + if (refl == 2) if ((d -= 1) < 0) d += 4; + // turn back -- no need to trace again the same way + if (refl == 3) break; + if (!directionChanged) { + x += scx; + y += scy; + } + } + return type; +} + +/* + Remaps the gameBoard to its graphic representation. +*/ + +void KBBGame::remap( RectOnArray *gam, RectOnArray *gra ) +{ + int i, j; + for (j = 0; j < (gam->height()); j++) { + for (i = 0; i < (gam->width()); i++) { + switch (gam->get( i,j )) { + case BALLBBT: if (tutorial) { gra->set( i,j, WBALLBBG ); break; } + case INNERBBT: gra->set( i,j, INNERBBG ); break; + case OUTERBBT: gra->set( i,j, OUTERBBG ); break; + case LASERBBT: gra->set( i,j, LASERBBG ); break; + default: gra->set( i,j, OUTERBBG ); + } + } + } +} + +/* + Processes the user input. +*/ + +void KBBGame::gotInputAt( int col, int row, int state ) +{ + RectOnArray *r = gr->getGraphicBoard(); + int type = r->get( col, row ); + int x, y; + int ex, ey; + int w = gameBoard->width() - 2; + int h = gameBoard->height() - 2; + + if (state & LeftButton) { + switch (type) { + case WBALLBBG: // because of the tutorial mode + case INNERBBG: + r->set( col, row, TBALLBBG ); + ballsPlaced++; + break; + case MARK1BBG: + r->set( col, row, INNERBBG ); + break; + case TBALLBBG: + r->set( col, row, INNERBBG ); + ballsPlaced--; + break; + case LASERBBG: + int endX, endY, result; + result = traceRay( col, row, &endX, &endY ); + r->set( col, row, LFIREBBG ); + //kdDebug << endX << " " << endY << endl; + if (col == 1) x = 0; else + if (col == w) x = w + 1; + else x = col; + + if (row == 1) y = 0; else + if (row == h) y = h + 1; + else y = row; + + switch (result) { + case DETOUR: + r->set( endX, endY, LFIREBBG ); + r->set( x, y, detourCounter ); + if (endX == 1) ex = 0; else + if (endX == w) ex = w + 1; + else ex = endX; + if (endY == 1) ey = 0; else + if (endY == h) ey = h + 1; + else ey = endY; + r->set( ex, ey, detourCounter-- ); + gr->updateElement( x, y ); + gr->updateElement( ex, ey ); + gr->updateElement( endX, endY ); + setScore( score+2 ); + break; + case REFLECTION: + r->set( x, y, RLASERBBG ); + gr->updateElement( x, y ); + setScore( score+1 ); + break; + case HIT: + r->set( x, y, HLASERBBG ); + gr->updateElement( x, y ); + setScore( score+1 ); + break; + case WRONGSTART: + kdDebug(12009) << "Wrong start?! It should't happen!!" << endl; + break; + } + break; + } + } else if (state & RightButton) { + switch (type) { + case INNERBBG: + r->set( col, row, MARK1BBG ); + break; + /*case MARK1BBG: + r->set( col, row, INNERBBG ); + break;*/ + } + } + gr->updateElement( col, row ); + updateStats(); +} + +void KBBGame::initKAction() +{ +// game + KStdGameAction::gameNew(this, SLOT(newGame()), actionCollection()); + (void)new KAction( i18n("&Give Up"), SmallIcon("giveup"), 0, this, SLOT(giveUp()), actionCollection(), "game_giveup" ); + (void)new KAction( i18n("&Done"), SmallIcon("done"), 0, this, SLOT(gameFinished()), actionCollection(), "game_done" ); + (void)new KAction( i18n("&Resize"), 0, this, SLOT(slotResize()), actionCollection(), "game_resize" ); + KStdGameAction::quit(this, SLOT(close()), actionCollection()); + + +// settings + sizeAction = new KSelectAction( i18n("&Size"), 0, this, SLOT(slotSize()), actionCollection(), "options_size"); + QStringList list; + list.append(i18n(" 8 x 8 ")); + list.append(i18n(" 10 x 10 ")); + list.append(i18n(" 12 x 12 ")); + sizeAction->setItems(list); + + ballsAction = new KSelectAction( i18n("&Balls"), 0, this, SLOT(slotBalls()), actionCollection(), "options_balls"); + list.clear(); + list.append(i18n(" 4 ")); + list.append(i18n(" 6 ")); + list.append(i18n(" 8 ")); + ballsAction->setItems(list); + tutorialAction = new KToggleAction( i18n("&Tutorial"), 0, this, SLOT(tutorialSwitch()), actionCollection(), "options_tutorial" ); +// KStdAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), +//actionCollection()); + +// keyboard only + (void)new KAction( i18n("Move Down"), Qt::Key_Down, gr, SLOT(slotDown()), actionCollection(), "move_down" ); + (void)new KAction( i18n("Move Up"), Qt::Key_Up, gr, SLOT(slotUp()), actionCollection(), "move_up" ); + (void)new KAction( i18n("Move Left"), Qt::Key_Left, gr, SLOT(slotLeft()), actionCollection(), "move_left" ); + (void)new KAction( i18n("Move Right"), Qt::Key_Right, gr, SLOT(slotRight()), actionCollection(), "move_right" ); + (void)new KAction( i18n("Trigger Action"), Qt::Key_Return, gr, SLOT(slotInput()), actionCollection(), "move_trigger" ); +} + +void KBBGame::slotResize() +{ + setMinSize(); + gameResize(); +} + +#include "kbbgame.moc" |