commit 95bf722c1e9de3ff9bceb0788dd3d73d0459620d Author: night0721 Date: Fri Oct 4 00:39:12 2024 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a663e10 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vtr +*.o +*.tar.gz diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f9856d5 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +.POSIX: +.SUFFIXES: + +CC = cc +VERSION = 1.0 +TARGET = vtr +MANPAGE = $(TARGET).1 +PREFIX ?= /usr/local +BINDIR = $(PREFIX)/bin +MANDIR = $(PREFIX)/share/man/man1 + +# Flags +CFLAGS = -O3 -march=native -mtune=native -pipe -s -std=c99 -pedantic -Wall -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -Iinclude + +SRC = src/*.c + +$(TARGET): $(SRC) + $(CC) $(SRC) -o $@ $(CFLAGS) $(LDFLAGS) + +dist: + mkdir -p $(TARGET)-$(VERSION) + cp -R README.md $(MANPAGE) $(TARGET) $(TARGET)-$(VERSION) + tar -cf $(TARGET)-$(VERSION).tar $(TARGET)-$(VERSION) + gzip $(TARGET)-$(VERSION).tar + rm -rf $(TARGET)-$(VERSION) + +install: $(TARGET) + mkdir -p $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(MANDIR) + cp -p $(TARGET) $(DESTDIR)$(BINDIR)/$(TARGET) + chmod 755 $(DESTDIR)$(BINDIR)/$(TARGET) + cp -p $(MANPAGE) $(DESTDIR)$(MANDIR)/$(MANPAGE) + chmod 644 $(DESTDIR)$(MANDIR)/$(MANPAGE) + +uninstall: + $(RM) $(DESTDIR)$(BINDIR)/$(TARGET) + $(RM) $(DESTDIR)$(MANDIR)/$(MANPAGE) + +clean: + $(RM) $(TARGET) + +all: $(TARGET) + +.PHONY: all dist install uninstall clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..135dfcc --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# vtr +vtr is a 2D game aimed to recreate Skyblock. + +# Usage +``` +./vtr +``` + +# Building +You will need to run these with elevated privilages. +``` +$ make +# make install +``` + +# Contributions +Contributions are welcomed, feel free to open a pull request. + +# License +This project is licensed under the BSD-3-Clause License. See [LICENSE](https://github.com/night0721/vtr/blob/master/LICENSE) for more information. diff --git a/src/vtr.c b/src/vtr.c new file mode 100644 index 0000000..85b8606 --- /dev/null +++ b/src/vtr.c @@ -0,0 +1,287 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MONSTER_COUNT 2 + +typedef struct { + int wood; + int stone; + int dirt; +} inventory_t; + +typedef struct { + int max_y; + int max_x; + int monster_x[MONSTER_COUNT]; + int monster_y[MONSTER_COUNT]; + char **grid; +} world_t; + +typedef struct { + int y; + int x; + int health; + int kills; + inventory_t inv; +} player_t; + +world_t world; +player_t player; + +char *statusline; +pthread_mutex_t world_lock; + +void initialize_world() +{ + for (int i = 0; i < world.max_y; i++) { + for (int j = 0; j < world.max_x; j++) { + int rand_value = rand() % 100; + if (rand_value < 1) { + /* 1% chance for a tree */ + world.grid[i][j] = 'T'; + } else if (rand_value < 2) { + /* 1% chance for stone */ + world.grid[i][j] = 'S'; + } else if (rand_value < 3) { + /* 1% chance for dirt */ + world.grid[i][j] = 'D'; + } else { + /* Empty space */ + world.grid[i][j] = ' '; + } + } + } + + player.y = world.max_y / 2; + player.x = world.max_x / 2; + /* Initialise player position */ + world.grid[player.y][player.x] = 'P'; + + /* Initialize monsters */ + for (int i = 0; i < MONSTER_COUNT; i++) { + world.monster_x[i] = rand() % world.max_x; + world.monster_y[i] = rand() % world.max_y; + /* Monster position */ + world.grid[world.monster_y[i]][world.monster_x[i]] = 'M'; + } +} + +void print_world() +{ + pthread_mutex_lock(&world_lock); + printf("\033[H"); + for (int i = world.max_y - 1; i >= 0; i--) { + for (int j = 0; j < world.max_x; j++) { + printf("%c", world.grid[i][j]); + } + printf("\n"); + } + printf("Health: %d Kills: %d Inventory: %d Dirt %d Wood %d Stone \n", player.health, player.kills, player.inv.dirt, player.inv.wood, player.inv.stone); + printf("%s", statusline); + fflush(stdout); + pthread_mutex_unlock(&world_lock); +} + +void gather_resources() +{ + int y = player.y; + int x = player.x; + + /* Check surrounding blocks for resources */ + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + /* Skip the player's current position */ + if (dy == 0 && dx == 0) continue; + + int ny = y + dy; + int nx = x + dx; + + /* Check bounds */ + if (ny >= 0 && ny < world.max_y && nx >= 0 && nx < world.max_x) { + char tile = world.grid[ny][nx]; + if (tile == 'T') { + player.inv.wood += 1; + world.grid[ny][nx] = ' '; + } else if (tile == 'S') { + player.inv.stone += 1; + world.grid[ny][nx] = ' '; + } else if (tile == 'D') { + player.inv.dirt += 1; + world.grid[ny][nx] = ' '; + } + } + } + } +} + +void *clear_status_thread(void *arg) +{ + /* Wait for 2 seconds */ + sleep(2); + pthread_mutex_lock(&world_lock); + /* Clear the status line */ + strcpy(statusline, ""); + pthread_mutex_unlock(&world_lock); + return NULL; +} + +void set_status(char *new_status) +{ + pthread_mutex_lock(&world_lock); + /* Set the new status */ + strcpy(statusline, new_status); + pthread_mutex_unlock(&world_lock); + + pthread_t status_thread; + /* Start a thread to clear status asynchronously */ + pthread_create(&status_thread, NULL, clear_status_thread, NULL); + /* Detach thread so it cleans up itself after execution */ + pthread_detach(status_thread); +} + +void move_player(char direction) +{ + int new_x = player.x; + int new_y = player.y; + + /* Determine the target position based on the direction */ + switch (direction) { + case 'w': + new_y = (player.y < world.max_y - 1) ? player.y + 1 : player.y; + break; + case 's': + new_y = (player.y > 0) ? player.y - 1 : player.y; + break; + case 'a': + new_x = (player.x > 0) ? player.x - 1 : player.x; + break; + case 'd': + new_x = (player.x < world.max_x - 1) ? player.x + 1 : player.x; + break; + default: + break; + } + + switch (world.grid[new_y][new_x]) { + case 'M': + set_status("There's a monster in the way!"); + return; + case 'T': + set_status("There's a tree in the way!"); + return; + case 'S': + set_status("There's a stone in the way!"); + return; + case 'D': + set_status("There's a dirt in the way!"); + return; + } + + /* Clear current player position */ + world.grid[player.y][player.x] = ' '; + + /* Update player position */ + player.x = new_x; + player.y = new_y; + world.grid[player.y][player.x] = 'P'; +} + +void move_monsters() +{ + for (int i = 0; i < MONSTER_COUNT; i++) { + /* Clear current monster position */ + world.grid[world.monster_y[i]][world.monster_x[i]] = ' '; + + /* Random movement for monsters */ + world.monster_x[i] = (world.monster_x[i] + (rand() % 3 - 1) + world.max_x) % world.max_x; + world.monster_y[i] = (world.monster_y[i] + (rand() % 3 - 1) + world.max_y) % world.max_y; + + /* Update monster position */ + world.grid[world.monster_y[i]][world.monster_x[i]] = 'M'; + } +} + +void *monster_movement(void *arg) +{ + while (1) { + move_monsters(); + print_world(); + usleep(500000); + } + return NULL; +} + +void *player_input(void *arg) +{ + struct termios oldt, newt; + + /* Setup non-blocking input */ + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + /* Disable canonical mode and echo */ + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + + while (1) { + char ch = getchar(); + if (ch == 'q') { + break; + } else if (ch == 'g') { + gather_resources(); + } else { + move_player(ch); + } + + print_world(); + } + + /* Restore old terminal settings */ + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + return NULL; +} + +int main() +{ + srand(time(NULL)); + pthread_t monster_thread, input_thread; + + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + int lines = w.ws_row; + int columns = w.ws_col; + + world.max_x = columns; + world.max_y = lines - 2; + world.grid = malloc(world.max_y * sizeof(char *)); + + char status[columns]; + memset(status, 0, columns); + statusline = status; + + for (int i = 0; i < world.max_y; i++) { + world.grid[i] = malloc(columns); + } + + /* Hide cursor */ + printf("\033[?25l"); + pthread_mutex_init(&world_lock, NULL); + initialize_world(); + print_world(); + + /* Create threads for monster movement and player input */ + pthread_create(&monster_thread, NULL, monster_movement, NULL); + pthread_create(&input_thread, NULL, player_input, NULL); + + /* Wait for threads to finish */ + pthread_join(input_thread, NULL); + pthread_join(monster_thread, NULL); + + return 0; +}