/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: * Mikael Lagerkvist * * Contributing authors: * Guido Tack * * Copyright: * Mikael Lagerkvist, 2006 * Guido Tack, 2006 * * Last modified: * $Date: 2008-01-13 14:21:22 +0100 (Sun, 13 Jan 2008) $ by $Author: zayenz $ * $Revision: 5861 $ * * This file is part of Gecode, the generic constraint * development environment: * http://www.gecode.org * * 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 "examples/support.hh" #include "gecode/minimodel.hh" /** \brief Specification of one tile * * This structure can be used to specify a tile with specified width * and height, number of such tiles (all with unique values), and a * char-array tile showing the tile in row-major order. * * \relates Pentominoes */ struct TileSpec { int width, height; int amount; const char *tile; }; /** \brief Board specifications * * Each board specification repurposes the first two TileSpecs to * record width and height of the board (TileSpec 0) as well as the * number of tiles and whether the board is filled (TileSpec 1). * * \relates Pentominoes */ extern const TileSpec *examples[]; /** \brief Board specification sizes * * \relates Pentominoes */ extern const int examples_size[]; /** \brief Number of board specifications * * \relates Pentominoes */ extern const unsigned int n_examples; namespace { /** \name Symmetry functions * * These functions implement the 8 symmetries of 2D planes. The * functions are templatized so that they can be used both for the * pieces (defined using C-strings) and for arrays of variables. * * \relates Pentominoes */ //@{ /** Return index of (\a h, \a w) in the row-major layout of a matrix with * width \a w1 and height \a h1. */ int pos(int h, int w, int h1, int w1); /// Type for tile symmetry functions typedef void (*tsymmfunc)(const char*, int, int, char*, int&, int&); /// Type for board symmetry functions typedef void (*bsymmfunc)(const IntVarArgs, int, int, IntVarArgs&, int&, int&); /// Identity symmetry template void id(CArray t1, int w1, int h1, Array t2, int& w2, int&h2); /// Rotate 90 degrees template void rot90(CArray t1, int w1, int h1, Array t2, int& w2, int& h2); /// Rotate 180 degrees template void rot180(CArray t1, int w1, int h1, Array t2, int& w2, int& h2); /// Rotate 270 degrees template void rot270(CArray t1, int w1, int h1, Array t2, int& w2, int& h2); /// Flip x-wise template void flipx(CArray t1, int w1, int h1, Array t2, int& w2, int& h2); /// Flip y-wise template void flipy(CArray t1, int w1, int h1, Array t2, int& w2, int& h2); /// Flip diagonal 1 template void flipd1(CArray t1, int w1, int h1, Array t2, int& w2, int& h2); /// Flip diagonal 2 template void flipd2(CArray t1, int w1, int h1, Array t2, int& w2, int& h2); //@} } /** * \brief %Example: %Pentominoes * * \section ExamplePentominoesProblem The Problem * * This example places pieces of a puzzle, where each piece is * composed of a collection of squares, onto a grid. The pieces may * all be rotated and flipped freely. The goal is to place all the * pieces on the grid, without any overlaps. An example piece to be * placed looks like * \code * XXX * X * XXX * \endcode * in one of its rotations. * * The most famous instance of such a puzzle is the Pentominoes * puzzle, where the pieces are all pieces formed by 5 four-connected * squares. * * * \section ExamplePentominoesVariables The Variables * * The variables for the model is the grid of squares that the pieces * are placed on, where each of the variables for the squares takes * the value of the number of the piece which is placed overonto it. * * * \section ExamplePentominoesOnePiece Placing one piece * * The constraint for each piece placement uses regular expressions * (and consequently the \ref extensional constraint) for expressing * placement of (rotated) pieces on the grid. Consider the simple * example of placing the piece * \code * XX * X * X * \endcode * onto the 4 by 4 board * \code * 0123 * 4567 * 89AB * CDEF * \endcode * * Let the variables 0-F be 0/1-variables indicating if the piece is * placed on that position or not. First consider placing the piece on * some location, say covering 1,2,6, and A. Then, writing the * sequence of values for the variables 0-F out, we get the string * 0110001000100000. This string and all other strings corresponding * to placing the above piece in that particular rotation can be * described using the regular expression \f$0^*11000100010^*\f$. The * expression indicates that first comes some number of zeroes, then * two ones in a row (covering places 1 and 2 in our example * placement), then comes exactly three 0's (not covering places 3, 4, * and 5), and so on. The variable number of 0's at the beginning and at the end * makes the expression match any placement of the piece on the board. * * There is one problem with the above constraint, since it allows * placing the piece covering places 3, 4, 8, and C. That is, the * piece may wrap around the board. To prohibit this, we add a new * dummy-column to the board, so that it looks like * \code * 0123G * 4567H * 89ABI * CDEFJ * \endcode * The variables for places G to J are all set to zero initially, and the * regular expression for the placement of the piece is modified to * include the extra column, \f$0^*1100001000010^*\f$. * * * \section ExamplePentominoesRotatingPiece Rotating pieces * * To handle rotations of the piece, we can use disjunctions of * regular expressions for all the relevant rotations. Consider the * rotated version of the above piece, depicted below. * \code * X * XXX * \endcode * The corresponding regular expression for this piece is * \f$0^*1001110^*\f$. To combine these two regular expressions, we * can simply use disjunction of regular expressions, arriving at the * expression \f$0^*1100001000010^*|0^*1001110^*\f$ for enforcing * the placement of the piece in one of the above two rotations. * * There are 8 symmetries for the pieces in general. The 8 disjuncts * for a particular piece might, however, contain less than 8 distinct * expressions (for example, any square has only one distinct * disjunct). This is removed when then automaton for the expression * is computed, since it is minimized. * * * \section ExamplePentominoesSeveral Placing several pieces * * To generalize the above model to several pieces, we let the * variables range from 0 to n, where n is the number of pieces to * place. Given that we place three pieces, and that the above shown * piece is number one, we will replace each \f$0\f$-expression with * the expression \f$(0|2|3)\f$. Thus, the second regular expression * becomes \f$(0|2|3)^*1(0|2|3)(0|2|3)111(0|2|3)^*\f$. Additionaly, * the end of line marker gets its own value. * * This generalization suffers from the fact that the automata become * much more complex. This problem can be solved by instead * projecting out each component, which gives a new board of * 0/1-variables for each piece to place. * * * \section ExamplePentominoesHeuristic The Branching * * This model does not use any advanced heuristic for the * branching. The variables selection is simply in order, and the * value selection is minimum value first. * * The static value selection allows us to order the pieces in the * specification of the problem. The pieces are approximately ordered by * largness or hardness to place. * * * \section ExamplePentominoesSymmetries Removing board symmetries * * Especially when searching for all solutions of a puzzle instance, * we might want to remove the symmetrical boards from the * solutions-space. This is done using the same symmetry functions as * for the piece symmetries and lexicographical order constraints. * * * \ingroup ExProblem * */ class Pentominoes : public Example { public: /// Choice of propagators enum { PROPAGATION_INT, ///< Use integer propagators PROPAGATION_BOOLEAN, ///< Use Boolean propagators }; /// Choice of symmetry breaking enum { MODEL_NONE, ///< Do not remove symmetric solutions MODEL_SYMMETRY, ///< Remove symmetric solutions }; private: /// Specification of the tiles to place. const TileSpec *spec; /// Width and height of the board int width, height; /// Whether the board is filled or not bool filled; /// Number of specifications of tiles to place int nspecs; /// Number of tiles to place int ntiles; /// The variables for the board. IntVarArray board; /// Compute number of tiles int compute_number_of_tiles(const TileSpec* ts, int nspecs) { int res = 0; for (int i = nspecs; i--; ) { res += ts[i].amount; } return res; } /// Returns the regular expression for placing a specific tile \a /// tile in a specific rotation. REG tile_reg(int twidth, int theight, const char* tile, REG mark, REG other, REG eol) { REG oe = other | eol; REG res = *oe; REG color[] = {other, mark}; for (int h = 0; h < theight; ++h) { for (int w = 0; w < twidth; ++w) { int which = tile[h*twidth + w] == 'X'; res += color[which]; } if (h < theight-1) { res += oe(width-twidth, width-twidth); } } res += *oe + oe; return res; } /// Returns the regular expression for placing tile number \a t in /// any rotation. REG get_constraint(int t, REG mark, REG other, REG eol) { // This should be done for all rotations REG res; char *t2 = new char[width*height]; int w2, h2; tsymmfunc syms[] = {id, flipx, flipy, flipd1, flipd2, rot90, rot180, rot270}; int symscnt = sizeof(syms)/sizeof(tsymmfunc); for (int i = 0; i < symscnt; ++i) { syms[i](spec[t].tile, spec[t].width, spec[t].height, t2, w2, h2); res = res | tile_reg(w2, h2, t2, mark, other, eol); } delete [] t2; return res; } public: /// Construction of the model. Pentominoes(const SizeOptions& opt) : spec(examples[opt.size()]), width(spec[0].width+1), // Add one for extra row at end. height(spec[0].height), filled(spec[0].amount), nspecs(examples_size[opt.size()]-1), ntiles(compute_number_of_tiles(spec+1, nspecs)), board(this, width*height, filled,ntiles+1) { spec += 1; // No need for the specification-part any longer // Set end-of-line markers for (int h = 0; h < height; ++h) { for (int w = 0; w < width-1; ++w) rel(this, board[h*width + w], IRT_NQ, ntiles+1); rel(this, board[h*width + width - 1], IRT_EQ, ntiles+1); } // Post constraints if (opt.propagation() == PROPAGATION_INT) { int tile = 0; for (int i = 0; i < nspecs; ++i) { for (int j = 0; j < spec[i].amount; ++j) { // Color int col = tile+1; // Expression for color col REG mark(col); // Build expression for complement to color col REG other; bool first = true; for (int j = filled; j <= ntiles; ++j) { if (j == col) continue; if (first) { other = REG(j); first = false; } else { other |= REG(j); } } // End of line marker REG eol(ntiles+1); extensional(this, board, get_constraint(i, mark, other, eol)); ++tile; } } } else { // opt.propagation() == PROPAGATION_BOOLEAN int ncolors = ntiles + 2; // Boolean variables for channeling BoolVarArgs p(ncolors * board.size()); for (int i=p.size(); i--; ) p[i].init(this,0,1); // Post channel constraints for (int i=board.size(); i--; ) { BoolVarArgs c(ncolors); for (int j=ncolors; j--; ) c[j]=p[i*ncolors+j]; channel(this, c, board[i]); } // For placing tile i, we construct the expression over // 0/1-variables and apply it to the projection of // the board on the color for the tile. REG other(0), mark(1); int tile = 0; for (int i = 0; i < nspecs; ++i) { for (int j = 0; j < spec[i].amount; ++j) { int col = tile+1; // Projection for color col BoolVarArgs c(board.size()); for (int k = board.size(); k--; ) { c[k] = p[k*ncolors+col]; } extensional(this, c, get_constraint(i, mark, other, other)); ++tile; } } } if (opt.model() == MODEL_SYMMETRY) { // Remove symmetrical boards IntVarArgs orig(board.size()-height), symm(board.size()-height); int pos = 0; for (int i = 0; i < board.size(); ++i) { if ((i+1)%width==0) continue; orig[pos++] = board[i]; } int w2, h2; bsymmfunc syms[] = {flipx, flipy, flipd1, flipd2, rot90, rot180, rot270}; int symscnt = sizeof(syms)/sizeof(bsymmfunc); for (int i = 0; i < symscnt; ++i) { syms[i](orig, width-1, height, symm, w2, h2); if (width-1 == w2 && height == h2) rel(this, orig, IRT_LQ, symm); } } // Install branching branch(this, board, INT_VAR_NONE, INT_VAL_MIN); } /// Constructor for cloning \a s Pentominoes(bool share, Pentominoes& s) : Example(share,s), spec(s.spec), width(s.width), height(s.height), filled(s.filled), nspecs(s.nspecs) { board.update(this, share, s.board); } /// Copy space during cloning virtual Space* copy(bool share) { return new Pentominoes(share,*this); } /// Print solution virtual void print(std::ostream& os) { for (int h = 0; h < height; ++h) { os << "\t"; for (int w = 0; w < width-1; ++w) { int val = board[h*width + w].val(); char c = val < 10 ? '0'+val : 'A' + (val-10); os << c; } os << std::endl; } os << std::endl; } }; /** \brief Main-function * \relates Pentominoes */ int main(int argc, char* argv[]) { SizeOptions opt("Pentominoes"); opt.size(1); opt.model(Pentominoes::MODEL_SYMMETRY); opt.model(Pentominoes::MODEL_NONE, "none", "do not remove symmetric solutions"); opt.model(Pentominoes::MODEL_SYMMETRY, "sym", "remove symmetric solutions"); opt.propagation(Pentominoes::PROPAGATION_BOOLEAN); opt.propagation(Pentominoes::PROPAGATION_INT, "int", "use integer propagators"); opt.propagation(Pentominoes::PROPAGATION_BOOLEAN, "bool", "use Boolean propagators"); opt.parse(argc,argv); if (opt.size() >= n_examples) { std::cerr << "Error: size must be between 0 and " << n_examples-1 << std::endl; return 1; } Example::run(opt); return 0; } /** \name Puzzle specifications * * \relates Pentominoes */ //@{ /// Small specification static const TileSpec puzzle0[] = { // Width and height of board {4, 4, true, ""}, {2, 3, 1, "XX" "X " "X "}, {2, 1, 1, "XX"}, {3, 3, 1, " XX" " X" "XXX"}, {1, 1, 1, "X"}, {3, 1, 1, "XXX"} }; /// Standard specification static const TileSpec puzzle1[] = { // Width and height of board {8, 8, true, ""}, {3, 3, 1, "XXX" "XXX" "XX "}, {5, 3, 1, " XXX" " X " "XXX "}, {3, 4, 1, "XXX" "XXX" " X" " X"}, {3, 4, 1, "XXX" " X" " X" " X"}, {2, 5, 1, " X" " X" " X" "XX" "XX"}, {4, 2, 1, "XX " "XXXX"}, {3, 3, 1, "XXX" " X" " X"}, {2, 3, 1, "XX" "X " "X "}, {2, 4, 1, "XX" "XX" "XX" "XX"}, {3, 2, 1, "XX " "XXX"} }; // Perfect square number 2 from examples/perfect-square.cc static const TileSpec square2[] = { // Width and height of board {10, 10, true, ""}, {6, 6, 1, "XXXXXX" "XXXXXX" "XXXXXX" "XXXXXX" "XXXXXX" "XXXXXX" }, {4, 4, 3, "XXXX" "XXXX" "XXXX" "XXXX"}, {2, 2, 4, "XX" "XX"} }; // Perfect square number 3 from examples/perfect-square.cc static const TileSpec square3[] = { // Width and height of board {20, 20, true, ""}, {9, 9, 1, "XXXXXXXXX" "XXXXXXXXX" "XXXXXXXXX" "XXXXXXXXX" "XXXXXXXXX" "XXXXXXXXX" "XXXXXXXXX" "XXXXXXXXX" "XXXXXXXXX" }, {8, 8, 2, "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" }, {7, 7, 1, "XXXXXXX" "XXXXXXX" "XXXXXXX" "XXXXXXX" "XXXXXXX" "XXXXXXX" "XXXXXXX" }, {5, 5, 1, "XXXXX" "XXXXX" "XXXXX" "XXXXX" "XXXXX" }, {4, 4, 5, "XXXX" "XXXX" "XXXX" "XXXX"}, {3, 3, 3, "XXX" "XXX" "XXX"}, {2, 2, 2, "XX" "XX"}, {1, 1, 2, "X"} }; static const TileSpec pentomino6x10[] = { // Width and height of board {10, 6, true, ""}, {2, 4, 1, "X " "X " "X " "XX"}, {3,3, 1, "XX " " XX" " X "}, {3,3, 1, "XXX" " X " " X "}, {3,3, 1, " X" " XX" "XX "}, {2,4, 1, " X" "XX" " X" " X"}, {5,1, 1, "XXXXX"}, {3,3, 1, "X " "XXX" " X"}, {4,2, 1, " XXX" "XX "}, {2,3, 1, "XX" "XX" " X"}, {3,2, 1, "X X" "XXX"}, {3,3, 1, " X " "XXX" " X "}, {3,3, 1, " X" " X" "XXX"} }; static const TileSpec pentomino5x12[] = { // Width and height of board {12, 5, true, ""}, {2, 4, 1, "X " "X " "X " "XX"}, {3,3, 1, "XX " " XX" " X "}, {3,3, 1, "XXX" " X " " X "}, {3,3, 1, " X" " XX" "XX "}, {2,4, 1, " X" "XX" " X" " X"}, {5,1, 1, "XXXXX"}, {3,3, 1, "X " "XXX" " X"}, {4,2, 1, " XXX" "XX "}, {2,3, 1, "XX" "XX" " X"}, {3,2, 1, "X X" "XXX"}, {3,3, 1, " X " "XXX" " X "}, {3,3, 1, " X" " X" "XXX"} }; static const TileSpec pentomino4x15[] = { // Width and height of board {15, 4, true, ""}, {2, 4, 1, "X " "X " "X " "XX"}, {3,3, 1, "XX " " XX" " X "}, {3,3, 1, "XXX" " X " " X "}, {3,3, 1, " X" " XX" "XX "}, {2,4, 1, " X" "XX" " X" " X"}, {5,1, 1, "XXXXX"}, {3,3, 1, "X " "XXX" " X"}, {4,2, 1, " XXX" "XX "}, {2,3, 1, "XX" "XX" " X"}, {3,2, 1, "X X" "XXX"}, {3,3, 1, " X " "XXX" " X "}, {3,3, 1, " X" " X" "XXX"} }; static const TileSpec pentomino3x20[] = { // Width and height of board {20, 3, true, ""}, {2, 4, 1, "X " "X " "X " "XX"}, {3,3, 1, "XX " " XX" " X "}, {3,3, 1, "XXX" " X " " X "}, {3,3, 1, " X" " XX" "XX "}, {2,4, 1, " X" "XX" " X" " X"}, {5,1, 1, "XXXXX"}, {3,3, 1, "X " "XXX" " X"}, {4,2, 1, " XXX" "XX "}, {2,3, 1, "XX" "XX" " X"}, {3,2, 1, "X X" "XXX"}, {3,3, 1, " X " "XXX" " X "}, {3,3, 1, " X" " X" "XXX"} }; /// List of specifications const TileSpec *examples[] = {puzzle0, puzzle1, square2, square3, pentomino6x10,pentomino5x12, pentomino4x15,pentomino3x20}; const int examples_size[] = {sizeof(puzzle0)/sizeof(TileSpec), sizeof(puzzle1)/sizeof(TileSpec), sizeof(square2)/sizeof(TileSpec), sizeof(square3)/sizeof(TileSpec), sizeof(pentomino6x10)/sizeof(TileSpec), sizeof(pentomino5x12)/sizeof(TileSpec), sizeof(pentomino4x15)/sizeof(TileSpec), sizeof(pentomino3x20)/sizeof(TileSpec)}; /// Number of specifications const unsigned n_examples = sizeof(examples)/sizeof(TileSpec*); //@} // Symmetry functions namespace { int pos(int h, int w, int h1, int w1) { if (!(0 <= h && h < h1) || !(0 <= w && w < w1)) { std::cerr << "Cannot place (" << h << "," << w << ") on board of size " << h1 << "x" << w1 << std::endl; } return h * w1 + w; } template void id(CArray t1, int w1, int h1, Array t2, int& w2, int&h2) { w2 = w1; h2 = h1; for (int h = 0; h < h1; ++h) for (int w = 0; w < w1; ++w) t2[pos(h, w, h2, w2)] = t1[pos(h, w, h1, w1)]; } template void rot90(CArray t1, int w1, int h1, Array t2, int& w2, int& h2) { w2 = h1; h2 = w1; for (int h = 0; h < h1; ++h) for (int w = 0; w < w1; ++w) t2[pos(w, w2-h-1, h2, w2)] = t1[pos(h, w, h1, w1)]; } template void rot180(CArray t1, int w1, int h1, Array t2, int& w2, int& h2) { w2 = w1; h2 = h1; for (int h = 0; h < h1; ++h) for (int w = 0; w < w1; ++w) t2[pos(h2-h-1, w2-w-1, h2, w2)] = t1[pos(h, w, h1, w1)]; } template void rot270(CArray t1, int w1, int h1, Array t2, int& w2, int& h2) { w2 = h1; h2 = w1; for (int h = 0; h < h1; ++h) for (int w = 0; w < w1; ++w) t2[pos(h2-w-1, h, h2, w2)] = t1[pos(h, w, h1, w1)]; } template void flipx(CArray t1, int w1, int h1, Array t2, int& w2, int& h2) { w2 = w1; h2 = h1; for (int h = 0; h < h1; ++h) for (int w = 0; w < w1; ++w) t2[pos(h, w2-w-1, h2, w2)] = t1[pos(h, w, h1, w1)]; } template void flipy(CArray t1, int w1, int h1, Array t2, int& w2, int& h2) { w2 = w1; h2 = h1; for (int h = 0; h < h1; ++h) for (int w = 0; w < w1; ++w) t2[pos(h2-h-1, w, h2, w2)] = t1[pos(h, w, h1, w1)]; } template void flipd1(CArray t1, int w1, int h1, Array t2, int& w2, int& h2) { w2 = h1; h2 = w1; for (int h = 0; h < h1; ++h) for (int w = 0; w < w1; ++w) t2[pos(w, h, h2, w2)] = t1[pos(h, w, h1, w1)]; } template void flipd2(CArray t1, int w1, int h1, Array t2, int& w2, int& h2) { w2 = h1; h2 = w1; for (int h = 0; h < h1; ++h) for (int w = 0; w < w1; ++w) t2[pos(h2-w-1, w2-h-1, h2, w2)] = t1[pos(h, w, h1, w1)]; } } // STATISTICS: example-any