SVN_SILENT made messages (.desktop file)
[kdegames.git] / kreversi / Engine.cpp
blob846b5196914b830dce10409f5869c7c971813fb4
1 /*******************************************************************
3 * Copyright 1997 Mario Weilguni <mweilguni@sime.com>
5 * This file is part of the KDE project "KREVERSI"
7 * KREVERSI is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
12 * KREVERSI is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with KREVERSI; see the file COPYING. If not, write to
19 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
22 *******************************************************************
25 * KREVERSI
28 *******************************************************************
30 * A Reversi (or sometimes called Othello) game
32 *******************************************************************
34 * Created 1997 by Mario Weilguni <mweilguni@sime.com>. This file
35 * is ported from Mats Luthman's <Mats.Luthman@sylog.se> JAVA applet.
36 * Many thanks to Mr. Luthman who has allowed me to put this port
37 * under the GNU GPL. Without his wonderful game engine kreversi
38 * would be just another of those Reversi programs a five year old
39 * child could beat easily. But with it it's a worthy opponent!
41 * If you are interested on the JAVA applet of Mr. Luthman take a
42 * look at http://www.sylog.se/~mats/
45 // The class Engine produces moves from a Game object through calls to the
46 // function ComputeMove().
48 // First of all: this is meant to be a simple example of a game playing
49 // program. Not everything is done in the most clever way, particularly not
50 // the way the moves are searched, but it is hopefully made in a way that makes
51 // it easy to understand. The function ComputeMove2() that does all the work
52 // is actually not much more than a hundred lines. Much could be done to
53 // make the search faster though, I'm perfectly aware of that. Feel free
54 // to experiment.
56 // The method used to generate the moves is called minimax tree search with
57 // alpha-beta pruning to a fixed depth. In short this means that all possible
58 // moves a predefined number of moves ahead are either searched or refuted
59 // with a method called alpha-beta pruning. A more thorough explanation of
60 // this method could be found at the world wide web at http:
61 // //yoda.cis.temple.edu:8080/UGAIWWW/lectures96/search/minimax/alpha-beta.html
62 // at the time this was written. Searching for "minimax" would also point
63 // you to information on this subject. It is probably possible to understand
64 // this method by reading the source code though, it is not that complicated.
66 // At every leaf node at the search tree, the resulting position is evaluated.
67 // Two things are considered when evaluating a position: the number of pieces
68 // of each color and at which squares the pieces are located. Pieces at the
69 // corners are valuable and give a high value, and having pieces at squares
70 // next to a corner is not very good and they give a lower value. In the
71 // beginning of a game it is more important to have pieces on "good" squares,
72 // but towards the end the total number of pieces of each color is given a
73 // higher weight. Other things, like how many legal moves that can be made in a
74 // position, and the number of pieces that can never be turned would probably
75 // make the program stronger if they were considered in evaluating a position,
76 // but that would make things more complicated (this was meant to be very
77 // simple example) and would also slow down computation (considerably?).
79 // The member m_board[10][10]) holds the current position during the
80 // computation. It is initiated at the start of ComputeMove() and
81 // every move that is made during the search is made on this board. It should
82 // be noted that 1 to 8 is used for the actual board, but 0 and 9 can be
83 // used too (they are always empty). This is practical when turning pieces
84 // when moves are made on the board. Every piece that is put on the board
85 // or turned is saved in the stack m_squarestack (see class SquareStack) so
86 // every move can easily be reversed after the search in a node is completed.
88 // The member m_bc_board[][] holds board control values for each square
89 // and is initiated by a call to the function private void SetupBcBoard()
90 // from Engines constructor. It is used in evaluation of positions except
91 // when the game tree is searched all the way to the end of the game.
93 // The two members m_coord_bit[9][9] and m_neighbor_bits[9][9] are used to
94 // speed up the tree search. This goes against the principle of keeping things
95 // simple, but to understand the program you do not need to understand them
96 // at all. They are there to make it possible to throw away moves where
97 // the piece that is played is not adjacent to a piece of opposite color
98 // at an early stage (because they could never be legal). It should be
99 // pointed out that not all moves that pass this test are legal, there will
100 // just be fewer moves that have to be tested in a more time consuming way.
102 // There are also two other members that should be mentioned: Score m_score
103 // and Score m_bc_score. They hold the number of pieces of each color and
104 // the sum of the board control values for each color during the search
105 // (this is faster than counting at every leaf node).
108 // The classes SquareStackEntry and SquareStack implement a
109 // stack that is used by Engine to store pieces that are turned during
110 // searching (see ComputeMove()).
112 // The class MoveAndValue is used by Engine to store all possible moves
113 // at the first level and the values that were calculated for them.
114 // This makes it possible to select a random move among those with equal
115 // or nearly equal value after the search is completed.
118 #include "Engine.h"
119 #include "kreversigame.h"
120 #include <QApplication>
121 #include <KDebug>
123 // ================================================================
124 // Classes SquareStackEntry and SquareStack
127 // A SquareStack is used to store changes to the squares on the board
128 // during search.
131 inline void SquareStackEntry::setXY(int x, int y) {
132 m_x = x;
133 m_y = y;
137 SquareStackEntry::SquareStackEntry()
139 setXY(0,0);
143 // ----------------------------------------------------------------
146 SquareStack::SquareStack() {
147 init(0);
151 SquareStack::SquareStack(int size) {
152 init(size);
156 void SquareStack::resize(int size)
158 m_squarestack.resize(size);
162 // (Re)initialize the stack so that is empty, and at the same time
163 // resize it to 'size'.
166 void SquareStack::init(int size)
168 resize(size);
170 m_top = 0;
171 for (int i = 0; i < size; i++)
172 m_squarestack[i].setXY(0,0);
177 inline SquareStackEntry SquareStack::Pop()
179 return m_squarestack[--m_top];
183 inline void SquareStack::Push(int x, int y)
185 m_squarestack[m_top].m_x = x;
186 m_squarestack[m_top++].m_y = y;
190 // ================================================================
191 // Class MoveAndValue
194 // Class MoveAndValue aggregates a move with its value.
198 inline void MoveAndValue::setXYV(int x, int y, int value)
200 m_x = x;
201 m_y = y;
202 m_value = value;
206 MoveAndValue::MoveAndValue()
208 setXYV(0,0,0);
212 MoveAndValue::MoveAndValue(int x, int y, int value)
214 setXYV(x, y, value);
217 // ================================================================
218 // class Score
220 /* This class keeps track of the score for both colors. Such a score
221 * could be either the number of pieces, the score from the evaluation
222 * function or anything similar.
224 class Score {
225 public:
226 Score()
228 m_score[White] = 0;
229 m_score[Black] = 0;
232 uint score(ChipColor color) const { return m_score[color]; }
234 void set(ChipColor color, uint score) { m_score[color] = score; }
235 void inc(ChipColor color) { m_score[color]++; }
236 void dec(ChipColor color) { m_score[color]--; }
237 void add(ChipColor color, uint s) { m_score[color] += s; }
238 void sub(ChipColor color, uint s) { m_score[color] -= s; }
240 private:
241 uint m_score[2];
244 // ================================================================
245 // The Engine itself
248 // Some special values used in the search.
249 static const int LARGEINT = 99999;
250 static const int ILLEGAL_VALUE = 8888888;
251 static const int BC_WEIGHT = 3;
254 Engine::Engine(int st, int sd)/* : SuperEngine(st, sd) */
255 : m_strength(st), m_computingMove( false )
257 m_random.setSeed(sd);
258 m_score = new Score;
259 m_bc_score = new Score;
260 SetupBcBoard();
261 SetupBits();
265 Engine::Engine(int st) //: SuperEngine(st)
266 : m_strength(st), m_computingMove(false)
268 m_random.setSeed(0);
269 m_score = new Score;
270 m_bc_score = new Score;
271 SetupBcBoard();
272 SetupBits();
276 Engine::Engine()// : SuperEngine(1)
277 : m_strength(1), m_computingMove(false)
279 m_random.setSeed(0);
280 m_score = new Score;
281 m_bc_score = new Score;
282 SetupBcBoard();
283 SetupBits();
286 Engine::~Engine()
288 delete m_score;
289 delete m_bc_score;
292 // keep GUI alive
293 void Engine::yield()
295 qApp->processEvents();
299 // Calculate the best move from the current position, and return it.
301 KReversiPos Engine::computeMove(const KReversiGame& game, bool competitive)
303 if( m_computingMove )
305 kDebug() << "I'm already computing move! Yours KReversi Engine.";
306 return KReversiPos();
308 m_computingMove = true;
310 ChipColor color;
312 // A competitive game is one where we try our damnedest to make the
313 // best move. The opposite is a casual game where the engine might
314 // make "a mistake". The idea behind this is not to scare away
315 // newbies. The member m_competitive is used during search for this
316 // very move.
317 m_competitive = competitive;
319 // Suppose that we should give a heuristic evaluation. If we are
320 // close to the end of the game we can make an exhaustive search,
321 // but that case is determined further down.
322 m_exhaustive = false;
324 // Get the color to calculate the move for.
325 color = game.currentPlayer();
326 if (color == NoColor)
328 m_computingMove = false;
329 return KReversiPos();
332 // Figure out the current score
333 m_score->set(White, game.playerScore(White));
334 m_score->set(Black, game.playerScore(Black));
336 // Treat the first move as a special case (we can basically just
337 // pick a move at random).
338 if (m_score->score(White) + m_score->score(Black) == 4)
340 m_computingMove = false;
341 return ComputeFirstMove(game);
344 // Let there be room for 3000 changes during the recursive search.
345 // This is more than will ever be needed.
346 m_squarestack.init(3000);
348 // Get the search depth. If we are close to the end of the game,
349 // the number of possible moves goes down, so we can search deeper
350 // without using more time.
351 m_depth = m_strength;
352 if (m_score->score(White) + m_score->score(Black) + m_depth + 3 >= 64)
353 m_depth = 64 - m_score->score(White) - m_score->score(Black);
354 else if (m_score->score(White) + m_score->score(Black) + m_depth + 4 >= 64)
355 m_depth += 2;
356 else if (m_score->score(White) + m_score->score(Black) + m_depth + 5 >= 64)
357 m_depth++;
359 // If we are very close to the end, we can even make the search
360 // exhaustive.
361 if (m_score->score(White) + m_score->score(Black) + m_depth >= 64)
362 m_exhaustive = true;
364 // The evaluation is a linear combination of the score (number of
365 // pieces) and the sum of the scores for the squares (given by
366 // m_bc_score). The earlier in the game, the more we use the square
367 // values and the later in the game the more we use the number of
368 // pieces.
369 m_coeff = 100 - (100*
370 (m_score->score(White) + m_score->score(Black)
371 + m_depth - 4)) / 60;
373 // Initialize the board that we use for the search.
374 for (uint x = 0; x < 10; x++)
375 for (uint y = 0; y < 10; y++) {
376 if (1 <= x && x <= 8
377 && 1 <= y && y <= 8)
378 m_board[x][y] = game.chipColorAt(y-1, x-1);
379 else
380 m_board[x][y] = NoColor;
383 // Initialize a lot of stuff that we will use in the search.
385 // Initialize m_bc_score to the current bc score. This is kept
386 // up-to-date incrementally so that way we won't have to calculate
387 // it from scratch for each evaluation.
388 m_bc_score->set(White, CalcBcScore(White));
389 m_bc_score->set(Black, CalcBcScore(Black));
391 quint64 colorbits = ComputeOccupiedBits(color);
392 quint64 opponentbits = ComputeOccupiedBits(opponentColorFor(color));
394 int maxval = -LARGEINT;
395 int max_x = 0;
396 int max_y = 0;
398 MoveAndValue moves[60];
399 int number_of_moves = 0;
400 int number_of_maxval = 0;
402 setInterrupt(false);
404 quint64 null_bits;
405 null_bits = 0;
407 // The main search loop. Step through all possible moves and keep
408 // track of the most valuable one. This move is stored in
409 // (max_x, max_y) and the value is stored in maxval.
410 m_nodes_searched = 0;
411 for (int x = 1; x < 9; x++) {
412 for (int y = 1; y < 9; y++) {
413 // Don't bother with non-empty squares and squares that aren't
414 // neighbors to opponent pieces.
415 if (m_board[x][y] != NoColor
416 || (m_neighbor_bits[x][y] & opponentbits) == null_bits)
417 continue;
420 int val = ComputeMove2(x, y, color, 1, maxval,
421 colorbits, opponentbits);
423 if (val != ILLEGAL_VALUE) {
424 moves[number_of_moves++].setXYV(x, y, val);
426 // If the move is better than all previous moves, then record
427 // this fact...
428 if (val > maxval) {
430 // ...except that we want to make the computer miss some
431 // good moves so that beginners can play against the program
432 // and not always lose. However, we only do this if the
433 // user wants a casual game, which is set in the settings
434 // dialog.
435 int randi = m_random.getLong(7);
436 if (maxval == -LARGEINT
437 || m_competitive
438 || randi < (int) m_strength) {
439 maxval = val;
440 max_x = x;
441 max_y = y;
443 number_of_maxval = 1;
446 else if (val == maxval)
447 number_of_maxval++;
450 // Jump out prematurely if interrupt is set.
451 if (interrupted())
452 break;
456 // long endtime = times(&tmsdummy);
458 // If there are more than one best move, the pick one randomly.
459 if (number_of_maxval > 1) {
460 int r = m_random.getLong(number_of_maxval) + 1;
461 int i;
463 for (i = 0; i < number_of_moves; ++i) {
464 if (moves[i].m_value == maxval && --r <= 0)
465 break;
468 max_x = moves[i].m_x;
469 max_y = moves[i].m_y;
472 m_computingMove = false;
473 // Return a suitable move.
474 if (interrupted())
475 return KReversiPos(NoColor, -1, -1);
476 else if (maxval != -LARGEINT)
477 return KReversiPos(color, max_y-1, max_x-1);
478 else
479 return KReversiPos(NoColor, -1, -1);
483 // Get the first move. We can pick any move at random.
486 KReversiPos Engine::ComputeFirstMove(const KReversiGame& game)
488 int r;
489 ChipColor color = game.currentPlayer();
491 r = m_random.getLong(4) + 1;
493 if (color == White) {
494 if (r == 1) return KReversiPos(color, 4, 2);
495 else if (r == 2) return KReversiPos(color, 5, 3);
496 else if (r == 3) return KReversiPos(color, 2, 4);
497 else return KReversiPos(color, 3, 5);
499 else {
500 if (r == 1) return KReversiPos(color, 3, 2);
501 else if (r == 2) return KReversiPos(color, 5, 4);
502 else if (r == 3) return KReversiPos(color, 2, 3);
503 else return KReversiPos(color, 4, 5);
508 // Play a move at (xplay, yplay) and generate a value for it. If we
509 // are at the maximum search depth, we get the value by calling
510 // EvaluatePosition(), otherwise we get it by performing an alphabeta
511 // search.
514 int Engine::ComputeMove2(int xplay, int yplay, ChipColor color, int level,
515 int cutoffval, quint64 colorbits,
516 quint64 opponentbits)
518 int number_of_turned = 0;
519 SquareStackEntry mse;
520 ChipColor opponent = opponentColorFor(color);
522 m_nodes_searched++;
524 // Put the piece on the board and incrementally update scores and bitmaps.
525 m_board[xplay][yplay] = color;
526 colorbits |= m_coord_bit[xplay][yplay];
527 m_score->inc(color);
528 m_bc_score->add(color, m_bc_board[xplay][yplay]);
530 // Loop through all 8 directions and turn the pieces that can be turned.
531 for (int xinc = -1; xinc <= 1; xinc++)
532 for (int yinc = -1; yinc <= 1; yinc++) {
533 if (xinc == 0 && yinc == 0)
534 continue;
536 int x, y;
538 for (x = xplay + xinc, y = yplay + yinc; m_board[x][y] == opponent;
539 x += xinc, y += yinc)
542 // If we found the end of a turnable row, then go back and turn
543 // all pieces on the way back. Also push the squares with
544 // turned pieces on the squarestack so that we can undo the move
545 // later.
546 if (m_board[x][y] == color)
547 for (x -= xinc, y -= yinc; x != xplay || y != yplay;
548 x -= xinc, y -= yinc) {
549 m_board[x][y] = color;
550 colorbits |= m_coord_bit[x][y];
551 opponentbits &= ~m_coord_bit[x][y];
553 m_squarestack.Push(x, y);
555 m_bc_score->add(color, m_bc_board[x][y]);
556 m_bc_score->sub(opponent, m_bc_board[x][y]);
557 number_of_turned++;
561 int retval = -LARGEINT;
563 // If we managed to turn at least one piece, then (xplay, yplay) was
564 // a legal move. Now find out the value of the move.
565 if (number_of_turned > 0) {
567 // First adjust the number of pieces for each side.
568 m_score->add(color, number_of_turned);
569 m_score->sub(opponent, number_of_turned);
571 // If we are at the bottom of the search, get the evaluation.
572 if (level >= m_depth)
573 retval = EvaluatePosition(color); // Terminal node
574 else {
575 int maxval = TryAllMoves(opponent, level, cutoffval, opponentbits,
576 colorbits);
578 if (maxval != -LARGEINT)
579 retval = -maxval;
580 else {
582 // No possible move for the opponent, it is colors turn again:
583 retval = TryAllMoves(color, level, -LARGEINT, colorbits, opponentbits);
585 if (retval == -LARGEINT) {
587 // No possible move for anybody => end of game:
588 int finalscore = m_score->score(color) - m_score->score(opponent);
590 if (m_exhaustive)
591 retval = finalscore;
592 else {
593 // Take a sure win and avoid a sure loss (may not be optimal):
595 if (finalscore > 0)
596 retval = LARGEINT - 65 + finalscore;
597 else if (finalscore < 0)
598 retval = -(LARGEINT - 65 + finalscore);
599 else
600 retval = 0;
606 m_score->add(opponent, number_of_turned);
607 m_score->sub(color, number_of_turned);
610 // Undo the move. Start by unturning the turned pieces.
611 for (int i = number_of_turned; i > 0; i--) {
612 mse = m_squarestack.Pop();
613 m_bc_score->add(opponent, m_bc_board[mse.m_x][mse.m_y]);
614 m_bc_score->sub(color, m_bc_board[mse.m_x][mse.m_y]);
615 m_board[mse.m_x][mse.m_y] = opponent;
618 // Now remove the new piece that we put down.
619 m_board[xplay][yplay] = NoColor;
620 m_score->sub(color, 1);
621 m_bc_score->sub(color, m_bc_board[xplay][yplay]);
623 // Return a suitable value.
624 if (number_of_turned < 1 || interrupted())
625 return ILLEGAL_VALUE;
626 else
627 return retval;
631 // Generate all legal moves from the current position, and do a search
632 // to see the value of them. This function returns the value of the
633 // most valuable move, but not the move itself.
636 int Engine::TryAllMoves(ChipColor opponent, int level, int cutoffval,
637 quint64 opponentbits, quint64 colorbits)
639 int maxval = -LARGEINT;
641 // Keep GUI alive by calling the event loop.
642 yield();
644 quint64 null_bits;
645 null_bits = 0;
647 for (int x = 1; x < 9; x++) {
648 for (int y = 1; y < 9; y++) {
649 if (m_board[x][y] == NoColor
650 && (m_neighbor_bits[x][y] & colorbits) != null_bits) {
651 int val = ComputeMove2(x, y, opponent, level+1, maxval, opponentbits,
652 colorbits);
654 if (val != ILLEGAL_VALUE && val > maxval) {
655 maxval = val;
656 if (maxval > -cutoffval || interrupted())
657 break;
662 if (maxval > -cutoffval || interrupted())
663 break;
666 if (interrupted())
667 return -LARGEINT;
669 return maxval;
673 // Calculate a heuristic value for the current position. If we are at
674 // the end of the game, do this by counting the pieces. Otherwise do
675 // it by combining the score using the number of pieces, and the score
676 // using the board control values.
679 int Engine::EvaluatePosition(ChipColor color)
681 int retval;
683 ChipColor opponent = opponentColorFor(color);
685 int score_color = m_score->score(color);
686 int score_opponent = m_score->score(opponent);
688 if (m_exhaustive)
689 retval = score_color - score_opponent;
690 else {
691 retval = (100-m_coeff) *
692 (m_score->score(color) - m_score->score(opponent))
693 + m_coeff * BC_WEIGHT * (m_bc_score->score(color)
694 - m_bc_score->score(opponent));
697 return retval;
701 // Calculate bitmaps for each square, and also for neighbors of each
702 // square.
705 void Engine::SetupBits()
707 //m_coord_bit = new long[9][9];
708 //m_neighbor_bits = new long[9][9];
710 quint64 bits = 1;
712 // Store a 64 bit unsigned it with the corresponding bit set for
713 // each square.
714 for (int i=1; i < 9; i++)
715 for (int j=1; j < 9; j++) {
716 m_coord_bit[i][j] = bits;
717 bits *= 2;
720 // Store a bitmap consisting of all neighbors for each square.
721 for (int i=1; i < 9; i++)
722 for (int j=1; j < 9; j++) {
723 m_neighbor_bits[i][j] = 0;
725 for (int xinc=-1; xinc<=1; xinc++)
726 for (int yinc=-1; yinc<=1; yinc++) {
727 if (xinc != 0 || yinc != 0)
728 if (i + xinc > 0 && i + xinc < 9 && j + yinc > 0 && j + yinc < 9)
729 m_neighbor_bits[i][j] |= m_coord_bit[i + xinc][j + yinc];
735 // Set up the board control values that will be used in evaluation of
736 // the position.
739 void Engine::SetupBcBoard()
741 // JAVA m_bc_board = new int[9][9];
743 for (int i=1; i < 9; i++)
744 for (int j=1; j < 9; j++) {
745 if (i == 2 || i == 7)
746 m_bc_board[i][j] = -1;
747 else
748 m_bc_board[i][j] = 0;
750 if (j == 2 || j == 7)
751 m_bc_board[i][j] -= 1;
754 m_bc_board[1][1] = 2;
755 m_bc_board[8][1] = 2;
756 m_bc_board[1][8] = 2;
757 m_bc_board[8][8] = 2;
759 m_bc_board[1][2] = -1;
760 m_bc_board[2][1] = -1;
761 m_bc_board[1][7] = -1;
762 m_bc_board[7][1] = -1;
763 m_bc_board[8][2] = -1;
764 m_bc_board[2][8] = -1;
765 m_bc_board[8][7] = -1;
766 m_bc_board[7][8] = -1;
770 // Calculate the board control score.
773 int Engine::CalcBcScore(ChipColor color)
775 int sum = 0;
777 for (int i=1; i < 9; i++)
778 for (int j=1; j < 9; j++)
779 if (m_board[i][j] == color)
780 sum += m_bc_board[i][j];
782 return sum;
786 // Calculate a bitmap of the occupied squares for a certain color.
789 quint64 Engine::ComputeOccupiedBits(ChipColor color)
791 quint64 retval = 0;
793 for (int i=1; i < 9; i++)
794 for (int j=1; j < 9; j++)
795 if (m_board[i][j] == color) retval |= m_coord_bit[i][j];
797 return retval;