| @@ -0,0 +1,281 @@ | ||
| #include "turtle.h" | ||
|
|
||
| #define min(X, Y) ((X) < (Y) ? (X) : (Y)) | ||
| #define max(X, Y) ((X) > (Y) ? (X) : (Y)) | ||
|
|
||
| void turtle_init(scene_turtle_t *st) { | ||
| scene_turtle_t t = { .fence = { .x1 = 0, .y1 = 0, .x2 = 3, .y2 = 63 }, | ||
| .mode = TURTLE_BUMP, | ||
| .heading = 180, | ||
| .speed = 100, | ||
| .stepped = false, | ||
| .script_number = TEMP_SCRIPT }; | ||
| memcpy(st, &t, sizeof(t)); | ||
| turtle_set_x(st, 0); | ||
| turtle_set_y(st, 0); | ||
| st->last = st->position; | ||
| } | ||
|
|
||
| typedef struct { | ||
| QT x1, y1, x2, y2; | ||
| } Q_fence_t; | ||
|
|
||
| static inline Q_fence_t normalize_fence(turtle_fence_t in, turtle_mode_t mode) { | ||
| Q_fence_t out; | ||
|
|
||
| if (mode == TURTLE_WRAP) { | ||
| out.x1 = TO_Q(in.x1); | ||
| out.x2 = TO_Q((in.x2 + 1)); | ||
| out.y1 = TO_Q(in.y1); | ||
| out.y2 = TO_Q((in.y2 + 1)); | ||
| } | ||
| else { | ||
| out.x1 = TO_Q(in.x1) + (Q_1 >> 1); | ||
| out.x2 = TO_Q((in.x2 + 1)) - (Q_1 >> 1); | ||
| out.y1 = TO_Q(in.y1) + (Q_1 >> 1); | ||
| out.y2 = TO_Q((in.y2 + 1)) - (Q_1 >> 1); | ||
| } | ||
|
|
||
| return out; | ||
| } | ||
|
|
||
| void turtle_check_step(scene_turtle_t *t) { | ||
| turtle_position_t here; | ||
| turtle_resolve_position(t, &t->position, &here); | ||
| if (here.x != t->last.x || here.y != t->last.y) { | ||
| t->last = here; | ||
| t->stepped = true; | ||
| } | ||
| } | ||
|
|
||
| void turtle_normalize_position(scene_turtle_t *t, turtle_position_t *tp, | ||
| turtle_mode_t mode) { | ||
| Q_fence_t f = normalize_fence(t->fence, mode); | ||
|
|
||
| QT fxl = f.x2 - f.x1; | ||
| QT fyl = f.y2 - f.y1; | ||
|
|
||
| if (mode == TURTLE_WRAP) { | ||
| if (fxl > Q_1 && tp->x < f.x1) | ||
| tp->x = f.x2 + ((tp->x - f.x1) % fxl); | ||
| else if (fxl > Q_1 && tp->x > f.x2) | ||
| tp->x = f.x1 + ((tp->x - f.x1) % fxl); | ||
|
|
||
| if (fyl > Q_1 && tp->y < f.y1) | ||
| tp->y = f.y2 + ((tp->y - f.y1) % fyl); | ||
| else if (fyl > Q_1 && tp->y > f.y2) | ||
| tp->y = f.y1 + ((tp->y - f.y1) % fyl); | ||
| } | ||
| else if (mode == TURTLE_BOUNCE) { | ||
| // pretty sure you can do this with a % but I couldn't get it right | ||
| // what with the edge bounceback effectively changing the length | ||
| // so here's a crappy while() loop wavefolder. --burnsauce / sliderule | ||
| turtle_position_t last, here; | ||
| turtle_resolve_position(t, &t->position, &last); | ||
| while (tp->x > f.x2 || tp->x < f.x1) { | ||
| if (tp->x > f.x2) { // right fence | ||
| if (t->stepping) turtle_set_heading(t, 360 - t->heading); | ||
| // right extent minus how far past we are | ||
| tp->x = f.x2 - (tp->x - f.x2); | ||
| } | ||
| else if (tp->x < f.x1) { // left fence | ||
| if (t->stepping) turtle_set_heading(t, 360 - t->heading); | ||
| // left extent minus how far past we are | ||
| tp->x = f.x1 + (f.x1 - tp->x); | ||
| } | ||
| turtle_resolve_position(t, &t->position, &here); | ||
| if (here.x == last.x) break; | ||
| last = here; | ||
| } | ||
| while (tp->y > f.y2 || tp->y < f.y1) { | ||
| if (tp->y >= f.y2) { // top fence | ||
| if (t->stepping) turtle_set_heading(t, 180 - t->heading); | ||
| tp->y = f.y2 - (tp->y - f.y2); | ||
| } | ||
| else if (tp->y < f.y1) { // bottom fence | ||
| if (t->stepping) turtle_set_heading(t, 180 - t->heading); | ||
| tp->y = f.y1 + (f.y1 - tp->y); | ||
| } | ||
| turtle_resolve_position(t, &t->position, &here); | ||
| if (here.y == last.y) break; | ||
| last = here; | ||
| } | ||
| if (tp->x == f.x2) tp->x -= 1; | ||
| if (tp->y == f.y2) tp->y -= 1; | ||
| } | ||
| // either mode is TURTLE_BUMP or something above is broken | ||
| // both cases call for constraining the turtle to the fence | ||
| tp->x = min(f.x2 - 1, max(f.x1, tp->x)); | ||
| tp->y = min(f.y2 - 1, max(f.y1, tp->y)); | ||
| turtle_check_step(t); | ||
| } | ||
|
|
||
| // Produce Q0 positions in dst | ||
| void turtle_resolve_position(scene_turtle_t *t, turtle_position_t *src, | ||
| turtle_position_t *dst) { | ||
| dst->x = TO_I(src->x); | ||
| dst->y = TO_I(src->y); | ||
| } | ||
|
|
||
| uint8_t turtle_get_x(scene_turtle_t *st) { | ||
| turtle_position_t t; | ||
| turtle_resolve_position(st, &st->position, &t); | ||
| return t.x; | ||
| } | ||
|
|
||
| void turtle_set_x(scene_turtle_t *st, int16_t x) { | ||
| st->position.x = TO_Q(x) + Q_05; // standing in the middle of the cell | ||
| turtle_normalize_position(st, &st->position, TURTLE_BUMP); | ||
| } | ||
|
|
||
| uint8_t turtle_get_y(scene_turtle_t *st) { | ||
| turtle_position_t t; | ||
| turtle_resolve_position(st, &st->position, &t); | ||
| return t.y; | ||
| } | ||
|
|
||
| void turtle_set_y(scene_turtle_t *st, int16_t y) { | ||
| st->position.y = TO_Q(y) + Q_05; | ||
| turtle_normalize_position(st, &st->position, TURTLE_BUMP); | ||
| } | ||
|
|
||
| void turtle_move(scene_turtle_t *st, int16_t x, int16_t y) { | ||
| st->position.y += TO_Q(y); | ||
| st->position.x += TO_Q(x); | ||
| turtle_normalize_position(st, &st->position, st->mode); | ||
| } | ||
|
|
||
| /// http://www.coranac.com/2009/07/sines/ | ||
| /// A sine approximation via a third-order approx. | ||
| /// @param x Angle (with 2^15 units/circle) | ||
| /// @return Sine value (Q12) | ||
| static inline int32_t _sin(int32_t x) { | ||
| // S(x) = x * ( (3<<p) - (x*x>>r) ) >> s | ||
| // n : Q-pos for quarter circle 13 | ||
| // A : Q-pos for output 12 | ||
| // p : Q-pos for parentheses intermediate 15 | ||
| // r = 2n-p 11 | ||
| // s = A-1-p-n 17 | ||
|
|
||
| // int qN = 13, qA = 12, qP = 15, qR = 2 * qN - qP, qS = qN + qP + 1 - qA; | ||
| static const int qN = 13, qP = 15, qR = 11, qS = 17; | ||
|
|
||
| x = x << (30 - qN); // shift to full s32 range (Q13->Q30) | ||
|
|
||
| if ((x ^ (x << 1)) < 0) // test for quadrant 1 or 2 | ||
| x = (1 << 31) - x; | ||
|
|
||
| x = x >> (30 - qN); | ||
|
|
||
| return x * ((3 << qP) - (x * x >> qR)) >> qS; | ||
| } | ||
|
|
||
| void turtle_step(scene_turtle_t *st) { | ||
| // watch out, it's a doozie ;) | ||
| QT dx = 0, dy = 0; | ||
| QT h1 = st->heading, h2 = st->heading; | ||
|
|
||
| h1 = ((h1 % 360) << 15) / 360; | ||
| h2 = (((h2 + 360 - 90) % 360) << 15) / 360; | ||
|
|
||
| int32_t dx_d_Q12 = (st->speed * _sin(h1)) / 100; | ||
| int32_t dy_d_Q12 = (st->speed * _sin(h2)) / 100; | ||
|
|
||
|
|
||
| if (dx_d_Q12 < 0) | ||
| // delta x = round(v *sin(heading)) | ||
| dx = ((dx_d_Q12 >> (11 - Q_BITS)) - 1) >> 1; | ||
| else | ||
| dx = ((dx_d_Q12 >> (11 - Q_BITS)) + 1) >> 1; | ||
| if (dy_d_Q12 < 0) | ||
| dy = ((dy_d_Q12 >> (11 - Q_BITS)) - 1) >> 1; | ||
| else | ||
| dy = ((dy_d_Q12 >> (11 - Q_BITS)) + 1) >> 1; | ||
|
|
||
|
|
||
| st->position.x += dx; | ||
| st->position.y += dy; | ||
| st->stepping = true; | ||
| turtle_normalize_position(st, &st->position, st->mode); | ||
| st->stepping = false; | ||
| } | ||
|
|
||
| inline void turtle_correct_fence(scene_turtle_t *st) { | ||
| int16_t t; | ||
| st->fence.x1 = min(3, max(0, st->fence.x1)); | ||
| st->fence.x2 = min(3, max(0, st->fence.x2)); | ||
| st->fence.y1 = min(63, max(0, st->fence.y1)); | ||
| st->fence.y2 = min(63, max(0, st->fence.y2)); | ||
|
|
||
| if (st->fence.x1 > st->fence.x2) { | ||
| t = st->fence.x2; | ||
| st->fence.x2 = st->fence.x1; | ||
| st->fence.x1 = t; | ||
| } | ||
| if (st->fence.y1 > st->fence.y2) { | ||
| t = st->fence.y2; | ||
| st->fence.y2 = st->fence.y1; | ||
| st->fence.y1 = t; | ||
| } | ||
| turtle_normalize_position(st, &st->position, TURTLE_BUMP); | ||
| } | ||
|
|
||
| void turtle_set_fence(scene_turtle_t *st, int16_t x1, int16_t y1, int16_t x2, | ||
| int16_t y2) { | ||
| st->fence.x1 = min(3, max(0, x1)); | ||
| st->fence.x2 = min(3, max(0, x2)); | ||
| st->fence.y1 = min(63, max(0, y1)); | ||
| st->fence.y2 = min(63, max(0, y2)); | ||
| turtle_correct_fence(st); | ||
| } | ||
|
|
||
| turtle_mode_t turtle_get_mode(scene_turtle_t *st) { | ||
| return st->mode; | ||
| } | ||
|
|
||
| void turtle_set_mode(scene_turtle_t *st, turtle_mode_t m) { | ||
| if (m != TURTLE_WRAP && m != TURTLE_BUMP && m != TURTLE_BOUNCE) | ||
| m = TURTLE_BUMP; | ||
| st->mode = m; | ||
| turtle_normalize_position(st, &st->position, m); | ||
| } | ||
|
|
||
| uint16_t turtle_get_heading(scene_turtle_t *st) { | ||
| return st->heading; | ||
| } | ||
|
|
||
| void turtle_set_heading(scene_turtle_t *st, int16_t h) { | ||
| while (h < 0) h += 360; | ||
| st->heading = h % 360; | ||
| } | ||
|
|
||
| int16_t turtle_get_speed(scene_turtle_t *st) { | ||
| return st->speed; | ||
| } | ||
|
|
||
| void turtle_set_speed(scene_turtle_t *st, int16_t v) { | ||
| st->speed = v; | ||
| } | ||
|
|
||
| void turtle_set_script(scene_turtle_t *st, script_number_t sn) { | ||
| if (sn >= METRO_SCRIPT) | ||
| st->script_number = TEMP_SCRIPT; | ||
| else | ||
| st->script_number = sn; | ||
| st->stepped = false; | ||
| } | ||
|
|
||
| script_number_t turtle_get_script(scene_turtle_t *st) { | ||
| return st->script_number; | ||
| } | ||
|
|
||
| bool turtle_get_shown(scene_turtle_t *st) { | ||
| return st->shown; | ||
| } | ||
|
|
||
| void turtle_set_shown(scene_turtle_t *st, bool shown) { | ||
| st->shown = shown; | ||
| } | ||
|
|
||
| #undef min | ||
| #undef max |
| @@ -0,0 +1,99 @@ | ||
| #ifndef _TURTLE_H_ | ||
| #define _TURTLE_H_ | ||
| #include <stdbool.h> | ||
| #include <stddef.h> | ||
| #include <stdint.h> | ||
| #include <string.h> | ||
|
|
||
| // In this code, we use signed fixed-point maths. This means that the int16_t | ||
| // is used to store a signed number with both an integer and a fraction part. | ||
| // | ||
| // Q_BITS represents how many are used for the fractional component. Because | ||
| // we work with signed integers, 1 bit facilitates the sign component. | ||
| // Therefore, INT_BITS = 16 - Q_BITS - 1 | ||
| // | ||
| // The main thing we do in fixed-point is keep a fractional index into the | ||
| // pattern data, so it will be our main reference point. | ||
| // | ||
| // 63 is 6 bits, so Q_BITS = 16 - 6 - 1 = 9 | ||
| // The notation for this system is Q6.9 | ||
|
|
||
| #define Q_BITS 9 | ||
| #define Q_1 (1 << Q_BITS) // 1.0 | ||
| #define Q_05 (1 << (Q_BITS - 1)) // 0.5 | ||
| #define Q_ROUND(X) \ | ||
| ((((X >> (Q_BITS - 1)) + 1) >> 1) << Q_BITS) // (int)(X + 0.5) | ||
| #define QT int32_t | ||
| #define TO_Q(X) (X << Q_BITS) | ||
| #define TO_I(X) ((X >> Q_BITS) & 0xFFFF) | ||
|
|
||
| // Can't include state.h first, so put script_number_t here | ||
| // really should be in a different header file? | ||
|
|
||
| typedef enum { | ||
| TT_SCRIPT_1 = 0, | ||
| TT_SCRIPT_2, | ||
| TT_SCRIPT_3, | ||
| TT_SCRIPT_4, | ||
| TT_SCRIPT_5, | ||
| TT_SCRIPT_6, | ||
| TT_SCRIPT_7, | ||
| TT_SCRIPT_8, | ||
| METRO_SCRIPT, | ||
| INIT_SCRIPT, | ||
| TEMP_SCRIPT | ||
| } script_number_t; | ||
|
|
||
| typedef struct { | ||
| int32_t x; // higher resolution to permit fixed-point math | ||
| int32_t y; | ||
| } turtle_position_t; | ||
|
|
||
| typedef struct { | ||
| uint8_t x1; | ||
| uint8_t y1; | ||
| uint8_t x2; | ||
| uint8_t y2; | ||
| } turtle_fence_t; | ||
|
|
||
| typedef enum { TURTLE_WRAP, TURTLE_BUMP, TURTLE_BOUNCE } turtle_mode_t; | ||
|
|
||
| typedef struct { | ||
| turtle_position_t position; | ||
| turtle_position_t last; | ||
| turtle_fence_t fence; | ||
| turtle_mode_t mode; | ||
| uint16_t heading; | ||
| int16_t speed; | ||
| script_number_t script_number; | ||
| bool stepping; | ||
| bool stepped; | ||
| bool shown; | ||
| } scene_turtle_t; | ||
|
|
||
| void turtle_init(scene_turtle_t*); | ||
| void turtle_normalize_position(scene_turtle_t*, turtle_position_t*, | ||
| turtle_mode_t); | ||
| void turtle_resolve_position(scene_turtle_t*, turtle_position_t*, | ||
| turtle_position_t*); | ||
| uint8_t turtle_get_x(scene_turtle_t*); | ||
| void turtle_set_x(scene_turtle_t*, int16_t); | ||
| uint8_t turtle_get_y(scene_turtle_t*); | ||
| void turtle_set_y(scene_turtle_t*, int16_t); | ||
| void turtle_move(scene_turtle_t*, int16_t, int16_t); | ||
| void turtle_step(scene_turtle_t*); | ||
| turtle_fence_t* turtle_get_fence(scene_turtle_t*); | ||
| void turtle_correct_fence(scene_turtle_t*); | ||
| void turtle_set_fence(scene_turtle_t*, int16_t, int16_t, int16_t, int16_t); | ||
| turtle_mode_t turtle_get_mode(scene_turtle_t*); | ||
| void turtle_set_mode(scene_turtle_t*, turtle_mode_t); | ||
| uint16_t turtle_get_heading(scene_turtle_t*); | ||
| void turtle_set_heading(scene_turtle_t*, int16_t); | ||
| int16_t turtle_get_speed(scene_turtle_t*); | ||
| void turtle_set_speed(scene_turtle_t*, int16_t); | ||
| script_number_t turtle_get_script(scene_turtle_t*); | ||
| void turtle_set_script(scene_turtle_t*, script_number_t); | ||
| void turtle_check_step(scene_turtle_t*); | ||
| bool turtle_get_shown(scene_turtle_t*); | ||
| void turtle_set_shown(scene_turtle_t*, bool); | ||
| #endif |
| @@ -0,0 +1,95 @@ | ||
| #include <stdarg.h> | ||
| #include <stdbool.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| #include "log.h" | ||
|
|
||
| static inline log_t *get_log() { | ||
| static log_t log; | ||
| return &log; | ||
| } | ||
|
|
||
| #define L get_log() | ||
|
|
||
| void log_init() { | ||
| L->size = LOG_SIZE; | ||
| L->buf = calloc(LOG_SIZE, sizeof(char)); | ||
| L->end = L->buf + LOG_SIZE; | ||
| } | ||
|
|
||
| void log_grow() { | ||
| char *new = calloc(L->size * 2, sizeof(char)); | ||
| memcpy(new, L->buf, L->size); | ||
| free(L->buf); | ||
| L->buf = new; | ||
| L->size *= 2; | ||
| L->end = L->buf + L->size; | ||
| } | ||
|
|
||
| log_position_t *log_seek() { | ||
| log_position_t *here = get_log()->buf; | ||
| log_position_t *last = here; | ||
|
|
||
| while (*here != 0) { | ||
| last = here; | ||
| here += *here + 1; | ||
| } | ||
| return last; | ||
| } | ||
|
|
||
|
|
||
| log_position_t *log_seek_next() { | ||
| log_position_t *here = get_log()->buf; | ||
|
|
||
| while (*here != 0) here += *here + 1; | ||
| return here; | ||
| } | ||
|
|
||
| bool catting = false; | ||
|
|
||
| void lprintf(const char *format, ...) { | ||
| log_t *log = L; | ||
| va_list arg; | ||
| char buf[LOG_LINE_SIZE]; | ||
| va_start(arg, format); | ||
| size_t len = vsnprintf(buf, LOG_LINE_SIZE, format, arg); | ||
| va_end(arg); | ||
| log_position_t *here = log_seek_next(); | ||
| if (here + len + 2 > log->end) log_grow(); | ||
| here = log_seek_next(); | ||
| strcpy(here + 1, buf); | ||
| *here = len + 1; | ||
| catting = false; | ||
| } | ||
|
|
||
| void lcat(const char *str) { | ||
| log_position_t *(*seek)(); // Function ponters: useful! | ||
| if (catting) | ||
| seek = log_seek; | ||
| else | ||
| seek = log_seek_next; | ||
| log_position_t *here = seek(); | ||
| if (here + strlen(str) + 1 > L->end) { | ||
| log_grow(); | ||
| here = seek(); | ||
| } | ||
|
|
||
| char *new = strcat(here + 1, str); | ||
| *here = strlen(new) + 1; | ||
| catting = true; | ||
| } | ||
|
|
||
| void log_clear() { | ||
| memset(L->buf, 0, L->size); | ||
| } | ||
|
|
||
| void log_print() { | ||
| puts("\n"); // two newlines, actually. | ||
| log_position_t *here = get_log()->buf; | ||
| while (*here != 0) { | ||
| puts(here + 1); | ||
| here += *here + 1; | ||
| } | ||
| } |
| @@ -0,0 +1,22 @@ | ||
| #ifndef _LOG_H | ||
| #define _LOG_H | ||
|
|
||
| #define LOG_SIZE 2048 | ||
| #define LOG_LINE_SIZE 255 | ||
|
|
||
| typedef char log_position_t; | ||
|
|
||
| typedef struct { | ||
| char *buf; | ||
| char *end; | ||
| size_t size; | ||
| } log_t; | ||
|
|
||
|
|
||
| void log_init(void); | ||
| void lprintf(const char *, ...); | ||
| void lcat(const char *); | ||
| void log_print(void); | ||
| void log_clear(void); | ||
|
|
||
| #endif |
| @@ -0,0 +1,302 @@ | ||
| #include "turtle_tests.h" | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <unistd.h> // ssize_t | ||
|
|
||
| #include "greatest/greatest.h" | ||
|
|
||
| #include "log.h" | ||
| #include "teletype.h" | ||
|
|
||
| int32_t count = 0; | ||
|
|
||
| #define NEWTEST() count = 0; | ||
|
|
||
| static const char *error_message(error_t e) { | ||
| static const char *em[] = { "E_OK", | ||
| "E_PARSE", | ||
| "E_LENGTH", | ||
| "E_NEED_PARAMS", | ||
| "E_EXTRA_PARAMS", | ||
| "E_NO_MOD_HERE", | ||
| "E_MANY_PRE_SEP", | ||
| "E_NEED_PRE_SEP", | ||
| "E_PLACE_PRE_SEP", | ||
| "E_NO_SUB_SEP_IN_PRE", | ||
| "E_NOT_LEFT", | ||
| "E_NEED_SPACE_PRE_SEP", | ||
| "E_NEED_SPACE_SUB_SEP" }; | ||
| return em[e]; | ||
| } | ||
|
|
||
| // runs multiple lines of commands and then asserts that the final answer is | ||
| // correct (allows contiuation of state) | ||
| TEST process_helper_state(scene_state_t *ss, size_t n, char *lines[], | ||
| int16_t answer) { | ||
| count++; | ||
| process_result_t result = { .has_value = false, .value = 0 }; | ||
| exec_state_t es; | ||
| memset(&es, 0, sizeof(es)); | ||
| es_init(&es); | ||
| es_push(&es); | ||
| es_variables(&es)->script_number = 1; | ||
|
|
||
| log_clear(); | ||
| lprintf("---- Test #%d ----", count); | ||
| lcat("Command: "); | ||
| // Format multi-line commands into one line | ||
| for (size_t i = 0; i < n; i++) { | ||
| lcat(lines[i]); | ||
| if (i < n - 1) lcat(" | "); | ||
| } | ||
|
|
||
| for (size_t i = 0; i < n; i++) { | ||
| tele_command_t cmd; | ||
| char error_msg[TELE_ERROR_MSG_LENGTH]; | ||
| error_t error = parse(lines[i], &cmd, error_msg); | ||
| if (error != E_OK) { | ||
| lprintf("%s: %s", error_message(error), error_msg); | ||
| log_print(); | ||
| FAILm("Parser failure."); | ||
| } | ||
| error = validate(&cmd, error_msg); | ||
| if (error != E_OK) { | ||
| lprintf("%s: %s", error_message(error), error_msg); | ||
| log_print(); | ||
| FAILm("Validation failure"); | ||
| } | ||
| result = process_command(ss, &es, &cmd); | ||
| } | ||
|
|
||
| if (result.has_value != true) { | ||
| lprintf("Expected a value, found none."); | ||
| log_print(); | ||
| FAIL(); | ||
| } | ||
| if (result.value != answer) { | ||
| lprintf("Value incorrect. Expected %d, got %d", answer, result.value); | ||
| log_print(); | ||
| FAIL(); | ||
| } | ||
|
|
||
| PASS(); | ||
| } | ||
|
|
||
| // runs multiple lines of commands and then asserts that the final answer is | ||
| // correct | ||
| TEST process_helper(size_t n, char *lines[], int16_t answer) { | ||
| scene_state_t ss; | ||
| memset(&ss, 0, sizeof(ss)); | ||
| ss_init(&ss); | ||
|
|
||
| CHECK_CALL(process_helper_state(&ss, n, lines, answer)); | ||
|
|
||
| PASS(); | ||
| } | ||
|
|
||
| TEST test_turtle_fence_normal() { | ||
| NEWTEST(); | ||
| char *test1[2] = { "@F 1 2 3 4", "@FX1" }; | ||
| CHECK_CALL(process_helper(2, test1, 1)); | ||
| char *test2[2] = { "@F 1 2 3 4", "@FY1" }; | ||
| CHECK_CALL(process_helper(2, test2, 2)); | ||
| char *test3[2] = { "@F 1 2 3 4", "@FX2" }; | ||
| CHECK_CALL(process_helper(2, test3, 3)); | ||
| char *test4[2] = { "@F 1 2 3 4", "@FY2" }; | ||
| CHECK_CALL(process_helper(2, test4, 4)); | ||
| PASS(); | ||
| } | ||
| TEST test_turtle_fence_swapped() { | ||
| NEWTEST(); | ||
| char *test1[2] = { "@F 3 4 1 2", "@FX1" }; | ||
| CHECK_CALL(process_helper(2, test1, 1)); | ||
| char *test2[2] = { "@F 3 4 1 2", "@FY1" }; | ||
| CHECK_CALL(process_helper(2, test2, 2)); | ||
| char *test3[2] = { "@F 3 4 1 2", "@FX2" }; | ||
| CHECK_CALL(process_helper(2, test3, 3)); | ||
| char *test4[2] = { "@F 3 4 1 2", "@FY2" }; | ||
| CHECK_CALL(process_helper(2, test4, 4)); | ||
| PASS(); | ||
| } | ||
| TEST test_turtle_fence_oob() { | ||
| NEWTEST(); | ||
| char *test1[2] = { "@F -1 -1 4 100", "@FX1" }; | ||
| CHECK_CALL(process_helper(2, test1, 0)); | ||
| char *test2[2] = { "@F -1 -1 4 100", "@FY1" }; | ||
| CHECK_CALL(process_helper(2, test2, 0)); | ||
| char *test3[2] = { "@F -1 -1 4 100", "@FX2" }; | ||
| CHECK_CALL(process_helper(2, test3, 3)); | ||
| char *test4[2] = { "@F -1 -1 4 100", "@FY2" }; | ||
| CHECK_CALL(process_helper(2, test4, 63)); | ||
| PASS(); | ||
| } | ||
| TEST test_turtle_fence_individual() { | ||
| NEWTEST(); | ||
| char *test1[2] = { "@FX1 1", "@FX1" }; | ||
| CHECK_CALL(process_helper(2, test1, 1)); | ||
| char *test2[2] = { "@FY1 1", "@FY1" }; | ||
| CHECK_CALL(process_helper(2, test2, 1)); | ||
| char *test3[2] = { "@FX2 1", "@FX2" }; | ||
| CHECK_CALL(process_helper(2, test3, 1)); | ||
| char *test4[2] = { "@FY2 1", "@FY2" }; | ||
| CHECK_CALL(process_helper(2, test4, 1)); | ||
| PASS(); | ||
| } | ||
| TEST test_turtle_fence_ind_swapped() { | ||
| NEWTEST(); | ||
| char *test1[3] = { "@FX1 1", "@FX2 0", "@FX1" }; | ||
| CHECK_CALL(process_helper(3, test1, 0)); | ||
| char *test2[3] = { "@FY1 1", "@FY2 0", "@FY1" }; | ||
| CHECK_CALL(process_helper(3, test2, 0)); | ||
| char *test3[3] = { "@FX1 1", "@FX2 0", "@FX2" }; | ||
| CHECK_CALL(process_helper(3, test3, 1)); | ||
| char *test4[3] = { "@FY1 1", "@FY2 0", "@FY2" }; | ||
| CHECK_CALL(process_helper(3, test4, 1)); | ||
| PASS(); | ||
| } | ||
| TEST test_turtle_fence_ind_oob() { | ||
| NEWTEST(); | ||
| char *test1[2] = { "@FX1 -1", "@FX1" }; | ||
| CHECK_CALL(process_helper(2, test1, 0)); | ||
| char *test2[2] = { "@FY1 -1", "@FY1" }; | ||
| CHECK_CALL(process_helper(2, test2, 0)); | ||
| char *test3[2] = { "@FX2 4", "@FX2" }; | ||
| CHECK_CALL(process_helper(2, test3, 3)); | ||
| char *test4[2] = { "@FY2 63", "@FY2" }; | ||
| CHECK_CALL(process_helper(2, test4, 63)); | ||
| // TODO more tests | ||
| PASS(); | ||
| } | ||
|
|
||
| TEST test_turtle_wrap() { | ||
| NEWTEST(); | ||
| char *test1[3] = { "@WRAP 1", "@MOVE 0 -1", "@Y" }; | ||
| CHECK_CALL(process_helper(3, test1, 63)); | ||
| char *test1b[3] = { "@WRAP 1", "@MOVE 0 -1", "@X" }; | ||
| CHECK_CALL(process_helper(3, test1b, 0)); | ||
| char *test1c[3] = { "@WRAP 1", "@F 0 0 1 1; @MOVE 0 3", "@Y" }; | ||
| CHECK_CALL(process_helper(3, test1c, 1)); | ||
| char *test1d[3] = { "@WRAP 1", "@F 0 0 1 1; @MOVE 0 3", "@X" }; | ||
| CHECK_CALL(process_helper(3, test1d, 0)); | ||
| char *test1e[3] = { "@WRAP 1", "@STEP", "@X" }; | ||
| CHECK_CALL(process_helper(3, test1e, 0)); | ||
| char *test2[4] = { "@WRAP 1", "@FY2 1", "@MOVE 0 -1", "@Y" }; | ||
| CHECK_CALL(process_helper(4, test2, 1)); | ||
| char *test2b[4] = { "@WRAP 1", "@FY2 1", "@MOVE 0 -1", "@X" }; | ||
| CHECK_CALL(process_helper(4, test2b, 0)); | ||
| char *test3[3] = { "@WRAP 1", "@MOVE -1 0", "@X" }; | ||
| CHECK_CALL(process_helper(3, test3, 3)); | ||
| char *test4[4] = { "@WRAP 1", "@FX1 1", "@MOVE -1 0", "@X" }; | ||
| CHECK_CALL(process_helper(4, test4, 3)); | ||
| PASS(); | ||
| } | ||
|
|
||
| TEST test_turtle_bounce() { | ||
| NEWTEST(); | ||
| char *test1[3] = { "@BOUNCE 1", "@MOVE 0 -2", "@Y" }; | ||
| CHECK_CALL(process_helper(3, test1, 2)); | ||
| char *test2[3] = { "@BOUNCE 1", "@MOVE 0 64", "@Y" }; | ||
| CHECK_CALL(process_helper(3, test2, 62)); | ||
| char *test3[3] = { "@BOUNCE 1", "@MOVE -1 0", "@X" }; | ||
| CHECK_CALL(process_helper(3, test3, 1)); | ||
| char *test4[3] = { "@BOUNCE 1", "@MOVE -2 0", "@X" }; | ||
| CHECK_CALL(process_helper(3, test4, 2)); | ||
| char *test5[3] = { "@BOUNCE 1", "@MOVE 4 0", "@X" }; | ||
| CHECK_CALL(process_helper(3, test5, 2)); | ||
| char *test6[3] = { "@BOUNCE 1", "@MOVE 0 -1", "@Y" }; | ||
| CHECK_CALL(process_helper(3, test6, 1)); | ||
| char *test7[3] = { "@BOUNCE 1", "@MOVE 0 126", "@Y" }; | ||
| CHECK_CALL(process_helper(3, test7, 0)); | ||
| char *test8[3] = { "@BOUNCE 1", "@MOVE 0 130", "@Y" }; | ||
| CHECK_CALL(process_helper(3, test8, 4)); | ||
| char *test9[3] = { "@BOUNCE 1", "@MOVE 3 0", "@X" }; | ||
| CHECK_CALL(process_helper(3, test9, 3)); | ||
| char *test10[4] = { "@BOUNCE 1", "@F 0 0 1 1", "@STEP", "@Y" }; | ||
| CHECK_CALL(process_helper(4, test10, 1)); | ||
| char *test10b[4] = { "@BOUNCE 1", "@F 0 0 1 1", "@STEP", "@DIR" }; | ||
| CHECK_CALL(process_helper(4, test10b, 180)); | ||
|
|
||
| // The following tests reveal the charade that is the length between fences | ||
| #if 0 | ||
| char *test10c[4] = { "@BOUNCE 1", "@F 0 0 1 1", "L 1 2: @STEP", "@DIR" }; | ||
| CHECK_CALL(process_helper(4, test10c, 0)); | ||
| char *test10d[4] = { "@BOUNCE 1", "@F 0 0 1 1", "L 1 3: @STEP", "@SPEED" }; | ||
| CHECK_CALL(process_helper(4, test10d, 100)); | ||
| char *test13[5] = { | ||
| "@F 0 0 1 1", | ||
| "@BOUNCE 1", | ||
| "@SPEED 300", | ||
| "@STEP", | ||
| "@DIR" | ||
| }; | ||
| CHECK_CALL(process_helper(5, test13, 0)); | ||
| char *test14[6] = { | ||
| "@F 0 0 1 1", | ||
| "@BOUNCE 1", | ||
| "@SPEED 400", | ||
| "@DIR 135", | ||
| "@STEP", | ||
| "@DIR" | ||
| }; | ||
| CHECK_CALL(process_helper(6, test14, 315)); | ||
| char *test15[6] = { | ||
| "@F 0 0 1 1", | ||
| "@BOUNCE 1", | ||
| "@SPEED 141", | ||
| "@DIR 135", | ||
| "@STEP", | ||
| "@Y" | ||
| }; | ||
| CHECK_CALL(process_helper(6, test15, 1)); | ||
| #endif | ||
|
|
||
| char *test12[4] = { "@BOUNCE 1; @SPEED 141", "@DIR 135", "@STEP", "@X" }; | ||
| CHECK_CALL(process_helper(4, test12, 1)); | ||
| PASS(); | ||
| } | ||
|
|
||
| /* | ||
| TEST test_turtle_F() { | ||
| char *testX[Y] = { | ||
| "@", | ||
| "@", | ||
| "@", | ||
| "@", | ||
| "@", | ||
| "@" | ||
| }; | ||
| CHECK_CALL(process_helper(Y, testX, xpct)); | ||
| PASS(); | ||
| } | ||
| */ | ||
|
|
||
| TEST test_turtle_vars() { | ||
| NEWTEST(); | ||
| char *test1[2] = { "@X 1", "@X" }; | ||
| CHECK_CALL(process_helper(2, test1, 1)); | ||
| PASS(); | ||
| } | ||
|
|
||
| TEST test_turtle_step() { | ||
| char *test1[2] = { "@STEP", "@Y" }; | ||
| CHECK_CALL(process_helper(2, test1, 1)); | ||
| char *test2[2] = { "@STEP", "@X" }; | ||
| CHECK_CALL(process_helper(2, test2, 0)); | ||
| PASS(); | ||
| } | ||
|
|
||
| SUITE(turtle_suite) { | ||
| log_init(); | ||
| RUN_TEST(test_turtle_fence_normal); | ||
| RUN_TEST(test_turtle_fence_swapped); | ||
| RUN_TEST(test_turtle_fence_oob); | ||
| RUN_TEST(test_turtle_fence_individual); | ||
| RUN_TEST(test_turtle_fence_ind_swapped); | ||
| RUN_TEST(test_turtle_fence_ind_oob); | ||
| RUN_TEST(test_turtle_wrap); | ||
| RUN_TEST(test_turtle_bounce); | ||
| RUN_TEST(test_turtle_vars); | ||
| RUN_TEST(test_turtle_step); | ||
| } |
| @@ -0,0 +1,8 @@ | ||
| #ifndef _OP_TURTLE_TESTS_H_ | ||
| #define _OP_TURTLE_TESTS_H_ | ||
|
|
||
| #include "greatest/greatest.h" | ||
|
|
||
| SUITE_EXTERN(turtle_suite); | ||
|
|
||
| #endif |