1
0
This repository has been archived on 2025-03-06. You can view files and clone it, but cannot push or open issues or pull requests.
Jip J. Dekker 981be2067e Squashed 'software/gecode_on_replay/' content from commit 8051d92b9
git-subtree-dir: software/gecode_on_replay
git-subtree-split: 8051d92b9c89e49cccfbd1c201371580d7703ab4
2021-06-16 14:04:29 +10:00

586 lines
21 KiB
C++

/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
* Main authors:
* Vincent Barichard <Vincent.Barichard@univ-angers.fr>
*
* Copyright:
* Vincent Barichard, 2013
*
* Last modified:
* $Date$ by $Author$
* $Revision$
*
* This file is part of Quacode:
* http://quacode.barichard.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include <iostream>
#include <vector>
#include <string>
#include <quacode/qspaceinfo.hh>
#include <gecode/minimodel.hh>
#include <gecode/driver.hh>
using namespace Gecode;
#ifdef GECODE_HAS_GIST
namespace Gecode { namespace Driver {
/// Specialization for QDFS
template<typename S>
class GistEngine<QDFS<S> > {
public:
static void explore(S* root, const Gist::Options& opt) {
(void) Gist::explore(root, false, opt);
}
};
}}
#endif
/**
* \brief Options taking one additional parameter
*/
class ConnectFourOptions : public Options {
protected:
/// Print strategy or not
Gecode::Driver::BoolOption _printStrategy;
/// Model name
Gecode::Driver::StringOption _QCSPmodel;
/// Heuristic in branching
Gecode::Driver::BoolOption _heuristic;
/// File name of recorded moves
Gecode::Driver::StringValueOption _file;
/// Optional number of rows
Gecode::Driver::UnsignedIntOption _row;
/// Optional number of cols
Gecode::Driver::UnsignedIntOption _col;
public:
/// Initialize options for example with name \a s
ConnectFourOptions(const char* s)
: Options(s),
_printStrategy("-printStrategy","Print strategy",false),
_QCSPmodel("-QCSPmodel","Name of the model used for modeling problem",3),
_heuristic("-heuristic","Use heuristic when branching (only for model + and ++)",true),
_file("-file","File name of recorded moves"),
_row("-row","Number of rows (minimum 4)",6),
_col("-col","Number of cols (minimum 4)",7) {
_QCSPmodel.add(1,"AllState","Model with all states as defined by P. Nightingale. Without Pure Value and heuristic setup.");
_QCSPmodel.add(2,"AllState+","Model with all states as defined by P. Nightingale. With cut.");
_QCSPmodel.add(3,"AllState++","Model with all states as defined by P. Nightingale. With cut and additional constraints.");
add(_printStrategy);
add(_QCSPmodel);
add(_heuristic);
add(_file);
add(_row);
add(_col);
}
/// Return true if the strategy must be printed
bool printStrategy(void) const {
return _printStrategy.value();
}
/// Return model name
int QCSPmodel(void) const {
return _QCSPmodel.value();
}
/// Return if heuristic must be used
bool heuristic(void) const {
return _heuristic.value();
}
/// Return file name
const char *file(void) const {
return _file.value();
}
/// Return number of rows
int row(void) const {
return _row.value();
}
/// Return number of cols
int col(void) const {
return _col.value();
}
};
/// Succeed the space
static void gf_success(Space& home) {
Space::Branchers b(home);
while (b()) {
BrancherHandle bh(b.brancher());
++b;
bh.kill(home);
}
}
/// Dummy function
static void gf_dummy(Space& ) { }
/// Adding cut
static void cut(Space& home, const BoolExpr& expr) {
BoolVar o(home,0,1);
rel(home, o == expr);
when(home, o, &gf_success, &gf_dummy);
}
template <int N>
struct c4Heuristic {
static int value(const Space& _home, IntVar x, int);
};
// Template loop to avoid to write a hundred lines of code
template <int N>
struct FOR {
static void go(IntBranchVal t[]) {
t[N] = &c4Heuristic<N>::value;
FOR<N-1>::go(t);
}
};
template <>
struct FOR<0> {
static void go(IntBranchVal t[]) {
t[0] = &c4Heuristic<0>::value;
}
};
class ConnectFourAllState : public Script, public QSpaceInfo {
static const int Red = 0;
static const int Black = 1;
static const int Nil = 2;
IntVarArray M; // Move variables (the column played)
IntVarArray U; // Additional move variables (the column played) only usefull for simple model
IntVarArray board; // State of board
IntVarArray h; // Number of token in col c
BoolVarArray mh; // Representing if the move i was made in column c (move-here)
IntVarArray gameWinner; // Representing winner 0 = player red wins, 1 = player black wins
BoolVarArray line; // Indicating the presence of line in each row, column or diagonal (numbered)
BoolVarArray lineMove; // Indicating the presence of line for a move
BoolVarArray pos; // Indicating the presence of empty slots
BoolVarArray moveDone; // Is true if the move k has been done
int row;
int col;
int kOffset;
const ConnectFourOptions& opt;
public:
ConnectFourAllState(const ConnectFourOptions& _opt) : Script(_opt), QSpaceInfo(), opt(_opt)
{
std::cout << "Loading problem" << std::endl;
if (!opt.printStrategy()) strategyMethod(0); // disable build and print strategy
using namespace Int;
// Define constants
row = opt.row();
col = opt.col();
kOffset = 0;
int nbDecisionVar = row*col;
// Create array of heuristics, one for each brancher
assert(nbDecisionVar <= 100);
IntBranchVal heuristicArray[100];
FOR<100>::go(heuristicArray);
// Create board variables
M = IntVarArray(*this,nbDecisionVar,0,col-1);
if (opt.QCSPmodel() == 1) U = IntVarArray(*this,nbDecisionVar/2,0,col-1);
board = IntVarArray(*this, nbDecisionVar*row*col, 0, 2);
pos = BoolVarArray(*this, nbDecisionVar*row*col, 0, 1);
h = IntVarArray(*this, nbDecisionVar*col, 0, row);
mh = BoolVarArray(*this, nbDecisionVar*col, 0, 1);
lineMove = BoolVarArray(*this, nbDecisionVar, 0, 1);
gameWinner = IntVarArray(*this, nbDecisionVar, 0, 2);
moveDone = BoolVarArray(*this, nbDecisionVar, 0, 1);
// Test if a file was given in argument
// We will update kOffset according to the file number of moves
IntArgs rMoves;
if (opt.file()) {
std::ifstream f(opt.file());
if (!f)
throw Gecode::Exception("Connect four",
"Unable to open file");
int move;
while (f >> move) {
rMoves << move;
kOffset++;
}
f.close();
assert((kOffset%2) == 0);
}
// Defining the player variables
IntVarArgs m, uWm;
for (int k=0; k<nbDecisionVar; k++)
{
// Post brancher
if (k >= kOffset) {
if ((k%2) == 1) setForAll(*this, M[k]);
if (opt.QCSPmodel() == 1)
branch(*this, M[k], INT_VAR_NONE(), INT_VALUES_MIN());
else if (opt.heuristic())
branch(*this, M[k], INT_VAR_NONE(), INT_VAL(heuristicArray[k]));
else
branch(*this, M[k], INT_VAR_NONE(), INT_VAL_MIN());
}
if (opt.QCSPmodel() == 1) {
// Model from P. Nightingale without Pure Value and heuristic setup
if ((k%2) == 0) m << M[k];
else {
if (k >= kOffset) {
// With this simple model, we link some new existential variables to
// the universal one if the move is legal.
// As a result, we increase the number of branched variable and the search space
branch(*this, U[k/2], INT_VAR_NONE(), INT_VALUES_MIN());
}
m << U[k/2];
for (int i=0; i < col; i++)
rel(*this, ((gameWinner[k-1] == Nil) && (h[(k-1)*col+i] < row) && (M[k] == i)) >> (U[k/2] == i), IPL_DOM); // Forbid illegal move
}
} else {
// Model from P. Nightingale but we add cut and prune universal in order
// to achieve same work as Pure Value. To compare with Queso, disable the
// heurisitic has we do not have one here.
m << M[k];
}
// We build the array of unwatched variables
if (((k%2)==0) || (k<kOffset)) uWm << M[k];
else uWm << getUnWatched(M[k]);
// Some moves has been recorded, we play them here
if (rMoves.size() > k) rel(*this, uWm[k] == rMoves[k], IPL_DOM);
// Set the move-here variables
if (k==0)
for (int i=0; i < col; i++)
rel(*this, (m[0] == i) == (mh[0*col+i] && moveDone[0]), IPL_DOM);
else {
for (int i=0; i < col; i++) {
if (opt.QCSPmodel() <= 1) {
// Not exactly as the article, we have drop the part with !lineMove[k-1].
// We have to do this because it is not compatible with the constraints
// which force the last board to be full
// rel(*this, (!lineMove[k-1] && (h[(k-1)*col+i] < row) && (m[k] == i)) == mh[k*col+i], IPL_DOM);
rel(*this, ((h[(k-1)*col+i] < row) && (m[k] == i)) == mh[k*col+i], IPL_DOM);
} else { // opt.QCSPmodel() > 1
// rel(*this, (!lineMove[k-1] && (h[(k-1)*col+i] < row) && (m[k] == i)) == (mh[k*col+i] && moveDone[k]), IPL_DOM);
rel(*this, ((h[(k-1)*col+i] < row) && (m[k] == i)) == (mh[k*col+i] && moveDone[k]), IPL_DOM);
// Prune for universal
rel(*this, (h[(k-1)*col+i] == row) >> (uWm[k] != i), IPL_DOM); // Prune illegal move from universal
// Add cut
if ((k%2) == 1) cut(*this, (gameWinner[k-1] == Red) && moveDone[k-1]);
}
}
}
}
// Fill the holes
for (int k=0, offSet = 0; k<nbDecisionVar; k++, offSet += row*col)
for (int i=0; i < col; i++)
for (int j=0; j < row-1; j++) {
BoolExpr be;
be = expr(*this, board[offSet+i*row+j] != (((k%2)==0)?Black:Red));
for (int jj=j+1; jj < row; jj++)
be = expr(*this, be && (board[offSet+i*row+jj] == Nil));
rel(*this, pos[offSet+i*row+j] == be, IPL_DOM);
}
for (int k=0, offSet = 0; k<nbDecisionVar; k++, offSet += row*col)
if (k == 0) {
for (int i=0; i < col; i++) {
rel(*this, pos[offSet+i*row], IPL_DOM);
rel(*this, pos[offSet+i*row+row-1], IPL_DOM);
rel(*this, !mh[0*col+i] >> (board[offSet+i*row] == Nil), IPL_DOM);
rel(*this, mh[0*col+i] >> (board[offSet+i*row] == Red), IPL_DOM);
}
} else {
for (int i=0; i < col; i++) {
rel(*this, (h[(k-1)*col+i] == row) == !pos[offSet+i*row+row-1], IPL_DOM);
for (int j=0; j < row; j++) {
rel(*this, (h[(k-1)*col+i] == j) >> pos[offSet+i*row+j], IPL_DOM);
rel(*this, (!mh[k*col+i] && (h[(k-1)*col+i] == j)) >> (board[offSet+i*row+j] == Nil), IPL_DOM);
rel(*this, ( mh[k*col+i] && (h[(k-1)*col+i] == j)) >> (board[offSet+i*row+j] == (((k%2)==0)?Red:Black)), IPL_DOM);
}
}
}
// Map pieces from board at move i-1 to board at move k
for (int k=1, offSet = row*col; k<nbDecisionVar; k++, offSet += row*col)
for (int i=0; i < col; i++)
for (int j=0; j < row; j++) {
rel(*this, (board[(offSet-row*col)+i*row+j] == Black) >> (board[offSet+i*row+j] == Black), IPL_DOM);
rel(*this, (board[(offSet-row*col)+i*row+j] == Red) >> (board[offSet+i*row+j] == Red), IPL_DOM);
}
// Link height and board state
for (int k=0, offSet = 0; k<nbDecisionVar; k++, offSet += row*col)
for (int i=0; i < col; i++)
for (int j=0; j < row+1; j++)
if (j==0)
rel(*this, (board[offSet+i*row] == Nil) >> (h[k*col+i] == 0), IPL_DOM);
else if (j==row)
rel(*this, (board[offSet+i*row+j-1] != Nil) >> (h[k*col+i] == row), IPL_DOM);
else
rel(*this, ((board[offSet+i*row+j-1] != Nil) && (board[offSet+i*row+j] == Nil)) >> (h[k*col+i] == j), IPL_DOM);
// Detect lines
BoolVarArgs l;
// Detect winning blocks
for (int k=0, offSet = 0; k<nbDecisionVar; k++, offSet += row*col) {
BoolVarArgs lk;
for (int z=0; z<4; z++) { // Row(0) / Col(1) / Diag1(2) / Diag2(3)
for (int i=0; i < col; i++) {
for (int j=0; j < row; j++) {
bool post = false;
IntVarArgs x;
if (((z%4)==0) && (i+3) < col) { // Line in row
x << board[offSet+i*row+j] << board[offSet+(i+1)*row+j] << board[offSet+(i+2)*row+j] << board[offSet+(i+3)*row+j];
post = true;
}
if (((z%4)==1) && (j+3) < row) {// Line in column
x << board[offSet+i*row+j] << board[offSet+i*row+j+1] << board[offSet+i*row+j+2] << board[offSet+i*row+j+3];
post = true;
}
if (((z%4)==2) && ((i+3) < col) && ((j+3) < row)) { // Line in diag1
x << board[offSet+i*row+j] << board[offSet+(i+1)*row+j+1] << board[offSet+(i+2)*row+j+2] << board[offSet+(i+3)*row+j+3];
post = true;
}
if (((z%4)==3) && ((i-3) >= 0) && ((j+3) < row)) { // Line in diag2
x << board[offSet+i*row+j] << board[offSet+(i-1)*row+j+1] << board[offSet+(i-2)*row+j+2] << board[offSet+(i-3)*row+j+3];
post = true;
}
if (post) {
if ((k%2) == 0) {
BoolVar bRed(*this,0,1);
lk << bRed;
l << bRed;
if (k>0)
rel(*this,(lineMove[k-1] || (x[0] != Red) || (x[1] != Red) || (x[2] != Red) || (x[3] != Red)) == !bRed, IPL_DOM);
else
rel(*this,((x[0] != Red) || (x[1] != Red) || (x[2] != Red) || (x[3] != Red)) == !bRed, IPL_DOM);
} else {
BoolVar bBlack(*this,0,1);
lk << bBlack;
l << bBlack;
rel(*this,(lineMove[k-1] || (x[0] != Black) || (x[1] != Black) || (x[2] != Black) || (x[3] != Black)) == !bBlack, IPL_DOM);
}
}
}
}
}
if (k>0) lk << lineMove[k-1];
rel(*this, BOT_OR, lk, lineMove[k], IPL_DOM);
}
line = BoolVarArray(*this, l);
// Set GameState variables
rel(*this, gameWinner[0] == Nil, IPL_DOM);
for (int k=1; k < nbDecisionVar; k++) {
rel(*this, (gameWinner[k-1] == Black) >> (gameWinner[k] == Black), IPL_DOM);
rel(*this, (gameWinner[k-1] == Red) >> (gameWinner[k] == Red), IPL_DOM);
rel(*this, ((gameWinner[k-1] == Nil) && !lineMove[k]) >> (gameWinner[k] == Nil), IPL_DOM);
if ((k%2) == 0)
rel(*this, ((gameWinner[k-1] == Nil) && lineMove[k]) >> (gameWinner[k] == Red), IPL_DOM);
else
rel(*this, ((gameWinner[k-1] == Nil) && lineMove[k]) >> (gameWinner[k] == Black), IPL_DOM);
if (opt.QCSPmodel() == 3) {
// If not winner before, only current player have a chance to win
// the game at this move -- NOT IN INITIAL MODEL
if ((k%2) == 0)
rel(*this, (gameWinner[k-1] == Nil) >> (gameWinner[k] != Black), IPL_DOM);
else
rel(*this, (gameWinner[k-1] == Nil) >> (gameWinner[k] != Red), IPL_DOM);
}
}
// For first move, symmetry is broken by removing the rightmost (upper): col - (col div 2)
if (kOffset == 0) rel(*this, m[0], IRT_LE, col - (col / 2), IPL_DOM);
// Force a winner at the end of the game
rel(*this, gameWinner[nbDecisionVar-1], IRT_EQ, Red, IPL_DOM);
if (opt.QCSPmodel() == 1) {
// Set the last board full.
// Useless if we prune universal, but needed for the simple model.
// Notice that it is not compatible with the -depth argument as all board
// doesn't have to be filled.
for (int i=0; i < col; i++)
for (int j=0; j < row; j++)
rel(*this, board[(nbDecisionVar-1)*row*col+i*row+j] != Nil, IPL_DOM);
}
}
ConnectFourAllState(bool share, ConnectFourAllState& p)
: Script(share,p), QSpaceInfo(*this,share,p), row(p.row), col(p.col), kOffset(p.kOffset), opt(p.opt)
{
M.update(*this,share,p.M);
if (opt.QCSPmodel() == 1) U.update(*this,share,p.U);
board.update(*this,share,p.board);
h.update(*this,share,p.h);
mh.update(*this,share,p.mh);
line.update(*this,share,p.line);
lineMove.update(*this,share,p.lineMove);
pos.update(*this,share,p.pos);
gameWinner.update(*this,share,p.gameWinner);
moveDone.update(*this,share,p.moveDone);
}
virtual Space* copy(bool share) { return new ConnectFourAllState(share,*this); }
int c4Heuristic(IntVar x, int k) const {
if (k == 0) return x.max();
std::vector<int> boardBefore(row*col);
int offSet = row*col*(k-1);
for (int i=row-1; i>=0; i--)
for (int j=0; j<col; j++) {
assert(board[offSet+j*row+i].assigned());
boardBefore[j*row+i] = board[offSet+j*row+i].val();
}
// now we have move number and
// previous board state
int bestScore=0;
int bestMove=x.min();
for (IntVarValues vv(x); vv(); ++vv) {
int j = vv.val();
assert(h[(k-1)*col+j].assigned());
int i = h[(k-1)*col+j].val();
if ((i = h[(k-1)*col+j].val()) < row) { // The column is not full
boardBefore[j*row+i] = ((k%2)==0)?Red:Black;
if (((k%2) == 0) && checklines<Red>(boardBefore)) return j; // Leftmost winning move
else if (((k%2) == 1) && checklines<Black>(boardBefore)) return j; // Leftmost winning move
int score;
if ((k%2) == 0) { // Red player
score = check3lines<Red>(boardBefore);
} else { // Black player
score = check3lines<Black>(boardBefore);
}
if (score > bestScore) {
bestScore = score;
bestMove = j;
}
boardBefore[j*row+i] = Nil;
}
}
return bestMove;
}
template <int Player>
int check3lines(std::vector<int>& board) const {
int lines = 0;
// Detect winning blocks
for (int z=0; z<4; z++) { // Row(0) / Col(1) / Diag1(2) / Diag2(3)
for (int i=0; i < col; i++) {
for (int j=0; j < row; j++) {
if (((z%4)==0) && (i+3) < col) { // Line in row
if (check4for3<Player>(board[i*row+j], board[(i+1)*row+j], board[(i+2)*row+j], board[(i+3)*row+j]))
lines++;
}
if (((z%4)==1) && (j+3) < row) {// Line in column
if (check4for3<Player>(board[i*row+j], board[i*row+j+1], board[i*row+j+2], board[i*row+j+3]))
lines++;
}
if (((z%4)==2) && ((i+3) < col) && ((j+3) < row)) { // Line in diag1
if (check4for3<Player>(board[i*row+j], board[(i+1)*row+j+1], board[(i+2)*row+j+2], board[(i+3)*row+j+3]))
lines++;
}
if (((z%4)==3) && ((i-3) >= 0) && ((j+3) < row)) { // Line in diag2
if (check4for3<Player>(board[i*row+j], board[(i-1)*row+j+1], board[(i-2)*row+j+2], board[(i-3)*row+j+3]))
lines++;
}
}
}
}
return lines;
}
template <int Player>
bool check4for3(int a, int b, int c, int d) const {
if ((a == Player) && (b == Player) && (c == Player)) return (d == Nil);
else if ((a == Player) && (b == Player) && (d == Player)) return (c == Nil);
else if ((a == Player) && (c == Player) && (d == Player)) return (b == Nil);
else if ((b == Player) && (c == Player) && (d == Player)) return (a == Nil);
else return false;
}
template <int Player>
bool checklines(std::vector<int>& board) const {
// Detect winning blocks
for (int z=0; z<4; z++) { // Row(0) / Col(1) / Diag1(2) / Diag2(3)
for (int i=0; i < col; i++) {
for (int j=0; j < row; j++) {
if (((z%4)==0) && (i+3) < col) { // Line in row
if ((board[i*row+j] == Player) && (board[(i+1)*row+j] == Player) && (board[(i+2)*row+j] == Player) && (board[(i+3)*row+j] == Player)) return true;
}
if (((z%4)==1) && (j+3) < row) {// Line in column
if ((board[i*row+j] == Player) && (board[i*row+j+1] == Player) && (board[i*row+j+2] == Player) && (board[i*row+j+3] == Player)) return true;
}
if (((z%4)==2) && ((i+3) < col) && ((j+3) < row)) { // Line in diag1
if ((board[i*row+j] == Player) && (board[(i+1)*row+j+1] == Player) && (board[(i+2)*row+j+2] == Player) && (board[(i+3)*row+j+3] == Player)) return true;
}
if (((z%4)==3) && ((i-3) >= 0) && ((j+3) < row)) { // Line in diag2
if ((board[i*row+j] == Player) && (board[(i-1)*row+j+1] == Player) && (board[(i-2)*row+j+2] == Player) && (board[(i-3)*row+j+3] == Player)) return true;
}
}
}
}
return false;
}
void print(std::ostream& os) const {
strategyPrint(os);
}
};
template <int N> int
c4Heuristic<N>::value(const Space& _home, IntVar x, int) {
const ConnectFourAllState& home = static_cast<const ConnectFourAllState&>(_home);
return home.c4Heuristic(x,N);
}
const int ConnectFourAllState::Red;
const int ConnectFourAllState::Black;
const int ConnectFourAllState::Nil;
int main(int argc, char* argv[])
{
ConnectFourOptions opt("QCSP Connect-Four-Game");
opt.parse(argc,argv);
Script::run<ConnectFourAllState,QDFS,ConnectFourOptions>(opt);
return 0;
}