diff --git a/Engine.cpp b/Engine.cpp index fe0f8f8..1b24218 100644 --- a/Engine.cpp +++ b/Engine.cpp @@ -1,807 +1,806 @@ /******************************************************************* * * Copyright 1997 Mario Weilguni * * This file is part of the KDE project "KREVERSI" * * KREVERSI is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KREVERSI is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KREVERSI; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ******************************************************************* * * * KREVERSI * * ******************************************************************* * * A Reversi (or sometimes called Othello) game * ******************************************************************* * * Created 1997 by Mario Weilguni . This file * is ported from Mats Luthman's JAVA applet. * Many thanks to Mr. Luthman who has allowed me to put this port * under the GNU GPL. Without his wonderful game engine kreversi * would be just another of those Reversi programs a five year old * child could beat easily. But with it it's a worthy opponent! * * If you are interested on the JAVA applet of Mr. Luthman take a * look at http://www.sylog.se/~mats/ */ // The class Engine produces moves from a Game object through calls to the // function ComputeMove(). // // First of all: this is meant to be a simple example of a game playing // program. Not everything is done in the most clever way, particularly not // the way the moves are searched, but it is hopefully made in a way that makes // it easy to understand. The function ComputeMove2() that does all the work // is actually not much more than a hundred lines. Much could be done to // make the search faster though, I'm perfectly aware of that. Feel free // to experiment. // // The method used to generate the moves is called minimax tree search with // alpha-beta pruning to a fixed depth. In short this means that all possible // moves a predefined number of moves ahead are either searched or refuted // with a method called alpha-beta pruning. A more thorough explanation of // this method could be found at the world wide web at http: // //yoda.cis.temple.edu:8080/UGAIWWW/lectures96/search/minimax/alpha-beta.html // at the time this was written. Searching for "minimax" would also point // you to information on this subject. It is probably possible to understand // this method by reading the source code though, it is not that complicated. // // At every leaf node at the search tree, the resulting position is evaluated. // Two things are considered when evaluating a position: the number of pieces // of each color and at which squares the pieces are located. Pieces at the // corners are valuable and give a high value, and having pieces at squares // next to a corner is not very good and they give a lower value. In the // beginning of a game it is more important to have pieces on "good" squares, // but towards the end the total number of pieces of each color is given a // higher weight. Other things, like how many legal moves that can be made in a // position, and the number of pieces that can never be turned would probably // make the program stronger if they were considered in evaluating a position, // but that would make things more complicated (this was meant to be very // simple example) and would also slow down computation (considerably?). // // The member m_board[10][10]) holds the current position during the // computation. It is initiated at the start of ComputeMove() and // every move that is made during the search is made on this board. It should // be noted that 1 to 8 is used for the actual board, but 0 and 9 can be // used too (they are always empty). This is practical when turning pieces // when moves are made on the board. Every piece that is put on the board // or turned is saved in the stack m_squarestack (see class SquareStack) so // every move can easily be reversed after the search in a node is completed. // // The member m_bc_board[][] holds board control values for each square // and is initiated by a call to the function private void SetupBcBoard() // from Engines constructor. It is used in evaluation of positions except // when the game tree is searched all the way to the end of the game. // // The two members m_coord_bit[9][9] and m_neighbor_bits[9][9] are used to // speed up the tree search. This goes against the principle of keeping things // simple, but to understand the program you do not need to understand them // at all. They are there to make it possible to throw away moves where // the piece that is played is not adjacent to a piece of opposite color // at an early stage (because they could never be legal). It should be // pointed out that not all moves that pass this test are legal, there will // just be fewer moves that have to be tested in a more time consuming way. // // There are also two other members that should be mentioned: Score m_score // and Score m_bc_score. They hold the number of pieces of each color and // the sum of the board control values for each color during the search // (this is faster than counting at every leaf node). // // The classes SquareStackEntry and SquareStack implement a // stack that is used by Engine to store pieces that are turned during // searching (see ComputeMove()). // // The class MoveAndValue is used by Engine to store all possible moves // at the first level and the values that were calculated for them. // This makes it possible to select a random move among those with equal // or nearly equal value after the search is completed. #include "Engine.h" #include -#include // ================================================================ // Classes SquareStackEntry and SquareStack // A SquareStack is used to store changes to the squares on the board // during search. inline void SquareStackEntry::setXY(int x, int y) { m_x = x; m_y = y; } SquareStackEntry::SquareStackEntry() { setXY(0, 0); } // ---------------------------------------------------------------- SquareStack::SquareStack() { init(0); } SquareStack::SquareStack(int size) { init(size); } void SquareStack::resize(int size) { m_squarestack.resize(size); } // (Re)initialize the stack so that is empty, and at the same time // resize it to 'size'. // void SquareStack::init(int size) { resize(size); m_top = 0; for (int i = 0; i < size; i++) m_squarestack[i].setXY(0, 0); } inline SquareStackEntry SquareStack::Pop() { return m_squarestack[--m_top]; } inline void SquareStack::Push(int x, int y) { m_squarestack[m_top].m_x = x; m_squarestack[m_top++].m_y = y; } // ================================================================ // Class MoveAndValue // Class MoveAndValue aggregates a move with its value. // inline void MoveAndValue::setXYV(int x, int y, int value) { m_x = x; m_y = y; m_value = value; } MoveAndValue::MoveAndValue() { setXYV(0, 0, 0); } MoveAndValue::MoveAndValue(int x, int y, int value) { setXYV(x, y, value); } // ================================================================ // class Score /* This class keeps track of the score for both colors. Such a score * could be either the number of pieces, the score from the evaluation * function or anything similar. */ class Score { public: Score() { m_score[White] = 0; m_score[Black] = 0; } uint score(ChipColor color) const { return m_score[color]; } void set(ChipColor color, uint score) { m_score[color] = score; } void inc(ChipColor color) { m_score[color]++; } void dec(ChipColor color) { m_score[color]--; } void add(ChipColor color, uint s) { m_score[color] += s; } void sub(ChipColor color, uint s) { m_score[color] -= s; } private: uint m_score[2]; }; // ================================================================ // The Engine itself // Some special values used in the search. static const int LARGEINT = 99999; static const int ILLEGAL_VALUE = 8888888; static const int BC_WEIGHT = 3; Engine::Engine(int st, int sd)/* : SuperEngine(st, sd) */ : m_strength(st), m_computingMove(false) { m_random.setSeed(sd); m_score = new Score; m_bc_score = new Score; SetupBcBoard(); SetupBits(); } Engine::Engine(int st) //: SuperEngine(st) : m_strength(st), m_computingMove(false) { m_random.setSeed(0); m_score = new Score; m_bc_score = new Score; SetupBcBoard(); SetupBits(); } Engine::Engine()// : SuperEngine(1) : m_strength(1), m_computingMove(false) { m_random.setSeed(0); m_score = new Score; m_bc_score = new Score; SetupBcBoard(); SetupBits(); } Engine::~Engine() { delete m_score; delete m_bc_score; } // keep GUI alive void Engine::yield() { qApp->processEvents(); } // Calculate the best move from the current position, and return it. KReversiMove Engine::computeMove(const KReversiGame& game, bool competitive) { if (m_computingMove) return KReversiMove(); m_computingMove = true; ChipColor color; // A competitive game is one where we try our damnedest to make the // best move. The opposite is a casual game where the engine might // make "a mistake". The idea behind this is not to scare away // newbies. The member m_competitive is used during search for this // very move. m_competitive = competitive; // Suppose that we should give a heuristic evaluation. If we are // close to the end of the game we can make an exhaustive search, // but that case is determined further down. m_exhaustive = false; // Get the color to calculate the move for. color = game.currentPlayer(); if (color == NoColor) { m_computingMove = false; return KReversiMove(); } // Figure out the current score m_score->set(White, game.playerScore(White)); m_score->set(Black, game.playerScore(Black)); // Treat the first move as a special case (we can basically just // pick a move at random). if (m_score->score(White) + m_score->score(Black) == 4) { m_computingMove = false; return ComputeFirstMove(game); } // Let there be room for 3000 changes during the recursive search. // This is more than will ever be needed. m_squarestack.init(3000); // Get the search depth. If we are close to the end of the game, // the number of possible moves goes down, so we can search deeper // without using more time. m_depth = m_strength; if (m_score->score(White) + m_score->score(Black) + m_depth + 3 >= 64) m_depth = 64 - m_score->score(White) - m_score->score(Black); else if (m_score->score(White) + m_score->score(Black) + m_depth + 4 >= 64) m_depth += 2; else if (m_score->score(White) + m_score->score(Black) + m_depth + 5 >= 64) m_depth++; // If we are very close to the end, we can even make the search // exhaustive. if (m_score->score(White) + m_score->score(Black) + m_depth >= 64) m_exhaustive = true; // The evaluation is a linear combination of the score (number of // pieces) and the sum of the scores for the squares (given by // m_bc_score). The earlier in the game, the more we use the square // values and the later in the game the more we use the number of // pieces. m_coeff = 100 - (100 * (m_score->score(White) + m_score->score(Black) + m_depth - 4)) / 60; // Initialize the board that we use for the search. for (uint x = 0; x < 10; x++) for (uint y = 0; y < 10; y++) { if (1 <= x && x <= 8 && 1 <= y && y <= 8) m_board[x][y] = game.chipColorAt(KReversiPos(y - 1, x - 1)); else m_board[x][y] = NoColor; } // Initialize a lot of stuff that we will use in the search. // Initialize m_bc_score to the current bc score. This is kept // up-to-date incrementally so that way we won't have to calculate // it from scratch for each evaluation. m_bc_score->set(White, CalcBcScore(White)); m_bc_score->set(Black, CalcBcScore(Black)); quint64 colorbits = ComputeOccupiedBits(color); quint64 opponentbits = ComputeOccupiedBits(Utils::opponentColorFor(color)); int maxval = -LARGEINT; int max_x = 0; int max_y = 0; MoveAndValue moves[60]; int number_of_moves = 0; int number_of_maxval = 0; setInterrupt(false); quint64 null_bits; null_bits = 0; // The main search loop. Step through all possible moves and keep // track of the most valuable one. This move is stored in // (max_x, max_y) and the value is stored in maxval. m_nodes_searched = 0; for (int x = 1; x < 9; x++) { for (int y = 1; y < 9; y++) { // Don't bother with non-empty squares and squares that aren't // neighbors to opponent pieces. if (m_board[x][y] != NoColor || (m_neighbor_bits[x][y] & opponentbits) == null_bits) continue; int val = ComputeMove2(x, y, color, 1, maxval, colorbits, opponentbits); if (val != ILLEGAL_VALUE) { moves[number_of_moves++].setXYV(x, y, val); // If the move is better than all previous moves, then record // this fact... if (val > maxval) { // ...except that we want to make the computer miss some // good moves so that beginners can play against the program // and not always lose. However, we only do this if the // user wants a casual game, which is set in the settings // dialog. int randi = m_random.getLong(7); if (maxval == -LARGEINT || m_competitive || randi < (int) m_strength) { maxval = val; max_x = x; max_y = y; number_of_maxval = 1; } } else if (val == maxval) number_of_maxval++; } // Jump out prematurely if interrupt is set. if (interrupted()) break; } } // long endtime = times(&tmsdummy); // If there are more than one best move, the pick one randomly. if (number_of_maxval > 1) { int r = m_random.getLong(number_of_maxval) + 1; int i; for (i = 0; i < number_of_moves; ++i) { if (moves[i].m_value == maxval && --r <= 0) break; } max_x = moves[i].m_x; max_y = moves[i].m_y; } m_computingMove = false; // Return a suitable move. if (interrupted()) return KReversiMove(NoColor, -1, -1); else if (maxval != -LARGEINT) return KReversiMove(color, max_y - 1, max_x - 1); else return KReversiMove(NoColor, -1, -1); } // Get the first move. We can pick any move at random. // KReversiMove Engine::ComputeFirstMove(const KReversiGame& game) { int r; ChipColor color = game.currentPlayer(); r = m_random.getLong(4) + 1; if (color == White) { if (r == 1) return KReversiMove(color, 4, 2); else if (r == 2) return KReversiMove(color, 5, 3); else if (r == 3) return KReversiMove(color, 2, 4); else return KReversiMove(color, 3, 5); } else { if (r == 1) return KReversiMove(color, 3, 2); else if (r == 2) return KReversiMove(color, 5, 4); else if (r == 3) return KReversiMove(color, 2, 3); else return KReversiMove(color, 4, 5); } } // Play a move at (xplay, yplay) and generate a value for it. If we // are at the maximum search depth, we get the value by calling // EvaluatePosition(), otherwise we get it by performing an alphabeta // search. // int Engine::ComputeMove2(int xplay, int yplay, ChipColor color, int level, int cutoffval, quint64 colorbits, quint64 opponentbits) { int number_of_turned = 0; SquareStackEntry mse; ChipColor opponent = Utils::opponentColorFor(color); m_nodes_searched++; // Put the piece on the board and incrementally update scores and bitmaps. m_board[xplay][yplay] = color; colorbits |= m_coord_bit[xplay][yplay]; m_score->inc(color); m_bc_score->add(color, m_bc_board[xplay][yplay]); // Loop through all 8 directions and turn the pieces that can be turned. for (int xinc = -1; xinc <= 1; xinc++) for (int yinc = -1; yinc <= 1; yinc++) { if (xinc == 0 && yinc == 0) continue; int x, y; for (x = xplay + xinc, y = yplay + yinc; m_board[x][y] == opponent; x += xinc, y += yinc) ; // If we found the end of a turnable row, then go back and turn // all pieces on the way back. Also push the squares with // turned pieces on the squarestack so that we can undo the move // later. if (m_board[x][y] == color) for (x -= xinc, y -= yinc; x != xplay || y != yplay; x -= xinc, y -= yinc) { m_board[x][y] = color; colorbits |= m_coord_bit[x][y]; opponentbits &= ~m_coord_bit[x][y]; m_squarestack.Push(x, y); m_bc_score->add(color, m_bc_board[x][y]); m_bc_score->sub(opponent, m_bc_board[x][y]); number_of_turned++; } } int retval = -LARGEINT; // If we managed to turn at least one piece, then (xplay, yplay) was // a legal move. Now find out the value of the move. if (number_of_turned > 0) { // First adjust the number of pieces for each side. m_score->add(color, number_of_turned); m_score->sub(opponent, number_of_turned); // If we are at the bottom of the search, get the evaluation. if (level >= m_depth) retval = EvaluatePosition(color); // Terminal node else { int maxval = TryAllMoves(opponent, level, cutoffval, opponentbits, colorbits); if (maxval != -LARGEINT) retval = -maxval; else { // No possible move for the opponent, it is colors turn again: retval = TryAllMoves(color, level, -LARGEINT, colorbits, opponentbits); if (retval == -LARGEINT) { // No possible move for anybody => end of game: int finalscore = m_score->score(color) - m_score->score(opponent); if (m_exhaustive) retval = finalscore; else { // Take a sure win and avoid a sure loss (may not be optimal): if (finalscore > 0) retval = LARGEINT - 65 + finalscore; else if (finalscore < 0) retval = -(LARGEINT - 65 + finalscore); else retval = 0; } } } } m_score->add(opponent, number_of_turned); m_score->sub(color, number_of_turned); } // Undo the move. Start by unturning the turned pieces. for (int i = number_of_turned; i > 0; i--) { mse = m_squarestack.Pop(); m_bc_score->add(opponent, m_bc_board[mse.m_x][mse.m_y]); m_bc_score->sub(color, m_bc_board[mse.m_x][mse.m_y]); m_board[mse.m_x][mse.m_y] = opponent; } // Now remove the new piece that we put down. m_board[xplay][yplay] = NoColor; m_score->sub(color, 1); m_bc_score->sub(color, m_bc_board[xplay][yplay]); // Return a suitable value. if (number_of_turned < 1 || interrupted()) return ILLEGAL_VALUE; else return retval; } // Generate all legal moves from the current position, and do a search // to see the value of them. This function returns the value of the // most valuable move, but not the move itself. // int Engine::TryAllMoves(ChipColor opponent, int level, int cutoffval, quint64 opponentbits, quint64 colorbits) { int maxval = -LARGEINT; // Keep GUI alive by calling the event loop. yield(); quint64 null_bits; null_bits = 0; for (int x = 1; x < 9; x++) { for (int y = 1; y < 9; y++) { if (m_board[x][y] == NoColor && (m_neighbor_bits[x][y] & colorbits) != null_bits) { int val = ComputeMove2(x, y, opponent, level + 1, maxval, opponentbits, colorbits); if (val != ILLEGAL_VALUE && val > maxval) { maxval = val; if (maxval > -cutoffval || interrupted()) break; } } } if (maxval > -cutoffval || interrupted()) break; } if (interrupted()) return -LARGEINT; return maxval; } // Calculate a heuristic value for the current position. If we are at // the end of the game, do this by counting the pieces. Otherwise do // it by combining the score using the number of pieces, and the score // using the board control values. // int Engine::EvaluatePosition(ChipColor color) { int retval; ChipColor opponent = Utils::opponentColorFor(color); int score_color = m_score->score(color); int score_opponent = m_score->score(opponent); if (m_exhaustive) retval = score_color - score_opponent; else { retval = (100 - m_coeff) * (m_score->score(color) - m_score->score(opponent)) + m_coeff * BC_WEIGHT * (m_bc_score->score(color) - m_bc_score->score(opponent)); } return retval; } // Calculate bitmaps for each square, and also for neighbors of each // square. // void Engine::SetupBits() { //m_coord_bit = new long[9][9]; //m_neighbor_bits = new long[9][9]; quint64 bits = 1; // Store a 64 bit unsigned it with the corresponding bit set for // each square. for (int i = 1; i < 9; i++) for (int j = 1; j < 9; j++) { m_coord_bit[i][j] = bits; bits *= 2; } // Store a bitmap consisting of all neighbors for each square. for (int i = 1; i < 9; i++) for (int j = 1; j < 9; j++) { m_neighbor_bits[i][j] = 0; for (int xinc = -1; xinc <= 1; xinc++) for (int yinc = -1; yinc <= 1; yinc++) { if (xinc != 0 || yinc != 0) if (i + xinc > 0 && i + xinc < 9 && j + yinc > 0 && j + yinc < 9) m_neighbor_bits[i][j] |= m_coord_bit[i + xinc][j + yinc]; } } } // Set up the board control values that will be used in evaluation of // the position. // void Engine::SetupBcBoard() { // JAVA m_bc_board = new int[9][9]; for (int i = 1; i < 9; i++) for (int j = 1; j < 9; j++) { if (i == 2 || i == 7) m_bc_board[i][j] = -1; else m_bc_board[i][j] = 0; if (j == 2 || j == 7) m_bc_board[i][j] -= 1; } m_bc_board[1][1] = 2; m_bc_board[8][1] = 2; m_bc_board[1][8] = 2; m_bc_board[8][8] = 2; m_bc_board[1][2] = -1; m_bc_board[2][1] = -1; m_bc_board[1][7] = -1; m_bc_board[7][1] = -1; m_bc_board[8][2] = -1; m_bc_board[2][8] = -1; m_bc_board[8][7] = -1; m_bc_board[7][8] = -1; } // Calculate the board control score. // int Engine::CalcBcScore(ChipColor color) { int sum = 0; for (int i = 1; i < 9; i++) for (int j = 1; j < 9; j++) if (m_board[i][j] == color) sum += m_bc_board[i][j]; return sum; } // Calculate a bitmap of the occupied squares for a certain color. // quint64 Engine::ComputeOccupiedBits(ChipColor color) { quint64 retval = 0; for (int i = 1; i < 9; i++) for (int j = 1; j < 9; j++) if (m_board[i][j] == color) retval |= m_coord_bit[i][j]; return retval; } diff --git a/kexthighscore.cpp b/kexthighscore.cpp index 34d4dc2..1e1fe4d 100644 --- a/kexthighscore.cpp +++ b/kexthighscore.cpp @@ -1,297 +1,296 @@ /* This file is part of the KDE games library Copyright (C) 2001-2004 Nicolas Hadacek (hadacek@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kexthighscore.h" -#include #include #include #include "kexthighscore_internal.h" #include "kexthighscore_gui.h" Q_LOGGING_CATEGORY(GAMES_EXTHIGHSCORE, "games.exthighscore") namespace KExtHighscore { //----------------------------------------------------------------------------- ManagerPrivate *internal = nullptr; uint gameType() { internal->checkFirst(); return internal->gameType(); } void setGameType(uint type) { internal->setGameType(type); } bool configure(QWidget *parent) { internal->checkFirst(); ConfigDialog *cd = new ConfigDialog(parent); cd->exec(); bool saved = cd->hasBeenSaved(); delete cd; return saved; } void show(QWidget *parent, int rank) { HighscoresDialog *hd = new HighscoresDialog(rank, parent); hd->exec(); delete hd; } void submitScore(const Score &score, QWidget *widget) { int rank = internal->submitScore(score, widget, internal->showMode!=Manager::NeverShow); switch (internal->showMode) { case Manager::AlwaysShow: show(widget, -1); break; case Manager::ShowForHigherScore: if ( rank!=-1) show(widget, rank); break; case Manager::ShowForHighestScore: if ( rank==0 ) show(widget, rank); break; case Manager::NeverShow: break; } } void show(QWidget *widget) { internal->checkFirst(); show(widget, -1); } Score lastScore() { internal->checkFirst(); internal->hsConfig().readCurrentConfig(); uint nb = internal->scoreInfos().maxNbEntries(); return internal->readScore(nb-1); } Score firstScore() { internal->checkFirst(); internal->hsConfig().readCurrentConfig(); return internal->readScore(0); } //----------------------------------------------------------------------------- Manager::Manager(uint nbGameTypes, uint maxNbEntries) { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); Q_ASSERT(nbGameTypes); Q_ASSERT(maxNbEntries); if (internal) { qCWarning(GAMES_EXTHIGHSCORE) << "A highscore object already exists"; abort(); } internal = new ManagerPrivate(nbGameTypes, *this); internal->init(maxNbEntries); } Manager::~Manager() { delete internal; internal = nullptr; } void Manager::setTrackLostGames(bool track) { internal->trackLostGames = track; } void Manager::setTrackDrawGames(bool track) { internal->trackDrawGames = track; } void Manager::setShowStatistics(bool show) { internal->showStatistics = show; } void Manager::setShowDrawGamesStatistic(bool show) { internal->showDrawGames = show; } void Manager::setWWHighscores(const QUrl &url, const QString &version) { Q_ASSERT( url.isValid() ); internal->serverURL = url; const char *HS_WW_URL = "ww hs url"; ConfigGroup cg; if ( cg.hasKey(HS_WW_URL) ) internal->serverURL = QUrl(cg.readEntry(HS_WW_URL)); else cg.writeEntry(HS_WW_URL, url.url()); internal->version = version; } void Manager::setScoreHistogram(const QVector &scores, ScoreTypeBound type) { Q_ASSERT( scores.size()>=2 ); for (int i=0; iplayerInfos().createHistoItems(scores, type==ScoreBound); } void Manager::setShowMode(ShowMode mode) { internal->showMode = mode; } void Manager::setScoreType(ScoreType type) { switch (type) { case Normal: return; case MinuteTime: { Item *item = createItem(ScoreDefault); item->setPrettyFormat(Item::MinuteTime); setScoreItem(0, item); item = createItem(MeanScoreDefault); item->setPrettyFormat(Item::MinuteTime); setPlayerItem(MeanScore, item); item = createItem(BestScoreDefault); item->setPrettyFormat(Item::MinuteTime); setPlayerItem(BestScore, item); return; } } } void Manager::submitLegacyScore(const Score &score) const { internal->submitLocal(score); } bool Manager::isStrictlyLess(const Score &s1, const Score &s2) const { return s1.score()setPrettyFormat(Item::OneDecimal); item->setPrettySpecial(Item::DefaultNotDefined); break; case BestScoreDefault: item = new Item((uint)0, i18n("Best Score"), Qt::AlignRight); item->setPrettySpecial(Item::DefaultNotDefined); break; case ElapsedTime: item = new Item((uint)0, i18n("Elapsed Time"), Qt::AlignRight); item->setPrettyFormat(Item::MinuteTime); item->setPrettySpecial(Item::ZeroNotDefined); break; } return item; } void Manager::setScoreItem(uint worstScore, Item *item) { item->setDefaultValue(worstScore); internal->scoreInfos().setItem(QStringLiteral( "score" ), item); internal->playerInfos().item(QStringLiteral( "mean score" )) ->item()->setDefaultValue(double(worstScore)); internal->playerInfos().item(QStringLiteral( "best score" )) ->item()->setDefaultValue(worstScore); } void Manager::addScoreItem(const QString &name, Item *item) { internal->scoreInfos().addItem(name, item, true); } void Manager::setPlayerItem(PlayerItemType type, Item *item) { const Item *scoreItem = internal->scoreInfos().item(QStringLiteral( "score" ))->item(); uint def = scoreItem->defaultValue().toUInt(); QString name; switch (type) { case MeanScore: name = QStringLiteral( "mean score" ); item->setDefaultValue(double(def)); break; case BestScore: name = QStringLiteral( "best score" ); item->setDefaultValue(def); break; } internal->playerInfos().setItem(name, item); } QString Manager::gameTypeLabel(uint gameType, LabelType type) const { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); if ( gameType!=0 ) { qCWarning(GAMES_EXTHIGHSCORE) << "You need to reimplement KExtHighscore::Manager for " << "multiple game types"; abort(); } switch (type) { case Icon: case Standard: case I18N: break; case WW: return QStringLiteral( "normal" ); } return QString(); } void Manager::addToQueryURL(QUrl &url, const QString &item, const QString &content) { QUrlQuery urlquery(url); Q_ASSERT( !item.isEmpty() && urlquery.queryItemValue(item).isEmpty() ); QString query = url.query(); if ( !query.isEmpty() ) query += QLatin1Char( '&' ); query += item + QLatin1Char( '=' ) + QLatin1String( QUrl::toPercentEncoding( content ) ); url.setQuery(query); } } // namescape diff --git a/kexthighscore_gui.cpp b/kexthighscore_gui.cpp index 11e00ae..56353ac 100644 --- a/kexthighscore_gui.cpp +++ b/kexthighscore_gui.cpp @@ -1,650 +1,649 @@ /* This file is part of the KDE games library Copyright (C) 2001-2003 Nicolas Hadacek (hadacek@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kexthighscore_gui.h" #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kexthighscore_internal.h" #include "kexthighscore.h" #include "kexthighscore_tab.h" namespace KExtHighscore { //----------------------------------------------------------------------------- ShowItem::ShowItem(QTreeWidget *list, bool highlight) : QTreeWidgetItem(list), _highlight(highlight) { // kDebug(11001) ; if (_highlight) { for (int i=0; i < columnCount();i++) { setForeground(i, Qt::red); } } } //----------------------------------------------------------------------------- ScoresList::ScoresList(QWidget *parent) : QTreeWidget(parent) { // kDebug(11001) ; setSelectionMode(QTreeWidget::NoSelection); // setItemMargin(3); setAllColumnsShowFocus(true); // setSorting(-1); header()->setSectionsClickable(false); header()->setSectionsMovable(false); } void ScoresList::addHeader(const ItemArray &items) { // kDebug(11001) ; addLineItem(items, 0, nullptr); } QTreeWidgetItem *ScoresList::addLine(const ItemArray &items, uint index, bool highlight) { // kDebug(11001) ; QTreeWidgetItem *item = new ShowItem(this, highlight); addLineItem(items, index, item); return item; } void ScoresList::addLineItem(const ItemArray &items, uint index, QTreeWidgetItem *line) { // kDebug(11001) ; uint k = 0; for (int i=0; iisVisible() ) { continue; } if (line) { line->setText(k, itemText(container, index)); line->setTextAlignment(k, container.item()->alignment()); } else { headerItem()->setText(k, container.item()->label() ); headerItem()->setTextAlignment(k, container.item()->alignment()); } k++; } update(); } //----------------------------------------------------------------------------- HighscoresList::HighscoresList(QWidget *parent) : ScoresList(parent) { // kDebug(11001) ; } QString HighscoresList::itemText(const ItemContainer &item, uint row) const { // kDebug(11001) ; return item.pretty(row); } void HighscoresList::load(const ItemArray &items, int highlight) { // kDebug(11001) ; clear(); QTreeWidgetItem *line = nullptr; for (int j=items.nbEntries()-1; j>=0; j--) { QTreeWidgetItem *item = addLine(items, j, j==highlight); if ( j==highlight ) line = item; } scrollTo(indexFromItem(line)); } //----------------------------------------------------------------------------- HighscoresWidget::HighscoresWidget(QWidget *parent) : QWidget(parent) { // kDebug(11001) << ": HighscoresWidget"; setObjectName( QStringLiteral("show_highscores_widget" )); const ScoreInfos &s = internal->scoreInfos(); const PlayerInfos &p = internal->playerInfos(); QVBoxLayout *vbox = new QVBoxLayout(this); vbox->setSpacing(QApplication::fontMetrics().lineSpacing()); _tw = new QTabWidget(this); connect(_tw, SIGNAL(currentChanged(int)), SLOT(tabChanged())); vbox->addWidget(_tw); // scores tab _scoresList = new HighscoresList(nullptr); _scoresList->addHeader(s); _tw->addTab(_scoresList, i18n("Best &Scores")); // players tab _playersList = new HighscoresList(nullptr); _playersList->addHeader(p); _tw->addTab(_playersList, i18n("&Players")); // statistics tab if ( internal->showStatistics ) { _statsTab = new StatisticsTab(nullptr); _tw->addTab(_statsTab, i18n("Statistics")); } // histogram tab if ( p.histogram().size()!=0 ) { _histoTab = new HistogramTab(nullptr); _tw->addTab(_histoTab, i18n("Histogram")); } // url labels if ( internal->isWWHSAvailable() ) { QUrl url = internal->queryUrl(ManagerPrivate::Scores); _scoresUrl = new KUrlLabel(url.url(), i18n("View world-wide highscores"), this); connect(_scoresUrl, SIGNAL(leftClickedUrl(QString)), SLOT(showURL(QString))); vbox->addWidget(_scoresUrl); url = internal->queryUrl(ManagerPrivate::Players); _playersUrl = new KUrlLabel(url.url(), i18n("View world-wide players"), this); connect(_playersUrl, SIGNAL(leftClickedUrl(QString)), SLOT(showURL(QString))); vbox->addWidget(_playersUrl); } load(-1); } void HighscoresWidget::changeTab(int i) { // kDebug(11001) ; if ( i!=_tw->currentIndex() ) _tw->setCurrentIndex(i); } void HighscoresWidget::showURL(const QString &url) { // kDebug(11001) ; (void)new KRun(QUrl(url), this); } void HighscoresWidget::load(int rank) { // kDebug(11001) << rank; _scoresList->load(internal->scoreInfos(), rank); _playersList->load(internal->playerInfos(), internal->playerInfos().id()); if (_scoresUrl) _scoresUrl->setUrl(internal->queryUrl(ManagerPrivate::Scores).url()); if (_playersUrl) _playersUrl->setUrl(internal->queryUrl(ManagerPrivate::Players).url()); if (_statsTab) _statsTab->load(); if (_histoTab) _histoTab->load(); } //----------------------------------------------------------------------------- HighscoresDialog::HighscoresDialog(int rank, QWidget *parent) : KPageDialog(parent), _rank(rank), _tab(0) { // kDebug(11001) << ": HighscoresDialog"; setWindowTitle( i18n("Highscores") ); // TODO setButtons( Close|User1|User2 ); // TODO setDefaultButton( Close ); if ( internal->nbGameTypes()>1 ) setFaceType( KPageDialog::Tree ); else setFaceType( KPageDialog::Plain ); // TODO setButtonGuiItem( User1, KGuiItem(i18n("Configure..."), QLatin1String( "configure" )) ); // TODO setButtonGuiItem( User2, KGuiItem(i18n("Export...")) ); for (uint i=0; inbGameTypes(); i++) { QString title = internal->manager.gameTypeLabel(i, Manager::I18N); QString icon = internal->manager.gameTypeLabel(i, Manager::Icon); HighscoresWidget *hsw = new HighscoresWidget(nullptr); KPageWidgetItem *pageItem = new KPageWidgetItem( hsw, title); pageItem->setIcon(QIcon::fromTheme(icon).pixmap(IconSize(KIconLoader::Toolbar))); // pageItem->setIcon( QIcon( BarIcon(icon, KIconLoader::SizeLarge) ) ); addPage( pageItem ); _pages.append(pageItem); connect(hsw, SIGNAL(tabChanged(int)), SLOT(tabChanged(int))); } connect(this, &KPageDialog::currentPageChanged, this, &HighscoresDialog::highscorePageChanged); setCurrentPage(_pages[internal->gameType()]); setStandardButtons(QDialogButtonBox::Close); } void HighscoresDialog::highscorePageChanged(KPageWidgetItem* page, KPageWidgetItem* pageold) { Q_UNUSED(pageold); // kDebug(11001) ; int idx = _pages.indexOf( page ); Q_ASSERT(idx != -1); internal->hsConfig().readCurrentConfig(); uint type = internal->gameType(); bool several = ( internal->nbGameTypes()>1 ); if (several) internal->setGameType(idx); HighscoresWidget *hsw = static_cast(page->widget()); hsw->load(uint(idx)==type ? _rank : -1); if (several) setGameType(type); hsw->changeTab(_tab); } void HighscoresDialog::slotUser1() { // kDebug(11001) ; if ( KExtHighscore::configure(this) ) highscorePageChanged(currentPage(), nullptr);//update data } void HighscoresDialog::slotUser2() { // kDebug(11001) ; QUrl url = QFileDialog::getSaveFileUrl(this, tr("HighscoresDialog"), QUrl(), QString()); if ( url.isEmpty() ) return; auto job = KIO::stat(url, KIO::StatJob::SourceSide, 0); KJobWidgets::setWindow(job, this); job->exec(); if (!job->error()) { KGuiItem gi = KStandardGuiItem::save(); gi.setText(i18n("Overwrite")); int res = KMessageBox::warningContinueCancel(this, i18n("The file already exists. Overwrite?"), i18n("Export"), gi); if ( res==KMessageBox::Cancel ) return; } QTemporaryFile tmp; tmp.open(); QTextStream stream(&tmp); internal->exportHighscores(stream); stream.flush(); // KIO::NetAccess::upload(tmp.fileName(), url, this); auto copyJob = KIO::copy(QUrl::fromLocalFile(tmp.fileName()), url); copyJob->exec(); } //----------------------------------------------------------------------------- LastMultipleScoresList::LastMultipleScoresList( const QVector &scores, QWidget *parent) : ScoresList(parent), _scores(scores) { // kDebug(11001) << ": LastMultipleScoresList"; const ScoreInfos &s = internal->scoreInfos(); addHeader(s); for (int i=0; isetText(i, itemText(container, index)); line->setTextAlignment(i, container.item()->alignment()); } else { headerItem()->setText(i, container.item()->label() ); headerItem()->setTextAlignment(i, container.item()->alignment()); } } } QString LastMultipleScoresList::itemText(const ItemContainer &item, uint row) const { // kDebug(11001) ; QString name = item.name(); if ( name==QLatin1String( "rank" ) ) return (_scores[row].type()==Won ? i18n("Winner") : QString()); QVariant v = _scores[row].data(name); if ( name==QLatin1String( "name" ) ) return v.toString(); return item.item()->pretty(row, v); } //----------------------------------------------------------------------------- TotalMultipleScoresList::TotalMultipleScoresList( const QVector &scores, QWidget *parent) : ScoresList(parent), _scores(scores) { // kDebug(11001) << ": TotalMultipleScoresList"; const ScoreInfos &s = internal->scoreInfos(); addHeader(s); for (int i=0; iplayerInfos(); uint k = 1; // skip "id" for (uint i=0; i<4; i++) { // skip additional fields const ItemContainer *container; if ( i==2 ) container = pi.item(QStringLiteral( "nb games" )); else if ( i==3 ) container = pi.item(QStringLiteral( "mean score" )); else { container = si[k]; k++; } if (line) { line->setText(i, itemText(*container, index)); line->setTextAlignment(i, container->item()->alignment()); } else { QString label = (i==2 ? i18n("Won Games") : container->item()->label()); headerItem()->setText(i, label ); headerItem()->setTextAlignment(i, container->item()->alignment()); } } } QString TotalMultipleScoresList::itemText(const ItemContainer &item, uint row) const { // kDebug(11001) ; QString name = item.name(); if ( name==QLatin1String( "rank" ) ) return QString::number(_scores.size()-row); else if ( name==QLatin1String( "nb games" ) ) return QString::number( _scores[row].data(QStringLiteral( "nb won games" )).toUInt() ); QVariant v = _scores[row].data(name); if ( name==QLatin1String( "name" ) ) return v.toString(); return item.item()->pretty(row, v); } //----------------------------------------------------------------------------- ConfigDialog::ConfigDialog(QWidget *parent) : QDialog(parent), _saved(false), _WWHEnabled(nullptr) { // kDebug(11001) << ": ConfigDialog"; setWindowTitle( i18n("Configure Highscores") ); setModal( true ); QWidget *page = nullptr; QTabWidget *tab = nullptr; QVBoxLayout *layout = new QVBoxLayout; setLayout(layout); if ( internal->isWWHSAvailable() ) { tab = new QTabWidget(this); layout->addWidget(tab); page = new QWidget; tab->addTab(page, i18n("Main")); } else { page = new QWidget(this); layout->addWidget(page); } QGridLayout *pageTop = new QGridLayout(page); //pageTop->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); //pageTop->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); layout->addLayout(pageTop); QLabel *label = new QLabel(i18n("Nickname:"), page); pageTop->addWidget(label, 0, 0); _nickname = new QLineEdit(page); connect(_nickname, &QLineEdit::textChanged, this, &ConfigDialog::modifiedSlot); connect(_nickname, &QLineEdit::textChanged, this, &ConfigDialog::nickNameChanged); _nickname->setMaxLength(16); pageTop->addWidget(_nickname, 0, 1); label = new QLabel(i18n("Comment:"), page); pageTop->addWidget(label, 1, 0); _comment = new QLineEdit(page); connect(_comment, &QLineEdit::textChanged, this, &ConfigDialog::modifiedSlot); _comment->setMaxLength(50); pageTop->addWidget(_comment, 1, 1); if (tab) { _WWHEnabled = new QCheckBox(i18n("World-wide highscores enabled"), page); connect(_WWHEnabled, &QAbstractButton::toggled, this, &ConfigDialog::modifiedSlot); pageTop->addWidget(_WWHEnabled, 2, 0, 1, 2 ); // advanced tab QWidget *page = new QWidget; tab->addTab(page, i18n("Advanced")); QVBoxLayout *pageTop = new QVBoxLayout(page); //pageTop->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin)); //pageTop->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); QGroupBox *group = new QGroupBox(page); group->setTitle( i18n("Registration Data") ); pageTop->addWidget(group); QGridLayout *groupLayout = new QGridLayout(group); //groupLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); label = new QLabel(i18n("Nickname:"), group); groupLayout->addWidget(label, 0, 0); _registeredName = new QLineEdit(group); _registeredName->setReadOnly(true); groupLayout->addWidget(_registeredName, 0, 1); label = new QLabel(i18n("Key:"), group); groupLayout->addWidget(label, 1, 0); _key = new QLineEdit(group); _key->setReadOnly(true); groupLayout->addWidget(_key, 1, 1); KGuiItem gi = KStandardGuiItem::clear(); gi.setText(i18n("Remove")); _removeButton = new QPushButton(group); KGuiItem::assign(_removeButton, gi); groupLayout->addWidget(_removeButton, 2, 0); connect(_removeButton, &QAbstractButton::clicked, this, &ConfigDialog::removeSlot); } buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); // TODO mapping for Apply button pageTop->addWidget(buttonBox); load(); buttonBox->button(QDialogButtonBox::Ok)->setEnabled( !_nickname->text().isEmpty() ); buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false ); } void ConfigDialog::nickNameChanged(const QString &text) { buttonBox->button(QDialogButtonBox::Ok)->setEnabled( !text.isEmpty() ); } void ConfigDialog::modifiedSlot() { buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true && !_nickname->text().isEmpty() ); } void ConfigDialog::accept() { if ( save() ) { QDialog::accept(); KSharedConfig::openConfig()->sync(); // safer } } void ConfigDialog::removeSlot() { KGuiItem gi = KStandardGuiItem::clear(); gi.setText(i18n("Remove")); int res = KMessageBox::warningContinueCancel(this, i18n("This will permanently remove your " "registration key. You will not be able to use " "the currently registered nickname anymore."), QString(), gi); if ( res==KMessageBox::Continue ) { internal->playerInfos().removeKey(); _registeredName->clear(); _key->clear(); _removeButton->setEnabled(false); _WWHEnabled->setChecked(false); modifiedSlot(); } } void ConfigDialog::load() { internal->hsConfig().readCurrentConfig(); const PlayerInfos &infos = internal->playerInfos(); _nickname->setText(infos.isAnonymous() ? QString() : infos.name()); _comment->setText(infos.comment()); if (_WWHEnabled) { _WWHEnabled->setChecked(infos.isWWEnabled()); if ( !infos.key().isEmpty() ) { _registeredName->setText(infos.registeredName()); _registeredName->home(false); _key->setText(infos.key()); _key->home(false); } _removeButton->setEnabled(!infos.key().isEmpty()); } } bool ConfigDialog::save() { bool enabled = (_WWHEnabled ? _WWHEnabled->isChecked() : false); // do not bother the user with "nickname empty" if he has not // messed with nickname settings ... QString newName = _nickname->text(); if ( newName.isEmpty() && !internal->playerInfos().isAnonymous() && !enabled ) return true; if ( newName.isEmpty() ) { KMessageBox::sorry(this, i18n("Please choose a non empty nickname.")); return false; } if ( internal->playerInfos().isNameUsed(newName) ) { KMessageBox::sorry(this, i18n("Nickname already in use. Please " "choose another one")); return false; } int res = internal->modifySettings(newName, _comment->text(), enabled, this); if (res) { load(); // needed to update view when "apply" is clicked buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); } _saved = true; return res; } //----------------------------------------------------------------------------- AskNameDialog::AskNameDialog(QWidget *parent) : QDialog(parent) { // kDebug(11001) << ": AskNameDialog"; setWindowTitle( i18n("Enter Your Nickname") ); internal->hsConfig().readCurrentConfig(); QVBoxLayout *top = new QVBoxLayout; //top->setMargin( QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin) ); //top->setSpacing( QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) ); setLayout(top); QLabel *label = new QLabel(i18n("Congratulations, you have won!"), this); top->addWidget(label); QHBoxLayout *hbox = new QHBoxLayout; top->addLayout(hbox); label = new QLabel(i18n("Enter your nickname:"), this); hbox->addWidget(label); _edit = new QLineEdit(this); _edit->setFocus(); connect(_edit, SIGNAL(textChanged(QString)), SLOT(nameChanged())); hbox->addWidget(_edit); //top->addSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); _checkbox = new QCheckBox(i18n("Do not ask again."), this); top->addWidget(_checkbox); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); top->addWidget(buttonBox); nameChanged(buttonBox); } void AskNameDialog::nameChanged(QDialogButtonBox *box) { box->button(QDialogButtonBox::Ok)->setEnabled( !name().isEmpty() && !internal->playerInfos().isNameUsed(name())); } } // namespace diff --git a/kexthighscore_internal.cpp b/kexthighscore_internal.cpp index 9802a97..c041614 100644 --- a/kexthighscore_internal.cpp +++ b/kexthighscore_internal.cpp @@ -1,891 +1,889 @@ /* This file is part of the KDE games library Copyright (C) 2001-2004 Nicolas Hadacek (hadacek@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kexthighscore_internal.h" #include #include #include #include #include -#include -#include #include #include #include #include #include #include #include #include #include "kexthighscore.h" #include "kexthighscore_gui.h" #include "kemailsettings.h" // TODO Decide if want to support // a build time HIGHSCORE_DIRECTORY or not // #include namespace KExtHighscore { //----------------------------------------------------------------------------- const char ItemContainer::ANONYMOUS[] = "_"; const char ItemContainer::ANONYMOUS_LABEL[] = I18N_NOOP("anonymous"); ItemContainer::ItemContainer() : _item(0) {} ItemContainer::~ItemContainer() { delete _item; } void ItemContainer::setItem(Item *item) { delete _item; _item = item; } QString ItemContainer::entryName() const { if ( _subGroup.isEmpty() ) return _name; return _name + QLatin1Char( '_' ) + _subGroup; } QVariant ItemContainer::read(uint i) const { Q_ASSERT(_item); QVariant v = _item->defaultValue(); if ( isStored() ) { internal->hsConfig().setHighscoreGroup(_group); v = internal->hsConfig().readPropertyEntry(i+1, entryName(), v); } return _item->read(i, v); } QString ItemContainer::pretty(uint i) const { Q_ASSERT(_item); return _item->pretty(i, read(i)); } void ItemContainer::write(uint i, const QVariant &value) const { Q_ASSERT( isStored() ); Q_ASSERT( internal->hsConfig().isLocked() ); internal->hsConfig().setHighscoreGroup(_group); internal->hsConfig().writeEntry(i+1, entryName(), value); } uint ItemContainer::increment(uint i) const { uint v = read(i).toUInt() + 1; write(i, v); return v; } //----------------------------------------------------------------------------- ItemArray::ItemArray() : _group(QLatin1String( "" )), _subGroup(QLatin1String( "" )) // no null groups {} ItemArray::~ItemArray() { for (int i=0; iname()==name ) return i; return -1; } const ItemContainer *ItemArray::item(const QString &name) const { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); int i = findIndex(name); if ( i==-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "no item named \"" << name << "\""; return at(i); } ItemContainer *ItemArray::item(const QString &name) { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); int i = findIndex(name); if ( i==-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "no item named \"" << name << "\""; return at(i); } void ItemArray::setItem(const QString &name, Item *item) { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); int i = findIndex(name); if ( i==-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "no item named \"" << name << "\""; bool stored = at(i)->isStored(); bool canHaveSubGroup = at(i)->canHaveSubGroup(); _setItem(i, name, item, stored, canHaveSubGroup); } void ItemArray::addItem(const QString &name, Item *item, bool stored, bool canHaveSubGroup) { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); if ( findIndex(name)!=-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "item already exists \"" << name << "\""; append(new ItemContainer); //at(i) = new ItemContainer; _setItem(size()-1, name, item, stored, canHaveSubGroup); } void ItemArray::_setItem(uint i, const QString &name, Item *item, bool stored, bool canHaveSubGroup) { at(i)->setItem(item); at(i)->setName(name); at(i)->setGroup(stored ? _group : QString()); at(i)->setSubGroup(canHaveSubGroup ? _subGroup : QString()); } void ItemArray::setGroup(const QString &group) { Q_ASSERT( !group.isNull() ); _group = group; for (int i=0; iisStored() ) at(i)->setGroup(group); } void ItemArray::setSubGroup(const QString &subGroup) { Q_ASSERT( !subGroup.isNull() ); _subGroup = subGroup; for (int i=0; icanHaveSubGroup() ) at(i)->setSubGroup(subGroup); } void ItemArray::read(uint k, Score &data) const { for (int i=0; iisStored() ) continue; data.setData(at(i)->name(), at(i)->read(k)); } } void ItemArray::write(uint k, const Score &data, uint nb) const { for (int i=0; iisStored() ) continue; for (uint j=nb-1; j>k; j--) at(i)->write(j, at(i)->read(j-1)); at(i)->write(k, data.data(at(i)->name())); } } void ItemArray::exportToText(QTextStream &s) const { for (uint k=0; kitem(); if ( item->isVisible() ) { if ( i!=0 ) s << '\t'; if ( k==0 ) s << item->label(); else s << at(i)->pretty(k-1); } } s << endl; } } //----------------------------------------------------------------------------- class ScoreNameItem : public NameItem { public: ScoreNameItem(const ScoreInfos &score, const PlayerInfos &infos) : _score(score), _infos(infos) {} QString pretty(uint i, const QVariant &v) const override { uint id = _score.item(QStringLiteral( "id" ))->read(i).toUInt(); if ( id==0 ) return NameItem::pretty(i, v); return _infos.prettyName(id-1); } private: const ScoreInfos &_score; const PlayerInfos &_infos; }; //----------------------------------------------------------------------------- ScoreInfos::ScoreInfos(uint maxNbEntries, const PlayerInfos &infos) : _maxNbEntries(maxNbEntries) { addItem(QStringLiteral( "id" ), new Item((uint)0)); addItem(QStringLiteral( "rank" ), new RankItem, false); addItem(QStringLiteral( "name" ), new ScoreNameItem(*this, infos)); addItem(QStringLiteral( "score" ), Manager::createItem(Manager::ScoreDefault)); addItem(QStringLiteral( "date" ), new DateItem); } uint ScoreInfos::nbEntries() const { uint i = 0; for (; i<_maxNbEntries; i++) if ( item(QStringLiteral( "score" ))->read(i)==item(QStringLiteral( "score" ))->item()->defaultValue() ) break; return i; } //----------------------------------------------------------------------------- const char *HS_ID = "player id"; const char *HS_REGISTERED_NAME = "registered name"; const char *HS_KEY = "player key"; const char *HS_WW_ENABLED = "ww hs enabled"; PlayerInfos::PlayerInfos() { setGroup(QStringLiteral( "players" )); // standard items addItem(QStringLiteral( "name" ), new NameItem); Item *it = new Item((uint)0, i18n("Games Count"),Qt::AlignRight); addItem(QStringLiteral( "nb games" ), it, true, true); it = Manager::createItem(Manager::MeanScoreDefault); addItem(QStringLiteral( "mean score" ), it, true, true); it = Manager::createItem(Manager::BestScoreDefault); addItem(QStringLiteral( "best score" ), it, true, true); addItem(QStringLiteral( "date" ), new DateItem, true, true); it = new Item(QString(), i18n("Comment"), Qt::AlignLeft); addItem(QStringLiteral( "comment" ), it); // statistics items addItem(QStringLiteral( "nb black marks" ), new Item((uint)0), true, true); // legacy addItem(QStringLiteral( "nb lost games" ), new Item((uint)0), true, true); addItem(QStringLiteral( "nb draw games" ), new Item((uint)0), true, true); addItem(QStringLiteral( "current trend" ), new Item((int)0), true, true); addItem(QStringLiteral( "max lost trend" ), new Item((uint)0), true, true); addItem(QStringLiteral( "max won trend" ), new Item((uint)0), true, true); QString username = KUser().loginName(); #ifdef HIGHSCORE_DIRECTORY internal->hsConfig().setHighscoreGroup("players"); for (uint i=0; ;i++) { if ( !internal->hsConfig().hasEntry(i+1, "username") ) { _newPlayer = true; _id = i; break; } if ( internal->hsConfig().readEntry(i+1, "username")==username ) { _newPlayer = false; _id = i; return; } } #endif internal->hsConfig().lockForWriting(); KEMailSettings emailConfig; emailConfig.setProfile(emailConfig.defaultProfileName()); QString name = emailConfig.getSetting(KEMailSettings::RealName); if ( name.isEmpty() || isNameUsed(name) ) name = username; if ( isNameUsed(name) ) name= QLatin1String(ItemContainer::ANONYMOUS); #ifdef HIGHSCORE_DIRECTORY internal->hsConfig().writeEntry(_id+1, "username", username); item("name")->write(_id, name); #endif ConfigGroup cg; _oldLocalPlayer = cg.hasKey(HS_ID); _oldLocalId = cg.readEntry(HS_ID).toUInt(); #ifdef HIGHSCORE_DIRECTORY if (_oldLocalPlayer) { // player already exists in local config file // copy player data QString prefix = QString::fromLatin1( "%1_").arg(_oldLocalId+1); #ifdef __GNUC__ #warning "kde4 port g.config()->entryMap"; #endif #if 0 QMap entries = cg.config()->entryMap("KHighscore_players"); QMap::const_iterator it; for (it=entries.begin(); it!=entries.end(); ++it) { QString key = it.key(); if ( key.find(prefix)==0 ) { QString name = key.right(key.length()-prefix.length()); if ( name!="name" || !isNameUsed(it.data()) ) internal->hsConfig().writeEntry(_id+1, name, it.data()); } } #endif } #else _newPlayer = !_oldLocalPlayer; if (_oldLocalPlayer) _id = _oldLocalId; else { _id = nbEntries(); cg.writeEntry(HS_ID, _id); item(QStringLiteral( "name" ))->write(_id, name); } #endif _bound = true; internal->hsConfig().writeAndUnlock(); } void PlayerInfos::createHistoItems(const QVector &scores, bool bound) { Q_ASSERT( _histogram.size()==0 ); _bound = bound; _histogram = scores; for (int i=1; ihsConfig().setHighscoreGroup(QStringLiteral( "players" )); const QStringList list = internal->hsConfig().readList(QStringLiteral( "name" ), -1); return list.count(); } QString PlayerInfos::key() const { ConfigGroup cg; return cg.readEntry(HS_KEY, QString()); } bool PlayerInfos::isWWEnabled() const { ConfigGroup cg; return cg.readEntry(HS_WW_ENABLED, false); } QString PlayerInfos::histoName(int i) const { const QVector &sh = _histogram; Q_ASSERT( iincrement(_id); switch (score.type()) { case Lost: item(QStringLiteral( "nb lost games" ))->increment(_id); break; case Won: break; case Draw: item(QStringLiteral( "nb draw games" ))->increment(_id); break; }; // update mean if ( score.type()==Won ) { uint nbWonGames = nbGames - item(QStringLiteral( "nb lost games" ))->read(_id).toUInt() - item(QStringLiteral( "nb draw games" ))->read(_id).toUInt() - item(QStringLiteral( "nb black marks" ))->read(_id).toUInt(); // legacy double mean = (nbWonGames==1 ? 0.0 : item(QStringLiteral( "mean score" ))->read(_id).toDouble()); mean += (double(score.score()) - mean) / nbWonGames; item(QStringLiteral( "mean score" ))->write(_id, mean); } // update best score Score best = score; // copy optional fields (there are not taken into account here) best.setScore( item(QStringLiteral( "best score" ))->read(_id).toUInt() ); if ( bestwrite(_id, score.score()); item(QStringLiteral( "date" ))->write(_id, score.data(QStringLiteral( "date" )).toDateTime()); } // update trends int current = item(QStringLiteral( "current trend" ))->read(_id).toInt(); switch (score.type()) { case Won: { if ( current<0 ) current = 0; current++; uint won = item(QStringLiteral( "max won trend" ))->read(_id).toUInt(); if ( (uint)current>won ) item(QStringLiteral( "max won trend" ))->write(_id, current); break; } case Lost: { if ( current>0 ) current = 0; current--; uint lost = item(QStringLiteral( "max lost trend" ))->read(_id).toUInt(); uint clost = -current; if ( clost>lost ) item(QStringLiteral( "max lost trend" ))->write(_id, clost); break; } case Draw: current = 0; break; } item(QStringLiteral( "current trend" ))->write(_id, current); // update histogram if ( score.type()==Won ) { const QVector &sh = _histogram; for (int i=1; iincrement(_id); break; } } } bool PlayerInfos::isNameUsed(const QString &newName) const { if ( newName==name() ) return false; // own name... for (uint i=0; iread(i).toString().toLower() ) return true; if ( newName==i18n(ItemContainer::ANONYMOUS_LABEL) ) return true; return false; } void PlayerInfos::modifyName(const QString &newName) const { item(QStringLiteral( "name" ))->write(_id, newName); } void PlayerInfos::modifySettings(const QString &newName, const QString &comment, bool WWEnabled, const QString &newKey) const { modifyName(newName); item(QStringLiteral( "comment" ))->write(_id, comment); ConfigGroup cg; cg.writeEntry(HS_WW_ENABLED, WWEnabled); if ( !newKey.isEmpty() ) cg.writeEntry(HS_KEY, newKey); if (WWEnabled) cg.writeEntry(HS_REGISTERED_NAME, newName); } QString PlayerInfos::registeredName() const { ConfigGroup cg; return cg.readEntry(HS_REGISTERED_NAME, QString()); } void PlayerInfos::removeKey() { ConfigGroup cg; // save old key/nickname uint i = 0; QString str = QStringLiteral( "%1 old #%2" ); QString sk; do { i++; sk = str.arg(QLatin1String( HS_KEY )).arg(i); } while ( !cg.readEntry(sk, QString()).isEmpty() ); cg.writeEntry(sk, key()); cg.writeEntry(str.arg(QLatin1String( HS_REGISTERED_NAME )).arg(i), registeredName()); // clear current key/nickname cg.deleteEntry(HS_KEY); cg.deleteEntry(HS_REGISTERED_NAME); cg.writeEntry(HS_WW_ENABLED, false); } //----------------------------------------------------------------------------- ManagerPrivate::ManagerPrivate(uint nbGameTypes, Manager &m) : manager(m), showStatistics(false), showDrawGames(false), trackLostGames(false), trackDrawGames(false), showMode(Manager::ShowForHigherScore), _first(true), _nbGameTypes(nbGameTypes), _gameType(0) {} void ManagerPrivate::init(uint maxNbEntries) { _hsConfig = new KHighscore(false, 0); _playerInfos = new PlayerInfos; _scoreInfos = new ScoreInfos(maxNbEntries, *_playerInfos); } ManagerPrivate::~ManagerPrivate() { delete _scoreInfos; delete _playerInfos; delete _hsConfig; } QUrl ManagerPrivate::queryUrl(QueryType type, const QString &newName) const { QUrl url = serverURL; QString nameItem = QStringLiteral( "nickname" ); QString name = _playerInfos->registeredName(); bool withVersion = true; bool key = false; bool level = false; switch (type) { case Submit: url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "submit.php" )); level = true; key = true; break; case Register: url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "register.php" )); name = newName; break; case Change: url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "change.php" )); key = true; if ( newName!=name ) Manager::addToQueryURL(url, QStringLiteral( "new_nickname" ), newName); break; case Players: url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "players.php" )); nameItem = QStringLiteral( "highlight" ); withVersion = false; break; case Scores: url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "highscores.php" )); withVersion = false; if ( _nbGameTypes>1 ) level = true; break; } if (withVersion) Manager::addToQueryURL(url, QStringLiteral( "version" ), version); if ( !name.isEmpty() ) Manager::addToQueryURL(url, nameItem, name); if (key) Manager::addToQueryURL(url, QStringLiteral( "key" ), _playerInfos->key()); if (level) { QString label = manager.gameTypeLabel(_gameType, Manager::WW); if ( !label.isEmpty() ) Manager::addToQueryURL(url, QStringLiteral( "level" ), label); } return url; } // strings that needs to be translated (coming from the highscores server) const char *DUMMY_STRINGS[] = { I18N_NOOP("Undefined error."), I18N_NOOP("Missing argument(s)."), I18N_NOOP("Invalid argument(s)."), I18N_NOOP("Unable to connect to MySQL server."), I18N_NOOP("Unable to select database."), I18N_NOOP("Error on database query."), I18N_NOOP("Error on database insert."), I18N_NOOP("Nickname already registered."), I18N_NOOP("Nickname not registered."), I18N_NOOP("Invalid key."), I18N_NOOP("Invalid submit key."), I18N_NOOP("Invalid level."), I18N_NOOP("Invalid score.") }; const char *UNABLE_TO_CONTACT = I18N_NOOP("Unable to contact world-wide highscore server"); bool ManagerPrivate::doQuery(const QUrl &url, QWidget *parent, QDomNamedNodeMap *map) { KIO::http_update_cache(url, true, QDateTime::fromTime_t(0)); // remove cache ! QTemporaryFile tmpFile; if ( !tmpFile.open() ) { QString details = i18n("Unable to open temporary file."); KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details); return false; } auto copyJob = KIO::file_copy(url, QUrl::fromLocalFile(tmpFile.fileName())); KJobWidgets::setWindow(copyJob, parent); copyJob->exec(); if( copyJob->error() ) { QString details = i18n("Server URL: %1", url.host()); KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details); return false; } QTextStream t(&tmpFile); QString content = t.readAll().trimmed(); tmpFile.close(); QDomDocument doc; if ( doc.setContent(content) ) { QDomElement root = doc.documentElement(); QDomElement element = root.firstChild().toElement(); if ( element.tagName()==QLatin1String( "success" ) ) { if (map) *map = element.attributes(); return true; } if ( element.tagName()==QLatin1String( "error" ) ) { QDomAttr attr = element.attributes().namedItem(QStringLiteral( "label" )).toAttr(); if ( !attr.isNull() ) { QString msg = i18n(attr.value().toLatin1()); QString caption = i18n("Message from world-wide highscores " "server"); KMessageBox::sorry(parent, msg, caption); return false; } } } QString msg = i18n("Invalid answer from world-wide highscores server."); QString details = i18n("Raw message: %1", content); KMessageBox::detailedSorry(parent, msg, details); return false; } bool ManagerPrivate::getFromQuery(const QDomNamedNodeMap &map, const QString &name, QString &value, QWidget *parent) { QDomAttr attr = map.namedItem(name).toAttr(); if ( attr.isNull() ) { KMessageBox::sorry(parent, i18n("Invalid answer from world-wide " "highscores server (missing item: %1).", name)); return false; } value = attr.value(); return true; } Score ManagerPrivate::readScore(uint i) const { Score score(Won); _scoreInfos->read(i, score); return score; } int ManagerPrivate::rank(const Score &score) const { uint nb = _scoreInfos->nbEntries(); uint i = 0; for (; imaxNbEntries() ? (int)i : -1); } bool ManagerPrivate::modifySettings(const QString &newName, const QString &comment, bool WWEnabled, QWidget *widget) { QString newKey; bool newPlayer = false; if (WWEnabled) { newPlayer = _playerInfos->key().isEmpty() || _playerInfos->registeredName().isEmpty(); QUrl url = queryUrl((newPlayer ? Register : Change), newName); Manager::addToQueryURL(url, QStringLiteral( "comment" ), comment); QDomNamedNodeMap map; bool ok = doQuery(url, widget, &map); if ( !ok || (newPlayer && !getFromQuery(map, QStringLiteral( "key" ), newKey, widget)) ) return false; } bool ok = _hsConfig->lockForWriting(widget); // no GUI when locking if (ok) { // check again name in case the config file has been changed... // if it has, it is unfortunate because the WWW name is already // committed but should be very rare and not really problematic ok = ( !_playerInfos->isNameUsed(newName) ); if (ok) _playerInfos->modifySettings(newName, comment, WWEnabled, newKey); _hsConfig->writeAndUnlock(); } return ok; } void ManagerPrivate::convertToGlobal() { // read old highscores KHighscore *tmp = _hsConfig; _hsConfig = new KHighscore(true, 0); QVector scores(_scoreInfos->nbEntries()); for (int i=0; ilockForWriting(); for (int i=0; ioldLocalId()+1 ) submitLocal(scores[i]); _hsConfig->writeAndUnlock(); } void ManagerPrivate::setGameType(uint type) { if (_first) { _first = false; if ( _playerInfos->isNewPlayer() ) { // convert legacy highscores for (uint i=0; i<_nbGameTypes; i++) { setGameType(i); manager.convertLegacy(i); } #ifdef HIGHSCORE_DIRECTORY if ( _playerInfos->isOldLocalPlayer() ) { // convert local to global highscores for (uint i=0; i<_nbGameTypes; i++) { setGameType(i); convertToGlobal(); } } #endif } } Q_ASSERT( type<_nbGameTypes ); _gameType = qMin(type, _nbGameTypes-1); QString str = QStringLiteral( "scores" ); QString lab = manager.gameTypeLabel(_gameType, Manager::Standard); if ( !lab.isEmpty() ) { _playerInfos->setSubGroup(lab); str += QLatin1Char( '_' ) + lab; } _scoreInfos->setGroup(str); } void ManagerPrivate::checkFirst() { if (_first) setGameType(0); } int ManagerPrivate::submitScore(const Score &ascore, QWidget *widget, bool askIfAnonymous) { checkFirst(); Score score = ascore; score.setData(QStringLiteral( "id" ), _playerInfos->id() + 1); score.setData(QStringLiteral( "date" ), QDateTime::currentDateTime()); // ask new name if anonymous and winner const QLatin1String dontAskAgainName = QLatin1String( "highscore_ask_name_dialog" ); QString newName; KMessageBox::ButtonCode dummy; if ( score.type()==Won && askIfAnonymous && _playerInfos->isAnonymous() && KMessageBox::shouldBeShownYesNo(dontAskAgainName, dummy) ) { AskNameDialog d(widget); if ( d.exec()==QDialog::Accepted ) newName = d.name(); if ( d.dontAskAgain() ) KMessageBox::saveDontShowAgainYesNo(dontAskAgainName, KMessageBox::No); } int rank = -1; if ( _hsConfig->lockForWriting(widget) ) { // no GUI when locking // check again new name in case the config file has been changed... if ( !newName.isEmpty() && !_playerInfos->isNameUsed(newName) ) _playerInfos->modifyName(newName); // commit locally _playerInfos->submitScore(score); if ( score.type()==Won ) rank = submitLocal(score); _hsConfig->writeAndUnlock(); } if ( _playerInfos->isWWEnabled() ) submitWorldWide(score, widget); return rank; } int ManagerPrivate::submitLocal(const Score &score) { int r = rank(score); if ( r!=-1 ) { uint nb = _scoreInfos->nbEntries(); if ( nb<_scoreInfos->maxNbEntries() ) nb++; _scoreInfos->write(r, score, nb); } return r; } bool ManagerPrivate::submitWorldWide(const Score &score, QWidget *widget) const { if ( score.type()==Lost && !trackLostGames ) return true; if ( score.type()==Draw && !trackDrawGames ) return true; QUrl url = queryUrl(Submit); manager.additionalQueryItems(url, score); int s = (score.type()==Won ? score.score() : (int)score.type()); QString str = QString::number(s); Manager::addToQueryURL(url, QStringLiteral( "score" ), str); QCryptographicHash context(QCryptographicHash::Md5); context.addData(QString(_playerInfos->registeredName() + str).toLatin1()); Manager::addToQueryURL(url, QStringLiteral( "check" ), QLatin1String( context.result().toHex() )); return doQuery(url, widget); } void ManagerPrivate::exportHighscores(QTextStream &s) { uint tmp = _gameType; for (uint i=0; i<_nbGameTypes; i++) { setGameType(i); if ( _nbGameTypes>1 ) { if ( i!=0 ) s << endl; s << "--------------------------------" << endl; s << "Game type: " << manager.gameTypeLabel(_gameType, Manager::I18N) << endl; s << endl; } s << "Players list:" << endl; _playerInfos->exportToText(s); s << endl; s << "Highscores list:" << endl; _scoreInfos->exportToText(s); } setGameType(tmp); } } // namespace diff --git a/kexthighscore_internal.h b/kexthighscore_internal.h index 9f70e21..08b449e 100644 --- a/kexthighscore_internal.h +++ b/kexthighscore_internal.h @@ -1,282 +1,281 @@ /* This file is part of the KDE games library Copyright (C) 2001-2004 Nicolas Hadacek (hadacek@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KEXTHIGHSCORE_INTERNAL_H #define KEXTHIGHSCORE_INTERNAL_H #include #include #include #include #include #include "kexthighscore.h" #include -#include #include #include #include class QTextStream; class QDomNamedNodeMap; namespace KExtHighscore { class PlayerInfos; class Score; class Manager; //----------------------------------------------------------------------------- class RankItem : public Item { public: RankItem() : Item((uint)0, i18n("Rank"), Qt::AlignRight) {} QVariant read(uint i, const QVariant &value) const override { Q_UNUSED(value); return i; } QString pretty(uint i, const QVariant &value) const override { Q_UNUSED(value); return QString::number(i+1); } }; class NameItem : public Item { public: NameItem() : Item(QString(), i18n("Name"), Qt::AlignLeft) { setPrettySpecial(Anonymous); } }; class DateItem : public Item { public: DateItem() : Item(QDateTime(), i18n("Date"), Qt::AlignRight) { setPrettyFormat(DateTime); } }; class SuccessPercentageItem : public Item { public: SuccessPercentageItem() : Item((double)-1, i18n("Success"), Qt::AlignRight) { setPrettyFormat(Percentage); setPrettySpecial(NegativeNotDefined); } }; //----------------------------------------------------------------------------- class ItemContainer { public: ItemContainer(); ~ItemContainer(); void setItem(Item *item); const Item *item() const { return _item; } Item *item() { return _item; } void setName(const QString &name) { _name = name; } QString name() const { return _name; } void setGroup(const QString &group) { _group = group; } bool isStored() const { return !_group.isNull(); } void setSubGroup(const QString &subGroup) { _subGroup = subGroup; } bool canHaveSubGroup() const { return !_subGroup.isNull(); } static const char ANONYMOUS[]; // name assigned to anonymous players static const char ANONYMOUS_LABEL[]; QVariant read(uint i) const; QString pretty(uint i) const; void write(uint i, const QVariant &value) const; // for UInt QVariant (return new value) uint increment(uint i) const; private: Item *_item; QString _name, _group, _subGroup; QString entryName() const; ItemContainer(const ItemContainer &); ItemContainer &operator =(const ItemContainer &); }; //----------------------------------------------------------------------------- /** * Manage a bunch of @ref Item which are saved under the same group * in KHighscores config file. */ class ItemArray : public QVector { public: ItemArray(); virtual ~ItemArray(); virtual uint nbEntries() const = 0; const ItemContainer *item(const QString &name) const; ItemContainer *item(const QString &name); void addItem(const QString &name, Item *, bool stored = true, bool canHaveSubGroup = false); void setItem(const QString &name, Item *); int findIndex(const QString &name) const; void setGroup(const QString &group); void setSubGroup(const QString &subGroup); void read(uint k, Score &data) const; void write(uint k, const Score &data, uint maxNbLines) const; void exportToText(QTextStream &) const; private: QString _group, _subGroup; void _setItem(uint i, const QString &name, Item *, bool stored, bool canHaveSubGroup); ItemArray(const ItemArray &); ItemArray &operator =(const ItemArray &); }; //----------------------------------------------------------------------------- class ScoreInfos : public ItemArray { public: ScoreInfos(uint maxNbEntries, const PlayerInfos &infos); uint nbEntries() const override; uint maxNbEntries() const { return _maxNbEntries; } private: uint _maxNbEntries; }; //----------------------------------------------------------------------------- class ConfigGroup : public KConfigGroup { public: ConfigGroup(const QString &group = QLatin1String( "" )) : KConfigGroup(KSharedConfig::openConfig(), group) {} }; //----------------------------------------------------------------------------- class PlayerInfos : public ItemArray { public: PlayerInfos(); bool isNewPlayer() const { return _newPlayer; } bool isOldLocalPlayer() const { return _oldLocalPlayer; } uint nbEntries() const override; QString name() const { return item(QStringLiteral( "name" ))->read(_id).toString(); } bool isAnonymous() const; QString prettyName() const { return prettyName(_id); } QString prettyName(uint id) const { return item(QStringLiteral( "name" ))->pretty(id); } QString registeredName() const; QString comment() const { return item(QStringLiteral( "comment" ))->pretty(_id); } bool isWWEnabled() const; QString key() const; uint id() const { return _id; } uint oldLocalId() const { return _oldLocalId; } void createHistoItems(const QVector &scores, bool bound); QString histoName(int i) const; int histoSize() const; const QVector &histogram() const { return _histogram; } void submitScore(const Score &) const; // return true if the nickname is already used locally bool isNameUsed(const QString &name) const; void modifyName(const QString &newName) const; void modifySettings(const QString &newName, const QString &comment, bool WWEnabled, const QString &newKey) const; void removeKey(); private: bool _newPlayer, _bound, _oldLocalPlayer; uint _id, _oldLocalId; QVector _histogram; }; //----------------------------------------------------------------------------- class ManagerPrivate { public: ManagerPrivate(uint nbGameTypes, Manager &manager); void init(uint maxNbentries); ~ManagerPrivate(); bool modifySettings(const QString &newName, const QString &comment, bool WWEnabled, QWidget *widget); void setGameType(uint type); void checkFirst(); int submitLocal(const Score &score); int submitScore(const Score &score, QWidget *widget, bool askIfAnonymous); Score readScore(uint i) const; uint gameType() const { return _gameType; } uint nbGameTypes() const { return _nbGameTypes; } bool isWWHSAvailable() const { return !serverURL.isEmpty(); } ScoreInfos &scoreInfos() { return *_scoreInfos; } PlayerInfos &playerInfos() { return *_playerInfos; } KHighscore &hsConfig() { return *_hsConfig; } enum QueryType { Submit, Register, Change, Players, Scores }; QUrl queryUrl(QueryType type, const QString &newName = QLatin1String("")) const; void exportHighscores(QTextStream &); Manager &manager; QUrl serverURL; QString version; bool showStatistics, showDrawGames, trackLostGames, trackDrawGames; Manager::ShowMode showMode; private: KHighscore *_hsConfig; PlayerInfos *_playerInfos; ScoreInfos *_scoreInfos; bool _first; const uint _nbGameTypes; uint _gameType; // return -1 if not a local best score int rank(const Score &score) const; bool submitWorldWide(const Score &score, QWidget *parent) const; static bool doQuery(const QUrl &url, QWidget *parent, QDomNamedNodeMap *map = nullptr); static bool getFromQuery(const QDomNamedNodeMap &map, const QString &name, QString &value, QWidget *parent); void convertToGlobal(); }; } // namespace #endif diff --git a/kexthighscore_item.cpp b/kexthighscore_item.cpp index 72db0a2..ecab0f9 100644 --- a/kexthighscore_item.cpp +++ b/kexthighscore_item.cpp @@ -1,326 +1,325 @@ /* This file is part of the KDE games library Copyright (C) 2001-2003 Nicolas Hadacek (hadacek@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kexthighscore_item.h" #include #include -#include #include #include #include #include #include #include "kexthighscore_internal.h" #include "kexthighscore_gui.h" namespace KExtHighscore { //----------------------------------------------------------------------------- Item::Item(const QVariant &def, const QString &label, Qt::AlignmentFlag alignment) : _default(def), _label(label), _alignment(alignment), _format(NoFormat), _special(NoSpecial) {} Item::~Item() {} QVariant Item::read(uint, const QVariant &value) const { return value; } void Item::setPrettyFormat(Format format) { bool buint = ( _default.type()==QVariant::UInt ); bool bdouble = ( _default.type()==QVariant::Double ); bool bnum = ( buint || bdouble || _default.type()==QVariant::Int ); switch (format) { case OneDecimal: case Percentage: Q_ASSERT(bdouble); break; case MinuteTime: Q_ASSERT(bnum); break; case DateTime: Q_ASSERT( _default.type()==QVariant::DateTime ); break; case NoFormat: break; } _format = format; } void Item::setPrettySpecial(Special special) { bool buint = ( _default.type()==QVariant::UInt ); bool bnum = ( buint || _default.type()==QVariant::Double || _default.type()==QVariant::Int ); switch (special) { case ZeroNotDefined: Q_ASSERT(bnum); break; case NegativeNotDefined: Q_ASSERT(bnum && !buint); break; case DefaultNotDefined: break; case Anonymous: Q_ASSERT( _default.type()==QVariant::String ); break; case NoSpecial: break; } _special = special; } QString Item::timeFormat(uint n) { Q_ASSERT( n<=3600 && n!=0 ); n = 3600 - n; return QString::number(n / 60).rightJustified(2, QLatin1Char( '0' )) + QLatin1Char( ':' ) + QString::number(n % 60).rightJustified(2, QLatin1Char( '0' )); } QString Item::pretty(uint, const QVariant &value) const { switch (_special) { case ZeroNotDefined: if ( value.toUInt()==0 ) return QStringLiteral( "--" ); break; case NegativeNotDefined: if ( value.toInt()<0 ) return QStringLiteral( "--" ); break; case DefaultNotDefined: if ( value==_default ) return QStringLiteral( "--" ); break; case Anonymous: if ( value.toString()==QLatin1String( ItemContainer::ANONYMOUS ) ) return i18n(ItemContainer::ANONYMOUS_LABEL); break; case NoFormat: break; } switch (_format) { case OneDecimal: return QString::number(value.toDouble(), 'f', 1); case Percentage: return QString::number(value.toDouble(), 'f', 1) + QLatin1Char( '%' ); case MinuteTime: return timeFormat(value.toUInt()); case DateTime: if ( value.toDateTime().isNull() ) return QStringLiteral( "--" ); return QLocale().toString(value.toDateTime()); case NoSpecial: break; } return value.toString(); } //----------------------------------------------------------------------------- Score::Score(ScoreType type) : _type(type) { const ItemArray &items = internal->scoreInfos(); for (int i=0; iname()] = items[i]->item()->defaultValue(); } Score::~Score() {} QVariant Score::data(const QString &name) const { Q_ASSERT( _data.contains(name) ); return _data[name]; } void Score::setData(const QString &name, const QVariant &value) { Q_ASSERT( _data.contains(name) ); Q_ASSERT( _data[name].type()==value.type() ); _data[name] = value; } bool Score::isTheWorst() const { Score s; return score()==s.score(); } bool Score::operator <(const Score &score) { return internal->manager.isStrictlyLess(*this, score); } QDataStream &operator <<(QDataStream &s, const Score &score) { s << (quint8)score.type(); s << score._data; return s; } QDataStream &operator >>(QDataStream &s, Score &score) { quint8 type; s >> type; score._type = (ScoreType)type; s >> score._data; return s; } //----------------------------------------------------------------------------- MultiplayerScores::MultiplayerScores() {} MultiplayerScores::~MultiplayerScores() {} void MultiplayerScores::clear() { Score score; for (int i=0; i<_scores.size(); i++) { _nbGames[i] = 0; QVariant name = _scores[i].data(QStringLiteral( "name" )); _scores[i] = score; _scores[i].setData(QStringLiteral( "name" ), name); _scores[i]._data[QStringLiteral( "mean score" )] = double(0); _scores[i]._data[QStringLiteral( "nb won games" )] = uint(0); } } void MultiplayerScores::setPlayerCount(uint nb) { _nbGames.resize(nb); _scores.resize(nb); clear(); } void MultiplayerScores::setName(uint i, const QString &name) { _scores[i].setData(QStringLiteral( "name" ), name); } void MultiplayerScores::addScore(uint i, const Score &score) { QVariant name = _scores[i].data(QStringLiteral( "name" )); double mean = _scores[i].data(QStringLiteral( "mean score" )).toDouble(); uint won = _scores[i].data(QStringLiteral( "nb won games" )).toUInt(); _scores[i] = score; _scores[i].setData(QStringLiteral( "name" ), name); _nbGames[i]++; mean += (double(score.score()) - mean) / _nbGames[i]; _scores[i]._data[QStringLiteral( "mean score" )] = mean; if ( score.type()==Won ) won++; _scores[i]._data[QStringLiteral( "nb won games" )] = won; } void MultiplayerScores::show(QWidget *parent) { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); // check consistency if ( _nbGames.size()<2 ) qCWarning(GAMES_EXTHIGHSCORE) << "less than 2 players"; else { bool ok = true; uint nb = _nbGames[0]; for (int i=1; i<_nbGames.size(); i++) if ( _nbGames[i]!=nb ) ok = false; if (!ok) qCWarning(GAMES_EXTHIGHSCORE) << "players have not same number of games"; } // order the players according to the number of won games QVector ordered; for (int i=0; i<_scores.size(); i++) { uint won = _scores[i].data(QStringLiteral( "nb won games" )).toUInt(); double mean = _scores[i].data(QStringLiteral( "mean score" )).toDouble(); QVector::iterator it; for(it = ordered.begin(); it!=ordered.end(); ++it) { uint cwon = (*it).data(QStringLiteral( "nb won games" )).toUInt(); double cmean = (*it).data(QStringLiteral( "mean score" )).toDouble(); if ( wonwidget()); //hbox->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); //hbox->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); QWidget *vbox = new QWidget(page->widget()); hbox->addWidget(vbox); if ( _nbGames[0]==0 ) (void)new QLabel(i18n("No game played."), vbox); else { (void)new QLabel(i18n("Scores for last game:"), vbox); (void)new LastMultipleScoresList(ordered, vbox); } if ( _nbGames[0]>1 ) { vbox = new QWidget(page->widget()); hbox->addWidget(vbox); (void)new QLabel(i18n("Scores for the last %1 games:", _nbGames[0]), vbox); (void)new TotalMultipleScoresList(ordered, vbox); } //dialog.showButtonSeparator(false); dialog.addPage(page); dialog.exec(); } QDataStream &operator <<(QDataStream &s, const MultiplayerScores &score) { s << score._scores; s << score._nbGames; return s; } QDataStream &operator >>(QDataStream &s, MultiplayerScores &score) { s >> score._scores; s >> score._nbGames; return s; } } // namespace diff --git a/kexthighscore_tab.cpp b/kexthighscore_tab.cpp index 06bde5c..c8c0347 100644 --- a/kexthighscore_tab.cpp +++ b/kexthighscore_tab.cpp @@ -1,294 +1,293 @@ /* This file is part of the KDE games library Copyright (C) 2002 Nicolas Hadacek (hadacek@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kexthighscore_tab.h" #include -#include #include #include #include #include #include #include #include #include "kexthighscore.h" #include "kexthighscore_internal.h" namespace KExtHighscore { //----------------------------------------------------------------------------- PlayersCombo::PlayersCombo(QWidget *parent) : QComboBox(parent) { const PlayerInfos &p = internal->playerInfos(); for (uint i = 0; i' )); connect(this, static_cast(&PlayersCombo::activated), this, &PlayersCombo::activatedSlot); } void PlayersCombo::activatedSlot(int i) { const PlayerInfos &p = internal->playerInfos(); if ( i==(int)p.nbEntries() ) emit allSelected(); else if ( i==(int)p.nbEntries()+1 ) emit noneSelected(); else emit playerSelected(i); } void PlayersCombo::load() { const PlayerInfos &p = internal->playerInfos(); for (uint i = 0; isetMargin( QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin) ); //top->setSpacing( QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) ); QHBoxLayout *hbox = new QHBoxLayout; top->addLayout(hbox); QLabel *label = new QLabel(i18n("Select player:"), this); hbox->addWidget(label); _combo = new PlayersCombo(this); connect(_combo, &PlayersCombo::playerSelected, this, &AdditionalTab::playerSelected); connect(_combo, &PlayersCombo::allSelected, this, &AdditionalTab::allSelected); hbox->addWidget(_combo); hbox->addStretch(1); } void AdditionalTab::init() { uint id = internal->playerInfos().id(); _combo->setCurrentIndex(id); playerSelected(id); } void AdditionalTab::allSelected() { display(internal->playerInfos().nbEntries()); } QString AdditionalTab::percent(uint n, uint total, bool withBraces) { if ( n==0 || total==0 ) return QString(); QString s = QStringLiteral( "%1%").arg(100.0 * n / total, 0, 'f', 1); return (withBraces ? QLatin1Char('(') + s + QLatin1Char( ')' ) : s); } void AdditionalTab::load() { _combo->load(); } //----------------------------------------------------------------------------- const char *StatisticsTab::COUNT_LABELS[Nb_Counts] = { I18N_NOOP("Total:"), I18N_NOOP("Won:"), I18N_NOOP("Lost:"), I18N_NOOP("Draw:") }; const char *StatisticsTab::TREND_LABELS[Nb_Trends] = { I18N_NOOP("Current:"), I18N_NOOP("Max won:"), I18N_NOOP("Max lost:") }; StatisticsTab::StatisticsTab(QWidget *parent) : AdditionalTab(parent) { setObjectName( QStringLiteral("statistics_tab" )); // construct GUI QVBoxLayout *top = static_cast(layout()); QHBoxLayout *hbox = new QHBoxLayout; QVBoxLayout *vbox = new QVBoxLayout; hbox->addLayout(vbox); top->addLayout(hbox); QGroupBox *group = new QGroupBox(i18n("Game Counts"), this); vbox->addWidget(group); QGridLayout *gridLay = new QGridLayout(group); //gridLay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); for (uint k=0; kshowDrawGames ) continue; gridLay->addWidget(new QLabel(i18n(COUNT_LABELS[k]), group), k, 0); _nbs[k] = new QLabel(group); gridLay->addWidget(_nbs[k], k, 1); _percents[k] = new QLabel(group); gridLay->addWidget(_percents[k], k, 2); } group = new QGroupBox(i18n("Trends"), this); vbox->addWidget(group); gridLay = new QGridLayout(group); //gridLay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); for (uint k=0; kaddWidget(new QLabel(i18n(TREND_LABELS[k]), group), k, 0); _trends[k] = new QLabel(group); gridLay->addWidget(_trends[k], k, 1); } hbox->addStretch(1); top->addStretch(1); } void StatisticsTab::load() { AdditionalTab::load(); const PlayerInfos &pi = internal->playerInfos(); uint nb = pi.nbEntries(); _data.resize(nb+1); for (int i=0; i<_data.size()-1; i++) { _data[i].count[Total] = pi.item(QStringLiteral( "nb games" ))->read(i).toUInt(); _data[i].count[Lost] = pi.item(QStringLiteral( "nb lost games" ))->read(i).toUInt() + pi.item(QStringLiteral( "nb black marks" ))->read(i).toUInt(); // legacy _data[i].count[Draw] = pi.item(QStringLiteral( "nb draw games" ))->read(i).toUInt(); _data[i].count[Won] = _data[i].count[Total] - _data[i].count[Lost] - _data[i].count[Draw]; _data[i].trend[CurrentTrend] = pi.item(QStringLiteral( "current trend" ))->read(i).toInt(); _data[i].trend[WonTrend] = pi.item(QStringLiteral( "max won trend" ))->read(i).toUInt(); _data[i].trend[LostTrend] = -(int)pi.item(QStringLiteral( "max lost trend" ))->read(i).toUInt(); } for (int k=0; kshowDrawGames ) continue; _nbs[k]->setText(QString::number(d.count[k])); _percents[k]->setText(percent(d, Count(k))); } for (uint k=0; k0 ) s = QLatin1Char( '+' ); int prec = (i==internal->playerInfos().nbEntries() ? 1 : 0); _trends[k]->setText(s + QString::number(d.trend[k], 'f', prec)); } } //----------------------------------------------------------------------------- HistogramTab::HistogramTab(QWidget *parent) : AdditionalTab(parent) { setObjectName( QStringLiteral("histogram_tab" )); // construct GUI QVBoxLayout *top = static_cast(layout()); _list = new QTreeWidget(this); _list->setSelectionMode(QAbstractItemView::NoSelection); /// @todo to port or no more necessary ? // _list->setItemMargin(3); _list->setAllColumnsShowFocus(true); _list->setSortingEnabled(false); _list->header()->setSectionsClickable(false); _list->header()->setSectionsMovable(false); top->addWidget(_list); _list->headerItem()->setText(0,i18n("From")); _list->headerItem()->setText(1,i18n("To")); _list->headerItem()->setText(2,i18n("Count")); _list->headerItem()->setText(3,i18n("Percent")); for (int i=0; i<4; i++) _list->headerItem()->setTextAlignment(i, Qt::AlignRight); _list->headerItem()->setText(4,QString()); const Item *sitem = internal->scoreInfos().item(QStringLiteral( "score" ))->item(); const PlayerInfos &pi = internal->playerInfos(); const QVector &sh = pi.histogram(); for (int k=1; k<( int )pi.histoSize(); k++) { QString s1 = sitem->pretty(0, sh[k-1]); QString s2; if ( k==sh.size() ) s2 = QStringLiteral( "..." ); else if ( sh[k]!=sh[k-1]+1 ) s2 = sitem->pretty(0, sh[k]); QStringList items; items << s1 << s2; (void)new QTreeWidgetItem(_list, items); } } void HistogramTab::load() { AdditionalTab::load(); const PlayerInfos &pi = internal->playerInfos(); uint n = pi.nbEntries(); uint s = pi.histoSize() - 1; _counts.resize((n+1) * s); _data.fill(0, n+1); for (uint k=0; kread(i).toUInt(); _counts[i*s + k] = nb; _counts[n*s + k] += nb; _data[i] += nb; _data[n] += nb; } } init(); } void HistogramTab::display(uint i) { const PlayerInfos &pi = internal->playerInfos(); uint itemNum = 0; QTreeWidgetItem *item = _list->topLevelItem(itemNum); uint s = pi.histoSize() - 1; for (int k=s-1; k>=0; k--) { uint nb = _counts[i*s + k]; item->setText(2, QString::number(nb)); item->setText(3, percent(nb, _data[i])); uint width = (_data[i]==0 ? 0 : qRound(150.0 * nb / _data[i])); QPixmap pixmap(width, 10); pixmap.fill(Qt::blue); item->setData(4, Qt::DecorationRole, pixmap); itemNum++; item = _list->topLevelItem(itemNum); } } } // namespace diff --git a/kreversiview.h b/kreversiview.h index ec54181..fc73331 100644 --- a/kreversiview.h +++ b/kreversiview.h @@ -1,184 +1,183 @@ /* Copyright 2006 Dmitry Suzdalev Copyright 2010 Brian Croom Copyright 2013 Denis Kuplyakov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KREVERSI_VIEW_H #define KREVERSI_VIEW_H -#include #include #include #include "commondefs.h" #include "kreversigame.h" /** * This class provides graphical representation of KReversiGame * using QML for graphics display. * It displays the reversi board in its current state, * receives a mouse events, translates them with signals, * receives board-changed notifications, nicely animates them. */ class KReversiView : public KgDeclarativeView { Q_OBJECT public: explicit KReversiView(KReversiGame* game, QWidget *parent, KgThemeProvider *provider); /** * Destructor used to delete game object owned by class */ ~KReversiView(); /** * Sets the game object which this view will visualize/use * * @param game pointer to game object for visualization. View takes * ownership over game object and will delete it */ void setGame(KReversiGame* game); /** * Sets the chips prefix to @p chipsPrefix */ void setChipsPrefix(ChipsPrefix chipsPrefix); /** * Sets whether to show board labels. * * @param show @c true to show labels * @c false to hide labels */ void setShowBoardLabels(bool show); /** * Sets the animation speed * * @param speed 0 - slow, 1 - normal, 2 - fast * * @return time for animation in milliseconds to pass it to KReversiGame */ void setAnimationSpeed(int speed); public slots: /** * This will make view visually mark the last made move * * @param show @c true to show last move * @c false to don't show last move */ void setShowLastMove(bool show); /** * This will make view visually mark squares with possible moves * * @param show @c true to show legal moves * @c false to don't show legal moves */ void setShowLegalMoves(bool show); /** * Shows hint for player */ void slotHint(); private slots: /** * Triggered on user click on board, connected to QML signal * * @param row index of the clicked cell row (starting from 0) * @param col index of the clicked cell column (starting from 0) */ void onPlayerMove(int row, int col); /** * Synchronizes graphical board with m_game's board */ void updateBoard(); void gameMoveFinished(); void gameOver(); void whitePlayerCantMove(); void blackPlayerCantMove(); signals: void userMove(KReversiPos); private: /** * 40 ms time per frame for animation */ static const int ANIMATION_SPEED_SLOW = 40 * 12; /** * 25 ms time per frame for animation */ static const int ANIMATION_SPEED_NORMAL = 25 * 12; /** * 15 ms time per frame for animation */ static const int ANIMATION_SPEED_FAST = 15 * 12; /** * Used to provide access to QML-implemented board */ QObject *m_qml_root; /** * Used to access theme engine from QML */ KgThemeProvider *m_provider; /** * Position of calculated hint. It is not valid if there is no hint */ KReversiMove m_hint; /** * Current animation time */ int m_delay; /** * Pointer to game object */ KReversiGame *m_game; /** * The SVG element prefix for the current chip set */ ChipsPrefix m_ColouredChips; /** * If true, then last made turn will be shown to the player */ bool m_showLastMove; /** * If true, then all possible moves will be shown to the player */ bool m_showLegalMoves; /** * If true board labels will be rendered */ bool m_showLabels; /** * Used to handle animation duration due to sequental turning of chips */ int m_maxDelay; }; #endif diff --git a/mainwindow.cpp b/mainwindow.cpp index 800cee1..77a2d46 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,441 +1,442 @@ /******************************************************************* * * Copyright 2006 Dmitry Suzdalev * Copyright 2010 Brian Croom * Copyright 2013 Denis Kuplyakov * * This file is part of the KDE project "KReversi" * * KReversi is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KReversi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KReversi; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ********************************************************************/ #include "mainwindow.h" #include #include #include #include +#include #include #include #include #include #include "commondefs.h" #include "kreversihumanplayer.h" #include "kreversicomputerplayer.h" #include "kexthighscore.h" static const int BLACK_STATUSBAR_ID = 1; static const int WHITE_STATUSBAR_ID = 2; static const int COMMON_STATUSBAR_ID = 0; KReversiMainWindow::KReversiMainWindow(QWidget* parent, bool startDemo) : KXmlGuiWindow(parent), m_startDialog(nullptr), m_view(nullptr), m_game(nullptr), m_historyDock(nullptr), m_historyView(nullptr), m_firstShow(true), m_startInDemoMode(startDemo), m_undoAct(nullptr), m_hintAct(nullptr) { memset(m_player, 0, sizeof(m_player)); m_provider = new KgThemeProvider(); m_provider->discoverThemes("appdata", QStringLiteral("pics")); for (auto &label : m_statusBarLabel) { label = new QLabel(this); label->setAlignment(Qt::AlignCenter); statusBar()->addWidget(label, 1); } m_statusBarLabel[common]->setText(i18n("Press start game!")); // initialize difficulty stuff Kg::difficulty()->addStandardLevelRange( KgDifficultyLevel::VeryEasy, KgDifficultyLevel::Impossible, KgDifficultyLevel::Easy //default ); KgDifficultyGUI::init(this); connect(Kg::difficulty(), &KgDifficulty::currentLevelChanged, this, &KReversiMainWindow::levelChanged); Kg::difficulty()->setEditable(false); // initialize history dock m_historyView = new QListWidget(this); m_historyView->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); m_historyDock = new QDockWidget(i18n("Move History")); m_historyDock->setWidget(m_historyView); m_historyDock->setObjectName(QStringLiteral("history_dock")); m_historyDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::RightDockWidgetArea, m_historyDock); // create main game view m_view = new KReversiView(m_game, this, m_provider); setCentralWidget(m_view); // initialise dialog handler m_startDialog = new StartGameDialog(this, m_provider); connect(m_startDialog, &StartGameDialog::startGame, this, &KReversiMainWindow::slotDialogReady); // initialise actions setupActionsInit(); // load saved settings loadSettings(); setupGUI(qApp->desktop()->availableGeometry().size() * 0.7); m_historyDock->hide(); } KReversiMainWindow::~KReversiMainWindow() { clearPlayers(); delete m_provider; } void KReversiMainWindow::setupActionsInit() { // Common actions KStandardGameAction::gameNew(this, SLOT(slotNewGame()), actionCollection()); KStandardGameAction::highscores(this, SLOT(slotHighscores()), actionCollection()); KStandardGameAction::quit(this, SLOT(close()), actionCollection()); // Undo m_undoAct = KStandardGameAction::undo(this, SLOT(slotUndo()), actionCollection()); m_undoAct->setEnabled(false); // nothing to undo at the start of the game // Hint m_hintAct = KStandardGameAction::hint(m_view, SLOT(slotHint()), actionCollection()); m_hintAct->setEnabled(false); // Last move m_showLast = new KToggleAction(QIcon::fromTheme(QStringLiteral("lastmoves")), i18n("Show Last Move"), this); actionCollection()->addAction(QStringLiteral("show_last_move"), m_showLast); connect(m_showLast, &KToggleAction::triggered, m_view, &KReversiView::setShowLastMove); // Legal moves m_showLegal = new KToggleAction(QIcon::fromTheme(QStringLiteral("legalmoves")), i18n("Show Legal Moves"), this); actionCollection()->addAction(QStringLiteral("show_legal_moves"), m_showLegal); connect(m_showLegal, &KToggleAction::triggered, m_view, &KReversiView::setShowLegalMoves); // Animation speed m_animSpeedAct = new KSelectAction(i18n("Animation Speed"), this); actionCollection()->addAction(QStringLiteral("anim_speed"), m_animSpeedAct); QStringList acts; acts << i18n("Slow") << i18n("Normal") << i18n("Fast"); m_animSpeedAct->setItems(acts); connect(m_animSpeedAct, static_cast(&KSelectAction::triggered), this, &KReversiMainWindow::slotAnimSpeedChanged); // Chip's color m_coloredChipsAct = new KToggleAction(i18n("Use Colored Chips"), this); actionCollection()->addAction(QStringLiteral("use_colored_chips"), m_coloredChipsAct); connect(m_coloredChipsAct, &KToggleAction::triggered, this, &KReversiMainWindow::slotUseColoredChips); // Move history // NOTE: read/write this from/to config file? Or not necessary? m_showMovesAct = m_historyDock->toggleViewAction(); m_showMovesAct->setIcon(QIcon::fromTheme(QStringLiteral("view-history"))); m_showMovesAct->setText(i18n("Show Move History")); actionCollection()->addAction(QStringLiteral("show_moves"), m_showMovesAct); connect(m_historyDock, &QDockWidget::visibilityChanged, this, &KReversiMainWindow::slotToggleBoardLabels); } void KReversiMainWindow::loadSettings() { // Animation speed m_animSpeedAct->setCurrentItem(Preferences::animationSpeed()); m_view->setAnimationSpeed(Preferences::animationSpeed()); // Chip's color m_coloredChipsAct->setChecked(Preferences::useColoredChips()); m_view->setChipsPrefix(Preferences::useColoredChips() ? Colored : BlackWhite); m_startDialog->setChipsPrefix(Preferences::useColoredChips() ? Colored : BlackWhite); } void KReversiMainWindow::levelChanged() { // we are assuming that level can be changed here only when it is // USER-AI or AI-USER match int skill = Utils::difficultyLevelToInt(); if (m_nowPlayingInfo.type[White] == GameStartInformation::AI) ((KReversiComputerPlayer *)(m_player[White]))->setSkill(skill); else if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human) ((KReversiComputerPlayer *)(m_player[Black]))->setSkill(skill); } void KReversiMainWindow::slotAnimSpeedChanged(int speed) { m_view->setAnimationSpeed(speed); Preferences::setAnimationSpeed(speed); Preferences::self()->save(); } void KReversiMainWindow::slotUseColoredChips(bool toggled) { ChipsPrefix chipsPrefix = m_coloredChipsAct->isChecked() ? Colored : BlackWhite; m_view->setChipsPrefix(chipsPrefix); m_startDialog->setChipsPrefix(chipsPrefix); Preferences::setUseColoredChips(toggled); Preferences::self()->save(); } void KReversiMainWindow::slotToggleBoardLabels(bool toggled) { m_view->setShowBoardLabels(toggled); } void KReversiMainWindow::slotNewGame() { m_startDialog->exec(); } void KReversiMainWindow::slotGameOver() { m_hintAct->setEnabled(false); m_undoAct->setEnabled(m_game->canUndo()); int blackScore = m_game->playerScore(Black); int whiteScore = m_game->playerScore(White); bool storeScore = false; KExtHighscore::Score score; QString res; if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { // we are playing black storeScore = true; KExtHighscore::setGameType(((KReversiComputerPlayer *)m_player[White])->lowestSkill()); score.setScore(blackScore); if (blackScore == whiteScore) { res = i18n("Game is drawn!"); score.setType(KExtHighscore::Draw); } else if (blackScore > whiteScore) { res = i18n("You win!"); score.setType(KExtHighscore::Won); } else { res = i18n("You have lost!"); score.setType(KExtHighscore::Lost); } } else if (m_nowPlayingInfo.type[White] == GameStartInformation::Human && m_nowPlayingInfo.type[Black] == GameStartInformation::AI) { // we are playing white storeScore = true; KExtHighscore::setGameType(((KReversiComputerPlayer *)m_player[Black])->lowestSkill()); score.setScore(whiteScore); if (blackScore == whiteScore) { res = i18n("Game is drawn!"); score.setType(KExtHighscore::Draw); } else if (blackScore < whiteScore) { res = i18n("You win!"); score.setType(KExtHighscore::Won); } else { res = i18n("You have lost!"); score.setType(KExtHighscore::Lost); } } else if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human && m_nowPlayingInfo.type[White] == GameStartInformation::Human) { // friends match if (blackScore == whiteScore) { res = i18n("Game is drawn!"); } else if (blackScore > whiteScore) { res = i18n("%1 has won!", m_nowPlayingInfo.name[Black]); } else { res = i18n("%1 has won!", m_nowPlayingInfo.name[White]); } } else { // using Black White names in other cases if (blackScore == whiteScore) { res = i18n("Game is drawn!"); } else if (blackScore > whiteScore) { res = i18n("%1 has won!", Utils::colorToString(Black)); } else { res = i18n("%1 has won!", Utils::colorToString(White)); } } if (m_nowPlayingInfo.type[Black] == GameStartInformation::AI && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { res += i18n("\n%1: %2", Utils::colorToString(Black), blackScore); res += i18n("\n%1: %2", Utils::colorToString(White), whiteScore); } else { res += i18n("\n%1: %2", m_nowPlayingInfo.name[Black], blackScore); res += i18n("\n%1: %2", m_nowPlayingInfo.name[White], whiteScore); } KMessageBox::information(this, res, i18n("Game over")); if (storeScore) KExtHighscore::submitScore(score, this); } void KReversiMainWindow::slotMoveFinished() { updateHistory(); updateStatusBar(); m_hintAct->setEnabled(m_game->isHintAllowed()); m_undoAct->setEnabled(m_game->canUndo()); } void KReversiMainWindow::updateHistory() { MoveList history = m_game->getHistory(); m_historyView->clear(); for (int i = 0; i < history.size(); i++) { QString numStr = QString::number(i + 1) + QStringLiteral(". "); m_historyView->addItem(numStr + Utils::moveToString(history.at(i))); } QListWidgetItem *last = m_historyView->item(m_historyView->count() - 1); m_historyView->setCurrentItem(last); m_historyView->scrollToItem(last); } void KReversiMainWindow::slotUndo() { // scene will automatically notice that it needs to update m_game->undo(); updateHistory(); updateStatusBar(); m_undoAct->setEnabled(m_game->canUndo()); m_hintAct->setEnabled(m_game->isHintAllowed()); } void KReversiMainWindow::slotHighscores() { KExtHighscore::show(this); } void KReversiMainWindow::slotDialogReady() { GameStartInformation info = m_startDialog->createGameStartInformation(); receivedGameStartInformation(info); } void KReversiMainWindow::showEvent(QShowEvent*) { if (m_firstShow && m_startInDemoMode) { qDebug() << "starting demo..."; startDemo(); } else if (m_firstShow) { QTimer::singleShot(0, this, &KReversiMainWindow::slotNewGame); } m_firstShow = false; } void KReversiMainWindow::updateStatusBar() { if (m_game->isGameOver()) { m_statusBarLabel[common]->setText(i18n("GAME OVER")); } if (m_nowPlayingInfo.type[Black] == GameStartInformation::AI && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { // using Black White names m_statusBarLabel[black]->setText(i18n("%1: %2", Utils::colorToString(Black), m_game->playerScore(Black))); m_statusBarLabel[white]->setText(i18n("%1: %2", Utils::colorToString(White), m_game->playerScore(White))); if (!m_game->isGameOver()) { m_statusBarLabel[common]->setText(i18n("%1 turn", Utils::colorToString(m_game->currentPlayer()))); } } else { // using player's names m_statusBarLabel[black]->setText(i18n("%1: %2", m_nowPlayingInfo.name[Black], m_game->playerScore(Black))); m_statusBarLabel[white]->setText(i18n("%1: %2", m_nowPlayingInfo.name[White], m_game->playerScore(White))); if (!m_game->isGameOver() && m_game->currentPlayer() != NoColor) { m_statusBarLabel[common]->setText(i18n("%1's turn", m_nowPlayingInfo.name[m_game->currentPlayer()])); } } } // TODO: test it!!! void KReversiMainWindow::startDemo() { GameStartInformation info; info.name[0] = info.name[1] = i18n("Computer"); info.type[0] = info.type[1] = GameStartInformation::AI; info.skill[0] = info.skill[1] = Utils::difficultyLevelToInt(); receivedGameStartInformation(info); } void KReversiMainWindow::clearPlayers() { for (int i = 0; i < 2; i++) // iterating through white to black if (m_player[i]) { m_player[i]->disconnect(); delete m_player[i]; m_player[i] = nullptr; } } void KReversiMainWindow::receivedGameStartInformation(const GameStartInformation &info) { clearPlayers(); m_nowPlayingInfo = info; for (int i = 0; i < 2; i++) // iterating through black and white if (info.type[i] == GameStartInformation::AI) { m_player[i] = new KReversiComputerPlayer(ChipColor(i), info.name[i]); ((KReversiComputerPlayer *)(m_player[i]))->setSkill(info.skill[i]); levelChanged(); } else { m_player[i] = new KReversiHumanPlayer(ChipColor(i), info.name[i]); } m_game = new KReversiGame(m_player[Black], m_player[White]); m_view->setGame(m_game); connect(m_game, &KReversiGame::gameOver, this, &KReversiMainWindow::slotGameOver); connect(m_game, &KReversiGame::moveFinished, this, &KReversiMainWindow::slotMoveFinished); for (int i = 0; i < 2; i++) // iterating white to black if (info.type[i] == GameStartInformation::Human) connect(m_view, &KReversiView::userMove, (KReversiHumanPlayer *)(m_player[i]), &KReversiHumanPlayer::onUICellClick); updateStatusBar(); updateHistory(); if (info.type[White] == GameStartInformation::AI && info.type[Black] == GameStartInformation::Human) { Kg::difficulty()->setEditable(true); Kg::difficulty()->select(Utils::intToDifficultyLevel(info.skill[White])); } else if (info.type[White] == GameStartInformation::Human && info.type[Black] == GameStartInformation::AI) { Kg::difficulty()->setEditable(true); Kg::difficulty()->select(Utils::intToDifficultyLevel(info.skill[Black])); } else Kg::difficulty()->setEditable(false); m_hintAct->setEnabled(m_game->isHintAllowed()); m_undoAct->setEnabled(m_game->canUndo()); } diff --git a/mainwindow.h b/mainwindow.h index 546568b..f298284 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -1,103 +1,102 @@ /******************************************************************* * * Copyright 2006 Dmitry Suzdalev * Copyright 2013 Denis Kuplyakov * * This file is part of the KDE project "KReversi" * * KReversi is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KReversi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KReversi; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ********************************************************************/ #ifndef KREVERSI_MAIN_WINDOW_H #define KREVERSI_MAIN_WINDOW_H -#include #include #include #include #include #include #include "preferences.h" #include "startgamedialog.h" #include "kreversigame.h" #include "kreversiview.h" #include class KReversiGame; class KReversiView; class QAction; class KReversiMainWindow : public KXmlGuiWindow { Q_OBJECT public: explicit KReversiMainWindow(QWidget* parent = nullptr, bool startDemo = false); ~KReversiMainWindow(); public slots: void slotNewGame(); void levelChanged(); void slotAnimSpeedChanged(int); void slotUndo(); void slotMoveFinished(); void slotGameOver(); void slotUseColoredChips(bool); void slotToggleBoardLabels(bool); void slotHighscores(); void slotDialogReady(); private: void showEvent(QShowEvent*) override; void setupActionsInit(); void setupActionsStart(); void setupActionsGame(); void loadSettings(); void updateStatusBar(); void updateHistory(); void startDemo(); void clearPlayers(); void receivedGameStartInformation(const GameStartInformation &info); KReversiPlayer *m_player[2]; StartGameDialog *m_startDialog; GameStartInformation m_nowPlayingInfo; KReversiView *m_view; KReversiGame *m_game; QDockWidget *m_historyDock; QListWidget *m_historyView; bool m_firstShow; bool m_startInDemoMode; KgThemeProvider *m_provider; QAction *m_undoAct; QAction *m_hintAct; KToggleAction *m_showLast; KToggleAction *m_showLegal; QAction *m_showMovesAct; KSelectAction *m_animSpeedAct; KToggleAction *m_coloredChipsAct; enum { common = 1, black, white }; QLabel *m_statusBarLabel[4]; }; #endif diff --git a/startgamedialog.cpp b/startgamedialog.cpp index 7f2e35b..356d853 100644 --- a/startgamedialog.cpp +++ b/startgamedialog.cpp @@ -1,184 +1,181 @@ /******************************************************************* * * Copyright 2013 Denis Kuplyakov * * This file is part of the KDE project "KReversi" * * KReversi is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KReversi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KReversi; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ********************************************************************/ #include "startgamedialog.h" #include "ui_startgamedialog.h" -#include #include #include -#include #include #include #include #include -#include #include #include StartGameDialog::StartGameDialog(QWidget *parent, KgThemeProvider *provider) : QDialog(parent), ui(new Ui::StartGameDialog), m_provider(provider), m_chipsPrefix(BlackWhite) { setModal(true); setWindowTitle(i18n("New game")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Close); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &StartGameDialog::slotAccepted); connect(buttonBox, &QDialogButtonBox::rejected, this, &StartGameDialog::reject); okButton->setText(i18n("Start game")); okButton->setToolTip(i18n("Let's start playing!")); buttonBox->button(QDialogButtonBox::Close)->setText(i18n("Quit")); buttonBox->button(QDialogButtonBox::Close)->setToolTip(i18n("Quit KReversi")); m_contents = new QWidget(this); mainLayout->addWidget(m_contents); mainLayout->addWidget(buttonBox); ui->setupUi(m_contents); loadChipImages(); ui->whiteTypeGroup->setId(ui->whiteHuman, GameStartInformation::Human); ui->whiteTypeGroup->setId(ui->whiteAI, GameStartInformation::AI); ui->whiteAI->setIcon(QIcon::fromTheme(QStringLiteral("computer"))); ui->whiteHuman->setIcon(QIcon::fromTheme(QStringLiteral("user-identity"))); ui->blackTypeGroup->setId(ui->blackHuman, GameStartInformation::Human); ui->blackTypeGroup->setId(ui->blackAI, GameStartInformation::AI); ui->blackAI->setIcon(QIcon::fromTheme(QStringLiteral("computer"))); ui->blackHuman->setIcon(QIcon::fromTheme(QStringLiteral("user-identity"))); QList< const KgDifficultyLevel * > diffList = Kg::difficulty()->levels(); const QIcon icon = QIcon::fromTheme(QStringLiteral("games-difficult")); for (int i = 0; i < diffList.size(); ++i) { ui->blackSkill->addItem(icon, diffList.at(i)->title()); ui->whiteSkill->addItem(icon, diffList.at(i)->title()); if (diffList.at(i)->isDefault()) { ui->whiteSkill->setCurrentIndex(i); ui->blackSkill->setCurrentIndex(i); } } connect(ui->blackTypeGroup, static_cast(&QButtonGroup::buttonClicked), this, &StartGameDialog::slotUpdateBlack); connect(ui->whiteTypeGroup, static_cast(&QButtonGroup::buttonClicked), this, &StartGameDialog::slotUpdateWhite); slotUpdateBlack(GameStartInformation::Human); slotUpdateWhite(GameStartInformation::AI); } StartGameDialog::~StartGameDialog() { delete ui; } void StartGameDialog::loadChipImages() { QSvgRenderer svgRenderer; svgRenderer.load(m_provider->currentTheme()->graphicsPath()); QPixmap blackChip(QSize(46, 46)); blackChip.fill(Qt::transparent); QPixmap whiteChip(QSize(46, 46)); whiteChip.fill(Qt::transparent); QPainter *painter = new QPainter(&blackChip); QString prefix = Utils::chipPrefixToString(m_chipsPrefix); svgRenderer.render(painter, prefix + QStringLiteral("_1")); delete painter; painter = new QPainter(&whiteChip); // TODO: get 12 from some global constant that is shared with QML svgRenderer.render(painter, prefix + QStringLiteral("_12")); delete painter; ui->blackLabel->setPixmap(blackChip); ui->whiteLabel->setPixmap(whiteChip); QGraphicsDropShadowEffect *blackShadow = new QGraphicsDropShadowEffect(this); blackShadow->setBlurRadius(10.0); blackShadow->setColor(Qt::black); blackShadow->setOffset(0.0); QGraphicsDropShadowEffect *whiteShadow = new QGraphicsDropShadowEffect(this); whiteShadow->setBlurRadius(10.0); whiteShadow->setColor(Qt::black); whiteShadow->setOffset(0.0); ui->blackLabel->setGraphicsEffect(blackShadow); ui->whiteLabel->setGraphicsEffect(whiteShadow); } void StartGameDialog::slotAccepted() { emit startGame(); accept(); } GameStartInformation StartGameDialog::createGameStartInformation() const { GameStartInformation info; info.name[Black] = ui->blackName->text(); info.name[White] = ui->whiteName->text(); info.type[Black] = (GameStartInformation::PlayerType)ui->blackTypeGroup->checkedId(); info.type[White] = (GameStartInformation::PlayerType)ui->whiteTypeGroup->checkedId(); info.skill[Black] = ui->blackSkill->currentIndex(); info.skill[White] = ui->whiteSkill->currentIndex(); return info; } void StartGameDialog::setChipsPrefix(ChipsPrefix prefix) { m_chipsPrefix = prefix; loadChipImages(); } void StartGameDialog::slotUpdateBlack(int clickedId) { ui->blackSkill->setEnabled(clickedId == GameStartInformation::AI); ui->blackName->setEnabled(clickedId == GameStartInformation::Human); if (clickedId == GameStartInformation::Human) ui->blackName->setText(m_user.loginName()); else ui->blackName->setText(i18n("Computer")); } void StartGameDialog::slotUpdateWhite(int clickedId) { ui->whiteSkill->setEnabled(clickedId == GameStartInformation::AI); ui->whiteName->setEnabled(clickedId == GameStartInformation::Human); if (clickedId == GameStartInformation::Human) ui->whiteName->setText(m_user.loginName()); else ui->whiteName->setText(i18n("Computer")); }