You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegames/konquest/gameboard.cc

992 lines
27 KiB

#include <tqlayout.h>
#include <tqcolor.h>
#include <tqlabel.h>
#include <tqslider.h>
#include <tqevent.h>
#include <tqkeycode.h>
#include <tqlistbox.h>
#include <tqpushbutton.h>
#include <tqlineedit.h>
#include <tqvalidator.h>
#include <tqtextedit.h>
#include <tdeapplication.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdeglobal.h>
#include <kiconloader.h>
#include <ctype.h>
#include <math.h>
#include "gamecore.h"
#include "int_validator.h"
#include "newgamedlg.h"
#include "gameenddlg.h"
#include "scoredlg.h"
#include "fleetdlg.h"
#include "gameboard.h"
#include "gameboard.moc"
/*********************************************************************
Game Board
*********************************************************************/
GameBoard::GameBoard( TQWidget *parent )
: TQWidget( parent ), gameInProgress( false ), gameState( NONE )
{
TQColorGroup cg( white, black, green.light(), green.dark(), green, green.dark(75), green.dark() );
TQPalette palette( cg, cg, cg );
neutralPlayer = Player::createNeutralPlayer();
map = new Map;
planets.setAutoDelete(true);
players.setAutoDelete(true);
//********************************************************************
// Create the widgets in the main window
//********************************************************************
mapWidget = new ConquestMap( map, this );
msgWidget = new TQTextEdit( this );
msgWidget->setTextFormat(LogText);
msgWidget->setMinimumHeight(100);
msgWidget->setHScrollBarMode(TQScrollView::AlwaysOff);
msgWidget->setPaper(TQBrush(TQt::black));
planetInfo = new PlanetInfo( this, palette );
gameMessage = new TQLabel( this );
gameMessage->setPalette( palette );
turnCounter = new TQLabel( this );
turnCounter->setPalette( palette );
turnCounter->setText( "Turn" );
turnCounter->setMaximumHeight( turnCounter->sizeHint().height() );
endTurn = new TQPushButton( i18n("End Turn"), this );
endTurn->setFixedSize( endTurn->sizeHint() );
endTurn->setPalette( palette );
shipCountEdit = new TQLineEdit( this );
IntValidator *v = new IntValidator( 1, 32767, this );
shipCountEdit->setValidator( v );
shipCountEdit->setMinimumSize( 40, 0 );
shipCountEdit->setMaximumSize( 32767, 40 );
shipCountEdit->setEnabled(false);
shipCountEdit->setPalette( palette );
shipCountEdit->setEchoMode( TQLineEdit::Password );
splashScreen = new TQLabel( this );
splashScreen->setPixmap(TQPixmap(IMAGE_SPLASH));
splashScreen->setGeometry( 0, 0, 600, 550 );
setMinimumSize( 600, 600 );
setMouseTracking( true );
setFocusPolicy( TQ_StrongFocus );
setFocus();
//********************************************************************
// Layout the main window
//********************************************************************
TQHBoxLayout *layout1 = new TQHBoxLayout( this );
TQVBoxLayout *layout2 = new TQVBoxLayout;
TQHBoxLayout *layout3 = new TQHBoxLayout;
TQVBoxLayout *layout4 = new TQVBoxLayout;
layout1->addLayout( layout2 );
layout2->addLayout( layout3 );
layout3->addSpacing( 5 );
layout3->addWidget( gameMessage, 10 );
layout3->addWidget( shipCountEdit, 1 );
layout3->addWidget( endTurn, 1 );
layout2->addSpacing( 5 );
layout2->addWidget( mapWidget, 0, AlignTop );
layout2->addWidget( msgWidget );
layout2->addStretch( 1 );
layout1->addSpacing( 5 );
layout1->addLayout( layout4, 10 );
layout4->addWidget( planetInfo, 1 );
layout4->addSpacing( 10 );
layout4->addWidget( turnCounter, 1 );
layout4->addStretch( 1 );
layout1->addStretch( 1 );
//**********************************************************************
// Set up signal/slot connections
//**********************************************************************
connect( mapWidget, TQT_SIGNAL( planetSelected(Planet *) ), this, TQT_SLOT(planetSelected(Planet *)) );
connect( shipCountEdit, TQT_SIGNAL(returnPressed()), this, TQT_SLOT(newShipCount()) );
connect( endTurn, TQT_SIGNAL( clicked() ), this, TQT_SLOT( nextPlayer() ) );
connect( mapWidget, TQT_SIGNAL( planetHighlighted(Planet *)), planetInfo, TQT_SLOT(showPlanet(Planet *)) );
changeGameBoard( false );
}
//**********************************************************************
// Destructor
//**********************************************************************
GameBoard::~GameBoard()
{
// Nothing much to do yet
}
#if 0
TQSize GameBoard::sizeHint() const
{
return TQSize( 600, 550 );
}
#endif
//************************************************************************
// Keyboard Event handlers
//************************************************************************
void
GameBoard::keyPressEvent( TQKeyEvent *e )
{
// Check for the escape key
if( e->key() == Key_Escape ) {
switch( gameState ) {
case DEST_PLANET:
case SHIP_COUNT:
case RULER_SOURCE:
case RULER_DEST:
gameState = SOURCE_PLANET;
haveSourcePlanet = false;
haveDestPlanet = false;
turn();
break;
default:
break;
}
return;
}
if( !isgraph( e->ascii() ) ) {
e->ignore();
return;
}
PlanetListIterator planetSearch( planets );
TQString planetName;
planetName += toupper( e->ascii() );
for(Planet *p = planetSearch.toFirst();
p != NULL;
p = ++planetSearch ) {
if( p->getName() == planetName )
planetSelected( p );
}
}
TQString
GameBoard::playerString(Player *player)
{
if (!player)
player = currentPlayer->current();
return player->getColoredName();
}
//************************************************************************
// Game engine/state machine
//************************************************************************
void
GameBoard::turn()
{
PlanetListIterator planetAi( planets );
PlanetListIterator planetAttack( planets );
Planet *target = 0;
switch( gameState ) {
case NONE :
// stuff for none
gameState = SOURCE_PLANET;
haveSourcePlanet = false;
haveDestPlanet = false;
haveShipCount = false;
shipCount = 0;
mapWidget->unselectPlanet();
turn();
setFocus();
break;
case SOURCE_PLANET :
if( haveSourcePlanet ) {
gameState = DEST_PLANET;
sourcePlanet->select();
turn();
} else {
shipCountEdit->hide();
endTurn->setEnabled( true );
mapWidget->unselectPlanet();
gameMessage->setText( "<qt>" + playerString() + ": " +
i18n("Select source planet...") + "</qt>" );
setFocus();
}
break;
case DEST_PLANET :
if( haveDestPlanet ) {
mapWidget->unselectPlanet();
gameState = SHIP_COUNT;
turn();
} else {
shipCountEdit->hide();
endTurn->setEnabled( false );
sourcePlanet->select();
gameMessage->setText( "<qt>" + playerString() + ": " +
i18n("Select destination planet...") + "</qt>" );
setFocus();
}
break;
case SHIP_COUNT:
if( haveShipCount ) {
// We now have a complete fleet to send, so send it
sendAttackFleet( sourcePlanet, destPlanet, shipCount);
shipCountEdit->hide();
endTurn->setEnabled( true );
gameState = NONE;
turn();
endTurn->setFocus();
} else {
gameMessage->setText( currentPlayer->current()->getName() +
i18n(": How many ships?") );
shipCountEdit->setText( "" );
shipCountEdit->show();
shipCountEdit->setEnabled(true);
shipCountEdit->setFocus();
endTurn->setEnabled( false );
mapWidget->unselectPlanet();
}
break;
case RULER_SOURCE:
if( haveSourcePlanet ) {
gameState = RULER_DEST;
sourcePlanet->select();
turn();
} else {
shipCountEdit->hide();
endTurn->setEnabled( true );
mapWidget->unselectPlanet();
gameMessage->setText( i18n("Ruler: Select starting planet.") );
setFocus();
}
break;
case RULER_DEST:
if( haveDestPlanet ) {
mapWidget->unselectPlanet();
// Display the distance between the two planets
CoreLogic cl;
double dist = cl.distance( sourcePlanet, destPlanet );
TQString msg;
msg = i18n("The distance from Planet %1 to Planet %2 is %3 light years.\n"
"A ship leaving this turn will arrive on turn %4")
.arg(sourcePlanet->getName())
.arg(destPlanet->getName())
.arg(TDEGlobal::locale()->formatNumber( dist, 2 ))
.arg(TDEGlobal::locale()->formatNumber( turnNumber + (int)dist, 0 ));
KMessageBox::information( this, msg, i18n("Distance"));
gameState = NONE;
turn();
} else {
gameMessage->setText( i18n("Ruler: Select ending planet.") );
shipCountEdit->hide();
endTurn->setEnabled( false );
sourcePlanet->select();
setFocus();
}
break;
case AI_PLAYER:
endTurn->setEnabled( false );
gameMessage->setText( i18n("Computer Player thinking...") );
Planet *home;
int ships;
planetAi.toFirst();
while ((home = planetAi())) {
if (home->getPlayer() == currentPlayer->current()) {
bool hasAttack = false;
ships = (int)floor(home->getFleet().getShipCount() * 0.7 );
if (ships >= 20) {
Planet *attack;
double minDistance = 100;
planetAttack.toFirst();
while ((attack = planetAttack())) {
bool skip = false;
CoreLogic cl;
double dist = cl.distance( home, attack );
if ((dist < minDistance) && (attack->getPlayer() != currentPlayer->current()) &&
(attack->getFleet().getShipCount() < ships )) {
AttackFleetListIterator FleetsinFlight( currentPlayer->current()->getAttackList() );
AttackFleet *curFleet;
while ( (curFleet = FleetsinFlight())) {
if (curFleet->destination == attack) {
skip = true;
}
}
if (skip) continue;
target = attack;
hasAttack = true;
minDistance = dist;
}
}
if (hasAttack) {
sendAttackFleet( home, target, ships );
}
else {
planetAttack.toFirst();
minDistance = 100;
int shipsToSend = 0;
bool hasDestination = false;
while ((attack = planetAttack())) {
bool skip = false;
CoreLogic cl;
double dist = cl.distance( home, attack );
int homeships = (int)floor(home->getFleet().getShipCount() * 0.5 );
if ((dist < minDistance) && (attack->getPlayer() == currentPlayer->current()) &&
(attack->getFleet().getShipCount() < homeships )) {
AttackFleetListIterator FleetsinFlight( currentPlayer->current()->getAttackList() );
AttackFleet *curFleet;
while ( (curFleet = FleetsinFlight())) {
if (curFleet->destination == attack) {
skip = true;
}
}
if (skip) continue;
shipsToSend = (int)floor((home->getFleet().getShipCount() - attack->getFleet().getShipCount()) / 2) ;
target = attack;
hasDestination = true;
minDistance = dist;
}
}
if (hasDestination) {
sendAttackFleet( home, target, shipsToSend );
}
}
}
}
}
endTurn->setEnabled( true );
nextPlayer();
break;
default:
break;
}
TQString turnStr;
turnStr = i18n("Turn #: %1 of %2").arg(turnNumber).arg(lastTurn);
turnCounter->setText( turnStr );
emit newGameState( gameState );
}
//************************************************************************
// To the end turn processing (resolve combat, etc.)
//************************************************************************
void
GameBoard::nextTurn()
{
resolveShipsInFlight();
scanForSurvivors();
// advance to first living player
while( currentPlayer->current() && !currentPlayer->current()->isInPlay() ) {
++(*currentPlayer);
};
// advance turn counter
turnNumber++;
// update the planets
PlanetListIterator nextPlanet( planets );
Planet *planet;
while( (planet = nextPlanet()) )
{
planet->turn();
}
// Tell the status widget to update itself
planetInfo->rescanPlanets();
Player *winner = findWinner();
if (winner)
{
mapWidget->repaint(true);
KMessageBox::information(this,
i18n("The mighty %1 has conquered the galaxy!").arg(winner->getName()),
i18n("Game Over"));
}
if( (turnNumber == lastTurn) && !winner )
{
mapWidget->repaint(true);
GameEndDlg *dlg = new GameEndDlg( this );
if( dlg->exec() == KDialogBase::Yes ) {
lastTurn += dlg->extraTurns();
}
delete dlg;
}
if( winner || (turnNumber >= lastTurn) )
{
// Game over, man! Game over.
mapWidget->repaint(true);
gameOver();
};
}
//************************************************************************
// determine the fate of the ships in transit
//************************************************************************
void
GameBoard::resolveShipsInFlight()
{
AttackFleetList arrivingShips;
PlayerListIterator nextPlayer( players );
Player *plr;
while( (plr = nextPlayer()) ) {
AttackFleetListIterator nextFleet( plr->getAttackList() );
AttackFleet *fleet;
while( (fleet = nextFleet()) ) {
double fleetArrivalTurn = floor(fleet->arrivalTurn);
if( turnNumber == int (fleetArrivalTurn) ) {
doFleetArrival( fleet );
plr->getAttackList().removeRef( fleet );
delete fleet;
}
}
}
}
Player *
GameBoard::findWinner()
{
Player *winner = 0;
int activePlayers = 0;
PlayerListIterator nextPlayer( players );
Player *plr;
while( (plr = nextPlayer()) ) {
if (plr->isInPlay())
{
winner = plr;
activePlayers++;
}
else if (plr->getAttackList().count() != 0)
{
activePlayers++;
}
}
if (activePlayers == 1)
return winner;
return 0;
}
void
GameBoard::gameMsg(const TQString &msg, Player *player, Planet *planet, Player *planetPlayer)
{
bool isHumanInvolved = false;
TQString color = "white";
TQString colorMsg = msg;
TQString plainMsg = msg;
if (player)
{
if (!player->isAiPlayer())
isHumanInvolved = true;
colorMsg = colorMsg.arg(playerString(player));
plainMsg = plainMsg.arg(player->getName());
}
if (planet)
{
if (!planetPlayer)
planetPlayer = planet->getPlayer();
if (!planetPlayer->isAiPlayer() && !planetPlayer->isNeutral())
isHumanInvolved = true;
TQString color = planetPlayer->getColor().name();
colorMsg = colorMsg.arg(TQString("<font color=\"%1\">%2</font>").arg(color, planet->getName()));
plainMsg = plainMsg.arg(planet->getName());
}
msgWidget->append(("<qt><font color=\"white\">Turn %1:</font> <font color=\""+color+"\">").arg(turnNumber)+colorMsg+"</font></qt>");
msgWidget->scrollToBottom();
if (isHumanInvolved)
{
mapWidget->repaint(true);
KMessageBox::information(this, plainMsg);
}
}
//************************************************************************
// check to see any players have been eliminated
//************************************************************************
void
GameBoard::scanForSurvivors()
{
PlayerListIterator nextPlayer( players );
PlayerList activePlayers;
PlayerList inactivePlayers;
// insert all of the active players into a special
// list, the deactivate them
Player *plr;
while( (plr = nextPlayer()) ) {
if( plr->isInPlay() ) {
activePlayers.append( plr );
plr->setInPlay( false );
} else {
inactivePlayers.append( plr );
}
}
// iterate through the list of planets and
// mark their owners in play
PlanetListIterator nextPlanet( planets );
Planet *planet;
while( (planet = nextPlanet()) ) {
planet->getPlayer()->setInPlay( true );
}
PlayerListIterator nextActivePlayer( activePlayers );
while( (plr = nextActivePlayer()) ) {
if( !plr->isInPlay() ) {
// Player has bitten the dust
TQString msg;
msg = i18n("The once mighty empire of %1 has fallen in ruins.");
gameMsg(msg, plr);
}
}
PlayerListIterator nextInactivePlayer( inactivePlayers );
while( (plr = nextInactivePlayer()) ) {
if( plr->isInPlay() ) {
// Player has bitten the dust
TQString msg;
msg = i18n("The fallen empire of %1 has staggered back to life.");
gameMsg(msg, plr);
}
}
}
//************************************************************************
// handle the arrival of a fleet at a planet
//************************************************************************
void
GameBoard::doFleetArrival( AttackFleet *arrivingFleet )
{
// Check to see of (fleet owner) == (planet owner)
// if the planet and fleet owner are the same, then merge the fleets
// otherwise attack.
if( (*arrivingFleet->owner) == (*arrivingFleet->destination->getPlayer())) {
if (!arrivingFleet->owner->isAiPlayer()) {
arrivingFleet->destination->getFleet().absorb(arrivingFleet);
TQString msg;
msg = i18n("Reinforcements (%1 ships) have arrived for planet %2.")
.arg(arrivingFleet->getShipCount());
gameMsg(msg, 0, arrivingFleet->destination);
}
} else {
// let's get ready to rumble...
CoreLogic cl;
AttackFleet &attacker = *arrivingFleet;
DefenseFleet &defender = arrivingFleet->destination->getFleet();
Planet &prizePlanet = *(arrivingFleet->destination);
bool haveVictor = false;
bool planetHolds = true;
while( !haveVictor ) {
double attackerRoll = cl.roll();
double defenderRoll = cl.roll();
if( defenderRoll < prizePlanet.getKillPercentage() ) {
attacker.removeShips( 1 );
}
if( attacker.getShipCount() <= 0 ) {
haveVictor = true;
planetHolds = true;
continue;
}
if( attackerRoll < attacker.killPercentage ) {
defender.removeShips( 1 );
attacker.owner->statEnemyShipsDestroyed( 1 );
}
if( defender.getShipCount() <= 0 ) {
haveVictor = true;
planetHolds = false;
}
}
if( planetHolds ) {
prizePlanet.getPlayer()->statEnemyFleetsDestroyed(1);
TQString msg;
msg = i18n("Planet %2 has held against an attack from %1.");
gameMsg(msg, attacker.owner, &prizePlanet);
} else {
Player *defender = prizePlanet.getPlayer();
attacker.owner->statEnemyFleetsDestroyed( 1 );
arrivingFleet->destination->conquer( arrivingFleet );
TQString msg;
msg = i18n("Planet %2 has fallen to %1.");
gameMsg(msg, attacker.owner, &prizePlanet, defender);
}
}
mapWidget->repaint(true);
}
//************************************************************************
// Set up the game board for a new game
//************************************************************************
void
GameBoard::startNewGame()
{
shutdownGame();
if( gameInProgress )
return;
NewGameDlg *newGame = new NewGameDlg( this, map, &players, neutralPlayer, &planets );
if( !newGame->exec() )
{
delete newGame;
return;
}
newGame->save(); // Save settings for next time
msgWidget->clear();
changeGameBoard( true );
planetInfo->setPlanetList(planets);
shipCountEdit->hide();
endTurn->setEnabled( true );
currentPlayer = new PlayerListIterator( players );
currentPlayer->toFirst();
endTurn->show();
gameMessage->show();
lastTurn = newGame->turns();
turnNumber = 1;
turn();
delete newGame;
}
//************************************************************************
// Shut down the current game
//************************************************************************
void
GameBoard::shutdownGame()
{
if( !gameInProgress )
return;
int choice = KMessageBox::warningContinueCancel
( this,
i18n("Do you wish to retire this game?"),
i18n("End Game"),
KStdGuiItem::ok() );
if( choice == KMessageBox::Cancel )
return;
gameOver();
}
void
GameBoard::gameOver()
{
ScoreDlg *scoreDlg = new ScoreDlg( this, i18n("Final Standings"), &players );
scoreDlg->exec();
cleanupGame();
}
void
GameBoard::cleanupGame()
{
map->clearMap();
planets.clear();
players.clear();
delete currentPlayer;
currentPlayer = NULL;
shipCountEdit->hide();
endTurn->setEnabled( false );
gameMessage->hide();
endTurn->hide();
changeGameBoard( false );
gameState = NONE;
emit newGameState(gameState);
}
//************************************************************************
// Player selected a planet
//************************************************************************
void
GameBoard::planetSelected( Planet *planet )
{
switch( gameState ) {
case SOURCE_PLANET:
if( (*planet->getPlayer()) == (*currentPlayer->current()) ) {
// got a match
haveSourcePlanet = true;
sourcePlanet = planet;
turn();
}
break;
case RULER_SOURCE:
haveSourcePlanet = true;
sourcePlanet = planet;
turn();
break;
case DEST_PLANET:
case RULER_DEST:
if( planet != sourcePlanet ) {
// got a match
haveDestPlanet = true;
destPlanet = planet;
turn();
}
break;
default:
case NONE :
break;
}
}
//************************************************************************
// Player hit return in the ship count edit box
//************************************************************************
void
GameBoard::newShipCount()
{
TQString temp( shipCountEdit->text() );
bool ok;
switch( gameState ) {
case SHIP_COUNT:
shipCount = temp.toInt(&ok);
if( ok )
haveShipCount = true;
shipCountEdit->setText( "" );
turn();
break;
default:
break;
};
}
//**********************************************************************
// transition board from play to non-play
//**********************************************************************
void
GameBoard::changeGameBoard( bool inPlay )
{
gameInProgress = inPlay;
if( gameInProgress ) {
mapWidget->show();
planetInfo->show();
gameMessage->show();
endTurn->show();
shipCountEdit->show();
splashScreen->hide();
setBackgroundColor( black );
} else {
mapWidget->hide();
planetInfo->hide();
gameMessage->hide();
endTurn->hide();
shipCountEdit->hide();
splashScreen->show();
setBackgroundColor( black );
}
}
//************************************************************************
// Player clicked the 'End Turn' button
//************************************************************************
void
GameBoard::nextPlayer()
{
// end turn and advance to next player
Player *plr;
while( (plr = ++(*currentPlayer)) && !(plr->isInPlay()) ) {}
if( !plr ) {
// end of player list, new turn
currentPlayer->toFirst();
nextTurn();
}
if( gameInProgress ) {
if (currentPlayer->current()->isAiPlayer()) {
gameState = AI_PLAYER;
}
else {
gameState = SOURCE_PLANET;
}
turn();
}
}
//************************************************************************
// A complete set of source, destination planets and ship count has been
// entered, so do something about it
//************************************************************************
void
GameBoard::sendAttackFleet( Planet *source, Planet *dest, int ship )
{
bool ok;
ok = currentPlayer->current()->NewAttack( source, dest,
ship, turnNumber );
if( !ok ) {
KMessageBox::error( this,
i18n("Not enough ships to send.") );
}
}
//************************************************************************
// Toolbar items
//************************************************************************
void
GameBoard::measureDistance()
{
switch( gameState ) {
case SOURCE_PLANET:
gameState = RULER_SOURCE;
turn();
default:
break;
}
}
void
GameBoard::showScores()
{
ScoreDlg *scoreDlg = new ScoreDlg( this, i18n("Current Standings"), &players );
scoreDlg->show();
}
void
GameBoard::showFleets()
{
FleetDlg *fleetDlg = new FleetDlg( this, &(currentPlayer->current()->getAttackList()) );
fleetDlg->show();
}