
fixed a bug were stdin was set to nonblocking and never reset back to blocking. This caused STDOUT to become nonblock, and for some reason glibc cannot deal with it, but every other libc could. uh.
598 lines
15 KiB
C
598 lines
15 KiB
C
/*
|
|
* ----------------------------------------------------------------------------
|
|
* "THE BEER-WARE LICENSE" (Revision 43):
|
|
* vi@violette.town wrote this file. You can do whatever you want with this
|
|
* stuff. If we meet some day, and you think this stuff is worth it,
|
|
* you can buy me a beer in return. Violette Paulin
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <signal.h>
|
|
#include <fcntl.h>
|
|
#include <float.h>
|
|
#include "json.h"
|
|
|
|
#define SCREEN_WIDTH 80
|
|
#define SCREEN_HEIGHT 32
|
|
#define CUBE_WIDTH 10
|
|
|
|
enum faces {
|
|
FACE_FRONT = 0,
|
|
FACE_LEFT,
|
|
FACE_RIGHT,
|
|
FACE_BOTTOM,
|
|
FACE_TOP,
|
|
FACE_BACK,
|
|
NUM_FACES,
|
|
};
|
|
|
|
#define STEP 0.2
|
|
|
|
#define ACTION_STEP 0.1
|
|
|
|
#define PITCH_STEP 0.08
|
|
#define ROLL_STEP 0.05
|
|
#define YAW_STEP 0.05
|
|
|
|
#define K1 100
|
|
#define K2 50
|
|
|
|
#define SQ(n) n * n
|
|
|
|
#define GET_ROTATE_X_Q(a) ({ double _a = (a) ; struct Quaternions q = {}; q.w = cos(_a/2); q.x = sin(_a/2); q; })
|
|
#define GET_ROTATE_Y_Q(a) ({ double _a = (a) ; struct Quaternions q = {}; q.w = cos(_a/2); q.y = sin(_a/2); q; })
|
|
#define GET_ROTATE_Z_Q(a) ({ double _a = (a) ; struct Quaternions q = {}; q.w = cos(_a/2); q.z = sin(_a/2); q; })
|
|
|
|
//TODO Idle animations
|
|
#define IS_IDLE (Idle.x || Idle.y || Idle.z)
|
|
#define RESET_IDLE {Idle.x = 0; Idle.y = 0; Idle.z = 0;}
|
|
|
|
struct {
|
|
char x;
|
|
char y;
|
|
char z;
|
|
} Idle;
|
|
|
|
|
|
struct Quaternions {
|
|
double w;
|
|
double x;
|
|
double y;
|
|
double z;
|
|
} Target, Current, Last;
|
|
|
|
double interpolationStep = 0;
|
|
double zBuffer[SCREEN_HEIGHT * SCREEN_WIDTH];
|
|
char output[SCREEN_HEIGHT * SCREEN_WIDTH];
|
|
#ifdef VBUF
|
|
char buf[SCREEN_HEIGHT * SCREEN_WIDTH * 7 * 2]; // stdout buffer
|
|
#endif
|
|
|
|
static volatile char shouldBreak = 1;
|
|
static volatile char currentlyMoving = 0;
|
|
static struct termios originalTerm = {};
|
|
|
|
void
|
|
normalize(struct Quaternions *q)
|
|
{
|
|
double n = sqrt(SQ(q->w) + SQ(q->x) + SQ(q->y) + SQ(q->z));
|
|
if (n == 0)
|
|
return;
|
|
q->w /= n;
|
|
q->x /= n;
|
|
q->y /= n;
|
|
q->z /= n;
|
|
}
|
|
|
|
struct Quaternions
|
|
mult(struct Quaternions q, double x, double y, double z)
|
|
{
|
|
//p = q * p * qbar
|
|
struct Quaternions res;
|
|
|
|
res.w = 0;
|
|
res.x = x * (SQ(q.w) + SQ(q.x) - SQ(q.y) - SQ(q.z))
|
|
+ 2 * y * (q.x * q.y - q.w * q.z)
|
|
+ 2 * z * (q.x * q.z + q.w * q.y);
|
|
|
|
res.y = 2 * x * (q.x * q.y + q.w * q.z)
|
|
+ y * (SQ(q.w) - SQ(q.x) + SQ(q.y) - SQ(q.z))
|
|
+ 2 * z * (q.y * q.z - q.w * q.x);
|
|
|
|
res.z = 2 * x * (q.x * q.z - q.w * q.y)
|
|
+ 2 * y * (q.y * q.z + q.w * q.x)
|
|
+ z * (SQ(q.w) - SQ(q.x) - SQ(q.y) + SQ(q.z));
|
|
|
|
return res;
|
|
}
|
|
|
|
struct Quaternions
|
|
multQ(struct Quaternions p, struct Quaternions q)
|
|
{
|
|
if (p.x <= 0.001 && p.x >= -0.001
|
|
&& p.y <= 0.001 && p.y >= -0.001
|
|
&& p.z <= 0.001 && p.z >= -0.001)
|
|
return q;
|
|
if (q.x <= 0.001 && q.x >= -0.001
|
|
&& q.y <= 0.001 && q.y >= -0.001
|
|
&& q.z <= 0.001 && q.z >= -0.001)
|
|
return p;
|
|
|
|
struct Quaternions res = {
|
|
.w = p.w * q.w - p.x * q.x - p.y * q.y - p.z * q.z,
|
|
.x = p.w * q.x + p.x * q.w + p.y * q.z - p.z * q.y,
|
|
.y = p.w * q.y - p.x * q.z + p.y * q.w + p.z * q.x,
|
|
.z = p.w * q.z + p.x * q.y - p.y * q.x + p.z * q.w,
|
|
};
|
|
|
|
return res;
|
|
}
|
|
|
|
char *
|
|
chooseColor(char c)
|
|
{
|
|
switch (c) {
|
|
case FACE_FRONT:
|
|
return "\033[31;1;4m#\033[0m";
|
|
case FACE_BACK:
|
|
return "\033[32;1;4m0\033[0m";
|
|
case FACE_BOTTOM:
|
|
return "\033[33;1;4m!\033[0m";
|
|
case FACE_LEFT:
|
|
return "\033[34;1;4m%\033[0m";
|
|
case FACE_RIGHT:
|
|
return "\033[35;1;4m@\033[0m";
|
|
case FACE_TOP:
|
|
return "\033[36;1;4m-\033[0m";
|
|
case ' ':
|
|
return " ";
|
|
default:
|
|
//unused, but issues no warning
|
|
return "\033[0m";
|
|
}
|
|
}
|
|
|
|
char
|
|
chooseMainFace()
|
|
{
|
|
int total = 0;
|
|
int faces[NUM_FACES] = {0};
|
|
|
|
for (int k = 0; k < SCREEN_WIDTH * SCREEN_HEIGHT; ++k)
|
|
if (output[k] >= 0 && output[k] < NUM_FACES) {
|
|
faces[output[k]]++;
|
|
++total;
|
|
}
|
|
|
|
int max = 0, idx = 0;
|
|
for (int k = 0; k < NUM_FACES; ++k)
|
|
if (faces[k] > max) {
|
|
max = faces[k];
|
|
idx = k;
|
|
}
|
|
|
|
return max > total * 0.9 ? idx : -1;
|
|
}
|
|
|
|
void
|
|
printFrontFace(struct json_object_element_s *elt, int row)
|
|
{
|
|
printf("\033[%dCContacts:\r\n", row);
|
|
struct json_object_element_s *obj = json_value_as_object(elt->value)->start;
|
|
while (obj != NULL) {
|
|
struct json_string_s *str = json_value_as_string(obj->value);
|
|
printf("\033[%dC%s: %s\r\n", row + 4, obj->name->string, str->string);
|
|
obj = obj->next;
|
|
}
|
|
}
|
|
|
|
void
|
|
printLefttFace(struct json_object_element_s *elt, int row)
|
|
{
|
|
printf("\033[%dCSkills:\r\n", row);
|
|
struct json_array_element_s *arr = json_value_as_array(elt->value)->start;
|
|
while (arr != NULL) {
|
|
struct json_object_element_s *obj = json_value_as_object(arr->value)->start;
|
|
printf("\033[%dC%s\r\n", row + 4, json_value_as_string(obj->value)->string);
|
|
struct json_array_element_s *arr2 = json_value_as_array(obj->next->value)->start;
|
|
while (arr2 != NULL) {
|
|
struct json_string_s *str = json_value_as_string(arr2->value);
|
|
printf("\033[%dC%s\r\n", row + 8, str->string);
|
|
arr2 = arr2->next;
|
|
}
|
|
arr = arr->next;
|
|
}
|
|
}
|
|
|
|
void
|
|
printRightFace(struct json_object_element_s *elt, int row)
|
|
{
|
|
printf("\033[%dCLanguages:\r\n", row);
|
|
struct json_array_element_s *arr = json_value_as_array(elt->value)->start;
|
|
while (arr != NULL) {
|
|
struct json_object_element_s *obj = json_value_as_object(arr->value)->start;
|
|
printf("\033[%dC%s: %s / 5\r\n", row + 4, json_value_as_string(obj->value)->string, json_value_as_number(obj->next->value)->number);
|
|
arr = arr->next;
|
|
}
|
|
}
|
|
|
|
void
|
|
printBottomFace(struct json_object_element_s *elt, int row)
|
|
{
|
|
printf("\033[%dCProfessional experience:\r\n", row);
|
|
struct json_array_element_s *arr = json_value_as_array(elt->value)->start;
|
|
while (arr != NULL) {
|
|
struct json_object_element_s *obj = json_value_as_object(arr->value)->start;
|
|
struct json_string_s *position = json_value_as_string(obj->value);
|
|
struct json_object_element_s *company = json_value_as_object(obj->next->value)->start;
|
|
struct json_string_s *from = json_value_as_string(obj->next->next->value);
|
|
struct json_string_s *to = json_value_as_string(obj->next->next->next->value);
|
|
struct json_array_element_s *descr = json_value_as_array(obj->next->next->next->next->value)->start;
|
|
|
|
printf("\033[%dC%s: %s ; %s -> %s\r\n", row + 4, json_value_as_string(company->value)->string, position->string, from->string, to->string);
|
|
while (descr != NULL) {
|
|
printf("\033[%dC%s\r\n", row + 8, json_value_as_string(descr->value)->string);
|
|
descr = descr->next;
|
|
}
|
|
printf("\r\n");
|
|
arr = arr->next;
|
|
}
|
|
}
|
|
|
|
void
|
|
printBackFace(struct json_object_element_s *elt, int row)
|
|
{
|
|
printf("\033[%dCProjects:\r\n", row);
|
|
struct json_object_element_s *obj = json_value_as_object(elt->value)->start;
|
|
//ignore personnal projects
|
|
printf("\033[%dC- This cube! its made using Quaternion for 3d rotation, "
|
|
"from scratch (except the CV text), in C\r\n", row + 4);
|
|
printf("\r\n");
|
|
struct json_array_element_s *arr = json_value_as_array(obj->next->value)->start;
|
|
while (arr != NULL) {
|
|
struct json_string_s *main = json_value_as_string(json_value_as_object(arr->value)->start->value);
|
|
struct json_string_s *link = json_value_as_string(json_value_as_object(arr->value)->start->next->value);
|
|
struct json_array_element_s *descr = json_value_as_array(json_value_as_object(arr->value)->start->next->value)->start;
|
|
struct json_number_s *year = json_value_as_number(json_value_as_object(arr->value)->start->next->next->next->value);
|
|
|
|
printf("\033[%dC- %s, %s\r\n", row + 4, json_value_as_string(descr->value)->string, year->number);
|
|
while (descr != NULL) {
|
|
printf("\033[%dCo %s\r\n", row + 8, json_value_as_string(descr->value)->string);
|
|
descr = descr->next;
|
|
}
|
|
printf("\r\n");
|
|
arr = arr->next;
|
|
}
|
|
}
|
|
|
|
void
|
|
printTopFace(struct json_object_element_s *elt, int row)
|
|
{
|
|
printf("\033[%dCEducation:\r\n", row);
|
|
struct json_array_element_s *arr = json_value_as_array(elt->value)->start;
|
|
while (arr != NULL) {
|
|
struct json_object_element_s *obj = json_value_as_object(arr->value)->start;
|
|
struct json_string_s *univ_name =
|
|
json_value_as_string(json_value_as_object(obj->value)->start->value);
|
|
struct json_string_s *degree = json_value_as_string(obj->next->value);
|
|
struct json_string_s *major = json_value_as_string(obj->next->next->value);
|
|
struct json_string_s *track =
|
|
json_value_as_string(obj->next->next->next->value);
|
|
struct json_string_s *from =
|
|
json_value_as_string(obj->next->next->next->next->value);
|
|
struct json_string_s *to =
|
|
json_value_as_string(obj->next->next->next->next->next->value);
|
|
|
|
printf("\033[%dC%s: %s -> %s \r\n", row + 4, univ_name->string, from->string, to->string);
|
|
if (track == NULL || strcmp(track->string, "None") == 0)
|
|
printf("\033[%dC%s, %s\r\n", row + 8, degree->string, major->string);
|
|
else
|
|
printf("\033[%dC%s %s, %s\r\n", row + 8, degree->string, major->string, track->string);
|
|
printf("\r\n");
|
|
arr = arr->next;
|
|
}
|
|
}
|
|
|
|
void
|
|
printCV(char face, struct json_object_element_s *r, int row)
|
|
{
|
|
struct json_object_element_s start = *r;
|
|
struct json_object_element_s *next = &start;
|
|
|
|
char k = 0;
|
|
if (face >= 0)
|
|
for (; k < face; ++k)
|
|
next = next->next;
|
|
|
|
char *res;
|
|
switch (face) {
|
|
case FACE_FRONT:
|
|
printFrontFace(next, row);
|
|
break;
|
|
case FACE_LEFT:
|
|
printLefttFace(next, row);
|
|
break;
|
|
case FACE_RIGHT:
|
|
printRightFace(next, row);
|
|
break;
|
|
case FACE_BOTTOM:
|
|
printBottomFace(next, row);
|
|
break;
|
|
case FACE_BACK:
|
|
printBackFace(next, row);
|
|
break;
|
|
case FACE_TOP:
|
|
printTopFace(next, row);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
printAscii(struct json_value_s *json)
|
|
{
|
|
char face = chooseMainFace();
|
|
struct json_object_element_s *root = json_value_as_object(json)->start;
|
|
|
|
struct winsize w;
|
|
ioctl(0, TIOCGWINSZ, &w);
|
|
|
|
printCV(face, root, w.ws_row);
|
|
|
|
printf("\033[%d;%dH", (int)((w.ws_row - SCREEN_HEIGHT) * 1.5 / 2), (w.ws_col - SCREEN_WIDTH) / 2);
|
|
|
|
for (int k = 0; k < SCREEN_WIDTH * SCREEN_HEIGHT; ++k) {
|
|
if (k % SCREEN_WIDTH)
|
|
printf("%s", chooseColor(output[k]));
|
|
else
|
|
printf("\r\n\033[%dC", w.ws_row);
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
void
|
|
rotateCube(double cubeX, double cubeY, double cubeZ, char ch)
|
|
{
|
|
struct Quaternions q = mult(Current, cubeX, cubeY, cubeZ);
|
|
|
|
double x = q.x;
|
|
double y = q.y;
|
|
double invZ = 1 / (q.z + K2);
|
|
|
|
int screenX = (int)(SCREEN_WIDTH / 2) + floor((x) * K1) * invZ;
|
|
int screenY = (int)(SCREEN_HEIGHT / 2) + floor(((y) * K1 / 2) * invZ);
|
|
//TODO luminescence
|
|
|
|
if (screenX > SCREEN_WIDTH || screenX < 0) return;
|
|
|
|
int idx = screenY * SCREEN_WIDTH + screenX;
|
|
if (idx >= 0 && idx < SCREEN_WIDTH * SCREEN_HEIGHT)
|
|
if (zBuffer[idx] < invZ) {
|
|
zBuffer[idx] = invZ;
|
|
output[idx] = ch;
|
|
}
|
|
}
|
|
|
|
void
|
|
intHandler(int unused)
|
|
{
|
|
shouldBreak = 0;
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &originalTerm);
|
|
printf("\033[2J\033[?1049l"); // escape fullscreen mode
|
|
}
|
|
|
|
void
|
|
printBanner(char keyboardInput)
|
|
{
|
|
printf("Last input character: %c, "
|
|
"Current: w:%f x:%f y%f z:%f | "
|
|
"Last: w:%f x:%f y%f z:%f | "
|
|
"Target: w:%f x:%f y%f z:%f\r\n",
|
|
keyboardInput,
|
|
Current.w, Current.x, Current.y, Current.z,
|
|
Last.w, Last.x, Last.y, Last.z,
|
|
Target.w, Target.x, Target.y, Target.z);
|
|
}
|
|
|
|
struct Quaternions
|
|
interpolate(struct Quaternions qa, struct Quaternions qb)
|
|
{
|
|
struct Quaternions res;
|
|
double cosHalfTheta =
|
|
qa.w * qb.w +
|
|
qa.x * qb.x +
|
|
qa.y * qb.y +
|
|
qa.z * qb.z;
|
|
//if qa = qb or qa = -qb then theta = 0 and we can return qa
|
|
if (fabs(cosHalfTheta) >= 1.0) {
|
|
res.w = qa.w;
|
|
res.x = qa.x;
|
|
res.y = qa.y;
|
|
res.z = qa.z;
|
|
return res;
|
|
}
|
|
if (cosHalfTheta < 0) {
|
|
qb.w = -qb.w;
|
|
qb.x = -qb.x;
|
|
qb.y = -qb.y;
|
|
qb.z = qb.z;
|
|
cosHalfTheta = -cosHalfTheta;
|
|
}
|
|
|
|
double halfTheta = acos(cosHalfTheta);
|
|
double sinHalfTheta = sqrt(1.0 - cosHalfTheta * cosHalfTheta);
|
|
//if theta = 180 degrees then result is not fully defined
|
|
// we could rotate around any axis normal to qa or qb
|
|
if (fabs(sinHalfTheta) < 0.001) {
|
|
res.w = (qa.w * 0.5 + qb.w * 0.5);
|
|
res.x = (qa.x * 0.5 + qb.x * 0.5);
|
|
res.y = (qa.y * 0.5 + qb.y * 0.5);
|
|
res.z = (qa.z * 0.5 + qb.z * 0.5);
|
|
return res;
|
|
}
|
|
|
|
interpolationStep += ACTION_STEP;
|
|
double ratioA = sin((1 - interpolationStep) * halfTheta) / sinHalfTheta;
|
|
double ratioB = sin(interpolationStep * halfTheta) / sinHalfTheta;
|
|
|
|
res.w = (qa.w * ratioA + qb.w * ratioB);
|
|
res.x = (qa.x * ratioA + qb.x * ratioB);
|
|
res.y = (qa.y * ratioA + qb.y * ratioB);
|
|
res.z = (qa.z * ratioA + qb.z * ratioB);
|
|
|
|
return res;
|
|
}
|
|
|
|
void
|
|
handleAngle(char *input)
|
|
{
|
|
if (!currentlyMoving) {
|
|
currentlyMoving = *input;
|
|
Last = Current;
|
|
switch (*input) {
|
|
case 'w':
|
|
case 'W':
|
|
Target = multQ(GET_ROTATE_X_Q(M_PI_2), Target);
|
|
break;
|
|
case 'a':
|
|
case 'A':
|
|
Target = multQ(GET_ROTATE_Y_Q(-M_PI_2), Target);
|
|
break;
|
|
case 's':
|
|
case 'S':
|
|
Target = multQ(GET_ROTATE_X_Q(-M_PI_2), Target);
|
|
break;
|
|
case 'd':
|
|
case 'D':
|
|
Target = multQ(GET_ROTATE_Y_Q(M_PI_2), Target);
|
|
break;
|
|
case 'q':
|
|
case 'Q':
|
|
Target = multQ(GET_ROTATE_Z_Q(-M_PI_2), Target);
|
|
break;
|
|
case 'e':
|
|
case 'E':
|
|
Target = multQ(GET_ROTATE_Z_Q(M_PI_2), Target);
|
|
break;
|
|
default:
|
|
currentlyMoving = 0;
|
|
//TODO idle movement
|
|
}
|
|
normalize(&Target);
|
|
} else {
|
|
if (interpolationStep < 1 - ACTION_STEP)
|
|
Current = interpolate(Last, Target);
|
|
else {
|
|
//RESET_IDLE;
|
|
Current = Target;
|
|
*input = 0;
|
|
interpolationStep = 0;
|
|
currentlyMoving = 0;
|
|
}
|
|
normalize(&Current);
|
|
}
|
|
}
|
|
|
|
char
|
|
getInput(char *keyboardInput)
|
|
{
|
|
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
|
|
char c = getchar();
|
|
if (c == '\033') {
|
|
if ((c = getchar()) == '[')
|
|
switch (c = getchar()) {
|
|
case 'A':
|
|
c = 'W';
|
|
break;
|
|
case 'B':
|
|
c = 'S';
|
|
break;
|
|
case 'C':
|
|
c = 'D';
|
|
break;
|
|
case 'D':
|
|
c = 'A';
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (c != EOF)
|
|
*keyboardInput = c;
|
|
while ((c = getchar()) != '\n' && c != EOF) {} //clean stdin
|
|
//
|
|
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) & ~O_NONBLOCK);
|
|
handleAngle(keyboardInput);
|
|
return c;
|
|
}
|
|
|
|
struct json_value_s *
|
|
readJson()
|
|
{
|
|
int size;
|
|
FILE *fp = fopen("./cv.json", "r");
|
|
char *json;
|
|
|
|
if (fp) {
|
|
fseek(fp, 0, SEEK_END);
|
|
size = ftell(fp);
|
|
fseek(fp, 0, SEEK_SET);
|
|
|
|
json = (char *)malloc(sizeof(char) * size);
|
|
fread(json, 1, size, fp);
|
|
}
|
|
return json_parse(json, strlen(json));
|
|
}
|
|
|
|
int
|
|
main()
|
|
{
|
|
printf("\033[?1049h\033[H"); // enter fullscreen mode
|
|
char keyboardInput = 0;
|
|
signal(SIGINT, intHandler);
|
|
tcgetattr(STDIN_FILENO, &originalTerm);
|
|
|
|
struct termios t = {};
|
|
cfmakeraw(&t);
|
|
t.c_lflag |= ISIG;
|
|
t.c_cc[VINTR] = 3;
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &t);
|
|
|
|
#ifdef VBUF
|
|
setvbuf(stdout, buf, _IOFBF, sizeof(buf));
|
|
#endif
|
|
|
|
Current = GET_ROTATE_Z_Q(0);
|
|
|
|
struct json_value_s *json = readJson();
|
|
|
|
while (shouldBreak) {
|
|
#ifdef VBUF
|
|
memset(buf, ' ', sizeof(buf));
|
|
#endif
|
|
memset(output, ' ', SCREEN_WIDTH * SCREEN_HEIGHT);
|
|
memset(zBuffer, DBL_MIN, SCREEN_WIDTH * SCREEN_HEIGHT * sizeof(double));
|
|
|
|
for (double cubeX = -CUBE_WIDTH + STEP; cubeX < CUBE_WIDTH - STEP; cubeX += STEP) {
|
|
for (double cubeY = -CUBE_WIDTH + STEP; cubeY < CUBE_WIDTH - STEP; cubeY += STEP) {
|
|
rotateCube(cubeX, cubeY, -CUBE_WIDTH, FACE_FRONT);
|
|
rotateCube(cubeX, cubeY, CUBE_WIDTH, FACE_BACK);
|
|
rotateCube(CUBE_WIDTH, cubeX, cubeY, FACE_RIGHT);
|
|
rotateCube(-CUBE_WIDTH, cubeX, cubeY, FACE_LEFT);
|
|
rotateCube(cubeX, CUBE_WIDTH, cubeY, FACE_BOTTOM);
|
|
rotateCube(cubeX, -CUBE_WIDTH, cubeY, FACE_TOP);
|
|
}
|
|
}
|
|
|
|
printf("\033[1;1H\033[2J");
|
|
printAscii(json);
|
|
getInput(&keyboardInput);
|
|
usleep(1000000 / 60); // 60fps max
|
|
}
|
|
}
|