source code
// The Computer Language Benchmarks Game
// http://benchmarksgame.alioth.debian.org/
// contributed by Ben St. John
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <string>
#include <memory.h>
using namespace std;
#define FREE(p) {free(p); p = NULL;}
enum {X, Y, N_DIM};
enum {EVEN, ODD, N_PARITY};
typedef unsigned int TUInt32;
typedef unsigned long long TUInt64;
typedef signed char TInt8;
typedef TUInt64 BitVec;
namespace Meteor
{
static const int N_COL = 5;
static const int N_ROW = 10;
static const int N_CELL = N_COL * N_ROW;
class Piece;
//------------------------------------
class Solution
{
public:
static const int NO_PIECE = -1;
void addPiece(const BitVec & vec, int iPiece);
void removeLastPiece(void);
void setCells(void);
bool lessThan(Solution & r); ///< I don't feel like operator overloading
string toString(void) const;
void fill(int val);
bool isEmpty(void) {return (m_pieces.size() == 0);}
void spin(Solution & spun);
Solution(int fillVal);
Solution() {m_synched = false;}
private:
struct SPiece {
BitVec vec;
TUInt32 iPiece;
};
vector<SPiece> m_pieces;
TInt8 m_cells[N_ROW][N_COL];
bool m_synched;
};
//------------------------------------
class Board
{
public:
static const BitVec L_EDGE_MASK =
(1LL << 0) | (1LL << 5) | (1LL << 10) | (1LL << 15) |
(1LL << 20) | (1LL << 25) | (1LL << 30) | (1LL << 35) |
(1LL << 40) | (1LL << 45) | (1LL << 50) | (1LL << 55);
static const BitVec R_EDGE_MASK = L_EDGE_MASK << 4;
static const BitVec TOP_ROW = 0x1fLL;
static const BitVec ROW_0_MASK =
( TOP_ROW | (TOP_ROW << 10) | (TOP_ROW << 20) | (TOP_ROW << 30) |
(TOP_ROW << 40) | (TOP_ROW << 50));
static const BitVec ROW_1_MASK = ROW_0_MASK << 5;
static const BitVec BOARD_MASK = (1LL << N_CELL) - 1;
Board();
static TUInt32 getIndex(TUInt32 x, TUInt32 y) { return y * N_COL + x; }
static bool hasBadFirstRegion(BitVec & toFill);
static bool hasBadIslands(const BitVec & boardVec);
void genAllSolutions(BitVec boardVec, TUInt32 placedPieces, TUInt32 iNextFill, const BitVec & maskNextFill);
void recordSolution(Solution & s);
Solution m_curSolution;
Solution m_minSolution;
Solution m_maxSolution;
TUInt32 m_nSolutionFound;
};
//------------------------------------
class Piece
{
public:
struct Instance {
BitVec m_vec;
BitVec m_allowed;
TUInt32 m_offset;
TUInt32 m_w;
TUInt32 m_h;
};
static const int N_ELEM = 5;
static const int N_ORIENT = 12;
static const int N_TYPE = 10;
static const int ALL_PIECE_MASK = (1 << N_TYPE) - 1;
static const TUInt32 SKIP_PIECE = 5; // it's magic!
typedef int TCoordList[N_ELEM][N_DIM];
static const BitVec BaseDefinitions[N_TYPE];
static Piece s_basePiece[N_TYPE][N_ORIENT];
static const Instance & getPiece(TUInt32 iPiece, TUInt32 iOrient, TUInt32 iParity);
static bool checkBaseDefinitions(void);
static BitVec toBitVector(const TCoordList & coords);
static void genOrientation(const BitVec & vec, TUInt32 iOrient, Piece & target);
static void setCoordList(const BitVec & vec, TCoordList & coords);
static void shiftUpLines(TCoordList & coords, int shift);
static void shiftToX0(TCoordList & coords, Instance & instance, int offsetRow);
static void setAllowedPositions(Instance & p);
static void genAllOrientations(void);
Instance m_instance[N_PARITY];
};
//------------------------------------
Solution::Solution(int fillVal)
{
fill(fillVal);
m_pieces.reserve(Piece::N_TYPE);
}
void Solution::fill(int val)
{
m_synched = false;
memset(&m_cells[0][0], val, N_CELL);
}
string Solution::toString(void) const
{
string result;
result.reserve(N_CELL * 2);
for (int y = 0; y < N_ROW; y++) {
for (int x = 0; x < N_COL; x++) {
int val = m_cells[y][x];
result += ((val == NO_PIECE) ? '.' : char('0' + val));
result += ' ';
}
result += '\n';
// indent every second line
if (y % 2 == 0)
result += " ";
}
return result; // copies result. Oh well
}
void Solution::setCells(void)
{
if (m_synched)
return;
fill(NO_PIECE);
// could be more efficient
for (TUInt32 iPiece = 0; iPiece < m_pieces.size(); iPiece++) {
const BitVec & vec = m_pieces[iPiece].vec;
int pID = m_pieces[iPiece].iPiece;
BitVec mask = 1LL;
int nNewCells = 0;
for (int y = 0; y < N_ROW; y++) {
for (int x = 0; x < N_COL; x++) {
if (mask & vec) {
m_cells[y][x] = (TInt8)pID;
nNewCells++;
}
mask <<= 1;
}
if (nNewCells == Piece::N_ELEM)
break;
}
}
m_synched = true;
}
void Solution::addPiece(const BitVec & vec, int iPiece) {
SPiece p = {vec, iPiece};
m_pieces.push_back(p);
}
void Solution::removeLastPiece(void) {
m_pieces.pop_back();
m_synched = false;
}
bool Solution::lessThan(Solution & r)
{
if (m_pieces[0].iPiece != r.m_pieces[0].iPiece) {
return m_pieces[0].iPiece < r.m_pieces[0].iPiece;
}
setCells();
r.setCells();
int y;
for (y = 0; y < N_ROW; y++) {
for (int x = 0; x < N_COL; x++) {
int lval = m_cells[y][x];
int rval = r.m_cells[y][x];
if (lval != rval)
return (lval < rval);
}
}
return false; // solutions are equal
}
void Solution::spin(Solution & spun)
{
setCells();
// swap cells
for (int y = 0; y < N_ROW; y++) {
for (int x = 0; x < N_COL; x++) {
TInt8 flipped = m_cells[N_ROW - y - 1][N_COL - x - 1];
spun.m_cells[y][x] = flipped;
}
}
// swap first and last pieces (the rest aren't used)
spun.m_pieces.push_back(m_pieces[Piece::N_TYPE - 1]);
spun.m_synched = true;
}
//------------------------------------
Piece Piece::s_basePiece[N_TYPE][N_ORIENT];
const BitVec Piece::BaseDefinitions[] = {
0x010f, 0x00cb, 0x1087, 0x0427, 0x0465,
0x00c7, 0x08423, 0x00a7, 0x0187, 0x008f
};
int floor(int top, int bot) {
int toZero = top / bot;
// negative numbers should be rounded down, not towards zero
if ((toZero * bot != top) && ((top < 0) != (bot <= 0)))
toZero--;
return toZero;
}
TUInt32 getFirstOne(const BitVec & v, TUInt32 startPos = 0) {
if (v == (BitVec)0)
return 0;
static const TUInt32 firstOne[16] = {
0, 0, 1, 0,
2, 0, 1, 0,
3, 0, 1, 0,
2, 0, 1, 0,
};
TUInt32 iPos = startPos;
BitVec mask = 0xffLL << startPos;
while ((mask & v) == 0) {
mask <<= 8;
iPos += 8;
}
TUInt32 result = TUInt32((mask & v) >> iPos);
TUInt32 resultLow = result & 0x0f;
if (resultLow != 0)
iPos += firstOne[resultLow];
else
iPos += 4 + firstOne[result >> 4];
return iPos;
}
TUInt32 countOnes(BitVec v) {
TUInt32 n = 0;
while (v) {
n++;
v = v & (v - 1);
}
return n;
}
void Piece::genAllOrientations(void) {
for (int iPiece = 0; iPiece < N_TYPE; iPiece++) {
const BitVec & refPiece = BaseDefinitions[iPiece];
for (int iOrient = 0; iOrient < N_ORIENT; iOrient++)
genOrientation(refPiece, iOrient, s_basePiece[iPiece][iOrient]);
}
}
void Piece::setCoordList(const BitVec & vec, TCoordList & coords) {
int iCoord = 0;
BitVec mask = 1LL;
for (int y = 0; y < N_ROW; y++) {
for (int x = 0; x < N_COL; x++) {
if (mask & vec) {
coords[iCoord][X] = x;
coords[iCoord][Y] = y;
iCoord++;
}
mask <<= 1;
}
}
}
BitVec Piece::toBitVector(const TCoordList & coords)
{
int y, x;
BitVec result = 0;
for (int iCoord = 0; iCoord < N_ELEM; iCoord++) {
x = coords[iCoord][X];
y = coords[iCoord][Y];
int pos = Board::getIndex(x, y);
result |= (1LL << pos); // to generate a 64 bit representation of 1
}
return result;
}
void Piece::shiftUpLines(TCoordList & coords, int shift)
{
// apply shifts are not so simple in the vertical direction
for (int iCoord = 0; iCoord < N_ELEM; iCoord++) {
int & rx = coords[iCoord][X];
int & ry = coords[iCoord][Y];
if (ry & shift & 0x1)
rx++;
ry -= shift;
}
}
void Piece::shiftToX0(TCoordList & coords, Piece::Instance & instance, int offsetRow)
{
// .. determine shift
int x, y;
int xMin = coords[0][X];
int xMax = xMin;
int iCoord;
for (iCoord = 1; iCoord < N_ELEM; iCoord++) {
x = coords[iCoord][X];
y = coords[iCoord][Y];
if (x < xMin)
xMin = x;
else if (x > xMax)
xMax = x;
}
// I'm dying for a 'foreach' here
int offset = N_ELEM;
for (iCoord = 0; iCoord < N_ELEM; iCoord++) {
int & rx = coords[iCoord][X];
int & ry = coords[iCoord][Y];
rx -= xMin;
// check offset -- leftmost cell on top line
if ((ry == offsetRow) && (rx < offset))
offset = rx;
}
instance.m_w = xMax - xMin;
instance.m_offset = offset;
instance.m_vec = toBitVector(coords);
}
void Piece::setAllowedPositions(Piece::Instance & p)
{
BitVec & allowed = p.m_allowed = 0;
BitVec posMask = 1LL;
TUInt32 iPos = 0;
for (int y = 0; y < N_ROW; y++) {
for (int x = 0; x < N_COL; x++, iPos++, posMask <<= 1){
// check if the new position is on the board
int xPos = x - p.m_offset;
if ((xPos < 0) || (y + p.m_h >= N_ROW) || (xPos + p.m_w >= N_COL))
continue;
// move it to the desired location
BitVec pieceVec = p.m_vec << (iPos - p.m_offset);
if (Board::hasBadIslands(pieceVec))
continue;
// position is allowed
allowed |= posMask;
}
}
}
void Piece::genOrientation(const BitVec & vec, TUInt32 iOrient, Piece & target)
{
// get (x,y) coordinates
TCoordList coords;
setCoordList(vec, coords);
int y, x;
int iCoord = 0;
int rot = iOrient % 6;
int flip = iOrient >= 6;
if (flip) {
for (iCoord = 0; iCoord < N_ELEM; iCoord++)
coords[iCoord][Y] = -coords[iCoord][Y];
}
// rotate (if necessary)
while (rot--) {
for (iCoord = 0; iCoord < N_ELEM; iCoord++) {
x = coords[iCoord][X];
y = coords[iCoord][Y];
// I just worked this out by hand. Took a while.
int xNew = floor((2 * x - 3 * y + 1), 4);
int yNew = floor((2 * x + y + 1), 2);
coords[iCoord][X] = xNew;
coords[iCoord][Y] = yNew;
}
}
// shift vertically
// .. determine shift
int yMin = coords[0][Y];
int yMax = yMin;
for (iCoord = 1; iCoord < N_ELEM; iCoord++) {
y = coords[iCoord][Y];
if (y < yMin)
yMin = y;
else if (y > yMax)
yMax = y;
}
TUInt32 h = yMax - yMin;
target.m_instance[EVEN].m_h = h;
target.m_instance[ODD].m_h = h;
shiftUpLines(coords, yMin);
shiftToX0(coords, target.m_instance[EVEN], 0);
setAllowedPositions(target.m_instance[EVEN]);
// shift down one line
shiftUpLines(coords, -1);
shiftToX0(coords, target.m_instance[ODD], 1);
// shift the bitmask back one line
target.m_instance[ODD].m_vec >>= N_COL;
setAllowedPositions(target.m_instance[ODD]);
}
const Piece::Instance & Piece::getPiece(TUInt32 iPiece, TUInt32 iOrient, TUInt32 iParity) {
return s_basePiece[iPiece][iOrient].m_instance[iParity];
}
// ------------------------------------
Board::Board() :
m_curSolution(Solution::NO_PIECE), m_minSolution(Piece::N_TYPE),
m_maxSolution(Solution::NO_PIECE), m_nSolutionFound(0)
{
}
bool Board::hasBadFirstRegion(BitVec & toFill)
{
int iPos = getFirstOne(toFill);
// grow empty region, until it doesn't change any more
BitVec region;
BitVec rNew = 1LL << iPos;
do {
region = rNew;
// grow right/left
rNew |= (region & ~L_EDGE_MASK) >> 1;
rNew |= (region & ~R_EDGE_MASK) << 1;
// simple grow up/down
rNew |= (region >> N_COL);
rNew |= (region << N_COL);
// tricky growth
BitVec evenRegion = region & (ROW_0_MASK & ~L_EDGE_MASK);
rNew |= evenRegion >> (N_COL + 1);
rNew |= evenRegion << (N_COL - 1);
BitVec oddRegion = region & (ROW_1_MASK & ~R_EDGE_MASK);
rNew |= oddRegion >> (N_COL - 1);
rNew |= oddRegion << (N_COL + 1);
// clamp against existing pieces
rNew &= toFill;
}
while ((rNew != toFill) && (rNew != region));
// subtract empty region from board
toFill ^= rNew;
TUInt32 nEmptyCells = countOnes(toFill);
if (nEmptyCells % 5 != 0)
return true;
return false;
}
bool Board::hasBadIslands(const BitVec & boardVec)
{
BitVec toFill = ~boardVec & BOARD_MASK;
// a little pre-work to speed things up
BitVec row = (Board::TOP_ROW << ((N_ROW - 1) * N_COL));
bool filled = ((row & toFill) == row);
while ((row & toFill) == row) {
toFill ^= row;
row >>= N_COL;
}
// undo the last row, so regions stay connected
if (filled) {
row <<= N_COL;
toFill |= row;
}
while (toFill) {
if (hasBadFirstRegion(toFill))
return true;
}
return false;
}
// recursive vs iterative?
void Board::genAllSolutions(BitVec boardVec, TUInt32 placedPieces, TUInt32 iNextFill, const BitVec & maskNextFill)
{
BitVec pieceVec;
int pieceMask = 1;
int y = iNextFill / N_COL;
int isOddLine = y & 1;
for (int iPlacedPiece = 0; iPlacedPiece < Piece::N_TYPE; iPlacedPiece++, pieceMask <<= 1)
{
TUInt32 iPiece = iPlacedPiece; // leftover from when I remapped it
// skip if we've already used this piece
if (pieceMask & placedPieces)
continue;
// try to fit piece
bool skipFlippedOdd = (iPiece == Piece::SKIP_PIECE);
for (int iOrient = 0; iOrient < Piece::N_ORIENT; iOrient++)
{
if (skipFlippedOdd && ((iOrient / 3) & 1))
continue;
// get the particular piece in the particular orientation
const Piece::Instance & p = Piece::getPiece(iPiece, iOrient, isOddLine);
// check if the new position is allowed on the board
if (!(p.m_allowed & maskNextFill))
continue;
// move it to the desired location, if possible and
pieceVec = p.m_vec << (iNextFill - p.m_offset);
// check if piece conflicts with other pieces
if (pieceVec & boardVec)
continue;
// add the piece to the board
boardVec |= pieceVec;
if ((boardVec != pieceVec) && hasBadIslands(boardVec)) {
// remove the piece from the board vector
boardVec ^= pieceVec;
continue;
}
// mark piece as placed
placedPieces |= pieceMask;
m_curSolution.addPiece(pieceVec, iPiece);
// recur if not done
if (placedPieces != Piece::ALL_PIECE_MASK) {
// need to find the next unfilled cell
TUInt32 iCell = getFirstOne(~boardVec, iNextFill + 1);
BitVec mNextFill = (maskNextFill << (iCell - iNextFill));
genAllSolutions(boardVec, placedPieces, iCell, mNextFill);
}
else {
// done, record piece/solution and end recursion
recordSolution(m_curSolution);
}
// remove the piece before continuing with a new piece
boardVec ^= pieceVec;
m_curSolution.removeLastPiece();
}
placedPieces &= ~pieceMask;
}
}
void Board::recordSolution(Solution & s)
{
m_nSolutionFound += 2; // we add the solution and its rotation
if (m_minSolution.isEmpty()) {
m_minSolution = m_maxSolution = s;
return;
}
if (s.lessThan(m_minSolution))
m_minSolution = s;
else if (m_maxSolution.lessThan(s))
m_maxSolution = s;
Solution spun;
s.spin(spun);
if (spun.lessThan(m_minSolution))
m_minSolution = spun;
else if (m_maxSolution.lessThan(s))
m_maxSolution = spun;
}
} // namespace
using namespace Meteor;
int main(int argc, char * argv [])
{
const int N_SOLUTION = 2098;
TUInt32 nSolMax = N_SOLUTION;
if (argc > 2)
return 1; // spec says this is an error
else if (argc == 2)
nSolMax = *((TUInt32 *)argv[1]);
Board board;
Piece::genAllOrientations();
board.genAllSolutions(0, 0, 0, 1LL);
cout << board.m_nSolutionFound << " solutions found\n\n";
cout << board.m_minSolution.toString() << '\n';
cout << board.m_maxSolution.toString() << endl;
// if (nSolMax != N_SOLUTION)
// return 1;
return 0;
}