Something went wrong. Try again.
google balls
Something went wrong. Try again.
23 kB · 641 lines
C++
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641#include <iostream>#include <vector>#include <cmath>#include <string>#include <chrono>#include <thread>#include <sstream>#include <cstring>#include <map>#include <memory>#include <csignal>
#ifdef _WIN32#include <windows.h>#include <conio.h>#else#include <sys/ioctl.h>#include <termios.h>#include <unistd.h>#include <fcntl.h>#endif
// Global flag for window resizevolatile sig_atomic_t g_windowResized = 0;
#ifndef _WIN32void handleResize(int sig) { g_windowResized = 1;}#endif
struct Vector3 { double x, y, z; Vector3(double x = 0, double y = 0, double z = 0) : x(x), y(y), z(z) {}};
struct Color { uint8_t r, g, b; Color(uint8_t r = 255, uint8_t g = 255, uint8_t b = 255) : r(r), g(g), b(b) {} static Color fromHex(const std::string& hex) { if (hex[0] == '#') { unsigned int value = std::stoul(hex.substr(1), nullptr, 16); return Color((value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF); } return Color(); } std::string toAnsi() const { return "\033[38;2;" + std::to_string(r) + ";" + std::to_string(g) + ";" + std::to_string(b) + "m"; }};
class Point {public: Vector3 curPos, originalPos, targetPos, velocity; Color color; double radius, size; double friction = 0.8; double springStrength = 0.1; Point(double x, double y, double z, double size, const std::string& colorHex) : curPos(x, y, z), originalPos(x, y, z), targetPos(x, y, z), velocity(0, 0, 0), size(size), radius(size) { color = Color::fromHex(colorHex); } void update() { // X axis spring physics double dx = targetPos.x - curPos.x; double ax = dx * springStrength; velocity.x += ax; velocity.x *= friction; if (std::abs(dx) < 0.1 && std::abs(velocity.x) < 0.01) { curPos.x = targetPos.x; velocity.x = 0; } else { curPos.x += velocity.x; } // Y axis spring physics double dy = targetPos.y - curPos.y; double ay = dy * springStrength; velocity.y += ay; velocity.y *= friction; if (std::abs(dy) < 0.1 && std::abs(velocity.y) < 0.01) { curPos.y = targetPos.y; velocity.y = 0; } else { curPos.y += velocity.y; } // Z axis (depth) double dox = originalPos.x - curPos.x; double doy = originalPos.y - curPos.y; double dd = (dox * dox) + (doy * doy); double d = std::sqrt(dd); targetPos.z = d / 100.0 + 1.0; double dz = targetPos.z - curPos.z; double az = dz * springStrength; velocity.z += az; velocity.z *= friction; if (std::abs(dz) < 0.01 && std::abs(velocity.z) < 0.001) { curPos.z = targetPos.z; velocity.z = 0; } else { curPos.z += velocity.z; } radius = size * curPos.z; if (radius < 0.5) radius = 0.5; }};
struct PointData { int x, y, size; std::string color;};
class TerminalCanvas {private: int width, height; std::vector<std::string> buffer; std::vector<Color> colorBuffer; std::vector<std::pair<int, int>> dirtyPixels; public: TerminalCanvas(int w, int h) : width(w), height(h) { buffer.resize(height, std::string(width, ' ')); colorBuffer.resize(height * width, Color(255, 255, 255)); dirtyPixels.reserve(1000); } void clear() { // Only clear pixels that were actually drawn for (const auto& pixel : dirtyPixels) { int x = pixel.first; int y = pixel.second; if (y >= 0 && y < height && x >= 0 && x < width) { buffer[y][x] = ' '; colorBuffer[y * width + x] = Color(255, 255, 255); } } dirtyPixels.clear(); } void setPixel(int x, int y, const std::string& c, const Color& color) { if (x >= 0 && x < width && y >= 0 && y < height) { buffer[y][x] = c[0]; colorBuffer[y * width + x] = color; dirtyPixels.push_back({x, y}); } } void drawCircle(int cx, int cy, double radius, const Color& color) { // Use different characters based on size for better visual const char* chars = "●◉○◌"; int charIdx = std::min(3, std::max(0, static_cast<int>(radius / 2))); std::string c(1, chars[charIdx]); int r = static_cast<int>(radius); for (int y = -r; y <= r; y++) { for (int x = -r; x <= r; x++) { double dist = std::sqrt(x * x + y * y); if (dist <= radius) { setPixel(cx + x, cy + y, c, color); } } } } std::string render() { std::ostringstream oss; oss << "\033[H"; // Move cursor to home // Safety check - don't render if size is unreasonable if (width * height > 20000) { oss << "Terminal too large to render\n"; return oss.str(); } Color lastColor(0, 0, 0); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Color c = colorBuffer[y * width + x]; if (buffer[y][x] != ' ') { if (c.r != lastColor.r || c.g != lastColor.g || c.b != lastColor.b) { oss << c.toAnsi(); lastColor = c; } oss << buffer[y][x]; } else { oss << ' '; } } if (y < height - 1) oss << '\n'; } oss << "\033[0m"; // Reset color return oss.str(); }};
class Terminal {public:#ifdef _WIN32 static void setupWindows() { HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); DWORD dwMode = 0; GetConsoleMode(hOut, &dwMode); dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; SetConsoleMode(hOut, dwMode); // Hide cursor CONSOLE_CURSOR_INFO cursorInfo; GetConsoleCursorInfo(hOut, &cursorInfo); cursorInfo.bVisible = false; SetConsoleCursorInfo(hOut, &cursorInfo); } static void restoreWindows() { HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO cursorInfo; GetConsoleCursorInfo(hOut, &cursorInfo); cursorInfo.bVisible = true; SetConsoleCursorInfo(hOut, &cursorInfo); } static void getSize(int& w, int& h) { CONSOLE_SCREEN_BUFFER_INFO csbi; if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { w = csbi.srWindow.Right - csbi.srWindow.Left + 1; h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; } else { // Fallback to default size if call fails w = 80; h = 24; } }#else static struct termios orig_termios; static void setupLinux() { struct termios raw; tcgetattr(STDIN_FILENO, &orig_termios); raw = orig_termios; raw.c_lflag &= ~(ECHO | ICANON); raw.c_cc[VMIN] = 0; // Non-blocking read raw.c_cc[VTIME] = 0; tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); // Hide cursor std::cout << "\033[?25l" << std::flush; // Set non-blocking input int flags = fcntl(STDIN_FILENO, F_GETFL, 0); fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); // Setup resize signal handler signal(SIGWINCH, handleResize); } static void restoreLinux() { tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); std::cout << "\033[?25h" << std::flush; // Show cursor } static void getSize(int& w, int& h) { struct winsize ws; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0 || ws.ws_row == 0) { // Fallback to default size if ioctl fails w = 80; h = 24; } else { w = ws.ws_col; h = ws.ws_row; } }#endif static void setup() {#ifdef _WIN32 setupWindows();#else setupLinux();#endif std::cout << "\033[2J"; // Clear screen } static void restore() {#ifdef _WIN32 restoreWindows();#else restoreLinux();#endif }};
#ifndef _WIN32struct termios Terminal::orig_termios;#endif
class InputManager {private: std::map<int, bool> keyStates; bool shiftPressed = false; enum Keys { KEY_UP = 1, KEY_DOWN = 2, KEY_LEFT = 3, KEY_RIGHT = 4, KEY_SHIFT = 5, KEY_QUIT = 6 }; public: void update() {#ifdef _WIN32 if (_kbhit()) { int c = _getch(); if (c == 'q' || c == 'Q' || c == 27) { keyStates[KEY_QUIT] = true; } if (c == 224 || c == 0) { // Arrow keys c = _getch(); if (c == 72) keyStates[KEY_UP] = true; if (c == 80) keyStates[KEY_DOWN] = true; if (c == 75) keyStates[KEY_LEFT] = true; if (c == 77) keyStates[KEY_RIGHT] = true; } // Check for shift (capital WASD or actual shift detection) if (c == 'W' || c == 'A' || c == 'S' || c == 'D') { shiftPressed = true; if (c == 'W') keyStates[KEY_UP] = true; if (c == 'S') keyStates[KEY_DOWN] = true; if (c == 'A') keyStates[KEY_LEFT] = true; if (c == 'D') keyStates[KEY_RIGHT] = true; } else if (c == 'w' || c == 'a' || c == 's' || c == 'd') { shiftPressed = false; if (c == 'w') keyStates[KEY_UP] = true; if (c == 's') keyStates[KEY_DOWN] = true; if (c == 'a') keyStates[KEY_LEFT] = true; if (c == 'd') keyStates[KEY_RIGHT] = true; } }#else // Clear shift state each frame, will be set if shift+key detected bool newShift = false; char c; while (read(STDIN_FILENO, &c, 1) > 0) { if (c == 'q' || c == 'Q') { keyStates[KEY_QUIT] = true; } if (c == '\033') { char seq[5]; int bytesRead = read(STDIN_FILENO, &seq[0], 1); if (bytesRead == 1) { if (seq[0] == '[') { bytesRead = read(STDIN_FILENO, &seq[1], 1); if (bytesRead == 1) { // Regular arrow keys if (seq[1] == 'A') { keyStates[KEY_UP] = true; continue; } if (seq[1] == 'B') { keyStates[KEY_DOWN] = true; continue; } if (seq[1] == 'D') { keyStates[KEY_LEFT] = true; continue; } if (seq[1] == 'C') { keyStates[KEY_RIGHT] = true; continue; } // Check for modified keys (shift+arrow) if (seq[1] == '1') { bytesRead = read(STDIN_FILENO, &seq[2], 1); if (bytesRead == 1 && seq[2] == ';') { bytesRead = read(STDIN_FILENO, &seq[3], 1); if (bytesRead == 1 && seq[3] == '2') { bytesRead = read(STDIN_FILENO, &seq[4], 1); if (bytesRead == 1) { newShift = true; if (seq[4] == 'A') { keyStates[KEY_UP] = true; continue; } if (seq[4] == 'B') { keyStates[KEY_DOWN] = true; continue; } if (seq[4] == 'D') { keyStates[KEY_LEFT] = true; continue; } if (seq[4] == 'C') { keyStates[KEY_RIGHT] = true; continue; } } } } } } } else { // ESC key pressed alone - quit keyStates[KEY_QUIT] = true; } } else { // ESC with no following characters - quit keyStates[KEY_QUIT] = true; } } // WASD keys if (c == 'W' || c == 'A' || c == 'S' || c == 'D') { newShift = true; if (c == 'W') keyStates[KEY_UP] = true; if (c == 'S') keyStates[KEY_DOWN] = true; if (c == 'A') keyStates[KEY_LEFT] = true; if (c == 'D') keyStates[KEY_RIGHT] = true; } else if (c == 'w' || c == 'a' || c == 's' || c == 'd') { if (c == 'w') keyStates[KEY_UP] = true; if (c == 's') keyStates[KEY_DOWN] = true; if (c == 'a') keyStates[KEY_LEFT] = true; if (c == 'd') keyStates[KEY_RIGHT] = true; } } shiftPressed = newShift;#endif } void processMovement(double& x, double& y) { double speed = shiftPressed ? 4.0 : 2.0; double speedX = shiftPressed ? 2.0 : 1.0; if (keyStates[KEY_UP]) y -= speed; if (keyStates[KEY_DOWN]) y += speed; if (keyStates[KEY_LEFT]) x -= speedX; if (keyStates[KEY_RIGHT]) x += speedX; // Clear key states after processing keyStates.clear(); } bool shouldQuit() { return keyStates[KEY_QUIT]; }};
class App {private: std::vector<Point> points; Vector3 mousePos; int termWidth, termHeight; bool running; InputManager input; bool sizeChanged; public: App() : mousePos(0, 0, 0), termWidth(80), termHeight(24), running(true), sizeChanged(false) {} void init() { Terminal::setup(); Terminal::getSize(termWidth, termHeight); // Cap terminal size to prevent performance issues const int MAX_WIDTH = 200; const int MAX_HEIGHT = 60; termWidth = std::min(termWidth, MAX_WIDTH); termHeight = std::min(termHeight, MAX_HEIGHT); std::vector<PointData> pointData = { {202, 78, 9, "#ed9d33"}, {348, 83, 9, "#d44d61"}, {256, 69, 9, "#4f7af2"}, {214, 59, 9, "#ef9a1e"}, {265, 36, 9, "#4976f3"}, {300, 78, 9, "#269230"}, {294, 59, 9, "#1f9e2c"}, {45, 88, 9, "#1c48dd"}, {268, 52, 9, "#2a56ea"}, {73, 83, 9, "#3355d8"}, {294, 6, 9, "#36b641"}, {235, 62, 9, "#2e5def"}, {353, 42, 8, "#d53747"}, {336, 52, 8, "#eb676f"}, {208, 41, 8, "#f9b125"}, {321, 70, 8, "#de3646"}, {8, 60, 8, "#2a59f0"}, {180, 81, 8, "#eb9c31"}, {146, 65, 8, "#c41731"}, {145, 49, 8, "#d82038"}, {246, 34, 8, "#5f8af8"}, {169, 69, 8, "#efa11e"}, {273, 99, 8, "#2e55e2"}, {248, 120, 8, "#4167e4"}, {294, 41, 8, "#0b991a"}, {267, 114, 8, "#4869e3"}, {78, 67, 8, "#3059e3"}, {294, 23, 8, "#10a11d"}, {117, 83, 8, "#cf4055"}, {137, 80, 8, "#cd4359"}, {14, 71, 8, "#2855ea"}, {331, 80, 8, "#ca273c"}, {25, 82, 8, "#2650e1"}, {233, 46, 8, "#4a7bf9"}, {73, 13, 8, "#3d65e7"}, {327, 35, 6, "#f47875"}, {319, 46, 6, "#f36764"}, {256, 81, 6, "#1d4eeb"}, {244, 88, 6, "#698bf1"}, {194, 32, 6, "#fac652"}, {97, 56, 6, "#ee5257"}, {105, 75, 6, "#cf2a3f"}, {42, 4, 6, "#5681f5"}, {10, 27, 6, "#4577f6"}, {166, 55, 6, "#f7b326"}, {266, 88, 6, "#2b58e8"}, {178, 34, 6, "#facb5e"}, {100, 65, 6, "#e02e3d"}, {343, 32, 6, "#f16d6f"}, {59, 5, 6, "#507bf2"}, {27, 9, 6, "#5683f7"}, {233, 116, 6, "#3158e2"}, {123, 32, 6, "#f0696c"}, {6, 38, 6, "#3769f6"}, {63, 62, 6, "#6084ef"}, {6, 49, 6, "#2a5cf4"}, {108, 36, 6, "#f4716e"}, {169, 43, 6, "#f8c247"}, {137, 37, 6, "#e74653"}, {318, 58, 6, "#ec4147"}, {226, 100, 5, "#4876f1"}, {101, 46, 5, "#ef5c5c"}, {226, 108, 5, "#2552ea"}, {17, 17, 5, "#4779f7"}, {232, 93, 5, "#4b78f1"} }; // Scale to terminal and center double scaleX = termWidth / 360.0; double scaleY = (termHeight * 2.0) / 120.0; // Account for char aspect ratio double scale = std::min(scaleX, scaleY) * 0.6; double offsetX = termWidth / 2.0 - 180 * scale; double offsetY = termHeight - 60 * scale; for (const auto& data : pointData) { double x = offsetX + data.x * scale; double y = offsetY + data.y * scale; double size = data.size * scale * 0.3; points.emplace_back(x, y, 0.0, size, data.color); } // Start mouse in center mousePos.x = termWidth / 2.0; mousePos.y = termHeight; } void update() { for (auto& point : points) { double dx = mousePos.x - point.curPos.x; double dy = mousePos.y - point.curPos.y; double dd = (dx * dx) + (dy * dy); double d = std::sqrt(dd); if (d < 15) { point.targetPos.x = point.curPos.x - dx; point.targetPos.y = point.curPos.y - dy; } else { point.targetPos.x = point.originalPos.x; point.targetPos.y = point.originalPos.y; } point.update(); } } void render(TerminalCanvas& canvas) { canvas.clear(); // Debug: print some info at top static int frameCount = 0; if (frameCount++ % 30 == 0) { // Every 30 frames FILE* f = fopen("/tmp/balls_debug.txt", "a"); if (f) { fprintf(f, "Mouse: %.1f,%.1f Term: %dx%d Points: %zu\n", mousePos.x, mousePos.y, termWidth, termHeight, points.size()); if (!points.empty()) { fprintf(f, "First point at: %.1f,%.1f\n", points[0].curPos.x, points[0].curPos.y); } fclose(f); } } for (auto& point : points) { // Adjust for terminal character aspect ratio (chars are ~2x taller than wide) canvas.drawCircle( static_cast<int>(point.curPos.x), static_cast<int>(point.curPos.y / 2.0), point.radius * 0.5, point.color ); } // Draw cursor position int cx = static_cast<int>(mousePos.x); int cy = static_cast<int>(mousePos.y / 2.0); canvas.setPixel(cx, cy, "✦", Color(128, 128, 128)); } void run() { FILE* f = fopen("/tmp/balls_debug.txt", "w"); if (f) { fprintf(f, "Starting run() with termWidth=%d, termHeight=%d\n", termWidth, termHeight); fclose(f); } std::unique_ptr<TerminalCanvas> canvas(new TerminalCanvas(termWidth, termHeight)); std::cout << "\033[2J\033[H"; // Clear and home std::cout << "Google Balls Terminal Edition - Arrow keys/WASD to move | Hold Shift for speed boost | Q to quit\n" << std::flush; std::this_thread::sleep_for(std::chrono::milliseconds(1500)); auto lastFrame = std::chrono::steady_clock::now(); while (running) { // Cap terminal size to prevent performance issues const int MAX_WIDTH = 200; const int MAX_HEIGHT = 60; // Check for terminal resize int newWidth, newHeight; Terminal::getSize(newWidth, newHeight); // Cap the size newWidth = std::min(newWidth, MAX_WIDTH); newHeight = std::min(newHeight, MAX_HEIGHT); // Check if window was resized or size changed if (g_windowResized || newWidth != termWidth || newHeight != termHeight) { g_windowResized = 0; termWidth = newWidth; termHeight = newHeight; canvas.reset(new TerminalCanvas(termWidth, termHeight)); std::cout << "\033[2J\033[H" << std::flush; // Clear on resize } // Handle input input.update(); if (input.shouldQuit()) { running = false; break; } input.processMovement(mousePos.x, mousePos.y); // Clamp mouse mousePos.x = std::max(0.0, std::min(static_cast<double>(termWidth - 1), mousePos.x)); mousePos.y = std::max(0.0, std::min(static_cast<double>(termHeight * 2 - 1), mousePos.y)); update(); render(*canvas); std::cout << canvas->render() << std::flush; // Frame timing (30ms like original) auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastFrame); if (elapsed.count() < 30) { std::this_thread::sleep_for(std::chrono::milliseconds(30 - elapsed.count())); } lastFrame = std::chrono::steady_clock::now(); } } void cleanup() { Terminal::restore(); std::cout << "\033[2J\033[H"; // Clear screen std::cout << "buh bye\n"; }};
int main() { App app; app.init(); app.run(); app.cleanup(); return 0;}