diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6ab58b9 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +CC = gcc +CFLAGS = -Wall -Wextra -Werror -O3 -std=c11 +INCLUDES = -Iinclude +SRCDIR = src +OBJDIR = obj +BINDIR = bin + +SOURCES = $(wildcard $(SRCDIR)/*.c) +OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) +EXECUTABLE = $(BINDIR)/galaxies + +.PHONY: all clean + +all: $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) | $(BINDIR) + $(CC) $(CFLAGS) $(OBJECTS) -o $@ + +$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR) + $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(OBJDIR): + mkdir -p $@ + +$(BINDIR): + mkdir -p $@ + +clean: + rm -rf $(OBJDIR) $(BINDIR) + +# Header dependencies +$(OBJDIR)/main.o: include/generator.h include/solver.h include/output.h +$(OBJDIR)/generator.o: include/generator.h include/utils.h +$(OBJDIR)/solver.o: include/solver.h include/generator.h include/utils.h +$(OBJDIR)/output.o: include/output.h include/generator.h +$(OBJDIR)/utils.o: include/utils.h include/generator.h \ No newline at end of file diff --git a/include/generator.h b/include/generator.h new file mode 100644 index 0000000..92d28c1 --- /dev/null +++ b/include/generator.h @@ -0,0 +1,23 @@ +#ifndef GENERATOR_H +#define GENERATOR_H + +#include +#include "utils.h" + +#define CELL_EMPTY 0 +#define CELL_CENTER 1 +#define CELL_SYMMETRY 128 + +typedef struct GameBoard { + uint8_t width; + uint8_t height; + uint8_t *cells; + Point *centers; + int center_count; +} GameBoard; + +GameBoard *create_board(uint8_t width, uint8_t height); +void destroy_board(GameBoard *board); +int generate_puzzle(GameBoard *board); + +#endif // GENERATOR_H \ No newline at end of file diff --git a/include/output.h b/include/output.h new file mode 100644 index 0000000..b0de0b9 --- /dev/null +++ b/include/output.h @@ -0,0 +1,10 @@ +#ifndef OUTPUT_H +#define OUTPUT_H + +#include "generator.h" + +void print_unsolved_puzzle(GameBoard *board, const char *filename); +void print_solved_puzzle(GameBoard *board, const char *filename); +void print_ascii_puzzle(GameBoard *board, const char *filename); + +#endif // OUTPUT_H \ No newline at end of file diff --git a/include/solver.h b/include/solver.h new file mode 100644 index 0000000..2dc61f6 --- /dev/null +++ b/include/solver.h @@ -0,0 +1,9 @@ +#ifndef SOLVER_H +#define SOLVER_H + +#include +#include "generator.h" + +bool solve_puzzle(GameBoard *board); + +#endif // SOLVER_H \ No newline at end of file diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..419154e --- /dev/null +++ b/include/utils.h @@ -0,0 +1,24 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +// Ado you are GameBoard +struct GameBoard; + +typedef struct { + int8_t x; + int8_t y; +} Point; + +void init_random(unsigned int seed); +int random_int(int min, int max); + +void set_cell(struct GameBoard *board, uint8_t x, uint8_t y, uint8_t value); +uint8_t get_cell(const struct GameBoard *board, uint8_t x, uint8_t y); +int is_valid_coord(const struct GameBoard *board, int8_t x, int8_t y); + +Point get_symmetric_point(const struct GameBoard *board, Point center, Point p); +int is_symmetric(const struct GameBoard *board, Point center, Point p1, Point p2); + +#endif // UTILS_H \ No newline at end of file diff --git a/src/generator.c b/src/generator.c new file mode 100644 index 0000000..7c71637 --- /dev/null +++ b/src/generator.c @@ -0,0 +1,139 @@ +#include +#include +#include "../include/generator.h" +#include "../include/utils.h" + +static void place_centers(GameBoard *board); + +static int is_adjacent_to_center(const GameBoard *board, int x, int y) { + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + int nx = x + dx, ny = y + dy; + if (is_valid_coord(board, nx, ny) && get_cell(board, nx, ny) == CELL_CENTER) { + return 1; + } + } + } + return 0; +} + +static void grow_galaxy(GameBoard *board, int x, int y, int galaxy_id) { + Point center = {x, y}; + int directions[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; + + for (int i = 0; i < 4; i++) { + int dx = directions[i][0], dy = directions[i][1]; + int nx = x + dx, ny = y + dy; + Point sym = get_symmetric_point(board, center, (Point){nx, ny}); + int sym_x = sym.x, sym_y = sym.y; + + if (is_valid_coord(board, nx, ny) && is_valid_coord(board, sym_x, sym_y) && + get_cell(board, nx, ny) == CELL_EMPTY && get_cell(board, sym_x, sym_y) == CELL_EMPTY) { + set_cell(board, nx, ny, galaxy_id); + set_cell(board, sym_x, sym_y, galaxy_id); + grow_galaxy(board, nx, ny, galaxy_id); + } + } +} + +static void generate_galaxies(GameBoard *board) { + int galaxy_id = 2; + for (int y = 0; y < board->height; y++) { + for (int x = 0; x < board->width; x++) { + if (get_cell(board, x, y) == CELL_CENTER) { + grow_galaxy(board, x, y, galaxy_id++); + } + } + } +} + +static void generate_symmetry_lines(GameBoard *board) { + for (int y = 0; y < board->height; y++) { + for (int x = 0; x < board->width; x++) { + if (get_cell(board, x, y) == CELL_CENTER) { + for (int i = 0; i < 4; i++) { + int nx = x, ny = y; + while (1) { + nx += (i == 1) - (i == 3); + ny += (i == 0) - (i == 2); + if (!is_valid_coord(board, nx, ny) || get_cell(board, nx, ny) != get_cell(board, x, y)) break; + set_cell(board, nx, ny, get_cell(board, nx, ny) | CELL_SYMMETRY); + } + } + } + } + } +} + +static int find_neighbor_galaxy(const GameBoard *board, int x, int y) { + int directions[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; + for (int i = 0; i < 4; i++) { + int nx = x + directions[i][0], ny = y + directions[i][1]; + if (is_valid_coord(board, nx, ny) && get_cell(board, nx, ny) != CELL_EMPTY) { + return get_cell(board, nx, ny) & ~CELL_SYMMETRY; + } + } + return CELL_EMPTY; +} + +int generate_puzzle(GameBoard *board) { + memset(board->cells, CELL_EMPTY, board->width * board->height); + + place_centers(board); + generate_galaxies(board); + generate_symmetry_lines(board); + + for (int y = 0; y < board->height; y++) { + for (int x = 0; x < board->width; x++) { + if (get_cell(board, x, y) == CELL_EMPTY) { + int neighbor = find_neighbor_galaxy(board, x, y); + if (neighbor == CELL_EMPTY) return 0; + set_cell(board, x, y, neighbor); + } + } + } + + return 1; +} + +GameBoard *create_board(uint8_t width, uint8_t height) { + GameBoard *board = malloc(sizeof(GameBoard)); + if (!board) return NULL; + + board->width = width; + board->height = height; + board->cells = calloc(width * height, sizeof(uint8_t)); + board->centers = malloc(sizeof(Point) * (width * height / 25)); + board->center_count = 0; + + if (!board->cells || !board->centers) { + free(board->cells); + free(board->centers); + free(board); + return NULL; + } + + return board; +} + +void destroy_board(GameBoard *board) { + if (board) { + free(board->cells); + free(board->centers); + free(board); + } +} + +static void place_centers(GameBoard *board) { + int centers = board->width * board->height / 25; + while (centers > 0) { + int x = random_int(0, board->width - 1); + int y = random_int(0, board->height - 1); + if (get_cell(board, x, y) == CELL_EMPTY && !is_adjacent_to_center(board, x, y)) { + set_cell(board, x, y, CELL_CENTER); + board->centers[board->center_count++] = (Point){x, y}; + centers--; + } + } +} \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..51370d5 --- /dev/null +++ b/src/main.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include "../include/generator.h" +#include "../include/solver.h" +#include "../include/output.h" +#include "../include/utils.h" + +#define DEFAULT_SIZE 10 + +void create_puzzle_directory(unsigned int seed) { + char dirname[20]; + snprintf(dirname, sizeof(dirname), "puzzle_%u", seed); + + if (mkdir(dirname, 0777) == -1 && errno != EEXIST) { + fprintf(stderr, "Error creating directory: %s\n", dirname); + exit(1); + } +} + +int main(int argc, char *argv[]) { + unsigned int seed = (unsigned int)time(NULL); + int size = DEFAULT_SIZE; + + if (argc > 1) { + seed = (unsigned int)atoi(argv[1]); + } + if (argc > 2) { + size = atoi(argv[2]); + if (size < 5 || size > 20) { + fprintf(stderr, "Size must be between 5 and 20\n"); + return 1; + } + } + + create_puzzle_directory(seed); + init_random(seed); + + GameBoard *board = create_board(size, size); + if (!board) { + fprintf(stderr, "Failed to create board\n"); + return 1; + } + + if (!generate_puzzle(board)) { + fprintf(stderr, "Failed to generate puzzle\n"); + destroy_board(board); + return 1; + } + + char filename[50]; + snprintf(filename, sizeof(filename), "puzzle_%u/unsolved.pbm", seed); + print_unsolved_puzzle(board, filename); + + if (!solve_puzzle(board)) { + fprintf(stderr, "Generated puzzle is not solvable\n"); + destroy_board(board); + return 1; + } + + snprintf(filename, sizeof(filename), "puzzle_%u/solved.pbm", seed); + print_solved_puzzle(board, filename); + + snprintf(filename, sizeof(filename), "puzzle_%u/solved.txt", seed); + print_ascii_puzzle(board, filename); + + destroy_board(board); + + return 0; +} \ No newline at end of file diff --git a/src/output.c b/src/output.c new file mode 100644 index 0000000..d37d76f --- /dev/null +++ b/src/output.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include "../include/output.h" +#include "../include/utils.h" + +#define MAX_INTENSITY 255 +#define CENTER_RADIUS 2 +#define LINE_THICKNESS 1 + +void set_pixel(unsigned char *image, int width, int x, int y, unsigned char value) { + if (x >= 0 && x < width && y >= 0 && y < width) { + image[y * width + x] = value; + } +} + +void draw_circle(unsigned char *image, int width, int cx, int cy, int radius) { + for (int y = -radius; y <= radius; y++) { + for (int x = -radius; x <= radius; x++) { + if (x*x + y*y <= radius*radius) { + set_pixel(image, width, cx + x, cy + y, MAX_INTENSITY); + } + } + } +} + +void draw_line(unsigned char *image, int width, int x1, int y1, int x2, int y2) { + int dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1; + int dy = abs(y2 - y1), sy = y1 < y2 ? 1 : -1; + int err = (dx > dy ? dx : -dy) / 2, e2; + + while (1) { + set_pixel(image, width, x1, y1, MAX_INTENSITY); + if (x1 == x2 && y1 == y2) break; + e2 = err; + if (e2 > -dx) { err -= dy; x1 += sx; } + if (e2 < dy) { err += dx; y1 += sy; } + } +} + +void print_pgm(GameBoard *board, const char *filename, int show_solved) { + int width = board->width * 10; // ado you are killer + unsigned char *image = calloc(width * width, sizeof(unsigned char)); + if (!image) { + fprintf(stderr, "Failed to allocate memory for image\n"); + return; + } + + // ado you are center + for (int i = 0; i < board->center_count; i++) { + int cx = board->centers[i].x * 10 + 5; + int cy = board->centers[i].y * 10 + 5; + draw_circle(image, width, cx, cy, CENTER_RADIUS); + } + + // ado you are puzzle + if (show_solved) { + for (int y = 0; y < board->height; y++) { + for (int x = 0; x < board->width; x++) { + uint8_t cell = get_cell(board, x, y); + if (cell != CELL_EMPTY && cell != CELL_CENTER) { + int cx = x * 10 + 5, cy = y * 10 + 5; + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; + int nx = x + dx, ny = y + dy; + if (is_valid_coord(board, nx, ny) && get_cell(board, nx, ny) == cell) { + int nx_scaled = nx * 10 + 5, ny_scaled = ny * 10 + 5; + draw_line(image, width, cx, cy, nx_scaled, ny_scaled); + } + } + } + } + } + } + } + + // ado you are pgm + FILE *fp = fopen(filename, "wb"); + if (!fp) { + fprintf(stderr, "Error opening file: %s\n", filename); + free(image); + return; + } + + fprintf(fp, "P5\n%d %d\n%d\n", width, width, MAX_INTENSITY); + fwrite(image, sizeof(unsigned char), width * width, fp); + + fclose(fp); + free(image); +} + +void print_unsolved_puzzle(GameBoard *board, const char *filename) { + print_pgm(board, filename, 0); +} + +void print_solved_puzzle(GameBoard *board, const char *filename) { + print_pgm(board, filename, 1); +} + +void print_ascii_puzzle(GameBoard *board, const char *filename) { + FILE *fp = fopen(filename, "w"); + if (!fp) { + fprintf(stderr, "Error opening file: %s\n", filename); + return; + } + + for (int y = 0; y < board->height; y++) { + for (int x = 0; x < board->width; x++) { + uint8_t cell = get_cell(board, x, y); + if (cell == CELL_CENTER) { + fprintf(fp, "O"); + } else if (cell != CELL_EMPTY) { + fprintf(fp, "#"); + } else { + fprintf(fp, "."); + } + } + fprintf(fp, "\n"); + } + + fclose(fp); +} \ No newline at end of file diff --git a/src/solver.c b/src/solver.c new file mode 100644 index 0000000..a2c61aa --- /dev/null +++ b/src/solver.c @@ -0,0 +1,70 @@ +#include +#include +#include "../include/solver.h" +#include "../include/utils.h" + +#define MAX_ITERATIONS 1000000 + +typedef struct { + int x, y; + int center_index; +} CellAssignment; + +static bool is_valid_assignment(GameBoard *board, int x, int y, int center_index) { + Point center = board->centers[center_index]; + int dx = x - center.x; + int dy = y - center.y; + + int sym_x = center.x - dx; + int sym_y = center.y - dy; + + if (sym_x < 0 || sym_x >= board->width || sym_y < 0 || sym_y >= board->height) { + return false; + } + + uint8_t sym_cell = get_cell(board, sym_x, sym_y); + return sym_cell == CELL_EMPTY || sym_cell == (center_index + 1); +} + +static bool backtrack(GameBoard *board, CellAssignment *stack, int stack_size) { + if (stack_size == board->width * board->height) { + return true; + } + + int x = stack_size % board->width; + int y = stack_size / board->width; + + if (get_cell(board, x, y) != CELL_EMPTY) { + stack[stack_size].x = x; + stack[stack_size].y = y; + stack[stack_size].center_index = get_cell(board, x, y) - 1; + return backtrack(board, stack, stack_size + 1); + } + + for (int i = 0; i < board->center_count; i++) { + if (is_valid_assignment(board, x, y, i)) { + set_cell(board, x, y, i + 1); + stack[stack_size].x = x; + stack[stack_size].y = y; + stack[stack_size].center_index = i; + + if (backtrack(board, stack, stack_size + 1)) { + return true; + } + + set_cell(board, x, y, CELL_EMPTY); + } + } + + return false; +} + +bool solve_puzzle(GameBoard *board) { + CellAssignment *stack = malloc(board->width * board->height * sizeof(CellAssignment)); + if (!stack) return false; + + bool result = backtrack(board, stack, 0); + + free(stack); + return result; +} \ No newline at end of file diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..573e275 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,38 @@ +#include +#include "../include/utils.h" +#include "../include/generator.h" // add you are GameBoard definition +static unsigned int next = 1; + +void init_random(unsigned int seed) { + next = seed; +} + +int random_int(int min, int max) { + next = next * 1103515245 + 12345; + return min + (int)((next / 65536) % (max - min + 1)); +} + +void set_cell(struct GameBoard *board, uint8_t x, uint8_t y, uint8_t value) { + board->cells[y * board->width + x] = value; +} + +uint8_t get_cell(const struct GameBoard *board, uint8_t x, uint8_t y) { + return board->cells[y * board->width + x]; +} + +int is_valid_coord(const struct GameBoard *board, int8_t x, int8_t y) { + return x >= 0 && x < board->width && y >= 0 && y < board->height; +} + +Point get_symmetric_point(const struct GameBoard *board, Point center, Point p) { + (void)board; // ado you are suppressed + Point sym; + sym.x = 2 * center.x - p.x; + sym.y = 2 * center.y - p.y; + return sym; +} + +int is_symmetric(const struct GameBoard *board, Point center, Point p1, Point p2) { + Point sym = get_symmetric_point(board, center, p1); + return sym.x == p2.x && sym.y == p2.y; +} \ No newline at end of file