Skip to content

Commit c04b93e

Browse files
author
Liewe Gutter
committed
big chunk of documentation and refactoring done
Lots of comments added, WIP Readme added, lots of defines added and did some small functional improvements.
1 parent 8409c78 commit c04b93e

File tree

14 files changed

+342
-141
lines changed

14 files changed

+342
-141
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
# #
1111
# **************************************************************************** #
1212

13-
FILES := main.c fillit.c solver.c shift_corner.c place_tet.c smallest_map.c\
14-
check_tet.c increment_offset.c remove_tet.c print_result.c read_tet.c\
13+
FILES := main.c fillit.c solver.c shift_corner.c place_tet.c\
14+
check_tet.c increment_offset.c remove_tet.c print_result.c parse_tet.c\
1515
calc_empty.c
1616

1717
HEADER := fillit.h

README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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

check_tet.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212

1313
#include "fillit.h"
1414

15-
int check_tet(unsigned int *tet)
15+
int check_tet(t_tet_data *tet)
1616
{
17-
unsigned int temp;
18-
short check;
19-
short count;
17+
t_tet_data temp;
18+
short check;
19+
short count;
2020

2121
count = 0;
2222
check = 12;

fillit.c

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,38 @@
1212

1313
#include "fillit.h"
1414

15+
void ft_error(char *error)
16+
{
17+
ft_putstr_fd("fillit: ", STDERR_FILENO);
18+
ft_putendl_fd(error, STDERR_FILENO);
19+
exit(-1);
20+
}
21+
1522
void fillit(int fd)
1623
{
17-
unsigned int tet[27];
18-
char buff[20];
19-
ssize_t ret;
24+
t_tet_data tet[MAX_TETS + 1];
25+
char buff[20];
26+
ssize_t ret;
2027

21-
tet[26] = 0;
28+
tet[TET_COUNT] = 0;
2229
ret = read(fd, buff, 20);
23-
while (1)
30+
while (true)
2431
{
25-
tet[tet[26]] = 0;
26-
if (read_tet(&tet[tet[26]], buff) != 1 || check_tet(&tet[tet[26]]) != 1)
27-
ft_error();
28-
tet[26]++;
32+
tet[tet[TET_COUNT]] = 0;
33+
if (parse_tet(&tet[tet[TET_COUNT]], buff) != 1)
34+
ft_error("parsing error!");
35+
tet[TET_COUNT]++;
2936
if (read(fd, buff, 1) == 1)
3037
{
3138
if (buff[0] != '\n')
32-
ft_error();
39+
ft_error("tetromino definitions should be seperated by '\\n'");
3340
ret = read(fd, buff, 20);
34-
if (ret != 20 || tet[26] > 25)
35-
ft_error();
41+
if (ret != 20 || tet[TET_COUNT] >= MAX_TETS)
42+
ft_error(ret == 20 ? "too many tetrominos!" : "parsing error!");
3643
}
3744
else
3845
break ;
3946
}
4047
close(fd);
41-
map_control(&tet[0], tet[26]);
48+
map_control(&tet[0], tet[TET_COUNT]);
4249
}

fillit.h

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,50 @@
1313
#ifndef FILLIT_H
1414
# define FILLIT_H
1515

16-
# define MASK2B 3
17-
# define MASK4B 15
18-
# define MASKBY 255
19-
2016
# include "libft.h"
2117
# include <fcntl.h>
2218
# include <sys/types.h>
2319
# include <sys/uio.h>
2420

25-
int read_tet(unsigned int *tet, char *buff);
26-
void fillit(int fd);
27-
void map_control(unsigned int *tet, short tetcount);
28-
int place_tet(unsigned int *tet, unsigned short *map,\
29-
unsigned short di);
30-
unsigned short smallest_map(short tetcount);
31-
int check_tet(unsigned int *tet);
32-
void shift_corner(unsigned int *tet);
33-
void ft_error(void);
34-
unsigned short calc_empty(unsigned short *map, unsigned short di);
35-
int remove_tet(unsigned int *tet, unsigned short *map,\
21+
/*
22+
** Masks for different parts of the int we store every tet and its info in.
23+
*/
24+
# define MASK2BIT 3
25+
# define MASK4BIT 15
26+
# define MASKBYTE 255
27+
# define MASK2BYTE 65535
28+
# define TET_SHAPE 65535
29+
30+
/*
31+
** Defines of number with a specific meaning
32+
*/
33+
# define MAX_TETS 26
34+
# define TET_COUNT MAX_TETS
35+
# define MAP_SIZE 16
36+
# define VERTICAL_BAR 1164
37+
# define HORIZONTAL_BAR 291
38+
# define SQUARE 325
39+
40+
/*
41+
** Contains the shape of a tet in the first 2 bytes (4 bits for the position of
42+
** each block of the tet), the x offset within the map in the third byte, and
43+
** the y offset within the map in the fourth and last byte.
44+
*/
45+
typedef unsigned int t_tet_data;
46+
47+
int parse_tet(t_tet_data *tet, char *buff);
48+
void fillit(int fd);
49+
void map_control(t_tet_data *tet, short tetcount);
50+
int place_tet(t_tet_data *tet, unsigned short *map,\
51+
unsigned short di);
52+
unsigned short smallest_map(short tetcount);
53+
int check_tet(t_tet_data *tet);
54+
void shift_corner(t_tet_data *tet);
55+
void ft_error(char *error);
56+
unsigned short calc_empty(unsigned short *map, unsigned short di);
57+
int remove_tet(t_tet_data *tet, unsigned short *map,\
3658
unsigned short di);
37-
int increment_offset(unsigned int *tet, unsigned short di);
38-
void print_result(unsigned int *tet, unsigned short di);
59+
int increment_offset(t_tet_data *tet, unsigned short di);
60+
void print_result(t_tet_data *tet, unsigned short di);
3961

4062
#endif

increment_offset.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
/* */
1111
/* ************************************************************************** */
1212

13-
int increment_offset(unsigned int *tet, unsigned short di)
13+
#include "fillit.h"
14+
15+
int increment_offset(t_tet_data *tet, unsigned short di)
1416
{
1517
unsigned char *offx;
1618
unsigned char *offy;

main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ int main(int argc, char **argv)
2222
if (fd >= 0)
2323
fillit(fd);
2424
else
25-
ft_error();
25+
ft_error("error opening source_file!");
2626
}
2727
else
2828
ft_putendl("usage: ./fillit source_file");

read_tet.c renamed to parse_tet.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* ************************************************************************** */
22
/* */
33
/* :::::::: */
4-
/* read_tet.c :+: :+: */
4+
/* parse_tet.c :+: :+: */
55
/* +:+ */
66
/* By: lgutter <[email protected]> +#+ */
77
/* +#+ */
@@ -10,14 +10,16 @@
1010
/* */
1111
/* ************************************************************************** */
1212

13-
int read_tet(unsigned int *tet, char *buff)
13+
#include "fillit.h"
14+
15+
int parse_tet(t_tet_data *tet, char *buff)
1416
{
15-
short i;
16-
short hash;
17+
short i;
18+
short hash;
1719

1820
i = 0;
1921
hash = 0;
20-
if (buff[19] != '\n')
22+
if (ft_strlen(buff) < 20 || buff[19] != '\n')
2123
return (-1);
2224
while (i < 20)
2325
{
@@ -34,5 +36,5 @@ int read_tet(unsigned int *tet, char *buff)
3436
}
3537
if (hash != 4)
3638
return (-1);
37-
return (1);
39+
return (check_tet(tet));
3840
}

place_tet.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
#include "fillit.h"
1414

15-
static void place_final(unsigned int tet, unsigned short *map)
15+
static void place_final(t_tet_data tet, unsigned short *map)
1616
{
1717
unsigned char y;
1818
unsigned char x;
@@ -31,32 +31,32 @@ static void place_final(unsigned int tet, unsigned short *map)
3131
map[y] = map[y] | (1 << x);
3232
}
3333

34-
static int check_hash(unsigned int tet, unsigned short *map,\
34+
static int check_hash(t_tet_data tet, unsigned short *map,\
3535
unsigned short di)
3636
{
3737
unsigned char y;
3838
unsigned char x;
3939

40-
y = (((tet >> 16) & 255) + ((tet >> (0) & 3)));
40+
y = (((tet >> 16) & MASKBYTE) + ((tet >> (0) & 3)));
4141
x = ((tet >> 24) + ((tet >> ((0) + 2)) & 3));
4242
if (x >= di || y >= di || ((map[y] >> x) & 1) != 0)
4343
return (-1);
44-
y = (((tet >> 16) & 255) + ((tet >> (12) & 3)));
44+
y = (((tet >> 16) & MASKBYTE) + ((tet >> (12) & 3)));
4545
x = ((tet >> 24) + ((tet >> ((12) + 2)) & 3));
4646
if (x >= di || y >= di || ((map[y] >> x) & 1) != 0)
4747
return (-1);
48-
y = (((tet >> 16) & 255) + ((tet >> (8) & 3)));
48+
y = (((tet >> 16) & MASKBYTE) + ((tet >> (8) & 3)));
4949
x = ((tet >> 24) + ((tet >> ((8) + 2)) & 3));
5050
if (x >= di || y >= di || ((map[y] >> x) & 1) != 0)
5151
return (-1);
52-
y = (((tet >> 16) & 255) + ((tet >> (4) & 3)));
52+
y = (((tet >> 16) & MASKBYTE) + ((tet >> (4) & 3)));
5353
x = ((tet >> 24) + ((tet >> ((4) + 2)) & 3));
5454
if (x >= di || y >= di || ((map[y] >> x) & 1) != 0)
5555
return (-1);
5656
return (1);
5757
}
5858

59-
int place_tet(unsigned int *tet, unsigned short *map,\
59+
int place_tet(t_tet_data *tet, unsigned short *map,\
6060
unsigned short di)
6161
{
6262
while (check_hash(*tet, map, di) != 1)

print_result.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,28 @@
1212

1313
#include "fillit.h"
1414

15-
static void ft_converttet(unsigned int tet, unsigned int index,\
15+
static void ft_converttet(t_tet_data tet, unsigned short index,\
1616
char arr[16][16])
1717
{
1818
unsigned short offy;
1919
unsigned short offx;
2020
unsigned char offset;
2121

22-
offy = (tet >> 16) & MASKBY;
22+
offy = (tet >> 16) & MASKBYTE;
2323
offx = (tet >> 24);
2424
offset = 0;
2525
while (offset <= 12)
2626
{
27-
arr[offx + ((tet >> (offset + 2)) & MASK2B)]\
28-
[offy + ((tet >> offset) & MASK2B)] = 'A' + index;
27+
arr[offx + ((tet >> (offset + 2)) & MASK2BIT)]\
28+
[offy + ((tet >> offset) & MASK2BIT)] = 'A' + index;
2929
offset += 4;
3030
}
3131
}
3232

3333
static void ft_initsquare(unsigned short di, char arr[16][16])
3434
{
35-
unsigned int index;
36-
unsigned int sudex;
35+
unsigned short index;
36+
unsigned short sudex;
3737

3838
index = 0;
3939
while (index < di)
@@ -49,10 +49,10 @@ static void ft_initsquare(unsigned short di, char arr[16][16])
4949
}
5050
}
5151

52-
void print_result(unsigned int *tet, unsigned short di)
52+
void print_result(t_tet_data *tet, unsigned short di)
5353
{
5454
char arr[16][16];
55-
unsigned int index;
55+
unsigned short index;
5656

5757
index = 0;
5858
ft_initsquare(di, arr);

0 commit comments

Comments
 (0)