BitBully 0.0.39
Loading...
Searching...
No Matches
Solver.cpp
1/*
2 * This file is part of Connect4 Game Solver <http://connect4.gamesolver.org>
3 * Copyright (C) 2017-2019 Pascal Pons <contact@gamesolver.org>
4 *
5 * Connect4 Game Solver is free software: you can redistribute it and/or
6 * modify it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * Connect4 Game Solver is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with Connect4 Game Solver. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include <cassert>
20#include "Solver.hpp"
21#include "MoveSorter.hpp"
22
23using namespace GameSolver::Connect4;
24
25namespace GameSolver {
26namespace Connect4 {
27
39int Solver::negamax(const Position &P, int alpha, int beta) {
40 assert(alpha < beta);
41 assert(!P.canWinNext());
42
43 nodeCount++; // increment counter of explored nodes
44
45 Position::position_t possible = P.possibleNonLosingMoves();
46 if(possible == 0) // if no possible non losing move, opponent wins next move
47 return -(Position::WIDTH * Position::HEIGHT - P.nbMoves()) / 2;
48
49 if(P.nbMoves() >= Position::WIDTH * Position::HEIGHT - 2) // check for draw game
50 return 0;
51
52 int min = -(Position::WIDTH * Position::HEIGHT - 2 - P.nbMoves()) / 2; // lower bound of score as opponent cannot win next move
53 if(alpha < min) {
54 alpha = min; // there is no need to keep alpha below our max possible score.
55 if(alpha >= beta) return alpha; // prune the exploration if the [alpha;beta] window is empty.
56 }
57
58 int max = (Position::WIDTH * Position::HEIGHT - 1 - P.nbMoves()) / 2; // upper bound of our score as we cannot win immediately
59 if(beta > max) {
60 beta = max; // there is no need to keep beta above our max possible score.
61 if(alpha >= beta) return beta; // prune the exploration if the [alpha;beta] window is empty.
62 }
63
64 const Position::position_t key = P.key();
65 if(int val = transTable.get(key)) {
66 if(val > Position::MAX_SCORE - Position::MIN_SCORE + 1) { // we have an lower bound
67 min = val + 2 * Position::MIN_SCORE - Position::MAX_SCORE - 2;
68 if(alpha < min) {
69 alpha = min; // there is no need to keep beta above our max possible score.
70 if(alpha >= beta) return alpha; // prune the exploration if the [alpha;beta] window is empty.
71 }
72 } else { // we have an upper bound
73 max = val + Position::MIN_SCORE - 1;
74 if(beta > max) {
75 beta = max; // there is no need to keep beta above our max possible score.
76 if(alpha >= beta) return beta; // prune the exploration if the [alpha;beta] window is empty.
77 }
78 }
79 }
80
81 if(int val = book.get(P)) return val + Position::MIN_SCORE - 1; // look for solutions stored in opening book
82
83 MoveSorter moves;
84 for(int i = Position::WIDTH; i--;)
85 if(Position::position_t move = possible & Position::column_mask(columnOrder[i]))
86 moves.add(move, P.moveScore(move));
87
88 while(Position::position_t next = moves.getNext()) {
89 Position P2(P);
90 P2.play(next); // It's opponent turn in P2 position after current player plays x column.
91 int score = -negamax(P2, -beta, -alpha); // explore opponent's score within [-beta;-alpha] windows:
92 // no need to have good precision for score better than beta (opponent's score worse than -beta)
93 // no need to check for score worse than alpha (opponent's score worse better than -alpha)
94
95 if(score >= beta) {
96 transTable.put(key, score + Position::MAX_SCORE - 2 * Position::MIN_SCORE + 2); // save the lower bound of the position
97 return score; // prune the exploration if we find a possible move better than what we were looking for.
98 }
99 if(score > alpha) alpha = score; // reduce the [alpha;beta] window for next exploration, as we only
100 // need to search for a position that is better than the best so far.
101 }
102
103 transTable.put(key, alpha - Position::MIN_SCORE + 1); // save the upper bound of the position
104 return alpha;
105}
106
107int Solver::solve(const Position &P, bool weak) {
108 if(P.canWinNext()) // check if win in one move as the Negamax function does not support this case.
109 return (Position::WIDTH * Position::HEIGHT + 1 - P.nbMoves()) / 2;
110 int min = -(Position::WIDTH * Position::HEIGHT - P.nbMoves()) / 2;
111 int max = (Position::WIDTH * Position::HEIGHT + 1 - P.nbMoves()) / 2;
112 if(weak) {
113 min = -1;
114 max = 1;
115 }
116
117 while(min < max) { // iteratively narrow the min-max exploration window
118 int med = min + (max - min) / 2;
119 if(med <= 0 && min / 2 < med) med = min / 2;
120 else if(med >= 0 && max / 2 > med) med = max / 2;
121 int r = negamax(P, med, med + 1); // use a null depth window to know if the actual score is greater or smaller than med
122 if(r <= med) max = r;
123 else min = r;
124 }
125 return min;
126}
127
128std::vector<int> Solver::analyze(const Position &P, bool weak) {
129 std::vector<int> scores(Position::WIDTH, Solver::INVALID_MOVE);
130 for (int col = 0; col < Position::WIDTH; col++)
131 if (P.canPlay(col)) {
132 if(P.isWinningMove(col)) scores[col] = (Position::WIDTH * Position::HEIGHT + 1 - P.nbMoves()) / 2;
133 else {
134 Position P2(P);
135 P2.playCol(col);
136 scores[col] = -solve(P2, weak);
137 }
138 }
139 return scores;
140}
141
142// Constructor
143Solver::Solver() : nodeCount{0} {
144 for(int i = 0; i < Position::WIDTH; i++) // initialize the column exploration order, starting with center columns
145 columnOrder[i] = Position::WIDTH / 2 + (1 - 2 * (i % 2)) * (i + 1) / 2; // example for WIDTH=7: columnOrder = {3, 4, 2, 5, 1, 6, 0}
146}
147
148} // namespace Connect4
149} // namespace GameSolver
void add(const Position::position_t move, const int score)
Position::position_t getNext()
bool canPlay(int col) const
Definition Position.hpp:239
bool isWinningMove(int col) const
Definition Position.hpp:259
position_t possibleNonLosingMoves() const
Definition Position.hpp:200
int moveScore(position_t move) const
Definition Position.hpp:224