// // KBlackbox // // A simple game inspired by an emacs module // // File: kbbgame.cpp // // The implementation of the KBBGame widget // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; TQPixmap **pix = new TQPixmap * [NROFTYPES]; pix[0] = new TQPixmap(); *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 TQPixmap; *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, TQT_SIGNAL(inputAt(int,int,int)), this, TQT_SLOT(gotInputAt(int,int,int)) ); connect( this, TQT_SIGNAL(gameRuns(bool)), gr, TQT_SLOT(setInputAccepted(bool)) ); connect( gr, TQT_SIGNAL(endMouseClicked()), this, TQT_SLOT(gameFinished()) ); /* TQToolTip::add( doneButton, i18n( "Click here when you think you placed all the balls.") ); */ /* Game initializations */ running = FALSE; gameBoard = NULL; TDEConfig *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() { TDEConfig *kConf; TQString 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?"),TQString(),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) { TQString 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(TDEGlobal::locale()->formatNumber(score, 0))); } else { s = i18n( "You should place %1 balls!\n" "You have placed %2.") .arg(TDEGlobal::locale()->formatNumber(balls, 0)) .arg(TDEGlobal::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?"),TQString(),i18n("Give Up")) == KMessageBox::Continue; if (stop) { getResults(); abortGame(); } } } /* Displays game statistics. */ void KBBGame::updateStats() { TQString tmp; TQString 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!"),TQString(),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!"),TQString(),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 & Qt::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 & Qt::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(TQT_TQOBJECT(this), TQT_SLOT(newGame()), actionCollection()); (void)new KAction( i18n("&Give Up"), SmallIcon("giveup"), 0, TQT_TQOBJECT(this), TQT_SLOT(giveUp()), actionCollection(), "game_giveup" ); (void)new KAction( i18n("&Done"), SmallIcon("done"), 0, TQT_TQOBJECT(this), TQT_SLOT(gameFinished()), actionCollection(), "game_done" ); (void)new KAction( i18n("&Resize"), 0, TQT_TQOBJECT(this), TQT_SLOT(slotResize()), actionCollection(), "game_resize" ); KStdGameAction::quit(TQT_TQOBJECT(this), TQT_SLOT(close()), actionCollection()); // settings sizeAction = new KSelectAction( i18n("&Size"), 0, TQT_TQOBJECT(this), TQT_SLOT(slotSize()), actionCollection(), "options_size"); TQStringList 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, TQT_TQOBJECT(this), TQT_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, TQT_TQOBJECT(this), TQT_SLOT(tutorialSwitch()), actionCollection(), "options_tutorial" ); // KStdAction::keyBindings(guiFactory(), TQT_SLOT(configureShortcuts()), //actionCollection()); // keyboard only (void)new KAction( i18n("Move Down"), TQt::Key_Down, TQT_TQOBJECT(gr), TQT_SLOT(slotDown()), actionCollection(), "move_down" ); (void)new KAction( i18n("Move Up"), TQt::Key_Up, TQT_TQOBJECT(gr), TQT_SLOT(slotUp()), actionCollection(), "move_up" ); (void)new KAction( i18n("Move Left"), TQt::Key_Left, TQT_TQOBJECT(gr), TQT_SLOT(slotLeft()), actionCollection(), "move_left" ); (void)new KAction( i18n("Move Right"), TQt::Key_Right, TQT_TQOBJECT(gr), TQT_SLOT(slotRight()), actionCollection(), "move_right" ); (void)new KAction( i18n("Trigger Action"), TQt::Key_Return, TQT_TQOBJECT(gr), TQT_SLOT(slotInput()), actionCollection(), "move_trigger" ); } void KBBGame::slotResize() { setMinSize(); gameResize(); } #include "kbbgame.moc"