0

I'm trying to implement a Tetris solver in Minizinc, which is also known as the "packing" problem I suppose.

I'm brand new to Minizinc and have barely any idea what I'm doing, but I'm currently stuck on a particular constraint in my code.

I am trying to solve a 4x4 square by placing 4 "l" blocks in Tetris into the square so that I will fill up the entire square.

My one major constraint is this:

constraint forall(b in 1..total) %default
(
  forall(x,y in 1..n)
  (
      (Type(TypeOf[b], 1) /\ Loc(b,x,y) /\ Rot(b, 1) /\ not Ref(b) /\ (y+3<=n)) 
      -> (Has(x,y,b) /\ Has(x,y+1,b) /\ Has(x,y+2,b) /\ Has(x,y+3,b))
  )
);

This constraint is supposed to go through all of the provided blocks first (4 "l" blocks), then cycle through the actual board to find: If current block is of type 1 (type "l"), and its origin is placed at x,y, and it has a rotation of 1 (so no rotation in this case), and it's not reflected, and it has more than 3 blocks of space beneath it, then it must have the first block at x,y, y+1, y+2, and y+3.

However even with this constraint (and all my other constraints), I still get an output of something like this through the solver:

board: 
4 4 4 4
3 2 2 2
2 1 1 1
1 3 3 3

 loc: 
0 0 0 0
1 1 1 1
0 0 0 0
0 0 0 0

Blocks aren't even supposed to be placed at the 2nd row because it does not have 3 blocks of clearing going down, and the board does not match up at all to the origin points, and does not match the above constraint of having blocks go straight down.

I am really out of ideas on how to fix this. On paper the logic seems sound, but I can't figure out where I went wrong. I will have my full code posted with all my other constraints. Please note yes I realize I currently only have the constraints for the "l" block in one orientation, but I am trying to solve it with 4 "l" blocks, which should fit just fine in that one orientation, so there's no reason why it should be outputting what it's outputting.

Thanks for any help guys.

include "globals.mzn";
include "builtins.mzn";

%Given variables
int: n; %length of board size
set of int: ROW = 1..n;
int: m=n; %number of columns
set of int: COL = 1..m;

%Number of starting tetrominoes
int: nR;  %ID 1 for R
int: nS;  %ID 2 for S
int: nT;  %ID 3 for T
int: nL;  %ID 4 for L
int: total = nR+nS+nT+nL;

array[int] of int: R = [ 1 | i in 1..nR];
array[int] of int: S = [ 2 | i in 1..nS];
array[int] of int: T = [ 3 | i in 1..nT];
array[int] of int: L = [ 4 | i in 1..nL];
array[int] of int: TypeOf = R++S++T++L; %Array of all blocks

%Decision Variables
array[1..n*n] of var 1..total: board; %Stored via (y-1)*n+x, using 1D array for ease of access.
array[1..n*n] of var 0..4: loc; %A separate location board that maps the origin point of each block
array[1..total] of var 1..4: rot; %Block rotations
array[1..total] of var 0..1: ref; %Block reflections

constraint total*4 == n*n;
constraint 0 <= nR /\ nR <= n /\ 0 <= nS /\ nS <= n /\ 0 <= nT /\ nT<= n /\ 0 <= nL /\ nL <= n;
constraint forall(i in 1..total)(TypeOf[i] == 1 \/ TypeOf[i] == 2 \/ TypeOf[i] ==3 \/ TypeOf[i] == 4);
constraint count(TypeOf, 1, nR)/\count(TypeOf,2,nS)/\count(TypeOf,3,nT)/\count(TypeOf,4,nL);

predicate Has(int: x, int: y, int: b) = board[(y-1)*n+x] == b;
predicate IsBlock(int: b) = b == 1 \/ b==2 \/ b==3 \/ b==4;

% BOARD RECORDS BLOCK NUMBER
% LOC RECORDS TYPE
predicate Loc(int: b, int: x, int: y) = loc[(y-1)*n+x] == TypeOf[b];
predicate Type(int: b, int: x) = b == x;
predicate Ref(int: i) = ref[i] == 1;
predicate Rot(int: i, int: amt) = rot[i] == amt;

%Block type 1 ----
constraint forall(b in 1..total) %default
(
  forall(x,y in 1..n)
  (
      (Type(TypeOf[b], 1) /\ Loc(b,x,y) /\ Rot(b, 1) /\ not Ref(b) /\ (y+3<=n)) 
      -> (Has(x,y,b) /\ Has(x,y+1,b) /\ Has(x,y+2,b) /\ Has(x,y+3,b))
  )
);

% constraint forall(b in 1..total) %90 degrees counterclockwise
% (
%   forall(x in 1..n)
%   (
%     forall(y in 1..n)
%     (
%       ((Type(Blocks[b], 1) /\ Loc(b,x,y) /\ Rot(b, 2) /\ not Ref(b) /\ (x+3<=n)) -> 
%       (Has(x,y,b) /\ Has(x+1,y,b) /\ Has(x+2, y, b) /\ Has(x+3, y, b)))
%     )
%   )
% );
% constraint forall(b in 1..total) %180 degrees counterclockwise
% (
%   forall(x in 1..n)
%   (
%     forall(y in 1..n)
%     (
%       ((Type(Blocks[b], 1) /\ Loc(b,x,y) /\ Rot(b, 3) /\ not Ref(b) /\ (y-3>=1)) -> 
%       (Has(x,y,b) /\ Has(x,y-1,b) /\ Has(x, y-2, b) /\ Has(x, y-3, b)))
%     )
%   )
% );
% constraint forall(b in 1..total) %270 degrees counterclockwise
% (
%   forall(x in 1..n)
%   (
%     forall(y in 1..n)
%     (
%       ((Type(Blocks[b], 1) /\ Loc(b,x,y) /\ Rot(b, 4) /\ not Ref(b) /\ (x-3>=1)) -> 
%       (Has(x,y,b) /\ Has(x-1,y,b) /\ Has(x-2, y, b) /\ Has(x-3, y, b)))
%     )
%   )
% );

% Make sure loc board doesn't have more blocks of each type than given
constraint count(loc, 1, nR)/\count(loc,2,nS)/\count(loc,3,nT)/\count(loc,4,nL);

% % Make sure each block in board is only used once
constraint forall(x in 1..total)(count(board, x, 4));

% Make sure board contains valid blocks
constraint forall(x in 1..n)
(
  forall(y in 1..n)
  (
    exists(b in 1..4)(IsBlock(b) /\ Has(x,y,b))
  )
);


solve satisfy;
output[
  "board: \n"]++[
  show(board[(c-1)*n+p]) ++
  if p == n then "\n" else " " endif

  | c in ROW, p in COL
  ]++[
  "\n loc: \n"]++[
  show(loc[(c-1)*n+p]) ++
  if p == n then "\n" else " " endif

  | c in ROW, p in COL
  ]
  ++["\n rot: \n" ++ show(rot)];
  • I suspect that the `Loc()` function is to general. It only checks that a block with the same type is located on the checked location; this allows could allow any block of the same type to be located on the consecutive fields. (Note that you also have functions accessing data out of bounds, this is very bad modeling practice as some solvers can crash because of this) – Dekker1 Dec 05 '16 at 10:32
  • I'm very new to Minizinc (literally my first project). How would you account for array out of bounds? I'm checking from 1 to array length, I can't see how that would create a problem. Thanks for your help, I will revise the Loc() function. Do you have any suggestions as to how to resolve this? The implementation I would've gone for was scrap Loc altogether, and put all the points information in a 2D array in the actual board, but I have no idea how to use functions such as count() on a 2D array so I have to make do with these :/ Thanks again! – Henry Zhao Dec 05 '16 at 17:01

1 Answers1

0

As you say it's your first MiniZinc model; I've made small working version of the stated problem. The main differences are:

  • Use of Parameters/Variables instead of functions. (Functions and predicates are, in MiniZinc, used to express more complicated concepts; not for the use of array access.)
  • Integration of the location and board variables using element constraints.
  • Removal of redundant variables (I didn't know what some of them did).

MiniZinc Model:

%% Parameters
% Board
int: width = 4;
set of int: COLUMNS = 1..width;
int: height = 4;
set of int: ROWS = 1..height;

% Tetrominoes
int: nr_shapes = 1;
set of int: SHAPES = 1..nr_shapes;
int: nr_I_shapes = 4;
int: I = 1;
set of int: BLOCKS = 1..sum([nr_I_shapes]);
array[BLOCKS] of SHAPES: TYPE = [ I | x in 1..nr_I_shapes];

%% Variables
% Block location
array[BLOCKS] of var COLUMNS: loc_x;
array[BLOCKS] of var ROWS: loc_y;

% Board
array[COLUMNS, ROWS] of var BLOCKS: board;

%% Constraints
% I block constraint (Inclusive channeling)
constraint forall (b in BLOCKS)
(
    if TYPE[b] = I then
    board[loc_x[b], loc_y[b]] = b /\ board[loc_x[b]+1, loc_y[b]] = b /\
    board[loc_x[b]+2, loc_y[b]] = b /\ board[loc_x[b]+3, loc_y[b]] = b
    else
    true
    endif
);

solve satisfy;
output[
  "board: \n"]++[
  show(board[c,r]) ++
  if c = width then "\n" else " " endif
  | r in ROWS, c in COLUMNS
  ] ++ ["\nlocations:\n"] ++
    [ "loc \(b): \(loc_x[b]), \(loc_y[b])\n" | b in BLOCKS];

This model does not yet cover (well):

  • Empty places in the board.
  • Rotation of the blocks.
Dekker1
  • 5,565
  • 25
  • 33
  • 1
    Hi thanks so much for your help! Your cleaned up code looks so much clearer. I'm confused about this line: array[BLOCKS] of SHAPES: TYPE = [if b <= nr_I_shapes then I else 1 endif | b in BLOCKS]; Could you please elaborate a little? if block b is less than or equal to number of I shapes? – Henry Zhao Dec 09 '16 at 04:27
  • This array is a shortened version of your `TypeOf` array. I didn't want to split up the line. If you're going to add multiple types you're approach might be more insightful in the end, although I don't think you need to safe the intermediary results. I've changed it in the answer. – Dekker1 Dec 09 '16 at 12:04
  • I see now! I wasn't too familiar with the notations, so I didn't quite understand what was happening. Thanks again for helping. I have one last question if you're still here. I'm trying to implement the various rotations now. What would be the best method to do this? I'm currently trying (..locations.. /\ rotation[b] = 0) \/ (..locations.. /\ rotation[b] =1) This is sort of saying "either this location setting for rotation 0, or that location setting for rotation 2. This isn't working because I'm trying to implement the "⊥" block, but I'm getting "Unsatisfiable". Do you have any suggestions? – Henry Zhao Dec 11 '16 at 05:09
  • I've tried putting it in if rotation[b,0]... -elseif rotation[b,1]...-elseif-rotation[b,2]. else true endif, I've tried putting them in just separate constraints, but none seems to work. if-elseif will calculate forever, and putting them in separate constraints will simply return non-satisfiable. – Henry Zhao Dec 11 '16 at 06:52
  • My question would be "why does rotation have two arguments?". Seems rotation should be another variable for each block, like `loc_x`, with a domain of 1..4. Then you can extend the if-statement in the constraint to `if TYPE[b] = I /\ rotation[b] in {1,3} then ... elseif TYPE[b] = I /\ rotation[b] in {2,4} then ... else true endif` – Dekker1 Dec 11 '16 at 12:02
  • I've tried that approach as well, my rotation had 2 arguments because I was still stuck in my old ways of use predicates to check truthness. I've since deleted that. I've tried `if TYPE[b] = I /\ rotation[b] in {0} then... elseif TYPE[b] = I /\ rotation[b] in {1} then...` but the solver simply returns an array filled with 1s. This is the similar result I've gotten when I tried other methods as well. I used {0} and {1} to force rotation to be either 0 or 1 for those particular loc settings. – Henry Zhao Dec 11 '16 at 20:09
  • This is what I have atm, I've changed `loc_x` to posx and posy because I'm more used to those variables: `constraint forall(b in BLOCKS) (% BLOCK I if TYPE[b] = I /\ rotation[b] in {0} then (board[xpos[b], ypos[b]] = b /\ board[xpos[b], ypos[b]+1] = b /\ board[xpos[b], ypos[b]+2] = b /\ board[xpos[b], ypos[b]+3] = b) elseif TYPE[b] = I /\ rotation[b] in {1} then (board[xpos[b], ypos[b]] = b /\ board[xpos[b]+1, ypos[b]] = b /\ board[xpos[b]+2, ypos[b]] = b /\ board[xpos[b]+3, ypos[b]] = b) else true endif );` – Henry Zhao Dec 11 '16 at 20:29
  • So why is it a problem that the solvers returns an `rotation` array filled with only 1's? There is an actual solution with those rotations. If you want to find all solutions, then for most solvers you can try the `-a` flag; that should give you a solutions with 0's as well. – Dekker1 Dec 12 '16 at 06:49