Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 22 additions & 35 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,48 @@
# +:+ #
# By: aholster <aholster@student.codam.nl> +#+ #
# +#+ #
# Created: 2019/03/08 15:18:49 by aholster #+# #+# #
# Created: 2019/03/08 15:18:49 by aholster #+# #+# #
# Updated: 2019/09/10 20:24:10 by lgutter ######## odam.nl #
# #
# **************************************************************************** #

FILES := main.c fillit.c solver.c shift_corner.c place_tet.c smallest_map.c\
check_tet.c increment_offset.c remove_tet.c print_result.c read_tet.c\
FILES := main.c fillit.c solver.c shift_corner.c place_tet.c\
check_tet.c increment_offset.c remove_tet.c print_result.c parse_tet.c\
calc_empty.c

HEADER := fillit.h

NORM := norminette $(FILES) $(HEADER) | grep -e "Error" -e "Warning" -B 1
OBJS := $(FILES:%.c= %.o)

CC := gcc -g -Wall -Werror -Wextra
INCLUDES := -I ./libft

CFLAGS := -Wall -Werror -Wextra

NAME := fillit

all: $(NAME)

assemble:
@${CC} $(FILES) -I ./libft -L ./libft -lft -o $(NAME)

$(NAME):
@make -C libft/
@echo "\033[0;33mStarting assembly of $(NAME)...\033[0;00m"
@time make assemble
$(NAME): $(OBJS)
@$(MAKE) --no-print-directory -C libft/
@${CC} $(OBJS) $(INCLUDES) -L ./libft -lft -o $(NAME)
@echo "\033[0;32m$(NAME) successfully assembled!\033[0;00m\n"

clean:
@rm -rf *~ \#*\# .DS_Store
@make clean -C libft/
%.o: %.c
@$(CC) $(CFLAGS) $(INCLUDES) -c $^ -o $@

lclean:
@rm -rf *~ \#*\# .DS_Store $(OBJS)
@echo "\033[0;31mPests exterminated!\033[0;00m\n"

fclean: clean
clean: lclean
@$(MAKE) --no-print-directory clean -C libft/

fclean: lclean
@rm -rf $(NAME)
@make fclean -C libft/
@$(MAKE) --no-print-directory fclean -C libft/
@echo "\033[0;31mObituary of $(NAME): Deceased on $(shell date).\033[0;00m\n"

re: fclean all

norm:
@echo "**+++=====*=====*=====*=====*{\033[0;31mOUT\033[0;00m}\
*=====*=====*=====*=====+++**"
@$(NORM) || TRUE
@echo "**+++=====*=====*=====*=====*=====*=====*=====*=====*=====+++**"

add:
@git add $(FILES) $(HEADER) Makefile author
git status
re: fclean
@$(MAKE) --no-print-directory all

push:
#ifdef MSG
@make norm
@git commit
git push
#else
# @echo "\033[0;31mUsage: make push MSG=\"Message here\"\033[0;00m"
#endif
98 changes: 98 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Fillit

This project is part of the 42 curriculum as was available at Codam in 2019.
The goal of the project is to take a file containing tetrominoes (tetris pieces)
as input from a text file, and calculating plus displaying the smallest
possible square containing all tetrominoes, without using rotation, and respecting the original order as much as possible.

Considering the quickly increasing complexity, and the chosen display format of
a letter for each tetromino, the maximum amount of tetrominoes is `26`.

## Looking for a challenge
I built this project with my friend Arend, and when thinking about how we would
approach this project, we were talking to other students for ideas. One of them
jokingly made the ridiculous suggestion of doing everything with bitwise operations.
After initially laughing about this we thought "why not, it sounds like a challenge!"
and thus we made the decision that would cause us headaches and sleepless nights
for weeks...

Since we only had a few months of programming experience, neither of us really
understood the purpose and power of bitwise operations, so our only plan was
"do ANYTHING we can using bitwise!". This led to some simply stupid ideas, but as
the project developed, our understand improved and so did our use of bitwise.

The final product you find here has been refactored to make some stuff at least
remotely understandable, but since I did not change any of the actual logic,
the code is still nothing to be proud of...
It is however very fast thanks to some nice tricks we were able to do thanks to our use of bitwise.

## Storing tetrominos
Every tetromino gets `32 bits` of storage. Why `32`? We need `24 bits` to store
a tetromino and its coordinates in a `16x16` map, but that is not an easily storable and processable amount.
The next available amount is the size of an `unsigned int`: `32 bits` or `4 bytes`, so we used that.

How do store the tetromino inside these bits? Here is visualisation of the layout, per `byte`:
```
| pos 1 & pos 2 | pos 3 & pos 4 | X coordinate | Y coordinate |
```
### Storing the shape
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`)
This method allows us to cast the value to normal variable if needed.

### Storing the position
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.

### Why store it this way?
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.

## Input parsing
A valid input file looks a little like this:
```
.#..
.#..
.##.
....

....
.##.
..#.
..#.

####
....
....
....

....
.#..
.###
....

```
Every tetromino is provided in a 4x4 block, with `#` being part of the tetromino,
and `.` being blank spots. To seperate the tetromino blocks an extra `newline` is insterted.

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.

## A map to solve in
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)

## Solving

### Map size
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:
```
8 * 4 = 32
√32 = 5.656854249
round up = 6
```
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`.

### Calculating usable space
TBC

### The almighty shortcut for duplicates
TBC

### Recursive trial & error
TBC
8 changes: 4 additions & 4 deletions check_tet.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

#include "fillit.h"

int check_tet(unsigned int *tet)
int check_tet(t_tet_data *tet)
{
unsigned int temp;
short check;
short count;
t_tet_data temp;
short check;
short count;

count = 0;
check = 12;
Expand Down
35 changes: 21 additions & 14 deletions fillit.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,45 @@
/* +:+ */
/* By: aholster <aholster@student.codam.nl> +#+ */
/* +#+ */
/* Created: 2019/03/25 16:12:43 by lgutter #+# #+# */
/* Created: 2019/03/25 16:12:43 by lgutter #+# #+# */
/* Updated: 2019/04/01 15:13:00 by aholster ######## odam.nl */
/* */
/* ************************************************************************** */

#include "fillit.h"

void ft_error(char *error)
{
ft_putstr_fd("fillit: ", STDERR_FILENO);
ft_putendl_fd(error, STDERR_FILENO);
exit(-1);
}

void fillit(int fd)
{
unsigned int tet[27];
char buff[20];
ssize_t ret;
t_tet_data tet[MAX_TETS + 1];
char buff[20];
ssize_t ret;

tet[26] = 0;
tet[TET_COUNT] = 0;
ret = read(fd, buff, 20);
while (1)
while (true)
{
tet[tet[26]] = 0;
if (read_tet(&tet[tet[26]], buff) != 1 || check_tet(&tet[tet[26]]) != 1)
ft_error();
tet[26]++;
tet[tet[TET_COUNT]] = 0;
if (parse_tet(&tet[tet[TET_COUNT]], buff) != 1)
ft_error("parsing error!");
tet[TET_COUNT]++;
if (read(fd, buff, 1) == 1)
{
if (buff[0] != '\n')
ft_error();
ft_error("tetromino definitions should be seperated by '\\n'");
ret = read(fd, buff, 20);
if (ret != 20 || tet[26] > 25)
ft_error();
if (ret != 20 || tet[TET_COUNT] >= MAX_TETS)
ft_error(ret == 20 ? "too many tetrominos!" : "parsing error!");
}
else
break ;
}
close(fd);
map_control(&tet[0], tet[26]);
map_control(&tet[0], tet[TET_COUNT]);
}
58 changes: 40 additions & 18 deletions fillit.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,58 @@
/* +:+ */
/* By: aholster <aholster@student.codam.nl> +#+ */
/* +#+ */
/* Created: 2019/03/11 16:20:57 by aholster #+# #+# */
/* Created: 2019/03/11 16:20:57 by aholster #+# #+# */
/* Updated: 2019/03/25 15:37:55 by aholster ######## odam.nl */
/* */
/* ************************************************************************** */

#ifndef FILLIT_H
# define FILLIT_H

# define MASK2B 3
# define MASK4B 15
# define MASKBY 255

# include "libft.h"
# include <fcntl.h>
# include <sys/types.h>
# include <sys/uio.h>

int read_tet(unsigned int *tet, char *buff);
void fillit(int fd);
void map_control(unsigned int *tet, short tetcount);
int place_tet(unsigned int *tet, unsigned short *map,\
unsigned short di);
unsigned short smallest_map(short tetcount);
int check_tet(unsigned int *tet);
void shift_corner(unsigned int *tet);
void ft_error(void);
unsigned short calc_empty(unsigned short *map, unsigned short di);
int remove_tet(unsigned int *tet, unsigned short *map,\
/*
** Masks for different parts of the int we store every tet and its info in.
*/
# define MASK2BIT 3
# define MASK4BIT 15
# define MASKBYTE 255
# define MASK2BYTE 65535
# define TET_SHAPE 65535

/*
** Defines of number with a specific meaning
*/
# define MAX_TETS 26
# define TET_COUNT MAX_TETS
# define MAP_SIZE 16
# define VERTICAL_BAR 1164
# define HORIZONTAL_BAR 291
# define SQUARE 325

/*
** Contains the shape of a tet in the first 2 bytes (4 bits for the position of
** each block of the tet), the x offset within the map in the third byte, and
** the y offset within the map in the fourth and last byte.
*/
typedef unsigned int t_tet_data;

int parse_tet(t_tet_data *tet, char *buff);
void fillit(int fd);
void map_control(t_tet_data *tet, short tetcount);
int place_tet(t_tet_data *tet, unsigned short *map,\
unsigned short di);
unsigned short smallest_map(short tetcount);
int check_tet(t_tet_data *tet);
void shift_corner(t_tet_data *tet);
void ft_error(char *error);
unsigned short calc_empty(unsigned short *map, unsigned short di);
int remove_tet(t_tet_data *tet, unsigned short *map,\
unsigned short di);
int increment_offset(unsigned int *tet, unsigned short di);
void print_result(unsigned int *tet, unsigned short di);
int increment_offset(t_tet_data *tet, unsigned short di);
void print_result(t_tet_data *tet, unsigned short di);

#endif
4 changes: 3 additions & 1 deletion increment_offset.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
/* */
/* ************************************************************************** */

int increment_offset(unsigned int *tet, unsigned short di)
#include "fillit.h"

int increment_offset(t_tet_data *tet, unsigned short di)
{
unsigned char *offx;
unsigned char *offy;
Expand Down
Loading