|
| 1 | +# Fillit |
| 2 | + |
| 3 | +This project is part of the 42 curriculum as was available at Codam in 2019. |
| 4 | +The goal of the project is to take a file containing tetrominoes (tetris pieces) |
| 5 | +as input from a text file, and calculating plus displaying the smallest |
| 6 | +possible square containing all tetrominoes, without using rotation, and respecting the original order as much as possible. |
| 7 | + |
| 8 | +Considering the quickly increasing complexity, and the chosen display format of |
| 9 | +a letter for each tetromino, the maximum amount of tetrominoes is `26`. |
| 10 | + |
| 11 | +# Looking for a challenge |
| 12 | +I build this project with my friend Arend, and when thinking about how we would |
| 13 | +approach this project, we were talking to other students for ideas. One of them |
| 14 | +jokingly made the ridiculous suggestion of doing everything with bitwise operations. |
| 15 | +After initially laughing about this we thought "why not, it sounds like a challenge!" |
| 16 | +and thus we made the decision that would cause us headaches and sleepless nights |
| 17 | +for weeks... |
| 18 | + |
| 19 | +Since we only had a few months of programming experience, neither of us really |
| 20 | +understood the purpose and power of bitwise operations, so our only plan was |
| 21 | +"do ANYTHING we can using bitwise!". This led to some simply stupid ideas, but as |
| 22 | +the project developed, our understand improved and so did our use of bitwise. |
| 23 | + |
| 24 | +The final product you find here has been refactored to make some stuff at least |
| 25 | +remotely understandable, but since I did not change any of the actual logic, |
| 26 | +the code is still nothing to be proud of... |
| 27 | +It is however very fast thanks to some nice tricks we were able to do thanks to our use of bitwise. |
| 28 | + |
| 29 | +# Storing tetrominos |
| 30 | +Every tetromino gets `32 bits` of storage. Why `32`? We need `24 bits` to store |
| 31 | +a tetromino and its coordinates in a `16x16` map, but that is not an easily storable and processable amount. |
| 32 | +The next available amount is the size of an `unsigned int`: `32 bits` or `4 bytes`, so we used that. |
| 33 | + |
| 34 | +How do store the tetromino inside these bits? Here is visualisation of the layout, per `byte`: |
| 35 | +``` |
| 36 | +| pos 1 & pos 2 | pos 3 & pos 4 | X coordinate | Y coordinate | |
| 37 | +``` |
| 38 | +### Storing the shape |
| 39 | +The first `16 bits` are used to store the shape of the tetromino, using `4 bits` for the position of every block of the tetromino within a `4x4` grid, or a chain of `16` positions. (values `0` through `15`) |
| 40 | +This method allows us to cast the value to normal variable if needed. |
| 41 | + |
| 42 | +### Storing the position |
| 43 | +With a maximum of 26 tetrominoes, the largest amount of tetromino blocks we will need to handle is `26 * 4 = 104`, which would theoretically fit in an `11x11` square. But again, that does not result in an amount that is easily stored, so we allocate `1 byte` for the X coordinate, and `1 byte` for the Y coordinate, without any fancy bit manipulations. |
| 44 | + |
| 45 | +### Why store it this way? |
| 46 | +Storing the tetrominoes like this allows us to be very efficient with memory, but also allows us to compare tetrominoes and their position with very basic math. For example, the `16 bits` that store the shape will always hold the same value when the shape is the same. We do have to make sure all tetrominoes are always positioned in the top left of the `4x4` grid, but this is a one-time operation. |
| 47 | + |
| 48 | +# Input parsing |
| 49 | +A valid input file looks a little like this: |
| 50 | +``` |
| 51 | +.#.. |
| 52 | +.#.. |
| 53 | +.##. |
| 54 | +.... |
| 55 | +
|
| 56 | +.... |
| 57 | +.##. |
| 58 | +..#. |
| 59 | +..#. |
| 60 | +
|
| 61 | +#### |
| 62 | +.... |
| 63 | +.... |
| 64 | +.... |
| 65 | +
|
| 66 | +.... |
| 67 | +.#.. |
| 68 | +.### |
| 69 | +.... |
| 70 | +
|
| 71 | +``` |
| 72 | +Every tetromino is provided in a 4x4 block, with `#` being part of the tetromino, |
| 73 | +and `.` being blank spots. To seperate the tetromino blocks an extra `newline` is insterted. |
| 74 | + |
| 75 | +This blocks in this text file are then converted and stored in the format described above, shifted to the top left corner if needed, and checked for valid tetromino shapes. |
| 76 | + |
| 77 | +# A map to solve in |
| 78 | +As said earlier, we need to be able to handle a map size of at least `11x11` to make sure we can solve any combination of tetrominoes. We want to use a single bit per position in the map, which means we need `11 * 11 = 121` bits. As usual, this is not an easy amount to store, especially since the largest datatype we have available is `64 bits`. So we decided to use `16 unsigned shorts`, which each consist of `16 bits`, giving us a `16x16` map to work with. More than we need, but much easier to work with. Even if we only need an `8x8` square, we always work within this `16x16` grid, so IF we need to try with a larger square size, we don't have to allocate anything. (in fact, we don't use malloc at all) |
| 79 | + |
| 80 | +# Solving |
| 81 | + |
| 82 | +### Map size |
| 83 | +The first step in solving is determining the smallest possible square that could theoretically fit the amount of tetrominoes. This is essentially `√(n * 4)` rounded up to the nearest integer. So for `8` tetrominoes: |
| 84 | +``` |
| 85 | + 8 * 4 = 32 |
| 86 | + √32 = 5.656854249 |
| 87 | + round up = 6 |
| 88 | + ``` |
| 89 | +These values were pre-calculated since they are constant, but we take a few shortcuts based on simple tetromino shapes. Here we start using the fact that each unique shape has a numerical value. For example, a square has the value `325`. You can see this in `size_exceptions` in `solver.c:33`. |
| 90 | + |
| 91 | +### Calculating usable space |
| 92 | +TBC |
| 93 | + |
| 94 | +### The almighty shortcut for duplicates |
| 95 | +TBC |
| 96 | + |
| 97 | +### Recursive trial & error |
| 98 | +TBC |
0 commit comments