nuke/nuke.c

379 lines
12 KiB
C

/**
* A program to nuke files from orbit.
*/
#include "colors.h"
#include "explosion.h"
#include "globe.h"
#include "list.h"
#include <curses.h>
#include <errno.h>
#include <ftw.h>
#include <math.h>
#include <poll.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}
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_files(argv[i], &files, &num_files, &capacity);
}
if (argc == 1) {
add_file("<testing grounds>", &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);
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));
mvprintw(targetr-2,targetc+2,"%s", argc > 1 ? argv[f+1] : "testing grounds");
attroff(COLOR_PAIR(BLACK_ON_RED));
if (argc > 2)
f = (f + 1) % (argc - 1);
}
// 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 */
for (int i = 1; i < argc; i++) {
int failure;
DIR *dir;
if (!(dir = opendir(argv[i]))) {
failure = remove(argv[i]);
} else {
closedir(dir);
failure = nftw(argv[i], remove_callback, 64, FTW_DEPTH | FTW_PHYS);
}
if (!failure) {
printf("deleted %s\n", argv[i]);
} else {
printf("unable to delete %s\n (%s)", argv[i], strerror(errno));
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
exit_failure:
curs_set(1); /* unhide cursor */
endwin(); /* Exit ncurses */
printf("%s\n",failure_message);
return EXIT_FAILURE;
}