Download
427 lines 13.9 KiBCModified 2019-02-13 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
/**
  * A program to nuke files from orbit.
  */
#include "colors.h"
#include "explosion.h"
#include "globe.h"
#include <curses.h>
#include <dirent.h>
#include <errno.h>
#define __USE_XOPEN_EXTENDED
#include <ftw.h>
#include <math.h>
#include <poll.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAX_FILES 2048
#define NUM_FRAMES 100
// 30 FPS:
#define FRAME_USEC 33333

static int color_ramp1[] = {1,1,1,2,2,2,3,3,3,1,1, -1};
static int bold_ramp1[] =  {1,1,0,1,0,0,1,0,0,0,0, -1};
static int color_ramp2[] = {1,1,1,1,2,2,2,2,2,3,3,3,3,3,1,1,1,1,1,1, -1};
static int bold_ramp2[] =  {1,1,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0, -1};

static void sighandler(int sig) {
    (void)sig;
    curs_set(1);
    endwin();
    exit(EXIT_FAILURE);
}

static int remove_callback(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
{
        int rv = remove(path);
        if (rv) perror(path);
        return rv;
}

static void add_file(char *name, char ***files, int *size, int *capacity)
{
    // Prevent from taking too long
    if (*size > MAX_FILES) return;
    if (*size == *capacity) {
        *capacity *= 2;
        *files = realloc(*files, sizeof(char*) * (*capacity));
    }
    if (*size == MAX_FILES)
        (*files)[*size] = strdup("...too many to list...");
    else
        (*files)[*size] = strdup(name);
    (*size)++;
}

int main(int argc, char *argv[])
{
    if (argc > 1 && strcmp(argv[1], "--help") == 0) {
        printf("nuke: Nuke files from orbit.\nUsage: nuke [files...]\n");
        return EXIT_SUCCESS;
    }

    char *failure_message = "nothing was deleted";

    int rows,cols;
    int delay=1E5;

    int capacity = 32, num_files = 0;
    char **files = malloc(sizeof(char*)*capacity);
    for (int i = 1; i < argc; i++) {
        add_file(argv[i], &files, &num_files, &capacity);
    }

    /* Initialize ncurses and get window dimensions */
    //initscr();
    char* term_type = getenv("TERM");
    if (term_type == NULL || *term_type == '\0') {
        term_type = "unknown";
    }
    FILE* term_in = fopen("/dev/tty", "r");
    if (term_in == NULL) {
        perror("fopen(/dev/tty)");
        exit(EXIT_FAILURE);
    }
    SCREEN* main_screen = newterm(term_type, stdout, term_in);
    set_term(main_screen);

    signal(SIGINT, sighandler);

    size_t buf_chunk = 1024;
    size_t buf_size = buf_chunk, buf_i = 0;
    char *outbuf = calloc(buf_size, sizeof(char));

    { // Read stdin if anything was piped in
        struct pollfd desc;
        desc.fd = STDIN_FILENO;
        desc.events = POLLIN;
        int ret = poll(&desc, 1, 50);
        if (ret > 0) {
            char *line = NULL;
            size_t linebuf_size;
            ssize_t line_len;
            while ((line_len = getline(&line, &linebuf_size, stdin)) >= 0) {
                if (line[line_len-1] == '\n')
                    line[--line_len] = '\0';
                if (line_len > 0)
                    add_file(line, &files, &num_files, &capacity);
            }
        }
    }

    int testing = 0;
    if (num_files == 0) {
        add_file("<testing grounds>", &files, &num_files, &capacity);
        testing = 1;
    }

    start_color();
    init_pair(WHITE, COLOR_WHITE, COLOR_BLACK);
    init_pair(YELLOW, COLOR_YELLOW, COLOR_BLACK);
    init_pair(RED, COLOR_RED, COLOR_BLACK);
    init_pair(BLUE, COLOR_BLUE, COLOR_BLACK);
    init_pair(GREEN, COLOR_GREEN, COLOR_BLACK);
    init_pair(BLACK, COLOR_BLACK, COLOR_BLACK);
    init_pair(WHITE_BG, COLOR_BLACK, COLOR_WHITE);
    init_pair(BLACK_ON_RED, COLOR_BLACK, COLOR_RED);
    init_pair(BLACK_ON_GREEN, COLOR_BLACK, COLOR_GREEN);
    
    for (int i = 0; color_ramp1[i] >= 0; i++)
        color_ramp1[i] = COLOR_PAIR(color_ramp1[i]) | (bold_ramp1[i] ? A_BOLD : 0);
    for (int i = 0; color_ramp2[i] >= 0; i++)
        color_ramp2[i] = COLOR_PAIR(color_ramp2[i]) | (bold_ramp2[i] ? A_BOLD : 0);

    getmaxyx(stdscr,rows,cols);

    curs_set(0); /* hide text cursor */
    noecho();
    keypad(stdscr, 1);

    // First frame
    int max_line = 0;
    for (int f = 0; f < num_files; f++) {
        int len = strlen(files[f]);
        if (len > max_line) max_line = len;
    }

    erase();
    int padwidth = max_line < cols*3/4 ? max_line : cols*3/4;
    int padheight = num_files < rows*3/4 ? num_files : rows*3/4;
    int padframer = rows/2-padheight/2-1;
    int padframec = cols/2-padwidth/2-1;
    WINDOW *padframe = newwin(padheight+2, padwidth+2, padframer, padframec);
    if (!padframe) {
        failure_message = "Couldn't create pad frame";
        goto exit_failure;
    }
    wattron(padframe, COLOR_PAIR(YELLOW) | A_BOLD);
    box(padframe, 0, 0);
    WINDOW *pad = newpad(padheight, max_line);
    if (!pad) {
        failure_message = "Couldn't create pad";
        goto exit_failure;
    }

    /* Display confirmation */
    attron(COLOR_PAIR(BLACK_ON_RED));
    char *description = num_files == 1 ? "        TARGET:       " :  "        TARGETS:      ";
    mvprintw(padframer-2, cols/2 - strlen(description)/2, "%s", description);
    attron(COLOR_PAIR(BLACK_ON_RED) | A_BLINK);
    const char confirm[] =     "    FIRE NUKE? y/n    ";
    mvprintw(padframer+padheight+3, cols/2 - strlen(confirm)/2, "%s", confirm);
    attroff(COLOR_PAIR(BLACK_ON_RED) | A_BLINK);
    refresh();

    //immedok(pad, 1);
    int scrollx = 0, scrolly = 0;

    timeout(10);
    while (1) {
        // Draw scroll bar
        if (padheight < num_files) {
            mvaddch(padframer, padframec+padwidth+2, ACS_UARROW);
            mvaddch(padframer+padheight+1, padframec+padwidth+2, ACS_DARROW);
            for (int i = 1; i <= padheight; i++)
                mvaddch(padframer+i, padframec+padwidth+2, ACS_VLINE);
            for (int i = scrolly*padheight/num_files; i <= scrolly*padheight/num_files+padheight*padheight/num_files; i++)
                mvaddch(padframer+1+i, padframec+padwidth+2, ACS_CKBOARD);
        }
        box(padframe, 0, 0);
        if (wrefresh(padframe) == ERR) goto exit_failure;
        // Redo this each time so that pad doesn't have to hold everything in memory
        werase(pad);
        for (int f = scrolly; f < scrolly+padheight; f++) {
            mvwprintw(pad,f-scrolly,0,"%s",files[f]);
        }
        if (prefresh(pad, 0, scrollx, padframer+1, padframec+1, padframer+padheight, padframec+padwidth) == ERR)
            goto exit_failure;
        //prefresh(pad, scrolly, scrollx, 1, 1, 1+padheight, 1+padwidth);
        //prefresh(pad, scrolly, scrollx, rows/2-padheight/2, cols/2-padwidth/2, rows/2+padheight/2, cols/2+padwidth/2);
        refresh();
      get_input:
        switch (getch()) {
            case 'y':
                goto do_the_nuking;
            case 'n': case 'q': case 27:
                goto exit_failure;
            case KEY_DOWN: case 'j': case ' ':
                scrolly++;
                break;
            case KEY_UP: case 'k':
                scrolly--;
                break;
            case KEY_RIGHT: case 'l':
                scrollx++;
                break;
            case KEY_LEFT: case 'h':
                scrollx--;
                break;
            case 'J': case KEY_NPAGE:
                scrolly += num_files/20+1;
                break;
            case 'K': case KEY_PPAGE:
                scrolly -= num_files/20+1;
                break;
            case KEY_SRIGHT: case 'L':
                scrollx += 10;
                break;
            case KEY_SLEFT: case 'H':
                scrollx -= 10;
                break;
            case KEY_HOME: case 'g':
                scrolly = 0;
                break;
            case KEY_END: case 'G':
                scrolly = num_files-padheight;
                break;
            default:
                goto get_input;
        }
        if (scrolly < 0) scrolly = 0;
        if (scrolly > num_files-padheight) scrolly = num_files-padheight;
        if (scrollx < 0) scrollx = 0;
        if (scrollx > max_line-padwidth) scrollx = max_line-padwidth;
    }

    do_the_nuking: ;

    double zoom = .8; // percent of viewport globe will fill

    const double targeting_time = 2.5;
    const double anticipation = 0.5;
    const double firing_time = 1.0;
    const double nuking_time = .5;
    const double aftermath = 100.0;

    int f = 0;
    int targetr, targetc;
    double t = 0.0;
    for (; t < targeting_time + anticipation + firing_time; t += 1./30.) {
        erase();
        draw_stars(t);
        draw_globe(t, zoom);
        if (get_target_pos(t, zoom, &targetr, &targetc)) {
            // If visible draw '*' on globe
            attron(COLOR_PAIR(YELLOW));
            mvaddch(targetr,targetc, '*' | A_BOLD);
            attroff(COLOR_PAIR(YELLOW));
        }
        draw_clouds(t, zoom);
        // Draw crosshair
        double wobble = t > targeting_time ? 0. : targeting_time - t;
        int r = targetr, c = targetc;
        if (wobble > .01) {
            r = mix(r, rows*hillnoise(784,5.*t), wobble);
            c = mix(c, cols*hillnoise(-784,5.*t), wobble);
        }
        int attr = COLOR_PAIR(RED);
        attron(attr);
        mvaddch(r-1,c-2,ACS_ULCORNER | A_BOLD);
        mvaddch(r-1,c-1,ACS_HLINE | A_BOLD);
        mvaddch(r-1,c+1,ACS_HLINE | A_BOLD);
        mvaddch(r-1,c+2,ACS_URCORNER | A_BOLD);
        mvaddch(r+1,c+2,ACS_LRCORNER | A_BOLD);
        mvaddch(r+1,c+1,ACS_HLINE | A_BOLD);
        mvaddch(r+1,c-1,ACS_HLINE | A_BOLD);
        mvaddch(r+1,c-2,ACS_LLCORNER | A_BOLD);
        mvaddch(r,c,'X' | A_BOLD);
        attroff(attr);

        // Draw label
        if (get_target_pos(t, zoom, &targetr, &targetc)) {
            attron(COLOR_PAIR(RED));
            mvaddch(targetr-1,targetc+1,'/');
            attroff(COLOR_PAIR(RED));
            attron(COLOR_PAIR(BLACK_ON_RED));
            if (num_files <= 0) {
                mvprintw(targetr-2,targetc+2,"%s", "testing grounds");
            } else {
                for (int t=0; t < num_files; t++) {
                    mvprintw(targetr-2-t,targetc+2,"%s", files[num_files-t-1]);
                }
            }
            attroff(COLOR_PAIR(BLACK_ON_RED));
        }

        // Draw nuke
        if (t > targeting_time + anticipation) {
            double k = (t-targeting_time-anticipation)/firing_time;
            int nuker = mix(rows, targetr, k), nukec = mix(cols/2, targetc, k);
            attron(COLOR_PAIR(WHITE) | A_BOLD);
            mvaddch(nuker, nukec, "A^' "[(int)(k*4.)]);
            attroff(COLOR_PAIR(WHITE) | A_BOLD);

            // Draw nuke label
            attron(COLOR_PAIR(RED));
            mvaddch(nuker-1,nukec-1,'\\');
            attroff(COLOR_PAIR(RED));
            attron(COLOR_PAIR(BLACK_ON_RED));
            mvprintw(nuker-2,nukec-2-5,"%0.3f", (targeting_time + anticipation + firing_time)-t);
            attroff(COLOR_PAIR(BLACK_ON_RED));
        }

        refresh();

        // Check for input
        int ch = getch();
        if (ch == 'q' || ch == 27) {
            if (t <= targeting_time + anticipation) {
                goto exit_failure;
            } else {
                goto exit_success;
            }
        }
        usleep(FRAME_USEC);
    }

    for (int i=0; i<100; i++) {
        draw_explosion(i, targetc, targetr);
        refresh();
        /* Quit early? */
        int ch = getch();
        if (ch == 'q' || ch == 27) {
            goto exit_success;
        }
        if (i == 0)
            usleep(8*FRAME_USEC); // flash for 8 frames
        else
            usleep(FRAME_USEC);
    }
    t += nuking_time;

    for (; t < targeting_time + anticipation + firing_time + nuking_time + aftermath; t += 1./30.) {
        erase();
        draw_stars(t);
        draw_globe(t, zoom);
        // Draw crater
        if (get_target_pos(t, zoom, &targetr, &targetc)) {
            attron(COLOR_PAIR(BLACK) | A_BOLD);
            for (int i = 31; i >= 0; i--) {
                double rad = 4./31.*i;
                double a = i*2*M_PI*GOLDEN_RATIO;
                int r = targetr+(int)(rad*sin(a)/2), c = targetc+(int)(rad*cos(a));
                if ((r-rows/2)*(r-rows/2) + (c/2-cols/4)*(c/2-cols/4) <= (zoom*rows/2)*(zoom*rows/2))
                    mvaddch(r, c, " #*."[i*4/31]);
            }
            attroff(COLOR_PAIR(BLACK) | A_BOLD);
            /*
            for (double =target_lat
            mvprintw(targetr-1,targetc-1,"****");
            mvprintw(targetr,targetc-2, "**  **");
            mvprintw(targetr+1,targetc-1,"****");
            */
        }
        draw_clouds(t, zoom);
        if (get_target_pos(t, zoom, &targetr, &targetc)) {
            // Draw label
            attron(COLOR_PAIR(GREEN));
            mvaddch(targetr-1,targetc+1,'/');
            attroff(COLOR_PAIR(GREEN));
            attron(COLOR_PAIR(BLACK_ON_GREEN));
            mvprintw(targetr-2,targetc+2," DELETED ");
            attroff(COLOR_PAIR(BLACK_ON_GREEN));
        }

        attron(COLOR_PAIR(BLACK_ON_GREEN) | A_BLINK);
        const char *any_key = "    PRESS ANY KEY TO EXIT    ";
        mvprintw(rows-(1.-zoom)*rows/4, cols/2-strlen(any_key)/2, any_key);
        attroff(COLOR_PAIR(BLACK_ON_GREEN) | A_BLINK);

        refresh();
        if (getch() != -1)
            goto exit_success;
        usleep(FRAME_USEC);
    }
    
  exit_success:
    curs_set(1); /* unhide cursor */
    endwin(); /* Exit ncurses */
    int exit_status = EXIT_SUCCESS;
    if (!testing) {
        for (int i = 0; i < num_files; i++) {
            int failure;
            DIR *dir;
            if (!(dir = opendir(files[i]))) {
                failure = remove(files[i]);
            } else {
                closedir(dir);
                failure = nftw(files[i], remove_callback, 64, FTW_DEPTH | FTW_PHYS);
            }
            if (!failure) {
                printf("%s\n", files[i]);
            } else {
                printf("Error: unable to delete %s (%s)\n", files[i], strerror(errno));
                exit_status = EXIT_FAILURE;
            }
        }
    }
    return exit_status;

  exit_failure:
    curs_set(1); /* unhide cursor */
    endwin(); /* Exit ncurses */
    printf("%s\n",failure_message);
    return EXIT_FAILURE;
}