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)
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 *******************************************************************
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
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.
119 #include "kreversigame.h"
120 #include <QApplication>
123 // ================================================================
124 // Classes SquareStackEntry and SquareStack
127 // A SquareStack is used to store changes to the squares on the board
131 inline void SquareStackEntry::setXY(int x
, int y
) {
137 SquareStackEntry::SquareStackEntry()
143 // ----------------------------------------------------------------
146 SquareStack::SquareStack() {
151 SquareStack::SquareStack(int 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
)
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
)
206 MoveAndValue::MoveAndValue()
212 MoveAndValue::MoveAndValue(int x
, int y
, int value
)
217 // ================================================================
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.
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
; }
244 // ================================================================
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
);
259 m_bc_score
= new Score
;
265 Engine::Engine(int st
) //: SuperEngine(st)
266 : m_strength(st
), m_computingMove(false)
270 m_bc_score
= new Score
;
276 Engine::Engine()// : SuperEngine(1)
277 : m_strength(1), m_computingMove(false)
281 m_bc_score
= new Score
;
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;
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
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)
356 else if (m_score
->score(White
) + m_score
->score(Black
) + m_depth
+ 5 >= 64)
359 // If we are very close to the end, we can even make the search
361 if (m_score
->score(White
) + m_score
->score(Black
) + m_depth
>= 64)
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
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
++) {
378 m_board
[x
][y
] = game
.chipColorAt(y
-1, x
-1);
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
;
398 MoveAndValue moves
[60];
399 int number_of_moves
= 0;
400 int number_of_maxval
= 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
)
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
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
435 int randi
= m_random
.getLong(7);
436 if (maxval
== -LARGEINT
438 || randi
< (int) m_strength
) {
443 number_of_maxval
= 1;
446 else if (val
== maxval
)
450 // Jump out prematurely if interrupt is set.
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;
463 for (i
= 0; i
< number_of_moves
; ++i
) {
464 if (moves
[i
].m_value
== maxval
&& --r
<= 0)
468 max_x
= moves
[i
].m_x
;
469 max_y
= moves
[i
].m_y
;
472 m_computingMove
= false;
473 // Return a suitable move.
475 return KReversiPos(NoColor
, -1, -1);
476 else if (maxval
!= -LARGEINT
)
477 return KReversiPos(color
, max_y
-1, max_x
-1);
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
)
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);
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
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
);
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
];
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)
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
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
]);
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
575 int maxval
= TryAllMoves(opponent
, level
, cutoffval
, opponentbits
,
578 if (maxval
!= -LARGEINT
)
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
);
593 // Take a sure win and avoid a sure loss (may not be optimal):
596 retval
= LARGEINT
- 65 + finalscore
;
597 else if (finalscore
< 0)
598 retval
= -(LARGEINT
- 65 + finalscore
);
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
;
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.
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
,
654 if (val
!= ILLEGAL_VALUE
&& val
> maxval
) {
656 if (maxval
> -cutoffval
|| interrupted())
662 if (maxval
> -cutoffval
|| interrupted())
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
)
683 ChipColor opponent
= opponentColorFor(color
);
685 int score_color
= m_score
->score(color
);
686 int score_opponent
= m_score
->score(opponent
);
689 retval
= score_color
- score_opponent
;
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
));
701 // Calculate bitmaps for each square, and also for neighbors of each
705 void Engine::SetupBits()
707 //m_coord_bit = new long[9][9];
708 //m_neighbor_bits = new long[9][9];
712 // Store a 64 bit unsigned it with the corresponding bit set for
714 for (int i
=1; i
< 9; i
++)
715 for (int j
=1; j
< 9; j
++) {
716 m_coord_bit
[i
][j
] = bits
;
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
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;
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
)
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
];
786 // Calculate a bitmap of the occupied squares for a certain color.
789 quint64
Engine::ComputeOccupiedBits(ChipColor color
)
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
];