File indexing completed on 2024-11-24 03:43:16
0001 /******************************************************************* 0002 * 0003 * Copyright 2007 Aron Boström <c02ab@efd.lth.se> 0004 * 0005 * Bovo is free software; you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation; either version 2, or (at your option) 0008 * any later version. 0009 * 0010 * Bovo is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License 0016 * along with Bovo; see the file COPYING. If not, write to 0017 * the Free Software Foundation, 51 Franklin Street, Fifth Floor, 0018 * Boston, MA 02110-1301, USA. 0019 * 0020 ********************************************************************/ 0021 0022 /** 0023 * @file AiBoard implementation 0024 */ 0025 0026 #include "aiboard.h" 0027 0028 #include <ctime> 0029 0030 #include <algorithm> 0031 #include <cstdlib> 0032 #include <iostream> 0033 #include <vector> 0034 0035 #include <QRandomGenerator> 0036 0037 #include "aisquare.h" 0038 #include "coord.h" 0039 #include "dimension.h" 0040 #include "move.h" 0041 0042 using namespace bovo; 0043 using namespace std; 0044 0045 namespace ai { 0046 0047 AiBoard::AiBoard(const usi width, const usi height, 0048 KGameDifficultyLevel::StandardLevel skill, Player player) 0049 : m_cleanBoard(true), m_player(player), m_skill(skill) { 0050 m_dimension = new Dimension(width, height); 0051 setup(); 0052 } 0053 0054 AiBoard::AiBoard(const Dimension& dimension, 0055 KGameDifficultyLevel::StandardLevel skill, Player player) 0056 : m_cleanBoard(true), m_player(player), m_skill(skill) { 0057 m_dimension = new Dimension(dimension.width(), dimension.height()); 0058 setup(); 0059 } 0060 0061 AiBoard::~AiBoard() { 0062 for (int x = 0; x < m_dimension->width(); ++x) { 0063 delete[] m_board[x]; 0064 } 0065 delete[] m_board; 0066 delete m_dimension; 0067 } 0068 0069 bool AiBoard::empty(const Coord& c) const { 0070 if (!m_dimension->ok(c)) { 0071 throw outOfBounds(); 0072 } 0073 return m_board[c.x()][c.y()].empty(); 0074 } 0075 0076 bool AiBoard::empty(const usi x, const usi y) const { 0077 return empty(Coord(x, y)); 0078 } 0079 0080 usi AiBoard::height() const { 0081 return m_dimension->height(); 0082 } 0083 0084 Coord AiBoard::move() { 0085 if (m_cleanBoard) { 0086 usi randX = QRandomGenerator::global()->bounded(m_dimension->width()/3) + m_dimension->width()/3; 0087 usi randY = QRandomGenerator::global()->bounded(m_dimension->height()/3) + m_dimension->height()/3; 0088 return Coord(randX, randY); 0089 } 0090 for (usi x = 0; x < m_dimension->width(); ++x) { 0091 for (usi y = 0; y < m_dimension->height(); ++y) { 0092 if (m_board[x][y].status()) { 0093 Coord c(x, y); 0094 setPoints(c, value(c, m_player)); 0095 addPoints(c, value(c, m_player == X ? O : X)); 0096 } 0097 } 0098 } 0099 Coord out = evaluate(); 0100 return out; 0101 } 0102 0103 Coord* AiBoard::moves() { 0104 // FIXME: Implement - Coord* AiBoard::moves(const Coord& c) 0105 return new Coord(); 0106 } 0107 0108 Player AiBoard::player(const Coord& c) const { 0109 if (!m_dimension->ok(c)) { 0110 throw outOfBounds(); 0111 } 0112 return m_board[c.x()][c.y()].player(); 0113 } 0114 0115 Player AiBoard::player(const usi x, const usi y) const { 0116 return player(Coord(x, y)); 0117 } 0118 0119 bool AiBoard::setPlayer(const Move& move) { 0120 m_cleanBoard = false; 0121 zero(move.coord()); 0122 m_board[move.coord().x()][move.coord().y()].setPlayer(move.player()); 0123 if (win(move.coord())) { 0124 m_gameover = true; 0125 return true; 0126 } 0127 return false; 0128 } 0129 0130 void AiBoard::setSkill(KGameDifficultyLevel::StandardLevel skill) { 0131 m_skill = skill; 0132 } 0133 0134 usi AiBoard::width() const { 0135 return m_dimension->width(); 0136 } 0137 0138 /* secret helper functions */ 0139 0140 Coord next(const Coord& c, usi dir) { 0141 const usi LEFT = 1; 0142 const usi UP = 2; 0143 const usi RIGHT = 4; 0144 const usi DOWN = 8; 0145 Coord tmp = c; 0146 if (dir & LEFT) { 0147 tmp = tmp.left(); 0148 } else if (dir & RIGHT) { 0149 tmp = tmp.right(); 0150 } 0151 if (dir & UP) { 0152 tmp = tmp.up(); 0153 } else if (dir & DOWN) { 0154 tmp = tmp.down(); 0155 } 0156 return tmp; 0157 } 0158 0159 bool cmp(const pair<uli, Coord> &a, const pair<uli, Coord> &b) { 0160 return a.first > b.first; 0161 } 0162 0163 /* Private methods */ 0164 0165 Coord AiBoard::evaluate() const { 0166 std::vector<std::pair<uli, Coord> > v, v2, v3; 0167 for (int x = 0; x < m_dimension->width(); ++x) { 0168 for (int y = 0; y < m_dimension->height(); ++y) { 0169 v.emplace_back(points(Coord(x, y)), Coord(x, y)); 0170 } 0171 } 0172 sort(v.begin(), v.end(), cmp); 0173 uli max = v.begin()->first; 0174 for (auto it = v.begin(); 0175 it != v.end(); ++it) { 0176 bool doBreak = false; 0177 switch (m_skill) { 0178 case KGameDifficultyLevel::Impossible: // @TODO: Implement Impossible 0179 case KGameDifficultyLevel::VeryHard: //@TODO: Implement Very Hard 0180 case KGameDifficultyLevel::Hard: 0181 if (it->first == max) { 0182 v2.push_back(*it); 0183 } else { 0184 doBreak = true; 0185 } 0186 break; 0187 case KGameDifficultyLevel::Medium: 0188 if (it->first * 1.2 >= max) { 0189 v2.push_back(*it); 0190 } else { 0191 shuffle(v2.begin(), v2.end(), *QRandomGenerator::global()); 0192 return v2.begin()->second; 0193 } 0194 break; 0195 case KGameDifficultyLevel::Easy: 0196 if (it->first * 2 >= max) { 0197 v2.push_back(*it); 0198 } else { 0199 shuffle(v2.begin(), v2.end(), *QRandomGenerator::global()); 0200 return v2.begin()->second; 0201 } 0202 break; 0203 case KGameDifficultyLevel::VeryEasy: 0204 if (it->first * 4 >= max) { 0205 v2.push_back(*it); 0206 } else { 0207 shuffle(v2.begin(), v2.end(), *QRandomGenerator::global()); 0208 return v2.begin()->second; 0209 } 0210 break; 0211 case KGameDifficultyLevel::RidiculouslyEasy: 0212 default: // in case the gui sets the level to an illegal value 0213 if (it->first * 7 >= max) { 0214 v2.push_back(*it); 0215 } else { 0216 shuffle(v2.begin(), v2.end(), *QRandomGenerator::global()); 0217 return v2.begin()->second; 0218 } 0219 break; 0220 } 0221 if (doBreak) { 0222 break; 0223 } 0224 } 0225 if (v2.empty()) { 0226 throw gameover(); 0227 } else if (v2.size() == 1) { 0228 return v2.begin()->second; 0229 } 0230 for (auto it = v2.begin(); 0231 it != v2.end(); ++it) { 0232 v3.emplace_back(value2(it->second), it->second); 0233 } 0234 sort(v3.begin(), v3.end(), cmp); 0235 if (v3.size() > 1) { 0236 shuffle(v3.begin(), v3.end(), *QRandomGenerator::global()); 0237 } 0238 return v3.begin()->second; 0239 } 0240 0241 uli AiBoard::points(const Coord& c) const { 0242 if (!m_dimension->ok(c)) { 0243 throw outOfBounds(); 0244 } 0245 return m_board[c.x()][c.y()].points(); 0246 } 0247 0248 void AiBoard::addPoints(const Coord& c, uli points) { 0249 if (!m_dimension->ok(c)) { 0250 throw outOfBounds(); 0251 } 0252 m_board[c.x()][c.y()].setPoints(m_board[c.x()][c.y()].points() + points); 0253 } 0254 0255 void AiBoard::setPoints(const Coord& c, uli points) { 0256 if (!m_dimension->ok(c)) { 0257 throw outOfBounds(); 0258 } 0259 m_board[c.x()][c.y()].setPoints(points); 0260 m_board[c.x()][c.y()].setStatus(false); 0261 } 0262 0263 void AiBoard::setup() { 0264 m_gameover = false; 0265 m_board = new AiSquare*[m_dimension->width()]; 0266 for (int x = 0; x < m_dimension->width(); ++x) { 0267 m_board[x] = new AiSquare[m_dimension->height()]; 0268 } 0269 } 0270 0271 uli AiBoard::value(const Coord& c, const usi pl) const { 0272 if (!empty(c)) { 0273 return 0; 0274 } 0275 usi oppositePlayer = (pl==1?2:1); 0276 usi tp; 0277 usi empty; 0278 usi await; 0279 usi leftsideEmpty; 0280 uli tmpPoint = 1; 0281 uli point = 0; 0282 bool enemy = false; 0283 for (int dir = 0; dir < 4; ++dir) { 0284 for (int i = 1; i <= 5; ++i) { 0285 tp = 0; 0286 enemy = false; 0287 empty = 0; 0288 await = 0; 0289 leftsideEmpty = 0; 0290 for (int diff = 5-i; diff > 0-i; --diff) { 0291 Coord tmp = c; 0292 switch (dir) { 0293 case 0: 0294 tmp = Coord(c.x()-diff, c.y()); 0295 break; 0296 case 1: 0297 tmp = Coord(c.x(), c.y()-diff); 0298 break; 0299 case 2: 0300 tmp = Coord(c.x()-diff, c.y()-diff); 0301 break; 0302 case 3: 0303 tmp = Coord(c.x()+diff, c.y()-diff); 0304 break; 0305 } 0306 if (m_dimension->ok(tmp)) { 0307 if (player(tmp) == pl) { 0308 empty += await; 0309 await = 0; 0310 ++tp; 0311 } else if (player(tmp) == oppositePlayer) { 0312 enemy = true; 0313 tp = 0; 0314 } else { 0315 if (tp > 0) { 0316 ++await; 0317 } else { 0318 ++leftsideEmpty; 0319 } 0320 } 0321 } else { 0322 enemy = true; 0323 tp = 0; 0324 } 0325 if (enemy) { 0326 break; 0327 } 0328 } 0329 tmpPoint = 1; 0330 switch (tp) { 0331 case 4: 0332 tmpPoint *= (m_skill == KGameDifficultyLevel::RidiculouslyEasy ? 7 : 231); 0333 [[fallthrough]]; 0334 case 3: 0335 tmpPoint *= (m_skill == KGameDifficultyLevel::VeryEasy ? 21 : 0336 (m_skill == KGameDifficultyLevel::RidiculouslyEasy ? 12 : 231)); 0337 [[fallthrough]]; 0338 case 2: 0339 tmpPoint *= (m_skill == KGameDifficultyLevel::VeryEasy ? 21 : 231 ); 0340 break; 0341 case 1: 0342 tmpPoint *= m_skill == KGameDifficultyLevel::RidiculouslyEasy ? 3 : 1; 0343 break; 0344 case 0: 0345 tmpPoint = 0; 0346 } 0347 if (pl == m_player && m_skill != KGameDifficultyLevel::RidiculouslyEasy 0348 && m_skill != KGameDifficultyLevel::VeryEasy) { 0349 tmpPoint *= 21; 0350 } 0351 if (empty < 2 && await > 0 && leftsideEmpty > 0) { 0352 tmpPoint *= 8; 0353 } 0354 point += tmpPoint; 0355 } 0356 } 0357 return point; 0358 } 0359 0360 uli AiBoard::value2(const Coord& c) const { 0361 uli p = 0; 0362 usi lp = 0; 0363 usi q = 1; 0364 Coord tmp(c.x(), c.y()); 0365 bool test = true; 0366 for (usi u = 1; u < 3; ++u) { 0367 for (usi i = 0; i < 4; ++i) { 0368 while (test) { 0369 switch (i) { 0370 case 0: 0371 tmp = Coord(c.x()-q, c.y()); 0372 break; 0373 case 1: 0374 tmp = Coord(c.x(), c.y()-q); 0375 break; 0376 case 2: 0377 tmp = Coord(c.x()-q, c.y()-q); 0378 break; 0379 case 3: 0380 tmp = Coord(c.x()+q, c.y()-q); 0381 break; 0382 } 0383 test = m_dimension->ok(tmp); 0384 if (test) { 0385 test = player(tmp) == u; 0386 } 0387 if (test) { 0388 ++lp; 0389 } 0390 ++q; 0391 } 0392 test = true; 0393 q = 1; 0394 while (test) { 0395 switch (i) { 0396 case 0: 0397 tmp = Coord(c.x()+q, c.y()); 0398 break; 0399 case 1: 0400 tmp = Coord(c.x(), c.y()+q); 0401 break; 0402 case 2: 0403 tmp = Coord(c.x()+q, c.y()+q); 0404 break; 0405 case 3: 0406 tmp = Coord(c.x()-q, c.y()+q); 0407 break; 0408 } 0409 test = m_dimension->ok(tmp); 0410 if (test) { 0411 test = player(tmp) == u; 0412 } 0413 if (test) { 0414 ++lp; 0415 } 0416 ++q; 0417 } 0418 switch (lp) { 0419 case 0: 0420 case 1: 0421 p += lp; 0422 break; 0423 case 2: 0424 p += (9*lp); 0425 break; 0426 case 3: 0427 p += (8*9*lp+1); 0428 break; 0429 default: 0430 p += 1000; 0431 break; 0432 } 0433 test = true; 0434 q = 1; 0435 } 0436 } 0437 return p; 0438 } 0439 0440 bool AiBoard::win(const Coord& c) const { 0441 const usi LEFT = 1; 0442 const usi UP = 2; 0443 const usi RIGHT = 4; 0444 const usi DOWN = 8; 0445 usi DIR[8] = { 0446 LEFT, 0447 RIGHT, 0448 UP, 0449 DOWN, 0450 LEFT | UP, 0451 RIGHT | DOWN, 0452 LEFT|DOWN, 0453 RIGHT|UP 0454 }; 0455 usi p = player(c); 0456 for (int i = 0; i < 4; ++i) { 0457 usi count = 1; 0458 Coord tmp = next(c, DIR[2*i]); 0459 while (m_dimension->ok(tmp) && player(tmp) == p) { 0460 ++count; 0461 tmp = next(tmp, DIR[2*i]); 0462 } 0463 tmp = next(c, DIR[2*i+1]); 0464 while (m_dimension->ok(tmp) && player(tmp) == p) { 0465 ++count; 0466 tmp = next(tmp, DIR[2*i+1]); 0467 } 0468 if (count >= 5) { 0469 return true; 0470 } 0471 } 0472 return false; 0473 } 0474 0475 void AiBoard::zero(const Coord& c) { 0476 usi minX = c.x()-5 < 0 ? 0 : c.x()-5; 0477 usi maxX = (c.x()+5 > m_dimension->width()-1) ? (m_dimension->width()-1) : c.x()+5; 0478 usi minY = c.y()-5 < 0 ? 0 : c.y()-5; 0479 usi maxY = (c.y()+5 > m_dimension->height()-1) ? (m_dimension->height()-1) : c.y()+5; 0480 for (int x = minX; x <= maxX; ++x) { 0481 for (int y = minY; y <= maxY; ++y) { 0482 m_board[x][y].setStatus(true); 0483 } 0484 } 0485 } 0486 0487 } /* namespace ai */