Version 1.0

This commit is contained in:
scriptod 2024-06-26 21:19:47 +03:00
parent a49a69acd5
commit 74ca7d42aa
10 changed files with 544 additions and 0 deletions

36
Makefile Normal file
View File

@ -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

23
include/generator.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef GENERATOR_H
#define GENERATOR_H
#include <stdint.h>
#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

10
include/output.h Normal file
View File

@ -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

9
include/solver.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef SOLVER_H
#define SOLVER_H
#include <stdbool.h>
#include "generator.h"
bool solve_puzzle(GameBoard *board);
#endif // SOLVER_H

24
include/utils.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef UTILS_H
#define UTILS_H
#include <stdint.h>
// 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

139
src/generator.c Normal file
View File

@ -0,0 +1,139 @@
#include <stdlib.h>
#include <string.h>
#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--;
}
}
}

72
src/main.c Normal file
View File

@ -0,0 +1,72 @@
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/stat.h>
#include <errno.h>
#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;
}

123
src/output.c Normal file
View File

@ -0,0 +1,123 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#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);
}

70
src/solver.c Normal file
View File

@ -0,0 +1,70 @@
#include <stdbool.h>
#include <stdlib.h>
#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;
}

38
src/utils.c Normal file
View File

@ -0,0 +1,38 @@
#include <stdlib.h>
#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;
}