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 */