var integerWidth = 15;
var epsilon = 0.001;
var storeDebugExpressions = false;
var cacheConstants = false;
var storeDebugConstraints = false;
var solver = new CplexMilpSolver(new CplexMilpSolverSettings
{
IntegerWidth = integerWidth,
Epsilon = epsilon,
StoreDebugExpressions = storeDebugExpressions,
CacheConstants = cacheConstants,
StoreDebugConstraints = storeDebugConstraints
});
solver.Cplex.SetParam(ILOG.CPLEX.Cplex.Param.MIP.Tolerances.Integrality, 0.000000000001);
var pieces = new []{
new []{
"XX",
"X ",
"X "
},
new[]{
" XX",
"XX "
},
new[]{
"XX ",
" XX"
},
new[]{
"X ",
"XX",
"X "
},
new[]{
"XX",
"XX"
},
new[]{
"XXXX"
},
new[]{
"X ",
"X ",
"XX"
}
};
var totalLength = pieces.Select(p => (int)Math.Max(p.Length, p[0].Length)).Sum();
var maxLength = pieces.Max(p => (int)Math.Max(p.Length, p[0].Length));
var width = 11; // Or totalLength + 2 to make sure it's big enough for any shapes
var height = 9; // Or totalLength + 2 to make sure it's big enough for any shapes
var piecesCount = pieces.Length;
// Right, down, left, up
var directions = 4;
var heads = Enumerable.Range(0, height).Select(h =>
Enumerable.Range(0, width).Select(w =>
Enumerable.Range(0, piecesCount).Select(p =>
Enumerable.Range(0, directions).Select(d =>
solver.CreateAnonymous(Domain.BinaryInteger)
).ToArray()
).ToArray()
).ToArray()
).ToArray();
var isInterior = Enumerable.Range(0, height).Select(h =>
Enumerable.Range(0, width).Select(w =>
solver.CreateAnonymous(Domain.BinaryInteger)
).ToArray()
).ToArray();
var isBoundary = Enumerable.Range(0, height).Select(h =>
Enumerable.Range(0, width).Select(w =>
solver.CreateAnonymous(Domain.BinaryInteger)
).ToArray()
).ToArray();
Func<string[], int, string[]> rotatePiece = (piece, direction) => {
if(direction == 0){
return piece;
}else if(direction == 2){
return piece.Select(l => string.Join("", l.Reverse())).Reverse().ToArray();
}else if(direction == 1){
return Enumerable.Range(0, piece[0].Length)
.Select(c => string.Join("", Enumerable.Range(0, piece.Length).Select(w => piece[piece.Length - 1 - w][c])))
.ToArray();
}else {
return Enumerable.Range(0, piece[0].Length)
.Select(c => string.Join("", Enumerable.Range(0, piece.Length).Select(w => piece[w][piece[0].Length - 1 - c])))
.ToArray();
}
};
// Put pieces on the board
for(int p=0;p<piecesCount;++p){
solver.Set<Equal>(
solver.FromConstant(1),
solver.Operation<Addition>(
Enumerable.Range(0, height).SelectMany(h =>
Enumerable.Range(0, width).SelectMany(w =>
Enumerable.Range(0, directions).Select(d =>
heads[h][w][p][d]
)
)
).ToArray()
)
);
}
// Make sure pieces don't fall off the board
for(int h = 0; h < height; ++h){
for(int w = 0; w < width; ++ w){
for(int p=0;p<piecesCount;++p){
for(int d = 0; d < directions;++d){
var piece = rotatePiece(pieces[p], d);
if(d == 0){
if(w + piece[0].Length - 1 >= width){
heads[h][w][p][d].Set<Equal>(solver.FromConstant(0));
}
}else if(d == 1){
if(h + piece.Length - 1 >= height){
heads[h][w][p][d].Set<Equal>(solver.FromConstant(0));
}
}else if(d == 2){
if(w - piece[0].Length + 1 < 0){
heads[h][w][p][d].Set<Equal>(solver.FromConstant(0));
}
}else if(d == 3){
if(h - piece.Length + 1 < 0){
heads[h][w][p][d].Set<Equal>(solver.FromConstant(0));
}
}
}
}
}
}
// Set impacts
IList<Tuple<int, int, int, int>>[][] allImpacts = Enumerable.Range(0, height).Select(h =>
Enumerable.Range(0, width).Select(w =>
new List<Tuple<int, int, int, int>>()
).ToArray()
).ToArray();
for(int h = 0; h < height; ++h){
for(int w = 0; w < width; ++ w){
for(int p=0;p<piecesCount;++p){
for(int d = 0; d < directions;++d){
var piece = rotatePiece(pieces[p], d);
var pieceWidth = piece[0].Length;
var pieceHeight = piece.Length;
for(int y=0;y<pieceHeight;++y){
for(int x=0;x<pieceWidth;++x){
var isPieceX = piece[y][x] == 'X';
if(!isPieceX) continue;
var finalY = d == 0 || d == 1 ? h + y : y - pieceHeight + 1 + y;
var finalX = d == 0 || d == 1 ? w + x : x - pieceWidth + 1 + x;
if(finalY >= 0 && finalY < height && finalX >=0 && finalX < width){
allImpacts[finalY][finalX].Add(Tuple.Create(h, w, p, d));
}
}
}
}
}
}
}
// Set the boundary
for(int h = 0; h < height; ++h){
for(int w = 0; w < width; ++ w){
isBoundary[h][w].Set<Equal>(
solver.Operation<Addition>(
allImpacts[h][w].Select(t => heads[t.Item1][t.Item2][t.Item3][t.Item4]).ToArray()
)
);
}
}
// Make sure pieces do not overlap
for(int h = 0; h < height; ++h){
for(int w = 0; w < width; ++ w){
solver.Set<LessOrEqual>(
solver.Operation<Addition>(
allImpacts[h][w].Select(t => heads[t.Item1][t.Item2][t.Item3][t.Item4]).ToArray()
),
solver.FromConstant(1)
);
}
}
// Extend interior
for(int h = 0; h < height; ++h){
for(int w = 0; w < width; ++ w){
for(int y=-1;y<=1;++y){
for(int x=-1;x<=1;++x){
if(y == 0 && x == 0){
continue;
}
if(h + y < 0 || h + y >= height){
continue;
}
if(w + x < 0 || w + x >= width){
continue;
}
solver.Set<Equal>(
solver.FromConstant(1),
solver.Operation<MaterialImplication>(
solver.Operation<Conjunction>(
isInterior[h][w],
isBoundary[h + y][w + x].Operation<BinaryNegation>()
),
isInterior[h + y][w + x]
)
);
}
}
}
}
// Make sure there is some exterior
for(int h = 0; h < height; ++h){
for(int w = 0; w < width; ++ w){
if(h == 0 || h == height - 1 || w == 0 || w == width - 1){
solver.Set<Equal>(
solver.FromConstant(0),
isInterior[h][w]
);
solver.Set<Equal>(
solver.FromConstant(0),
isBoundary[h][w]
);
}
}
}
// Make sure a field cannot be both exterior and boundary
for(int h = 0; h < height; ++h){
for(int w = 0; w < width; ++ w){
solver.Set<LessOrEqual>(
solver.Operation<Addition>(
isInterior[h][w],
isBoundary[h][w]
),
solver.FromConstant(1)
);
}
}
var goal = solver.Operation<Addition>(isInterior.SelectMany(x => x).ToArray());
solver.AddGoal("goal", goal);
solver.Solve();
Console.WriteLine(solver.GetValue(goal));
var board = Enumerable.Range(0, height).Select(h => new String(' ', width).ToCharArray()).ToArray();
for(int h = 0; h < height; ++h){
for(int w = 0; w < width; ++ w){
for(int p=0;p<piecesCount;++p){
for(int d = 0; d < directions;++d){
var piece = rotatePiece(pieces[p], d);
var pieceWidth = piece[0].Length;
var pieceHeight = piece.Length;
if(solver.GetValue(heads[h][w][p][d]) > 0.5){
for(int y=0;y<pieceHeight;++y){
for(int x=0;x<pieceWidth;++x){
var finalY = d == 0 || d == 1 ? h + y : y - pieceHeight + 1 + y;
var finalX = d == 0 || d == 1 ? w + x : x - pieceWidth + 1 + x;
if(finalY >= 0 && finalY < height && finalX >=0 && finalX < width){
board[finalY][finalX] = piece[y][x];
}else {
Console.WriteLine("Error in " + h + " " + w + " " + p + " " + d + " " + y + " " + x);
}
}
}
}
}
}
}
}
foreach(var l in board){
Console.WriteLine(string.Join("", l));
}
Console.WriteLine();
for(int h = 0; h < height; ++h){
for(int w = 0; w < width; ++ w){
Console.Write(Math.Round(solver.GetValue(isInterior[h][w])));
}
Console.WriteLine();
}
Console.WriteLine();
for(int h = 0; h < height; ++h){
for(int w = 0; w < width; ++ w){
Console.Write(Math.Round(solver.GetValue(isBoundary[h][w])));
}
Console.WriteLine();
}
Console.WriteLine();