Created
August 27, 2025 16:10
-
-
Save schwalbe-t/280d2878f4bcb94def32ac132a151bda to your computer and use it in GitHub Desktop.
C terminal ray caster
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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