- Given a sequence of tetrominos, this program will compute and display a way of dropping them into a grid such that the height of the resulting tetromino stack is minimised.
- The result displays which columns each tetromino should be dropped in, and the angle at which the tetrominos should be rotated before being dropped. The user can also specify to use the default rotations of tetrominos only.
- Dimensions of the grid are determined by the
GRID_HEIGHT
andGRID_WIDTH
macros ingrid.h
. - Maximum allowed length for a tetromino sequence is determined by the
MAX_SEQUENCE_SIZE
macro intetromino.h
.
- The best permutation (a column/rotation value for each tetromino) is calculated by trying all possible permutations and saving the one which produced the shortest stack. The order in which the permutations are tried is shown below:
- A tetromino can be dropped into
GRID_WIDTH + 1 - TETROMINO_WIDTH
columns, whereTETROMINO_WIDTH
is the width of a tetromino in a specific rotation (0, 90, 180, or 270 degrees). If the tetromino hasr
rotations, (assuming its width is the same in all rotations) the number of permutations for that tetromino becomesr * (GRID_WIDTH + 1 - TETROMINO_WIDTH)
. Therefore, the number of permutations for a sequence of lengthn
becomes(r * (GRID_WIDTH + 1 - TETROMINO_WIDTH)) ** n
. - In order to handle the exponentially growing number of permutations, certain optimisations are implemented:
- Divide and Conquer: The search space of all permutations is divided and assigned to
solver
units which independently try the permutations assigned to them. Each solver unit runs on a seperate solver thread for concurrent operation. Multi-threading is supported for Windows and Linux, otherwise a single solver unit is used which runs on the main thread. The number of solver units is determined by theNUMBER_OF_SOLVERS
macro insolver.h
(defaults to 16 for Windows/Linux). - Efficient Collision Detection: When dropping tetrominos into a grid, the state of the grid is stored and updated using the column heights of the grid/tetromino, instead of scanning the values in each cell of the pattern.
- Grid State Restoration: When trying a permutation, the grid state obtained after dropping each tetromino is individually saved. Given that the next permutation changes the column/rotation of piece
n
, restore the grid state from the previous permutation before dropping piecen
to avoid dropping these pieces again. This significantly reduces the number of collision detection calculations - Search Tree Pruning: During solving, given that the current lowest stack height is
m
, and the length of the sequence isn
, if dropping the firstp (p < n)
pieces gives a stack height>= m
, skip all permutations which have the prefix of the firstp
pieces at their current orientation:
- Divide and Conquer: The search space of all permutations is divided and assigned to
- Overflow detection: If the number of permutations for a sequence is greater than 2^64, an overflow in the 64-bit permutation counter is detected and the solving operation is aborted as all permutations cannot be tried.
- Debug mode: Creates an environment where the user can drop tetrominos into a grid one by one, in the specified column/rotation.
- Tests: The program solves the testcase tetromino sequences in
test.c
and compares the solutions with the testcase solutions. Used during development and for verifying correct compilation - VSCode Build File:
.vscode/tasks.json
contains the build configuration settings for compiling the code in this repository using VSCode.
If you need other shapes which are specific to your problem domain, you can add custom tetrominos:
- In
tetromino.c
define atetromino
array containing atetromino
struct for each rotation of your custom tetromino. In thePattern
attribute enter a'_'
for blank cells and in others enter a differentchar
which will represent your tetromino. For each rotation of your tetromino, a width/height value and a column heights array must also be supplied. Maximum dimensions for a tetromino are 4x4. - Declare your tetromino array in
tetromino.h
. - Change the
getTetromino
andgetRotations
functions intetromino.c
, and thegetSequence
function ininput_utils.c
to add a case for your new tetromino.