From 26ffbf852cb2be773a7e3f7296d1dcd99888c37a Mon Sep 17 00:00:00 2001 From: Denis Papp Date: Wed, 3 Mar 2021 09:12:09 -0600 Subject: [PATCH] + Swing Reserve: [fixes #16] - fixes Player::m_swing_dice[] not being updated when a swing reserve die is used (i.e. goes from !IsUsed() to IsUsed()) - EXPL: there is a co-dependency between the m_swing_dice[] counts and the Die::IsUsed() state. See BMC_Player::OnDiceParsed(). If m_swing_dice[] is not updated, then the AI doesn't know that it needs to include that swing type in it's list of candidate SetSwing actions. - added test input for bug + Warnings: fixed one size_t -> INT cast warning + Style: fixed several inconsistent if/else bracketing + Project: - partial work towards splitting up classes into distinct headers/source files - headers: setup several - source: only setup Stats.cpp - player.cpp: renamed to drop the prefix --- CMakeLists.txt | 4 +- src/bmai.cpp | 66 ++--- src/bmai.h | 399 +--------------------------- src/bmai_ai.cpp | 5 + src/die.h | 91 +++++++ src/dieindexstack.h | 37 +++ src/game.h | 105 ++++++++ src/logger.h | 20 ++ src/man.h | 16 ++ src/parser.h | 48 ++++ src/{bmai_player.cpp => player.cpp} | 13 +- src/player.h | 76 ++++++ src/stats.cpp | 33 +++ src/stats.h | 25 ++ test/bug16_in.txt | 23 ++ 15 files changed, 530 insertions(+), 431 deletions(-) create mode 100644 src/die.h create mode 100644 src/dieindexstack.h create mode 100644 src/game.h create mode 100644 src/logger.h create mode 100644 src/man.h create mode 100644 src/parser.h rename src/{bmai_player.cpp => player.cpp} (93%) create mode 100644 src/player.h create mode 100644 src/stats.cpp create mode 100644 src/stats.h create mode 100644 test/bug16_in.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b22a306..f1bf20b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,11 +8,11 @@ set(CMAKE_CXX_EXTENSIONS OFF) project(bmai) # to collect only needed source files -set(SOURCE ./src/bmai.cpp ./src/bmai_ai.cpp ./src/bmai_player.cpp) +set(SOURCE ./src/bmai.cpp ./src/bmai_ai.cpp ./src/player.cpp ./src/stats.cpp) # some IDEs really need the Headers added to the executable # to be able to index everything correctly -set(HEADERS ./src/bmai.h ./src/bmai_ai.h) +set(HEADERS ./src/bmai.h ./src/bmai_ai.h ./src/stats.h ./src/logger.h ./src/parser.h ./src/game.h ./src/player.h ./src/die.h ./src/dieindexstack.h) add_executable(${PROJECT_NAME} ${SOURCE} ${HEADERS}) diff --git a/src/bmai.cpp b/src/bmai.cpp index 21de181..09526bd 100644 --- a/src/bmai.cpp +++ b/src/bmai.cpp @@ -249,6 +249,12 @@ // includes #include "bmai.h" #include "bmai_ai.h" +#include "dieindexstack.h" +#include "game.h" +#include "player.h" +#include "parser.h" +#include "stats.h" +#include "logger.h" #include // for time() #include // for fabs() #include // for va_start() va_end() @@ -271,9 +277,6 @@ BME_ATTACK_TYPE g_attack_type[BME_ATTACK_MAX] = // MOOD dice - from BM page: //X?: Roll a d6. 1: d4; 2: d6; 3: d8; 4: d10; 5: d12; 6: d20. //V?: Roll a d4. 1: d6; 2: d8; 3: d10; 4: d12. -#define BMD_MOOD_SIDES_RANGE_X 6 -#define BMD_MOOD_SIDES_RANGE_V 4 - INT g_mood_sides_X[BMD_MOOD_SIDES_RANGE_X] = { 4, 6, 8, 10, 12, 20 }; INT g_mood_sides_V[BMD_MOOD_SIDES_RANGE_V] = { 6, 8, 10, 12 }; @@ -472,9 +475,12 @@ void BMC_RNG::SRand(UINT _seed) _seed = (UINT)time(NULL); // bad arbitrary way of tweaking bad seed values - if (!_seed) m_seed=-1; - else if((_seed>>16)==0) m_seed = _seed | (_seed<<16); - else m_seed = _seed; + if (!_seed) + m_seed=-1; + else if((_seed>>16)==0) + m_seed = _seed | (_seed<<16); + else + m_seed = _seed; } UINT BMC_RNG::GetRand() @@ -537,7 +543,9 @@ bool BMC_DieIndexStack::Cycle(bool _add_die) } if (_add_die) + { Push(GetTopDieIndex() + 1); + } else { // cycle top die @@ -1339,7 +1347,9 @@ INT BMC_Game::CheckInitiative() j--; } else + { return (delta > 0) ? 1 : 0; + } } } @@ -1542,12 +1552,16 @@ bool BMC_Game::ValidAttack(BMC_MoveAttack &_move) // for POWER - check value >= if (_move.m_attack == BME_ATTACK_POWER) + { return att_die->GetValueTotal() >= tgt_die->GetValueTotal(); + } // for SHADOW - check value <= and total else if (_move.m_attack == BME_ATTACK_SHADOW) + { return att_die->GetValueTotal() <= tgt_die->GetValueTotal() - && att_die->GetSidesMax() >= tgt_die->GetValueTotal() ; + && att_die->GetSidesMax() >= tgt_die->GetValueTotal(); + } // for TRIP - always legal, except when can't capture (one die TRIP against two die) else @@ -2587,6 +2601,7 @@ void BMC_Game::ApplyUseReserve(BMC_Move &_move) BMC_Die *die = player->GetDie(_move.m_use_reserve); die->OnUseReserve(); + player->OnReserveDieUsed(die); } // PARAM: _lock true means set to "SWING_SET_LOCKED", otherwise "SWING_SET_READY." If used inappropriately, the latter @@ -2811,7 +2826,9 @@ void BMC_Game::ApplyAttackNaturePost(BMC_Move &_move, bool &_extra_turn) { tgt_die = target->OnDieLost(_move.m_target); if (null_attacker) + { tgt_die->SetState(BME_STATE_NULLIFIED); + } else { tgt_die->SetState(BME_STATE_CAPTURED); @@ -2831,7 +2848,9 @@ void BMC_Game::ApplyAttackNaturePost(BMC_Move &_move, bool &_extra_turn) i2 = i - removed++; // determine true index tgt_die = target->OnDieLost(i2); if (null_attacker) + { tgt_die->SetState(BME_STATE_NULLIFIED); + } else { tgt_die->SetState(BME_STATE_CAPTURED); @@ -3401,7 +3420,7 @@ bool BMC_Parser::Read(bool _fatal) } // remove EOL - INT len = std::strlen(line); + INT len = (INT)std::strlen(line); if (len>0 && line[len-1]=='\n') line[len-1] = 0; @@ -3986,37 +4005,6 @@ void BMC_Parser::Parse() } } -/////////////////////////////////////////////////////////////////////////////////////////// -// BMC_Stats -/////////////////////////////////////////////////////////////////////////////////////////// - -BMC_Stats::BMC_Stats() -{ - m_start = m_end = 0; - m_sims = 0; - for (int i=0; i0 ? m_sims / diff : 0); - float leaves = 1; - for (int i=1; i m_attacks; - BMC_BitArray m_vulnerabilities; - //U8 m_value[BMD_MAX_TWINS]; // current value of dice, not really necessary (and often not known!) -}; - -class BMC_Man -{ -public: - // accessors - BMC_DieData *GetDieData(INT _d) { return &m_die[_d]; } - -protected: -private: - BMC_DieData m_die[BMD_MAX_DICE]; -}; - -class BMC_Player // 204b -{ - friend class BMC_Parser; - -public: - typedef enum { - SWING_SET_NOT, - SWING_SET_READY, // set this round, esults should not be "known" to opponent - to simulate simultaneous swing set - SWING_SET_LOCKED, // set from previous round - } SWING_SET; - - BMC_Player(); - void SetID(INT _id) { m_id = _id; } - void Reset(); - void OnDiceParsed(); - - // playing with normal rules - void SetButtonMan(BMC_Man *_man); - void SetSwingDice(INT _swing, U8 _value, bool _from_turbo=false); - void SetOptionDie(INT _i, INT _d); - void RollDice(); - - // methods - void Debug(BME_DEBUG _cat = BME_DEBUG_ALWAYS); - void DebugAllDice(BME_DEBUG _cat = BME_DEBUG_ALWAYS); - void SetSwingDiceStatus(SWING_SET _swing) { m_swing_set = _swing; } - bool NeedsSetSwing(); - - // events - void OnDieSidesChanging(BMC_Die *_die); - void OnDieSidesChanged(BMC_Die *_die); - BMC_Die * OnDieLost(INT _d); - void OnDieCaptured(BMC_Die *_die); - void OnRoundLost(); - void OnSurrendered(); - //void OnSwingDiceSet() { m_swing_set = true; } - void OnSwingDiceReady() { m_swing_set = SWING_SET_READY; } - void OnAttackFinished() { OptimizeDice(); } - void OnDieTripped() { OptimizeDice(); } - void OnChanceDieRolled() { OptimizeDice(); } - void OnFocusDieUsed() { OptimizeDice(); } - - // accessors - BMC_Die * GetDie(INT _d) { return &m_die[_d]; } - INT GetAvailableDice() { return m_available_dice; } - INT GetMaxValue() { return m_max_value; } - float GetScore() { return m_score; } - //bool SwingDiceSet() { return m_swing_set; } - SWING_SET GetSwingDiceSet() { return m_swing_set; } - INT HasDieWithProperty(INT _p, bool _check_all_dice=false); - INT GetTotalSwingDice(INT _s) { return m_swing_dice[_s]; } - INT GetID() { return m_id; } - - -protected: - // methods - void OptimizeDice(); - -private: - BMC_Man * m_man; - INT m_id; - SWING_SET m_swing_set; - BMC_Die m_die[BMD_MAX_DICE]; // as long as Optimize was called, these are sorted largest to smallest that are READY - U8 m_swing_value[BME_SWING_MAX]; - U8 m_swing_dice[BME_SWING_MAX]; // number of dice of each swing type - INT m_available_dice; // only valid after Optimize - INT m_max_value; // only valid after Optimize, useful to know for skill attacks - float m_score; -}; - -class BMC_Game // 420b -{ - friend class BMC_Parser; - -public: - BMC_Game(bool _simulation); - BMC_Game(const BMC_Game & _game); - const BMC_Game & operator=(const BMC_Game &_game); - - // game simulation - level 0 - void PlayGame(BMC_Man *_man1 = NULL, BMC_Man *_man2 = NULL); - - // game simulation - level 1 (for simulations) - BME_WLT PlayRound(BMC_Move *_start_action=NULL); - - // game methods - bool ValidAttack(BMC_MoveAttack &_move); - void GenerateValidAttacks(BMC_MoveList &_movelist); - bool ValidSetSwing(BMC_Move &_move); - void GenerateValidSetSwing(BMC_MoveList & _movelist); - void ApplySetSwing(BMC_Move &_move, bool _lock = true); - bool ValidUseFocus(BMC_Move &_move); - void GenerateValidFocus(BMC_MoveList & _movelist); - void ApplyUseFocus(BMC_Move &_move); - void ApplyUseReserve(BMC_Move &_move); - bool ValidUseChance(BMC_Move &_move); - void GenerateValidChance(BMC_MoveList & _movelist); - void ApplyUseChance(BMC_Move &_move); - - // initiative - INT CheckInitiative(); - - // managing fights - bool FightOver(); - void ApplyAttackPlayer(BMC_Move &_move); - void ApplyAttackNatureRoll(BMC_Move &_move); - void ApplyAttackNaturePost(BMC_Move &_move, bool &_extra_turn); - void SimulateAttack(BMC_MoveAttack &_move, bool & _extra_turn); - void RecoverDizzyDice(INT _player); - - // accessors - BMC_Player *GetPlayer(INT _i) { return &m_player[_i]; } - BMC_Player *GetPhasePlayer() { return GetPlayer(m_phase_player); } - BMC_Player *GetTargetPlayer() { return GetPlayer(m_target_player); } - INT GetPhasePlayerID() { return m_phase_player; } - bool IsPreround() { return m_phase == BME_PHASE_PREROUND || m_phase==BME_PHASE_RESERVE; } - BME_PHASE GetPhase() { return m_phase; } - INT GetStanding(INT _wlt) { return m_standing[_wlt]; } - INT GetInitiativeWinner() { return m_initiative_winner; } - bool IsSimulation() { return m_simulation; } - BMC_AI * GetAI(INT _p) { return m_ai[_p]; } - - // mutators - void SetAI(INT _p, BMC_AI *_ai) { m_ai[_p] = _ai; } - - // methods wrt. "percent chance to win" - float ConvertWLTToWinProbability(); - float PlayFight_EvaluateMove(INT _pov_player, BMC_Move &_move); - float PlayRound_EvaluateMove(INT _pov_player); - -protected: - // game simulation - level 1 - void Setup(BMC_Man *_man1 = NULL, BMC_Man *_man2 = NULL); - - // game simulation - level 2 - void PlayPreround(); - void PlayInitiative(); - void PlayInitiativeChance(); - void PlayInitiativeFocus(); - void PlayFight(BMC_Move *_start_action=NULL); - void FinishPreround(); - void FinishInitiative(); - void FinishInitiativeChance(bool _swap_phase_player); - void FinishInitiativeFocus(bool _swap_phase_player); - BME_WLT FinishRound(BME_WLT _wlt_0); - - // game simulation - level 3 - void FinishTurn(bool extra_turn=false); - -private: - BMC_Player m_player[BMD_MAX_PLAYERS]; - U8 m_standing[BME_WLT_MAX]; - U8 m_target_wins; - BME_PHASE m_phase; - U8 m_phase_player; - U8 m_target_player; - BME_ACTION m_last_action; - - // AI players - BMC_AI * m_ai[BMD_MAX_PLAYERS]; - - // stats accumulated during the round - U8 m_initiative_winner; // the player who originally won initiative (ignoring CHANCE and FOCUS) - - // is this a simulation run by the AI? - bool m_simulation; -}; - - -// INVARIANT: only contains dice in increasing order (e.g. 0,2,3 but not 2.0,3) -class BMC_DieIndexStack -{ -public: - BMC_DieIndexStack(BMC_Player *_owner); - - // mutators - void Pop() { die_stack_size--; } - void Push(INT _index); - bool Cycle(bool _add_die=true); - - // methods - void SetBits(BMC_BitArray & _bits); - void Debug(BME_DEBUG _cat = BME_DEBUG_ALWAYS); - - // accessors - INT GetValueTotal() { return value_total; } - bool ContainsAllDice() { return die_stack_size == owner->GetAvailableDice(); } - bool ContainsLastDie() { return GetTopDieIndex() == owner->GetAvailableDice()-1; } - INT GetTopDieIndex() { BM_ASSERT(die_stack_size>0); return die_stack[die_stack_size-1]; } - BMC_Die * GetTopDie() { return owner->GetDie(GetTopDieIndex()); } - bool Empty() { return die_stack_size == 0; } - INT GetStackSize() { return die_stack_size; } - BMC_Die * GetDie(int _index) { return owner->GetDie(die_stack[_index]); } - INT CountDiceWithProperty(BME_PROPERTY _property); - -protected: -private: - BMC_Player * owner; - INT die_stack[BMD_MAX_DICE]; - INT die_stack_size; - INT value_total; -}; -class BMC_Parser -{ -public: - BMC_Parser(); - void SetupTestGame(); - void ParseGame(); - void Parse(); - void Parse(FILE *_fp) { file = _fp; Parse(); } - -protected: - void GetAction(); - void PlayGame(INT _games); - void CompareAI(INT _games); - void PlayFairGames(INT _games, INT _mode, F32 _p); - void ParseDie(INT _p, INT _dice); - void ParsePlayer(INT _p, INT _dice); - bool Read(bool _fatal=true); - - // output - void Send(char *_fmt, ...); - void SendStats(); - void SendSetSwing(BMC_Move &_move); - void SendUseReserve(BMC_Move &_move); - void SendAttack(BMC_Move &_move); - void SendUseChance(BMC_Move &_move); - void SendUseFocus(BMC_Move &_move); - - // parsing dice - bool DieIsSwing(char _c) { return _c>=BMD_FIRST_SWING_CHAR && _c<=BMD_LAST_SWING_CHAR; } - bool DieIsNumeric(char _c) { return (_c>='0' && _c<='9'); } - bool DieIsValue(char _c) { return DieIsSwing(_c) || DieIsNumeric(_c); } - bool DieIsTwin(char _c) { return _c=='('; } - bool DieIsOption(char _c) { return _c=='/'; } - -private: - // parsing dice methods (uses 'line') - INT ParseDieNumber(INT & _pos); - void ParseDieSides(INT & _pos, INT _die); - INT ParseDieDefinedSides(INT _pos); - - // state for dice parsing - BMC_Player *p; - BMC_Die *d; - char line[BMD_MAX_STRING]; - FILE *file; -}; - -class BMC_Logger -{ -public: - BMC_Logger(); - - // methods - void Log(BME_DEBUG _cat, char *_fmt, ... ); - - // mutators - void SetLogging(BME_DEBUG _cat, bool _log) { m_logging[_cat] = _log; } - bool SetLogging(const char *_catname, bool _log); - - // accessors - bool IsLogging(BME_DEBUG _cat) { return m_logging[_cat]; } - -private: - bool m_logging[BME_DEBUG_MAX]; -}; - -class BMC_Stats -{ -public: - BMC_Stats(); - - // methods - void DisplayStats(); - - // events - void OnAppStarted() { m_start = time(NULL); } - void OnFullSimulation() { m_sims++; } - - // bmai-specific - void OnPlyAction(int _ply, int _moves, int _sims) { m_total_sims[_ply]+=_sims; m_total_moves[_ply]+=_moves; m_total_samples[_ply]++; } - -private: - time_t m_start, m_end; - int m_sims; - int m_total_sims[BMD_MAX_PLY]; - int m_total_moves[BMD_MAX_PLY]; - int m_total_samples[BMD_MAX_PLY]; - -}; // Utility functions diff --git a/src/bmai_ai.cpp b/src/bmai_ai.cpp index 0d58f07..4007f78 100644 --- a/src/bmai_ai.cpp +++ b/src/bmai_ai.cpp @@ -40,6 +40,11 @@ #include "bmai.h" #include "bmai_ai.h" +#include "die.h" +#include "player.h" +#include "game.h" +#include "logger.h" +#include "stats.h" #include #define BMD_DEFAULT_SIMS 500 diff --git a/src/die.h b/src/die.h new file mode 100644 index 0000000..7e21a81 --- /dev/null +++ b/src/die.h @@ -0,0 +1,91 @@ +#pragma once + +// TODO_HEADERS: drp030321 - cleanup headers +#include "bmai.h" + +// NOTES: +// - second die only used for Option and Twin +class BMC_DieData +{ + friend class BMC_Parser; + +public: + BMC_DieData(); + virtual void Reset(); + + // methods + void Debug(BME_DEBUG _cat = BME_DEBUG_ALWAYS); + + // accessors + bool HasProperty(INT _p) { return m_properties & _p; } + BME_SWING GetSwingType(INT _d) { return (BME_SWING)m_swing_type[_d]; } + bool Valid() { return (m_properties & BME_PROPERTY_VALID); } + // drp022521 - fixed to return INT instead of bool + INT Dice() { return (m_properties & BME_PROPERTY_TWIN) ? 2 : 1; } + INT GetSides(INT _t) { return m_sides[_t]; } + +protected: + U32 m_properties; + U8 m_sides[BMD_MAX_TWINS]; // number of sides if not a swing die, or current number of sides + +private: + U8 m_swing_type[BMD_MAX_TWINS]; // definition number of sides, should be BME_SWING +}; + +class BMC_Die : public BMC_DieData +{ + friend class BMC_Parser; + +public: + // setup + virtual void Reset(); + void SetDie(BMC_DieData *_data); + void Roll(); + void GameRoll(BMC_Player *_owner); + + // methods + void Debug(BME_DEBUG _cat = BME_DEBUG_ALWAYS); + void SetOption(INT _d); + void SetFocus(INT _v); + + // accessors + bool CanDoAttack(BMC_MoveAttack &_move) { return m_attacks.IsSet(_move.m_attack); } + bool CanBeAttacked(BMC_MoveAttack &_move) { return m_vulnerabilities.IsSet(_move.m_attack); } + INT GetValueTotal() { return m_value_total; } + INT GetSidesMax() { return m_sides_max; } + bool IsAvailable() { return m_state == BME_STATE_READY || m_state == BME_STATE_DIZZY; } + bool IsInReserve() { return m_state == BME_STATE_RESERVE; } + bool IsUsed() { return m_state != BME_STATE_NOTUSED && m_state != BME_STATE_RESERVE; } + float GetScore(bool _own); + INT GetOriginalIndex() { return m_original_index; } + BME_STATE GetState() { return (BME_STATE)m_state; } + + // mutators + void SetState(BME_STATE _state) { m_state = _state; } + void CheatSetValueTotal(INT _v) { m_value_total = _v; } // used for some functions + + // events + void OnDieChanged(); + void OnSwingSet(INT _swing, U8 _value); + void OnApplyAttackPlayer(BMC_Move &_move, BMC_Player *_owner, bool _actually_attacking = true); + void OnApplyAttackNatureRollAttacker(BMC_Move &_move, BMC_Player *_owner); + void OnApplyAttackNatureRollTripped(); + void OnBeforeRollInGame(BMC_Player *_owner); + void OnUseReserve(); + void OnDizzyRecovered(); + +protected: + + // call this once state changes + void RecomputeAttacks(); + +private: + U8 m_state; // should be BME_STATE + U8 m_value_total; // current total value of all dice, redundant + U8 m_sides_max; // max value of dice (based on m_sides), redundant + U8 m_original_index; // save original index for interface + BMC_BitArray m_attacks; + BMC_BitArray m_vulnerabilities; + //U8 m_value[BMD_MAX_TWINS]; // current value of dice, not really necessary (and often not known!) +}; + diff --git a/src/dieindexstack.h b/src/dieindexstack.h new file mode 100644 index 0000000..6396f9a --- /dev/null +++ b/src/dieindexstack.h @@ -0,0 +1,37 @@ +#pragma once + +#include "player.h" + +// INVARIANT: only contains dice in increasing order (e.g. 0,2,3 but not 2.0,3) +class BMC_DieIndexStack +{ +public: + BMC_DieIndexStack(BMC_Player *_owner); + + // mutators + void Pop() { die_stack_size--; } + void Push(INT _index); + bool Cycle(bool _add_die = true); + + // methods + void SetBits(BMC_BitArray & _bits); + void Debug(BME_DEBUG _cat = BME_DEBUG_ALWAYS); + + // accessors + INT GetValueTotal() { return value_total; } + bool ContainsAllDice() { return die_stack_size == owner->GetAvailableDice(); } + bool ContainsLastDie() { return GetTopDieIndex() == owner->GetAvailableDice() - 1; } + INT GetTopDieIndex() { BM_ASSERT(die_stack_size > 0); return die_stack[die_stack_size - 1]; } + BMC_Die * GetTopDie() { return owner->GetDie(GetTopDieIndex()); } + bool Empty() { return die_stack_size == 0; } + INT GetStackSize() { return die_stack_size; } + BMC_Die * GetDie(int _index) { return owner->GetDie(die_stack[_index]); } + INT CountDiceWithProperty(BME_PROPERTY _property); + +protected: +private: + BMC_Player * owner; + INT die_stack[BMD_MAX_DICE]; + INT die_stack_size; + INT value_total; +}; \ No newline at end of file diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..a08e1c4 --- /dev/null +++ b/src/game.h @@ -0,0 +1,105 @@ +#pragma once + +#include "player.h" + +class BMC_Game // 420b +{ + friend class BMC_Parser; + +public: + BMC_Game(bool _simulation); + BMC_Game(const BMC_Game & _game); + const BMC_Game & operator=(const BMC_Game &_game); + + // game simulation - level 0 + void PlayGame(BMC_Man *_man1 = NULL, BMC_Man *_man2 = NULL); + + // game simulation - level 1 (for simulations) + BME_WLT PlayRound(BMC_Move *_start_action = NULL); + + // game methods + bool ValidAttack(BMC_MoveAttack &_move); + void GenerateValidAttacks(BMC_MoveList &_movelist); + + bool ValidSetSwing(BMC_Move &_move); + void GenerateValidSetSwing(BMC_MoveList & _movelist); + void ApplySetSwing(BMC_Move &_move, bool _lock = true); + + bool ValidUseFocus(BMC_Move &_move); + void GenerateValidFocus(BMC_MoveList & _movelist); + void ApplyUseFocus(BMC_Move &_move); + + void ApplyUseReserve(BMC_Move &_move); + + bool ValidUseChance(BMC_Move &_move); + void GenerateValidChance(BMC_MoveList & _movelist); + void ApplyUseChance(BMC_Move &_move); + + // initiative + INT CheckInitiative(); + + // managing fights + bool FightOver(); + void ApplyAttackPlayer(BMC_Move &_move); + void ApplyAttackNatureRoll(BMC_Move &_move); + void ApplyAttackNaturePost(BMC_Move &_move, bool &_extra_turn); + void SimulateAttack(BMC_MoveAttack &_move, bool & _extra_turn); + void RecoverDizzyDice(INT _player); + + // accessors + BMC_Player *GetPlayer(INT _i) { return &m_player[_i]; } + BMC_Player *GetPhasePlayer() { return GetPlayer(m_phase_player); } + BMC_Player *GetTargetPlayer() { return GetPlayer(m_target_player); } + INT GetPhasePlayerID() { return m_phase_player; } + bool IsPreround() { return m_phase == BME_PHASE_PREROUND || m_phase == BME_PHASE_RESERVE; } + BME_PHASE GetPhase() { return m_phase; } + INT GetStanding(INT _wlt) { return m_standing[_wlt]; } + INT GetInitiativeWinner() { return m_initiative_winner; } + bool IsSimulation() { return m_simulation; } + BMC_AI * GetAI(INT _p) { return m_ai[_p]; } + + // mutators + void SetAI(INT _p, BMC_AI *_ai) { m_ai[_p] = _ai; } + + // methods wrt. "percent chance to win" + float ConvertWLTToWinProbability(); + float PlayFight_EvaluateMove(INT _pov_player, BMC_Move &_move); + float PlayRound_EvaluateMove(INT _pov_player); + +protected: + // game simulation - level 1 + void Setup(BMC_Man *_man1 = NULL, BMC_Man *_man2 = NULL); + + // game simulation - level 2 + void PlayPreround(); + void PlayInitiative(); + void PlayInitiativeChance(); + void PlayInitiativeFocus(); + void PlayFight(BMC_Move *_start_action = NULL); + void FinishPreround(); + void FinishInitiative(); + void FinishInitiativeChance(bool _swap_phase_player); + void FinishInitiativeFocus(bool _swap_phase_player); + BME_WLT FinishRound(BME_WLT _wlt_0); + + // game simulation - level 3 + void FinishTurn(bool extra_turn = false); + +private: + BMC_Player m_player[BMD_MAX_PLAYERS]; + U8 m_standing[BME_WLT_MAX]; + U8 m_target_wins; + BME_PHASE m_phase; + U8 m_phase_player; + U8 m_target_player; + BME_ACTION m_last_action; + + // AI players + BMC_AI * m_ai[BMD_MAX_PLAYERS]; + + // stats accumulated during the round + U8 m_initiative_winner; // the player who originally won initiative (ignoring CHANCE and FOCUS) + + // is this a simulation run by the AI? + bool m_simulation; +}; \ No newline at end of file diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 0000000..1d7cf5a --- /dev/null +++ b/src/logger.h @@ -0,0 +1,20 @@ +#pragma once + +class BMC_Logger +{ +public: + BMC_Logger(); + + // methods + void Log(BME_DEBUG _cat, char *_fmt, ...); + + // mutators + void SetLogging(BME_DEBUG _cat, bool _log) { m_logging[_cat] = _log; } + bool SetLogging(const char *_catname, bool _log); + + // accessors + bool IsLogging(BME_DEBUG _cat) { return m_logging[_cat]; } + +private: + bool m_logging[BME_DEBUG_MAX]; +}; \ No newline at end of file diff --git a/src/man.h b/src/man.h new file mode 100644 index 0000000..eb2b7ff --- /dev/null +++ b/src/man.h @@ -0,0 +1,16 @@ +#pragma once + +// TODO_HEADERS: drp030321 - clean up headers +#include "bmai.h" +#include "die.h" + +class BMC_Man +{ +public: + // accessors + BMC_DieData *GetDieData(INT _d) { return &m_die[_d]; } + +protected: +private: + BMC_DieData m_die[BMD_MAX_DICE]; +}; \ No newline at end of file diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 0000000..328fbb7 --- /dev/null +++ b/src/parser.h @@ -0,0 +1,48 @@ +#pragma once + +class BMC_Parser +{ +public: + BMC_Parser(); + void SetupTestGame(); + void ParseGame(); + void Parse(); + void Parse(FILE *_fp) { file = _fp; Parse(); } + +protected: + void GetAction(); + void PlayGame(INT _games); + void CompareAI(INT _games); + void PlayFairGames(INT _games, INT _mode, F32 _p); + void ParseDie(INT _p, INT _dice); + void ParsePlayer(INT _p, INT _dice); + bool Read(bool _fatal = true); + + // output + void Send(char *_fmt, ...); + void SendStats(); + void SendSetSwing(BMC_Move &_move); + void SendUseReserve(BMC_Move &_move); + void SendAttack(BMC_Move &_move); + void SendUseChance(BMC_Move &_move); + void SendUseFocus(BMC_Move &_move); + + // parsing dice + bool DieIsSwing(char _c) { return _c >= BMD_FIRST_SWING_CHAR && _c <= BMD_LAST_SWING_CHAR; } + bool DieIsNumeric(char _c) { return (_c >= '0' && _c <= '9'); } + bool DieIsValue(char _c) { return DieIsSwing(_c) || DieIsNumeric(_c); } + bool DieIsTwin(char _c) { return _c == '('; } + bool DieIsOption(char _c) { return _c == '/'; } + +private: + // parsing dice methods (uses 'line') + INT ParseDieNumber(INT & _pos); + void ParseDieSides(INT & _pos, INT _die); + INT ParseDieDefinedSides(INT _pos); + + // state for dice parsing + BMC_Player *p; + BMC_Die *d; + char line[BMD_MAX_STRING]; + FILE *file; +}; \ No newline at end of file diff --git a/src/bmai_player.cpp b/src/player.cpp similarity index 93% rename from src/bmai_player.cpp rename to src/player.cpp index 0f56e16..a4ff7d1 100644 --- a/src/bmai_player.cpp +++ b/src/player.cpp @@ -11,8 +11,10 @@ /////////////////////////////////////////////////////////////////////////////////////////// // includes -#include #include "bmai.h" +#include "player.h" +#include "logger.h" +#include /////////////////////////////////////////////////////////////////////////////////////////// // BMC_Player methods @@ -335,4 +337,13 @@ bool BMC_Player::NeedsSetSwing() } return false; +} + +// INVOKED: by BMC_Game when calling BMC_Die::OnUseReserve() +// CODEP: same as what OnDiceParsed() does - we are keeping m_swing_dice[] counts in sync with the "BMC_Die::IsUsed()" state. Would be more robust if was +// done through a pattern like "OnDieStateChanged()" +void BMC_Player::OnReserveDieUsed(BMC_Die *_die) +{ + for (INT j = 0; j < _die->Dice(); j++) + m_swing_dice[_die->GetSwingType(j)]++; } \ No newline at end of file diff --git a/src/player.h b/src/player.h new file mode 100644 index 0000000..6a0c0a2 --- /dev/null +++ b/src/player.h @@ -0,0 +1,76 @@ +#pragma once + +// TODO_HEADERS: drp030321 - clean up headers +#include "bmai.h" +#include "man.h" + +class BMC_Player // 204b +{ + friend class BMC_Parser; + +public: + typedef enum { + SWING_SET_NOT, + SWING_SET_READY, // set this round, esults should not be "known" to opponent - to simulate simultaneous swing set + SWING_SET_LOCKED, // set from previous round + } SWING_SET; + + BMC_Player(); + void SetID(INT _id) { m_id = _id; } + void Reset(); + void OnDiceParsed(); + + // playing with normal rules + void SetButtonMan(BMC_Man *_man); + void SetSwingDice(INT _swing, U8 _value, bool _from_turbo = false); + void SetOptionDie(INT _i, INT _d); + void RollDice(); + + // methods + void Debug(BME_DEBUG _cat = BME_DEBUG_ALWAYS); + void DebugAllDice(BME_DEBUG _cat = BME_DEBUG_ALWAYS); + void SetSwingDiceStatus(SWING_SET _swing) { m_swing_set = _swing; } + bool NeedsSetSwing(); + + // events + void OnDieSidesChanging(BMC_Die *_die); + void OnDieSidesChanged(BMC_Die *_die); + BMC_Die * OnDieLost(INT _d); + void OnDieCaptured(BMC_Die *_die); + void OnRoundLost(); + void OnSurrendered(); + //void OnSwingDiceSet() { m_swing_set = true; } + void OnSwingDiceReady() { m_swing_set = SWING_SET_READY; } + void OnAttackFinished() { OptimizeDice(); } + void OnDieTripped() { OptimizeDice(); } + void OnChanceDieRolled() { OptimizeDice(); } + void OnFocusDieUsed() { OptimizeDice(); } + void OnReserveDieUsed(BMC_Die *_die); + + // accessors + BMC_Die * GetDie(INT _d) { return &m_die[_d]; } + INT GetAvailableDice() { return m_available_dice; } + INT GetMaxValue() { return m_max_value; } + float GetScore() { return m_score; } + //bool SwingDiceSet() { return m_swing_set; } + SWING_SET GetSwingDiceSet() { return m_swing_set; } + INT HasDieWithProperty(INT _p, bool _check_all_dice = false); + INT GetTotalSwingDice(INT _s) { return m_swing_dice[_s]; } + INT GetID() { return m_id; } + + +protected: + // methods + void OptimizeDice(); + +private: + BMC_Man * m_man; + INT m_id; + SWING_SET m_swing_set; + BMC_Die m_die[BMD_MAX_DICE]; // as long as Optimize was called, these are sorted largest to smallest that are READY + U8 m_swing_value[BME_SWING_MAX]; + U8 m_swing_dice[BME_SWING_MAX]; // number of dice of each swing type + INT m_available_dice; // only valid after Optimize + INT m_max_value; // only valid after Optimize, useful to know for skill attacks + float m_score; +}; \ No newline at end of file diff --git a/src/stats.cpp b/src/stats.cpp new file mode 100644 index 0000000..e3803ba --- /dev/null +++ b/src/stats.cpp @@ -0,0 +1,33 @@ +#include "bmai.h" +#include "stats.h" + +/////////////////////////////////////////////////////////////////////////////////////////// +// BMC_Stats +/////////////////////////////////////////////////////////////////////////////////////////// + +BMC_Stats::BMC_Stats() +{ + m_start = m_end = 0; + m_sims = 0; + for (int i = 0; i < BMD_MAX_PLY; i++) + m_total_sims[i] = m_total_moves[i] = m_total_samples[i] = 0; +} + +void BMC_Stats::DisplayStats() +{ + double diff = difftime(time(NULL), m_start); + printf("Time: %lf s ", diff); + printf("Sim: %d Sims/Sec: %f Mvs/Sms ", m_sims, diff > 0 ? m_sims / diff : 0); + float leaves = 1; + for (int i = 1; i < BMD_MAX_PLY; i++) + { + if (m_total_samples[i] == 0) + break; + printf("| "); + float avg_moves = (float)m_total_moves[i] / m_total_samples[i]; + float avg_sims = (float)m_total_sims[i] / m_total_samples[i]; + printf("%.1f/%.1f ", avg_moves, avg_sims); + leaves *= avg_moves * avg_sims; + } + printf("= %.0f\n", leaves); +} \ No newline at end of file diff --git a/src/stats.h b/src/stats.h new file mode 100644 index 0000000..cd478c3 --- /dev/null +++ b/src/stats.h @@ -0,0 +1,25 @@ +#pragma once + +class BMC_Stats +{ +public: + BMC_Stats(); + + // methods + void DisplayStats(); + + // events + void OnAppStarted() { m_start = time(NULL); } + void OnFullSimulation() { m_sims++; } + + // bmai-specific + void OnPlyAction(int _ply, int _moves, int _sims) { m_total_sims[_ply] += _sims; m_total_moves[_ply] += _moves; m_total_samples[_ply]++; } + +private: + time_t m_start, m_end; + int m_sims; + int m_total_sims[BMD_MAX_PLY]; + int m_total_moves[BMD_MAX_PLY]; + int m_total_samples[BMD_MAX_PLY]; + +}; \ No newline at end of file diff --git a/test/bug16_in.txt b/test/bug16_in.txt new file mode 100644 index 0000000..c4c43ec --- /dev/null +++ b/test/bug16_in.txt @@ -0,0 +1,23 @@ +game 3 +reserve +player 0 8 0 +qT +qW +qX +qZ +rnR +rzS +rpU +rfV +player 1 5 0 +B8 +H8 +p8 +z8 +zU?-30 +ply 3 +max_sims 100 +min_sims 5 +maxbranch 400 +getaction +quit \ No newline at end of file