diff --git a/src/common/HighScoresManager.cxx b/src/common/HighScoresManager.cxx index 2197ba25e..bcf840d76 100644 --- a/src/common/HighScoresManager.cxx +++ b/src/common/HighScoresManager.cxx @@ -25,6 +25,7 @@ "", ; special label (5 chars) B, ; special format (BCD, HEX) 0, ; zero-based special + 0, ; invert score order Addresses (in hex): n*p-times xx, ; score info for each player, high to low xx, ; variation address (if more than 1 variation) @@ -141,6 +142,7 @@ bool HighScoresManager::get(const Properties& props, uInt32& numPlayersR, uInt32 info.special = specialLabel(props); info.specialBCD = specialBCD(props); info.specialZeroBased = specialZeroBased(props); + info.scoreInvert = scoreInvert(props); info.playersAddr = playerAddress(props); info.varsAddr = varAddress(props); @@ -177,12 +179,14 @@ void HighScoresManager::set(Properties& props, uInt32 numPlayers, uInt32 numVari props.set(PropType::Cart_Variations, to_string(min(numVariations, MAX_VARIATIONS))); // fill from the back to skip default values + if (output.length() || info.scoreInvert != DEFAULT_SCORE_REVERSED) + output.insert(0, info.scoreInvert ? ",1" : ",0"); if (output.length() || info.specialZeroBased != DEFAULT_SPECIAL_ZERO_BASED) - output = info.specialZeroBased ? ",1" : ",0"; + output.insert(0, info.specialZeroBased ? ",1" : ",0"); if (output.length() || info.specialBCD != DEFAULT_SPECIAL_BCD) output.insert(0, info.specialBCD ? ",B" : ",D"); if (output.length() || !info.special.empty()) - output.insert(0, "," + info.special); + output.insert(0, "," + (info.special.empty() ? "-" : info.special)); if (output.length() || info.varsZeroBased != DEFAULT_VARS_ZERO_BASED) output.insert(0, info.varsZeroBased ? ",1" : ",0"); @@ -220,7 +224,7 @@ void HighScoresManager::set(Properties& props, uInt32 numPlayers, uInt32 numVari // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 HighScoresManager::numDigits(const Properties& props) const { - string digits = getPropIdx(props, PropType::Cart_Formats, 0); + string digits = getPropIdx(props, PropType::Cart_Formats, IDX_SCORE_DIGITS); return min(uInt32(stringToInt(digits, DEFAULT_DIGITS)), MAX_SCORE_DIGITS); } @@ -228,7 +232,7 @@ uInt32 HighScoresManager::numDigits(const Properties& props) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 HighScoresManager::trailingZeroes(const Properties& props) const { - string trailing = getPropIdx(props, PropType::Cart_Formats, 1); + string trailing = getPropIdx(props, PropType::Cart_Formats, IDX_TRAILING_ZEROES); return min(uInt32(stringToInt(trailing, DEFAULT_TRAILING)), MAX_TRAILING); } @@ -236,7 +240,7 @@ uInt32 HighScoresManager::trailingZeroes(const Properties& props) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool HighScoresManager::scoreBCD(const Properties& props) const { - string bcd = getPropIdx(props, PropType::Cart_Formats, 2); + string bcd = getPropIdx(props, PropType::Cart_Formats, IDX_SCORE_BCD); return bcd == EmptyString ? DEFAULT_SCORE_BCD : bcd == "B"; } @@ -244,7 +248,7 @@ bool HighScoresManager::scoreBCD(const Properties& props) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool HighScoresManager::varBCD(const Properties& props) const { - string bcd = getPropIdx(props, PropType::Cart_Formats, 3); + string bcd = getPropIdx(props, PropType::Cart_Formats, IDX_VAR_BCD); return bcd == EmptyString ? DEFAULT_VARS_BCD : bcd == "B"; } @@ -252,7 +256,7 @@ bool HighScoresManager::varBCD(const Properties& props) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool HighScoresManager::varZeroBased(const Properties& props) const { - string zeroBased = getPropIdx(props, PropType::Cart_Formats, 4); + string zeroBased = getPropIdx(props, PropType::Cart_Formats, IDX_VAR_ZERO_BASED); return zeroBased == EmptyString ? DEFAULT_VARS_ZERO_BASED : zeroBased != "0"; } @@ -263,7 +267,7 @@ string HighScoresManager::specialLabel(const Properties& props) const string orgLabel, label; // some ugly formatting - orgLabel = label = getPropIdx(props, PropType::Cart_Formats, 5); + orgLabel = label = getPropIdx(props, PropType::Cart_Formats, IDX_SPECIAL_LABEL); label = BSPF::toLowerCase(label); label[0] = orgLabel[0]; @@ -273,7 +277,7 @@ string HighScoresManager::specialLabel(const Properties& props) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool HighScoresManager::specialBCD(const Properties& props) const { - string bcd = getPropIdx(props, PropType::Cart_Formats, 6); + string bcd = getPropIdx(props, PropType::Cart_Formats, IDX_SPECIAL_BCD); return bcd == EmptyString ? DEFAULT_SPECIAL_BCD : bcd == "B"; } @@ -281,11 +285,17 @@ bool HighScoresManager::specialBCD(const Properties& props) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool HighScoresManager::specialZeroBased(const Properties& props) const { - string zeroBased = getPropIdx(props, PropType::Cart_Formats, 7); + string zeroBased = getPropIdx(props, PropType::Cart_Formats, IDX_SPECIAL_ZERO_BASED); return zeroBased == EmptyString ? DEFAULT_SPECIAL_ZERO_BASED : zeroBased != "0"; } +bool HighScoresManager::scoreInvert(const Properties& props) const +{ + string reversed = getPropIdx(props, PropType::Cart_Formats, IDX_SCORE_INVERT); + + return reversed == EmptyString ? DEFAULT_SCORE_REVERSED : reversed != "0"; +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool HighScoresManager::playerZeroBased(const Properties& props) const @@ -293,11 +303,10 @@ bool HighScoresManager::playerZeroBased(const Properties& props) const return DEFAULT_PLAYERS_ZERO_BASED; } - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt16 HighScoresManager::playerAddress(const Properties& props) const { - uInt32 idx = numAddrBytes(props) * numPlayers(props) + 1; + uInt32 idx = numAddrBytes(props) * numPlayers(props) + IDX_PLAYERS_ADDRESS; string addr = getPropIdx(props, PropType::Cart_Addresses, idx); return stringToIntBase16(addr, DEFAULT_ADDRESS); @@ -306,7 +315,7 @@ uInt16 HighScoresManager::playerAddress(const Properties& props) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt16 HighScoresManager::varAddress(const Properties& props) const { - uInt32 idx = numAddrBytes(props) * numPlayers(props); + uInt32 idx = numAddrBytes(props) * numPlayers(props) + IDX_VARS_ADDRESS; string addr = getPropIdx(props, PropType::Cart_Addresses, idx); return stringToIntBase16(addr, DEFAULT_ADDRESS); @@ -315,7 +324,7 @@ uInt16 HighScoresManager::varAddress(const Properties& props) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt16 HighScoresManager::specialAddress(const Properties& props) const { - uInt32 idx = numAddrBytes(props) * numPlayers(props) + 2; + uInt32 idx = numAddrBytes(props) * numPlayers(props) + IDX_SPECIAL_ADDRESS; string addr = getPropIdx(props, PropType::Cart_Addresses, idx); return stringToIntBase16(addr, DEFAULT_ADDRESS); @@ -466,6 +475,12 @@ Int32 HighScoresManager::score() const return score(currentPlayer, numBytes, trailingZeroes(props), scoreBCD(props), scoreAddr); } +bool HighScoresManager::scoreInvert() const +{ + Properties props; + return scoreInvert(properties(props)); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Int32 HighScoresManager::special() const { diff --git a/src/common/HighScoresManager.hxx b/src/common/HighScoresManager.hxx index 787afcb10..b5466fd3c 100644 --- a/src/common/HighScoresManager.hxx +++ b/src/common/HighScoresManager.hxx @@ -45,6 +45,7 @@ namespace HSM { string special; bool specialBCD; bool specialZeroBased; + bool scoreInvert; // Addresses ScoresAddresses scoresAddr; uInt16 varsAddr; @@ -115,15 +116,34 @@ class HighScoresManager string specialLabel() const; Int32 variation() const; Int32 score() const; + bool scoreInvert() const; Int32 special() const; private: + enum { + IDX_SCORE_DIGITS = 0, + IDX_TRAILING_ZEROES, + IDX_SCORE_BCD, + IDX_VAR_BCD, + IDX_VAR_ZERO_BASED, + IDX_SPECIAL_LABEL, + IDX_SPECIAL_BCD, + IDX_SPECIAL_ZERO_BASED, + IDX_SCORE_INVERT + }; + enum { + IDX_VARS_ADDRESS = 0, + IDX_PLAYERS_ADDRESS, + IDX_SPECIAL_ADDRESS + }; + static const uInt32 MAX_VARIATIONS = 256; static const uInt32 MAX_TRAILING = 3; static const uInt32 DEFAULT_DIGITS = 4; static const uInt32 DEFAULT_TRAILING = 0; static const bool DEFAULT_SCORE_BCD = true; + static const bool DEFAULT_SCORE_REVERSED = false; static const bool DEFAULT_VARS_BCD = true; static const bool DEFAULT_VARS_ZERO_BASED = false; static const bool DEFAULT_PLAYERS_ZERO_BASED = true; @@ -140,6 +160,7 @@ class HighScoresManager uInt32 numDigits(const Properties& props) const; uInt32 trailingZeroes(const Properties& props) const; bool scoreBCD(const Properties& props) const; + bool scoreInvert(const Properties& props) const; bool varBCD(const Properties& props) const; bool varZeroBased(const Properties& props) const; string specialLabel(const Properties& props) const; diff --git a/src/gui/GameInfoDialog.cxx b/src/gui/GameInfoDialog.cxx index 2fabcbd2a..e41b5ba32 100644 --- a/src/gui/GameInfoDialog.cxx +++ b/src/gui/GameInfoDialog.cxx @@ -361,7 +361,7 @@ GameInfoDialog::GameInfoDialog( return (c >= '0' && c <= '9'); }; EditableWidget::TextFilter fSpecial = [](char c) { - return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.'|| c == '-,'; + return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.'|| c == '-'; }; xpos = HBORDER; ypos = VBORDER; @@ -459,6 +459,9 @@ GameInfoDialog::GameInfoDialog( myScoreBCD = new CheckboxWidget(myTab, font, myVarsBCD->getLeft(), ypos + 1, "BCD", kHiScoresChanged); wid.push_back(myScoreBCD); + myScoreInvert = new CheckboxWidget(myTab, font, myScoreBCD->getRight() + 16, ypos + 1, "Invert"); + wid.push_back(myScoreInvert); + for (uInt32 p = 0; p < HSM::MAX_PLAYERS; ++p) { uInt32 s_xpos = xpos; @@ -478,9 +481,9 @@ GameInfoDialog::GameInfoDialog( myScoreAddressVal[p][a]->setEditable(false); s_xpos += myScoreAddressVal[p][a]->getWidth() + 16; } - myCurrentScore[p] = new StaticTextWidget(myTab, font, s_xpos + 8+6, ypos + 1, "123456"); + myCurrentScore[p] = new StaticTextWidget(myTab, font, s_xpos, ypos + 1, "= 123456"); } - myCurrentScoreLabel = new StaticTextWidget(myTab, font, myCurrentScore[0]->getLeft(), myScoreBCD->getTop(), "Current"); + //myCurrentScoreLabel = new StaticTextWidget(myTab, font, myCurrentScore[0]->getLeft(), myScoreBCD->getTop(), "Current"); // Add items for tab 4 addToFocusList(wid, myTab, tabID); @@ -689,6 +692,7 @@ void GameInfoDialog::loadHighScoresProperties(const Properties& props) myScoreDigits->setSelected(info.numDigits); myTrailingZeroes->setSelected(info.trailingZeroes); myScoreBCD->setState(info.scoreBCD); + myScoreInvert->setState(info.scoreInvert); myVarsBCD->setState(info.varsBCD); myVarsZeroBased->setState(info.varsZeroBased); mySpecialName->setText(info.special); @@ -828,6 +832,7 @@ void GameInfoDialog::saveHighScoresProperties() info.numDigits = myScoreDigits->getSelected() + 1; info.trailingZeroes = myTrailingZeroes->getSelected(); info.scoreBCD = myScoreBCD->getState(); + info.scoreInvert = myScoreInvert->getState(); // fill addresses string strAddr; @@ -1047,7 +1052,8 @@ void GameInfoDialog::updateHighScoresWidgets() myScoreBCD->setEnabled(enable); myTrailingZeroesLabel->setEnabled(enable); myTrailingZeroes->setEnabled(enable); - myCurrentScoreLabel->setEnabled(enable); + myScoreInvert->setEnabled(enable); + //myCurrentScoreLabel->setEnabled(enable); for (uInt32 p = 0; p < HSM::MAX_PLAYERS; ++p) { @@ -1092,7 +1098,7 @@ void GameInfoDialog::updateHighScoresWidgets() ostringstream ss; ss.str(""); - ss << right << setw(myScoreDigits->getSelected() + 1) << setfill(' ') << score; + ss << "= " << right << setw(myScoreDigits->getSelected() + 1) << setfill(' ') << score; myCurrentScore[p]->setLabel(ss.str()); } else diff --git a/src/gui/GameInfoDialog.hxx b/src/gui/GameInfoDialog.hxx index d47b086af..382092735 100644 --- a/src/gui/GameInfoDialog.hxx +++ b/src/gui/GameInfoDialog.hxx @@ -143,11 +143,12 @@ class GameInfoDialog : public Dialog, public CommandSender StaticTextWidget* myTrailingZeroesLabel{nullptr}; PopUpWidget* myTrailingZeroes{nullptr}; CheckboxWidget* myScoreBCD{nullptr}; + CheckboxWidget* myScoreInvert{nullptr}; StaticTextWidget* myScoreAddressesLabel[HSM::MAX_PLAYERS]{nullptr}; EditTextWidget* myScoreAddress[HSM::MAX_PLAYERS][HSM::MAX_SCORE_ADDR]{nullptr}; EditTextWidget* myScoreAddressVal[HSM::MAX_PLAYERS][HSM::MAX_SCORE_ADDR]{nullptr}; - StaticTextWidget* myCurrentScoreLabel{nullptr}; + //StaticTextWidget* myCurrentScoreLabel{nullptr}; StaticTextWidget* myCurrentScore[HSM::MAX_PLAYERS]{nullptr}; enum { diff --git a/src/gui/HighScoresDialog.cxx b/src/gui/HighScoresDialog.cxx index 37f50774f..c5d38e7ad 100644 --- a/src/gui/HighScoresDialog.cxx +++ b/src/gui/HighScoresDialog.cxx @@ -20,9 +20,9 @@ #include "EventHandler.hxx" #include "Font.hxx" #include "FBSurface.hxx" -//#include "StringParser.hxx" #include "EditTextWidget.hxx" #include "PopUpWidget.hxx" +#include "MessageBox.hxx" #include "HighScoresManager.hxx" @@ -32,14 +32,14 @@ HighScoresDialog::HighScoresDialog(OSystem& osystem, DialogContainer& parent, const GUI::Font& font, int max_w, int max_h) : Dialog(osystem, parent, font, "High Scores"), - myInitials("") + _max_w(max_w), + _max_h(max_h), + myInitials(""), + myDirty(false) { const GUI::Font& ifont = instance().frameBuffer().infoFont(); const int lineHeight = font.getLineHeight(), fontWidth = font.getMaxCharWidth(); - //fontHeight = font.getFontHeight(), - //buttonHeight = font.getLineHeight() + 4; - //infoLineHeight = ifont.getLineHeight(); const int VBORDER = 8; const int HBORDER = 10; const int VGAP = 4; @@ -48,8 +48,8 @@ HighScoresDialog::HighScoresDialog(OSystem& osystem, DialogContainer& parent, WidgetArray wid; VariantList items; - _w = 44 * fontWidth + HBORDER * 2; // max_w - 20; - _h = 400; // max_h - 20; + _w = std::min(max_w, 44 * fontWidth + HBORDER * 2); + _h = std::min(max_h, 400); ypos = VBORDER + _th; xpos = HBORDER; @@ -135,14 +135,19 @@ void HighScoresDialog::loadConfig() } myVariationWidget->addItems(items); myVariationWidget->setSelected(instance().highScores().variation()); + myVariationWidget->setEnabled(instance().highScores().numVariations() > 1); - mySpecialLabelWidget->setLabel(instance().highScores().specialLabel()); + string label = " " + instance().highScores().specialLabel(); + if (label.length() > 5) + label = label.substr(label.length() - 5); + mySpecialLabelWidget->setLabel(label); myMD5 = instance().console().properties().get(PropType::Cart_MD5); myMD5Widget->setLabel("MD5: " + myMD5); myEditPos = myHighScorePos = -1; myNow = now(); + myDirty = false; handleVariation(true); } @@ -156,10 +161,7 @@ void HighScoresDialog::saveConfig() myNames[myHighScorePos] = myInitials; } // save selected variation - - Int32 variation = myVariationWidget->getSelectedTag().toInt(); - saveHighScores(variation); - + saveHighScores(myVariation); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -169,7 +171,7 @@ void HighScoresDialog::handleCommand(CommandSender* sender, int cmd, int data, i { case kOKCmd: saveConfig(); - // falls through... + [[fallthrough]]; case kCloseCmd: instance().eventHandler().leaveMenuMode(); break; @@ -189,6 +191,14 @@ void HighScoresDialog::handleCommand(CommandSender* sender, int cmd, int data, i updateWidgets(); break; + case kConfirmSave: + saveConfig(); + [[fallthrough]]; + case kCancelSave: + myDirty = false; + handleVariation(); + break; + default: Dialog::handleCommand(sender, cmd, data, 0); } @@ -197,17 +207,19 @@ void HighScoresDialog::handleCommand(CommandSender* sender, int cmd, int data, i // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void HighScoresDialog::handleVariation(bool init) { - // TODO: if anything changed, asked for saving - Int32 variation = myVariationWidget->getSelectedTag().toInt(); + if (handleDirty()) + { + myVariation = myVariationWidget->getSelectedTag().toInt(); - loadHighScores(variation); + loadHighScores(myVariation); - myEditPos = -1; + myEditPos = -1; - if (variation == instance().highScores().variation()) - handlePlayedVariation(); + if (myVariation == instance().highScores().variation()) + handlePlayedVariation(); - updateWidgets(init); + updateWidgets(init); + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -268,10 +280,14 @@ void HighScoresDialog::handlePlayedVariation() if (newScore > 0) { Int32 newSpecial = instance().highScores().special(); + bool scoreInvert = instance().highScores().scoreInvert(); + for (myHighScorePos = 0; myHighScorePos < NUM_POSITIONS; ++myHighScorePos) { - if (newScore > myHighScores[myHighScorePos] || - (newScore == myHighScores[myHighScorePos] && newSpecial > mySpecials[myHighScorePos])) + if ((!scoreInvert && newScore > myHighScores[myHighScorePos]) || + ((scoreInvert && newScore < myHighScores[myHighScorePos]) || myHighScores[myHighScorePos] == 0)) + break; + if (newScore == myHighScores[myHighScorePos] && newSpecial > mySpecials[myHighScorePos]) break; } @@ -289,6 +305,7 @@ void HighScoresDialog::handlePlayedVariation() //myNames[myHighScorePos] = ""; mySpecials[myHighScorePos] = newSpecial; myDates[myHighScorePos] = myNow; + myDirty = true; } else myHighScorePos = -1; @@ -320,6 +337,29 @@ void HighScoresDialog::deletePos(int pos) myEditPos--; myEditNamesWidget[myEditPos]->setText(myEditNamesWidget[myEditPos + 1]->getText()); } + myDirty = true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool HighScoresDialog::handleDirty() +{ + if (myDirty) + { + if (!myConfirmMsg) + { + StringList msg; + + msg.push_back("Do you want to save the changed"); + msg.push_back("high scores for this variation?"); + msg.push_back(""); + myConfirmMsg = make_unique + (this, _font, msg, _max_w, _max_h, kConfirmSave, kCancelSave, + "Yes", "No", "Save High Scores", false); + } + myConfirmMsg->show(); + return false; + } + return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/HighScoresDialog.hxx b/src/gui/HighScoresDialog.hxx index 539ab803c..ea2eeb236 100644 --- a/src/gui/HighScoresDialog.hxx +++ b/src/gui/HighScoresDialog.hxx @@ -26,8 +26,10 @@ class DialogContainer; class OSystem; class EditTextWidget; class PopUpWidget; - -#include "Serializable.hxx" +namespace GUI { + class MessageBox; +} +class Serializer; #include "Dialog.hxx" @@ -56,6 +58,7 @@ class HighScoresDialog : public Dialog void handlePlayedVariation(); void deletePos(int pos); + bool handleDirty(); void saveHighScores(Int32 variation) const; void loadHighScores(Int32 variation); @@ -80,10 +83,19 @@ class HighScoresDialog : public Dialog enum { kVariationChanged = 'Vach', - kDeleteSingle = 'DeSi' + kDeleteSingle = 'DeSi', + kConfirmSave = 'CfSv', + kCancelSave = 'CcSv' }; private: + bool myDirty; + unique_ptr myConfirmMsg; + int _max_w; + int _max_h; + + Int32 myVariation; + string myInitials; Int32 myEditPos; Int32 myHighScorePos;