File indexing completed on 2024-05-05 04:03:36
0001 /* 0002 SPDX-FileCopyrightText: 2007 Paolo Capriotti <p.capriotti@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "smartai.h" 0008 0009 #include <algorithm> 0010 #include <time.h> 0011 #include <QRandomGenerator> 0012 0013 class Strategy 0014 { 0015 protected: 0016 Sea::Player m_player; 0017 Sea* m_sea; 0018 SmartAI::State& m_state; 0019 public: 0020 Strategy(Sea::Player player, Sea* sea, SmartAI::State& state) 0021 : m_player(player) 0022 , m_sea(sea) 0023 , m_state(state) 0024 { 0025 } 0026 0027 virtual ~Strategy() { } 0028 virtual Coord getMove() = 0; 0029 virtual Strategy* notify(const Coord& c, const HitInfo& info) = 0; 0030 }; 0031 0032 class DestroyStrategy : public Strategy 0033 { 0034 Coord m_original; 0035 Coord m_begin; 0036 Coord m_end; 0037 int m_direction; 0038 0039 inline Coord direction() const 0040 { 0041 switch(m_direction) { 0042 case 0: 0043 return Coord(1, 0); 0044 case 1: 0045 return Coord(0, 1); 0046 case 2: 0047 return Coord(-1, 0); 0048 case 3: 0049 default: 0050 return Coord(0, -1); 0051 } 0052 } 0053 0054 bool next_try() 0055 { 0056 // if it only hit once 0057 if (m_begin == m_end) { 0058 // change direction 0059 m_direction++; 0060 0061 if (m_direction > 3) { 0062 return false; // no more to do 0063 } 0064 } 0065 else { 0066 if (m_direction > 1) { 0067 // no more to do on this line 0068 // we have probably hit more than one ship 0069 // in the process, so start over changing direction 0070 m_begin = m_original; 0071 m_end = m_original; 0072 m_direction--; 0073 } 0074 else { 0075 // change direction on the same line 0076 m_direction += 2; 0077 0078 // swap begin and end 0079 std::swap(m_begin, m_end); 0080 } 0081 } 0082 0083 return true; 0084 } 0085 public: 0086 DestroyStrategy(Sea::Player player, Sea* sea, SmartAI::State& state, const Coord& begin) 0087 : Strategy(player, sea, state) 0088 , m_original(begin) 0089 , m_begin(begin) 0090 , m_end(begin) 0091 , m_direction(0) 0092 { 0093 } 0094 0095 Coord getMove() override 0096 { 0097 for (;;) { 0098 Coord c = m_end + direction(); 0099 Sea::Player opp = Sea::opponent(m_player); 0100 while (m_sea->valid(opp, c) && m_sea->at(opp, c).type() == Element::DEAD) { 0101 // there's already a hit: go on! 0102 c += direction(); 0103 } 0104 if (m_sea->valid(opp, c) && m_sea->canHit(m_player, c)) { 0105 return c; 0106 } 0107 0108 0109 if (!next_try()) { 0110 return Coord::invalid(); 0111 } 0112 } 0113 } 0114 0115 Strategy* notify(const Coord& c, const HitInfo& info) override 0116 { 0117 if (info.shipDestroyed) { 0118 // success! 0119 m_state.destroyed(info.shipDestroyed->size()); 0120 return m_state.defaultStrategy(m_player, m_sea); 0121 } 0122 else if (info.type == HitInfo::HIT) { 0123 // hit, record info 0124 m_end = c; 0125 } 0126 else { 0127 if (!next_try()) { 0128 // give up 0129 qCDebug(KNAVALBATTLE_LOG) << "giving up (m_direction =" << m_direction << ")"; 0130 return m_state.defaultStrategy(m_player, m_sea); 0131 } 0132 } 0133 0134 return nullptr; 0135 } 0136 }; 0137 0138 class RandomStrategy : public Strategy 0139 { 0140 public: 0141 RandomStrategy(Sea::Player player, Sea* sea, SmartAI::State& state) 0142 : Strategy(player, sea, state) 0143 { 0144 } 0145 0146 Coord getMove() override 0147 { 0148 auto *generator = QRandomGenerator::global(); 0149 for (int i = 0; i < 10000; i++) { 0150 Coord c(generator->bounded(m_sea->size().x), generator->bounded(m_sea->size().y)); 0151 if (m_sea->canHit(m_player, c)) { 0152 return c; 0153 } 0154 } 0155 return Coord::invalid(); 0156 } 0157 0158 Strategy* notify(const Coord& c, const HitInfo& info) override 0159 { 0160 if (info.type == HitInfo::HIT && 0161 !info.shipDestroyed) { 0162 // non-fatal hit, destroy ship now 0163 return new DestroyStrategy(m_player, m_sea, m_state, c); 0164 } 0165 else { 0166 return nullptr; 0167 } 0168 } 0169 }; 0170 0171 class DiagonalStrategy : public Strategy 0172 { 0173 int m_gap; 0174 int m_offset; 0175 int m_range; 0176 0177 // following a diagonal, return true if there is water at the enemy's panel. 0178 bool movesAvailable() const { 0179 Sea::Player opp = Sea::opponent(m_player); 0180 for (int i = 0; i < m_sea->size().x; i++) 0181 for (int j = 0; j < m_sea->size().y; j++) { 0182 if ((j - i - m_offset) % m_gap == 0 && 0183 m_sea->at(opp, Coord(i,j)).free()) { 0184 return true; 0185 } 0186 } 0187 return false; 0188 } 0189 0190 Coord getMoveHelper() 0191 { 0192 int index = QRandomGenerator::global()->bounded(m_range); 0193 int current = 0; 0194 for (int y = m_offset; y < m_sea->size().y; y += m_gap) { 0195 int diag = m_sea->size().y - y; 0196 if (diag > m_sea->size().x) { 0197 diag = m_sea->size().x; 0198 } 0199 if (index < current + diag) { 0200 int x = index - current; 0201 y += x; 0202 return Coord(x, y); 0203 } 0204 current += diag; 0205 } 0206 for (int x = m_gap - m_offset; x < m_sea->size().x; x += m_gap) { 0207 int diag = m_sea->size().x - x; 0208 if (diag > m_sea->size().y) { 0209 diag = m_sea->size().y; 0210 } 0211 if (index < current + diag) { 0212 int y = index - current; 0213 x += y; 0214 return Coord(x, y); 0215 } 0216 current += diag; 0217 } 0218 0219 return Coord::invalid(); 0220 } 0221 0222 void setup() 0223 { 0224 auto *generator = QRandomGenerator::global(); 0225 do { 0226 m_offset = generator->bounded(m_gap); 0227 qCDebug(KNAVALBATTLE_LOG) << "offset =" << m_offset << " / " << m_gap; 0228 } while (!movesAvailable()); 0229 0230 m_range = 0; 0231 for (int y = m_offset; y < m_sea->size().y; y += m_gap) { 0232 int diag = m_sea->size().y - y; 0233 if (diag > m_sea->size().x) { 0234 diag = m_sea->size().x; 0235 } 0236 m_range += diag; 0237 } 0238 for (int x = m_gap - m_offset; x < m_sea->size().x; x += m_gap) { 0239 int diag = m_sea->size().x - x; 0240 if (diag > m_sea->size().y) { 0241 diag = m_sea->size().y; 0242 } 0243 m_range += diag; 0244 } 0245 } 0246 public: 0247 DiagonalStrategy(Sea::Player player, Sea* sea, SmartAI::State& state, int gap) 0248 : Strategy(player, sea, state) 0249 , m_gap(gap) 0250 { 0251 setup(); 0252 } 0253 0254 Coord getMove() override 0255 { 0256 0257 if (!movesAvailable()) { 0258 qCDebug(KNAVALBATTLE_LOG) << "no moves available"; 0259 setup(); 0260 } 0261 for (int i = 0; i < 50; i++) { 0262 Coord c = getMoveHelper(); 0263 0264 if (m_sea->canHit(m_player, c)) { 0265 return c; 0266 } 0267 } 0268 0269 return Coord::invalid(); 0270 } 0271 0272 Strategy* notify(const Coord& c, const HitInfo& info) override 0273 { 0274 if (info.type == HitInfo::HIT && 0275 !info.shipDestroyed) { 0276 // non-fatal hit, destroy ship now 0277 return new DestroyStrategy(m_player, m_sea, m_state, c); 0278 } 0279 else { 0280 return nullptr; 0281 } 0282 } 0283 }; 0284 0285 SmartAI::SmartAI(Sea::Player player, Sea* sea, bool random, const BattleShipsConfiguration* config) 0286 : AI(player, sea, config) 0287 , m_state(random, config) 0288 { 0289 m_strategy = std::unique_ptr<Strategy>(m_state.defaultStrategy(player, sea)); 0290 } 0291 0292 Coord SmartAI::getMove() 0293 { 0294 if (m_strategy.get() && 0295 m_sea->turn() == m_player && 0296 m_sea->status() == Sea::PLAYING) { 0297 Coord move = m_strategy->getMove(); 0298 if ( move == Coord::invalid() ) { 0299 return desperateMove(); 0300 } 0301 else { 0302 return move; 0303 } 0304 } 0305 else { 0306 return desperateMove(); 0307 } 0308 } 0309 0310 void SmartAI::notify(Sea::Player player, const Coord& c, const HitInfo& info) 0311 { 0312 if (player == m_player) { 0313 Strategy* new_strategy = m_strategy->notify(c, info); 0314 if (new_strategy) { 0315 m_strategy = std::unique_ptr<Strategy>(new_strategy); 0316 } 0317 } 0318 } 0319 0320 SmartAI::State::State(bool random, const BattleShipsConfiguration* config) 0321 : m_ships() 0322 , m_random(random) 0323 , m_config(config) 0324 { 0325 for (unsigned int i = 0; i < m_config->longestShip(); i++) { 0326 m_ships[i] = m_config->numberOfShipsOfSize(i+1); 0327 } 0328 } 0329 0330 Strategy* SmartAI::State::defaultStrategy(Sea::Player player, Sea* sea) 0331 { 0332 if (m_random) { 0333 return new RandomStrategy(player, sea, *this); 0334 } 0335 else { 0336 for (int i = m_config->longestShip() - 1; i >= 0; i--) { 0337 if (m_ships[i] > 0 || i == 0) { 0338 return new DiagonalStrategy(player, sea, *this, i + 1); 0339 } 0340 } 0341 0342 // unreachable 0343 return nullptr; 0344 } 0345 } 0346 0347 void SmartAI::State::destroyed(int size) 0348 { 0349 if ( size <= static_cast<int>(m_config->longestShip()) ) { 0350 int index = size - 1; 0351 if (m_ships[index] > 0) { 0352 m_ships[index]--; 0353 } 0354 } 0355 }