Files
exercism/c/two-bucket/two_bucket.c

229 lines
6.6 KiB
C

#include "two_bucket.h"
#include <stdlib.h>
/* We will choose to use a board of (X,Y) squares, with the sizes of the 2
* buckets (in fact +1, as buckets can have values 0...max), where we move
* the current point at each step.
* Current square (x,y) is enough to describe the status of the
* 2 buckets: b1 has x liters, b2 has y liters.
* At square (x, y), a maximum of 6 moves are possible :
* - (0, y) = empty b1
* - (X, y) = fill b1
* - (x, 0) = empty b2
* - (x, Y) = fill b2
* - (x - d, y + d) = b1 -> b2, with d = min(Y-y, x)
* - (x + d, y - d) = b2 -> b1, with d = min(X-x, y)
* The board keeps tracks of visited squares: We cannot pass twice the
* same one.
* We will use BFS algorithm from initial square, which is (0, Y) or (X, 0).
* We stop when x or y are equal to goal, or when no moves are possible.
*
*/
/* The board contains a positive integer for its current move, otherwise the
* following values
*/
#define AVAILABLE 0
#define FORBIDDEN (-1)
#define GOAL (-2)
/* from (col, row) to real array position (square), and vice-et-versa.
* X is column size.
*/
#define SQUARE(col, row, board) ((row) * (board->X) + (col))
#define COL(square, board) ((square) % (board->X))
#define ROW(square, board) ((square) / (board->X))
/* danger zone !! - this macro is BAD™ in general, dangerous double
* evaluation, but ok here.
* If unsure why this is very bad, leave me a comment on exercism.
*/
#define MIN(x, y) ((x) <= (y)? (x): (y))
/* a queue for moves (BFS). I use a max X*Y array, a linked list would
* be much better, would the buckets sizes being extremely huge.
*/
struct stack {
int first;
int last;
int s[];
};
/* our game board
*/
struct board {
int X;
int Y;
int board[];
};
static inline void board_init(struct board *board, int X, int Y, int goal)
{
int i;
board->X=X;
board->Y=Y;
for (i=0; i < board->X*board->Y; ++i)
*(board->board+i) = AVAILABLE;
/* set target on goal row */
if (board->X > goal) {
for (i = 0; i < board->Y; ++i)
board->board[SQUARE(goal, i, board)] = GOAL;
}
/* set target on goal col */
if (board->Y > goal) {
for (i = 0; i < board->X; ++i)
board->board[SQUARE(i, goal, board)] = GOAL;
}
}
static inline void enqueue(struct stack *s, int v)
{
s->s[s->last++] = v;
}
static inline int dequeue(struct stack *s)
{
return s->first < s->last ? s->s[s->first++] : -1;
}
static int move(struct board *board, struct stack *stack, int col, int row,
int level)
{
board->board[SQUARE(col, row, board)] = level;
enqueue (stack, SQUARE(col, row, board));
return 0;
}
static int move_maybe(struct board *board, struct stack *stack,
int col, int row, int level)
{
switch (board->board[SQUARE(col, row, board)]) {
case GOAL:
return 1;
case AVAILABLE:
return move(board, stack, col, row, level);
default:
return 0;
}
}
static bucket_result_t board_bfs(struct board *board, struct stack *stack,
const int goal)
{
int level = 0;
int cur_square;
int min, row, col, col1, row1;
int X=board->X, Y=board->Y;
bucket_result_t res = { .possible = false, .move_count = 0 };
/* now we consume as we can... */
while ((cur_square = dequeue(stack)) >= 0) {
col=COL(cur_square, board);
row=ROW(cur_square, board);
level=board->board[cur_square];
if (col > 0) {
col1 = 0;
row1 = row;
if (move_maybe(board, stack, col1, row1, level+1))
goto found;
}
if (row > 0) {
col1 = col;
row1 = 0;
if (move_maybe(board, stack, col1, row1, level+1))
goto found;
}
if (col < X-1) {
col1 = X-1;
row1 = row;
if (move_maybe(board, stack, col1, row1, level+1))
goto found;
}
if (row < Y-1) {
col1 = col;
row1 = Y-1;
if (move_maybe(board, stack, col1, row1, level+1))
goto found;
}
if (col > 0 && row < Y-1) {
min = MIN(col, Y-row-1);
col1 = col - min;
row1 = row + min;
if (move_maybe(board, stack, col1, row1, level+1))
goto found;
}
if (row > 0 && col < X-1) {
min = MIN(row, X-col-1);
col1 = col + min;
row1 = row - min;
if (move_maybe(board, stack, col1, row1, level+1))
goto found;
}
}
goto end; /* fail: no more moves */
found:
res.possible = true;
res.move_count = level+1;
if (col1 == goal) {
res.goal_bucket=BUCKET_ID_1;
res.other_bucket_liters=row1;
} else {
res.goal_bucket=BUCKET_ID_2;
res.other_bucket_liters=col1;
}
end:
return res;
}
bucket_result_t measure(const bucket_liters_t b1,
const bucket_liters_t b2,
const bucket_liters_t goal,
const bucket_id_t start)
{
bucket_result_t res = {
.possible = false,
.move_count = 0,
.other_bucket_liters = 0
};
struct board *board;
struct stack *stack;
/* impossible goal */
if (goal > b1 && goal > b2)
return res;
/* initial move is the solution ? */
if ((start == BUCKET_ID_1 && goal == b1) ||
(start == BUCKET_ID_2 && goal == b2)) {
res.possible = true;
res.move_count = 1;
res.goal_bucket = start;
return res;
}
if (!(stack = malloc(sizeof(*stack) + sizeof(*stack->s)*(b1+1)*(b2+1))))
return res;
//int i;
stack->first = stack->last = 0;
if (!(board = malloc(sizeof(*board) + sizeof(*board->board)*(b1+1)*(b2+1)))) {
free(stack);
return res;
}
board_init(board, b1+1, b2+1, goal);
/* only for exercism. better solutions could be found. Remove the next
* 3 lines to allow a different starting bucket (possibly better solution).
*/
board->board[SQUARE(0, 0, board)] = FORBIDDEN;
board->board[SQUARE(0, b2, board)] = FORBIDDEN;
board->board[SQUARE(b1, 0, board)] = FORBIDDEN;
/* initial move */
move(board, stack, start==BUCKET_ID_1? b1: 0, start==BUCKET_ID_2? b2: 0, 1);
res = board_bfs(board, stack, goal);
free(stack);
free(board);
return res;
}