Skip to content

Instantly share code, notes, and snippets.

@schwalbe-t
Created August 27, 2025 16:10
Show Gist options
  • Save schwalbe-t/280d2878f4bcb94def32ac132a151bda to your computer and use it in GitHub Desktop.
Save schwalbe-t/280d2878f4bcb94def32ac132a151bda to your computer and use it in GitHub Desktop.
C terminal ray caster
// Compile using any C compiler (except MSVC):
//
// cc raycast.c -lm -O3 -o raycast
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#endif
#ifdef _WIN32
static DWORD og_stdin_flags;
#else
static struct termios og_stdin_flags;
#endif
void term_deinit(void) {
#ifdef _WIN32
HANDLE in_h = GetStdHandle(STD_INPUT_HANDLE);
SetConsoleMode(in_h, og_stdin_flags);
#else
tcsetattr(STDIN_FILENO, TCSANOW, &og_stdin_flags);
#endif
}
void term_init(void) {
#ifdef _WIN32
DWORD mode;
HANDLE in_h = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(in_h, &og_stdin_flags);
GetConsoleMode(in_h, &mode);
mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
mode |= ENABLE_PROCESSED_INPUT;
if(!SetConsoleMode(in_h, mode)) {
fprintf(stderr, "Failed to configure terminal input");
abort();
}
#else
if(tcgetattr(STDIN_FILENO, &og_stdin_flags) == -1) {
fprintf(stderr, "Failed to configure terminal input");
abort();
}
struct termios stdin_flags = og_stdin_flags;
stdin_flags.c_lflag &= ~(ICANON | ECHO);
stdin_flags.c_cc[VMIN] = 1;
stdin_flags.c_cc[VTIME] = 0;
if(tcsetattr(STDIN_FILENO, TCSANOW, &stdin_flags) == -1) {
fprintf(stderr, "Failed to configure terminal input");
abort();
}
#endif
setvbuf(stdin, NULL, _IONBF, 0);
atexit(&term_deinit);
}
void get_term_size(unsigned int *columns, unsigned int *rows) {
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO t_info;
HANDLE out_h = GetStdHandle(STD_OUTPUT_HANDLE);
if(!GetConsoleScreenBufferInfo(out_h, &t_info)) {
fprintf(stderr, "Failed to get terminal dimensions");
abort();
}
*columns = t_info.srWindow.Right - t_info.srWindow.Left + 1;
*rows = t_info.srWindow.Bottom - t_info.srWindow.Top + 1;
#else
struct winsize t_size;
if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &t_size) == -1) {
fprintf(stderr, "Failed to get terminal dimensions");
abort();
}
*colums = t_size.ws_col;
*rows = t_size.ws_row;
#endif
}
void term_reset(void) {
#ifdef _WIN32
HANDLE out_h = GetStdHandle(STD_OUTPUT_HANDLE);
COORD home_coords = { 0, 0 };
SetConsoleCursorPosition(out_h, home_coords);
#else
printf("\033[H");
#endif
}
#define WORLD_WIDTH 16
#define WORLD_HEIGHT 16
static const char world_data[WORLD_WIDTH * WORLD_HEIGHT] = {
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
};
char world_data_at(double x, double y) {
unsigned int tx = x < 0.0? 0 : x > WORLD_WIDTH? 0 : (unsigned int) x;
unsigned int ty = y < 0.0? 0 : x > WORLD_HEIGHT? 0 : (unsigned int) y;
return world_data[tx + ty * WORLD_WIDTH];
}
#define TAU 6.2831853071
#define FOV TAU / 3.0
#define RAY_STEP 0.01
#define MAX_RAY_DEPTH 8.0
#define MIN_WALL_DIST 0.5
double cast_ray(double sx, double sy, double a) {
double dx = cos(a) * RAY_STEP;
double dy = sin(a) * RAY_STEP;
double x = sx;
double y = sy;
double d = 0.0;
for(;;) {
char tile = world_data_at(x, y);
if(tile != 0) { break; }
if(d >= MAX_RAY_DEPTH) { break; }
x += dx;
y += dy;
d += RAY_STEP;
}
return d;
}
#define DEPTH_COLORS " .,:-+*#&@"
#define DEPTH_COLOR_C 10
int main(void) {
term_init();
double player_x = WORLD_WIDTH / 2;
double player_y = WORLD_HEIGHT / 2;
double player_a = 0.0;
for(;;) {
int c = getchar();
switch(c) {
case 'a':
player_a += TAU / 12.0; // 30 degrees counter-clockwise
break;
case 'd':
player_a -= TAU / 12.0; // 30 degrees clockwise
break;
case 'w': {
double d = cast_ray(player_x, player_y, player_a);
d -= MIN_WALL_DIST;
if(d < 0.0) { d = 0.0; }
if(d > 1.0) { d = 1.0; }
player_x += cos(player_a) * d;
player_y += sin(player_a) * d;
break;
}
case 's': {
double d = cast_ray(player_x, player_y, player_a + TAU / 2.0);
d -= MIN_WALL_DIST;
if(d < 0.0) { d = 0.0; }
if(d > 1.0) { d = 1.0; }
player_x -= cos(player_a) * d;
player_y -= sin(player_a) * d;
break;
}
}
unsigned int columns, rows;
get_term_size(&columns, &rows);
columns -= 1; // for new lines
double ray_depths[columns];
double ray_base_a = player_a + FOV / 2.0;
for(unsigned int column = 0; column < columns; column += 1) {
double ray_a = ray_base_a
- (double) column / (double) columns * FOV;
ray_depths[column] = cast_ray(player_x, player_y, ray_a);
}
term_reset();
for(unsigned int row = 0; row < rows - 1; row += 1) {
for(unsigned int column = 0; column < columns; column += 1) {
double depth = ray_depths[column];
if(depth < 1.0) { depth = 1.0; }
int wall_h = (int) ((double) rows / depth);
int wall_rs = (rows - wall_h) / 2;
if(row < wall_rs || row > wall_rs + wall_h) {
putchar(' ');
continue;
}
int c_idx = (int) ((1.0 - depth / MAX_RAY_DEPTH) * DEPTH_COLOR_C);
if(c_idx < 0) { c_idx = 0; }
if(c_idx >= DEPTH_COLOR_C) { c_idx = DEPTH_COLOR_C - 1; }
char c = DEPTH_COLORS[c_idx];
putchar(c);
}
putchar('\n');
}
printf("x: %f\ty: %f\tangle: %f", player_x, player_y, player_a / TAU * 360.0);
fflush(stdout);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment