From 3d6a6142bd5783e233f5ccb8ce1cb17a29a8ac68 Mon Sep 17 00:00:00 2001 From: Andrew Haynes Date: Tue, 3 Mar 2026 13:06:45 -0500 Subject: [PATCH] converting to c --- .clang-format | 19 +++++ CMakeLists.txt | 12 ++- README.md | 32 -------- include/engine.h | 62 ++++++++++++++++ include/engine.hpp | 48 ------------ src/engine.c | 180 +++++++++++++++++++++++++++++++++++++++++++++ src/engine.cpp | 103 -------------------------- src/main.c | 42 +++++++++++ src/main.cpp | 7 -- 9 files changed, 308 insertions(+), 197 deletions(-) create mode 100644 .clang-format delete mode 100644 README.md create mode 100644 include/engine.h delete mode 100644 include/engine.hpp create mode 100644 src/engine.c delete mode 100644 src/engine.cpp create mode 100644 src/main.c delete mode 100644 src/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..369e701 --- /dev/null +++ b/.clang-format @@ -0,0 +1,19 @@ +--- +BasedOnStyle: Google +Standard: Latest +UseTab: Always +TabWidth: 8 +IndentWidth: 8 +BreakBeforeBraces: Allman +SpaceBeforeCpp11BracedList: true +SeparateDefinitionBlocks: Always +Cpp11BracedListStyle: false +BreakAfterReturnType: TopLevel +AlignTrailingComments: + Kind: Always +ReflowComments: Always +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: AllIfsAndElse +AllowShortLoopsOnASingleLine: true +IndentAccessModifiers: false # Indent access specifiers by one level +AccessModifierOffset: -4 # Optional: adjust offset relative to the class indentation level diff --git a/CMakeLists.txt b/CMakeLists.txt index 98793ba..616bcc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,13 @@ cmake_minimum_required(VERSION 3.16) -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_C_STANDARD 23) +set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -project(snake) +project(snake LANGUAGES C) set(SOURCES - src/main.cpp - src/engine.cpp + src/main.c + src/engine.c ) add_executable(snake ${SOURCES}) target_include_directories(snake PRIVATE include) -find_package(Curses REQUIRED) -target_link_libraries(snake PRIVATE ${CURSES_LIBRARIES}) diff --git a/README.md b/README.md deleted file mode 100644 index c4d08da..0000000 --- a/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Snake Game - -A simple terminal-based snake game written in C++. - -## Build - -To build the game, run the following commands: - -```bash -cmake -S . -B build && cmake --build build -``` - -## How to Run -After building the project, you can run the game with the following command: -```bash -./build/snake -``` - -## How to Play -- Use the `w`, `a`, `s`, and `d` keys to control the snake. - - `w`: move up - - `a`: move left - - `s`: move down - - `d`: move right -- The game ends if you collide with yourself. -- Press `q` to quit the game at any time. - -## About - -- This is a simple implementation of the classic Snake game. -- It is written in C++23. -- It uses the `ncurses` library to render the game in the terminal. diff --git a/include/engine.h b/include/engine.h new file mode 100644 index 0000000..ff54e74 --- /dev/null +++ b/include/engine.h @@ -0,0 +1,62 @@ +#ifndef ENGINE_HPP +#define ENGINE_HPP + +#include +#define ROW 15 +#define COL 21 + +typedef enum +{ + UP, + DOWN, + LEFT, + RIGHT +} Direction; + +typedef struct +{ + int x, y; +} vec2; + +int +equals(vec2* a, vec2* b); + +void +set(vec2* vec2, int _x, int _y); + +typedef struct +{ + unsigned char view[ROW][COL]; + vec2 head; + vec2 fruit; + vec2 prev_head; + int m_score; + Direction dir; + vec2* tail[(ROW * COL) - 1]; + int tail_size; + +} Engine; + +void +engine_init(Engine* engine); + +void +engine_display(Engine* engine); + +int +process_input(Direction* dir); + +int +is_open(char* status, size_t len, Engine* engine); + +void +determine_index_zero_pos(Engine* engine); + +void +add_tail_and_respawn_fruit(Engine* engine); + +void +move_tail_indicies(vec2** tail, int tail_size, vec2* prev_head, + unsigned char (*view)[COL]); + +#endif diff --git a/include/engine.hpp b/include/engine.hpp deleted file mode 100644 index 8a55027..0000000 --- a/include/engine.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -constexpr int ROW = 15; -constexpr int COL = 21; - -enum class Direction { - UP, DOWN, LEFT, RIGHT -}; - -struct vec2 { - int x, y; - vec2(int x, int y) : x(x), y(y) {} - void set(int _x, int _y) { x = _x; y = _y; } - auto operator <=> (const vec2&) const = default; -}; - -struct Window { - std::array, ROW> view; - void draw_display(); - void update_display(const vec2 fruit, std::deque &buf); -}; - -class Engine { -public: - Engine(); - ~Engine() { endwin(); } - void run(); - -private: - Window win; - std::deque tail; - std::string status; - vec2 head { COL / 2, ROW / 2 }; - vec2 fruit { rand() % COL, rand() % ROW }; - vec2 prev_head { head.x, head.y }; - Direction dir = Direction::RIGHT; - unsigned int m_score = 0; - bool process_input(); - void determine_index_zero_pos(); - void move_tail_indicies(); - void add_tail_and_respawn_fruit(); - bool exit_summary(); -}; diff --git a/src/engine.c b/src/engine.c new file mode 100644 index 0000000..a42be51 --- /dev/null +++ b/src/engine.c @@ -0,0 +1,180 @@ +#include "engine.h" + +#include +#include +#include +#include +#include +#include + +void +set(vec2* vec2, int _x, int _y) +{ + vec2->x = _x; + vec2->y = _y; +} + +int +equals(vec2* a, vec2* b) +{ + return (a->x == b->x && a->y == b->y); +} + +void +engine_init(Engine* engine) +{ + srand(time(0)); + set(&engine->head, COL / 2, ROW / 2); + set(&engine->prev_head, engine->head.x, engine->head.y); + set(&engine->fruit, rand() % COL, rand() % ROW); + + engine->dir = RIGHT; + engine->m_score = 0; + engine->tail_size = 0; + memset(engine->view, 0, sizeof(engine->view)); + + for (unsigned char i = 0; i < ROW; i++) + { + for (unsigned char j = 0; j < COL; j++) + { + engine->view[i][j] = '.'; + } + } +} + +void +engine_display(Engine* engine) +{ + engine->view[engine->fruit.y][engine->fruit.x] = 'o'; + if (engine->tail_size != 0) + { + for (char i = 0; i < engine->tail_size; i++) + { + if (engine->tail[i]->x < COL && + engine->tail[i]->y < ROW) + { + engine->view[engine->tail[i]->y] + [engine->tail[i]->x] = '~'; + } + } + } + write(1, "\033[H\033[J", sizeof("\033[H\033[J") - 1); + for (unsigned char i = 0; i < ROW; i++) + { + for (unsigned char j = 0; j < COL; j++) + { + write(1, &engine->view[i][j], 1); + write(1, " ", 1); + } + write(1, "\r\n", 2); + } +} + +int +process_input(Direction* dir) +{ + unsigned char ch; + if (read(1, &ch, 1) > 0) + { + if (ch == 'q') return false; + if (ch == 'w') + if (*dir != DOWN) *dir = UP; + if (ch == 's') + if (*dir != UP) *dir = DOWN; + if (ch == 'd') + if (*dir != LEFT) *dir = RIGHT; + if (ch == 'a') + if (*dir != RIGHT) *dir = LEFT; + } + return 1; +} + +int +is_open(char* status, size_t len, Engine* engine) +{ + if ((sizeof(engine->tail) / sizeof(engine->tail[0])) >= ROW * COL) + { + snprintf(status, len, + "You filled the whole board and won! " + "Score: %d", + engine->m_score); + return 0; + } + for (int i = 0; i < engine->tail_size; i++) + { + if (equals(engine->tail[i], &engine->head)) + { + snprintf(status, len, + "You collided with yourself and died! " + "Score: %d", + engine->m_score); + return 0; + } + } + return 1; +} + +void +determine_index_zero_pos(Engine* engine) +{ + set(&engine->prev_head, engine->head.x, engine->head.y); + + if (engine->dir == LEFT) + engine->head.x = (engine->head.x + COL - 1) % COL; + else if (engine->dir == RIGHT) + engine->head.x = (engine->head.x + 1) % COL; + else if (engine->dir == UP) + engine->head.y = (engine->head.y + ROW - 1) % ROW; + else if (engine->dir == DOWN) + engine->head.y = (engine->head.y + 1) % ROW; + engine->view[engine->prev_head.y][engine->prev_head.x] = '.'; + engine->view[engine->head.y][engine->head.x] = '*'; +} + +void +move_tail_indicies(vec2** tail, int tail_size, vec2* prev_head, + unsigned char (*view)[COL]) +{ + if (tail_size == 0) return; + + view[tail[tail_size - 1]->y][tail[tail_size - 1]->x] = '.'; + for (int i = tail_size - 1; i > 0; i--) + { + set(tail[i], tail[i - 1]->x, tail[i - 1]->y); + } + set(tail[0], prev_head->x, prev_head->y); +} + +void +add_tail_and_respawn_fruit(Engine* engine) +{ + if (!equals(&engine->fruit, &engine->head)) return; + vec2 new_pos; + set(&new_pos, 0, 0); + int collision = 0; + do + { + collision = 0; + set(&new_pos, (rand() % COL), (rand() % ROW)); + if (equals(&new_pos, &engine->head)) + { + collision = 1; + break; + } + if (engine->tail_size > 0) + { + for (int i = 0; i < engine->tail_size; i++) + { + if (equals(&new_pos, engine->tail[i])) + { + collision = 1; + break; + } + } + } + } while (collision); + engine->fruit = new_pos; + engine->tail[engine->tail_size] = (vec2*)malloc(sizeof(vec2)); + engine->tail_size++; + engine->m_score++; +} diff --git a/src/engine.cpp b/src/engine.cpp deleted file mode 100644 index 00687a5..0000000 --- a/src/engine.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -void Window::draw_display() { - for(int i : std::ranges::views::iota(0, ROW)) { - for(int j : std::ranges::views::iota(0, COL)) { - mvaddch(i, j * 2, view[i][j]); - } - } -} - -void Window::update_display(const vec2 fruit, std::deque &buf) { - view[fruit.y][fruit.x] = 'o'; - for(const auto& segment : buf) { - if(segment.x < COL && segment.y < ROW) { - view[segment.y][segment.x] = '~'; - } - } -} - -Engine::Engine() { - std::srand(std::time(0)); - initscr(); - cbreak(); - noecho(); - curs_set(0); - nodelay(stdscr, true); - for(auto& row : win.view) row.fill('.'); -} - -void Engine::run() { - while (exit_summary() && process_input()) { - add_tail_and_respawn_fruit(); - determine_index_zero_pos(); - move_tail_indicies(); - - win.update_display(fruit, tail); - win.draw_display(); - refresh(); - std::this_thread::sleep_for(std::chrono::milliseconds((int) (200))); - } - nodelay(stdscr, false); - mvprintw(ROW, 0, status.c_str()); - refresh(); - getch(); -} - -bool Engine::process_input() { - int ch; - while((ch = getch()) != ERR) { - if(ch == 'q') { return status = "Quitting", false; } - else if(ch == 'w') { if(dir != Direction::DOWN) dir = Direction::UP; } - else if(ch == 's') { if(dir != Direction::UP) dir = Direction::DOWN; } - else if(ch == 'd') { if(dir != Direction::LEFT) dir = Direction::RIGHT; } - else if(ch == 'a') { if(dir != Direction::RIGHT) dir = Direction::LEFT; } - } - return true; -} - -bool Engine::exit_summary() { - if(tail.size() >= ROW * COL) { - return status = std::format("You filled the whole board and won! Score: {}\n", m_score), false; - } - if(std::any_of(tail.begin(), tail.end(), [&](const vec2& segment) { return segment == head; })) { - return status = std::format("You collided with yourself and died! Score: {}\n", m_score), false; - } - return true; -} - -void Engine::determine_index_zero_pos() { - prev_head.set(head.x, head.y); - - if(dir == Direction::LEFT) { head.x = (head.x + COL - 1) % COL; } - else if(dir == Direction::RIGHT) { head.x = (head.x + 1) % COL; } - else if(dir == Direction::UP) { head.y = (head.y + ROW - 1) % ROW; } - else if(dir == Direction::DOWN) { head.y = (head.y + 1) % ROW; } - win.view[prev_head.y][prev_head.x] = '.'; - win.view[head.y][head.x] = '*'; -} - -void Engine::move_tail_indicies() { - if(tail.empty()) return; - vec2 last_tail = tail.back(); - tail.pop_back(); - tail.push_front(prev_head); - if(last_tail.x < COL && last_tail.y < ROW) { win.view[last_tail.y][last_tail.x] = '.'; } -} - -void Engine::add_tail_and_respawn_fruit() { - if(fruit != head) return; - vec2 new_pos { 0, 0 }; - do { new_pos.set((rand() % ROW), (rand() % COL)); } - while (new_pos == head || std::any_of(tail.begin(), tail.end(), [&](const vec2& seg) { return seg == new_pos; })); - fruit = new_pos; - tail.push_front(vec2 { ROW, COL }); - m_score++; -} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..e7c887f --- /dev/null +++ b/src/main.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include + +#include "engine.h" + +int +main() +{ + Engine engine; + engine_init(&engine); + char status[64] = { 0 }; + static struct termios oldt, newt; + struct timespec req; + + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + req.tv_nsec = 1.5e8; + req.tv_sec = 0; + fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK); + + while (is_open(status, 64, &engine) && process_input(&engine.dir)) + { + add_tail_and_respawn_fruit(&engine); + determine_index_zero_pos(&engine); + move_tail_indicies(engine.tail, engine.tail_size, &engine.prev_head, engine.view); + + engine_display(&engine); + nanosleep(&req, NULL); + } + for(int i = 0; i < engine.tail_size; i++) { + free(engine.tail[i]); + } + write(1, status, sizeof(status)); + write(1, "\033[?25h", 6); + tcsetattr(0, TCSANOW, &oldt); + return 0; +} diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index b7f50dd..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -int main () { - Engine engine; - engine.run(); - return 0; -}