/* ============================================================================================ */ /* This software is created by John Anthony and comes with no warranty of any kind. */ /* */ /* If you like this software and would like to contribute to its continued improvement */ /* then please feel free to submit bug reports here: www.github.com/JohnAnthony */ /* */ /* This program is licensed under the GPLv3 and is Free Software */ /* The full license can be found at http://www.gnu.org/licenses/gpl.html */ /* ============================================================================================ */ #include #include #include #include #include #include #include #include #include #include #include #include // linked list implementation #include #include /* Predecs */ void *ec_malloc(unsigned int size); void errout(char *str); void load_images(); void load_resource_data(); void load_music(); void restart_music(); void usage(char *exname); #include "globals.h" #include "draw.h" void get_screen_info() { int iscres, icrtc; Display *disp = XOpenDisplay(0); XRRScreenResources *screen = XRRGetScreenResources(disp, DefaultRootWindow(disp)); for (iscres = screen->noutput; iscres > 0; ) { --iscres; XRROutputInfo *info = XRRGetOutputInfo (disp, screen, screen->outputs[iscres]); if (info->connection == RR_Connected) { for (icrtc = info->ncrtc; icrtc > 0;) { --icrtc; XRRCrtcInfo *crtc_info = XRRGetCrtcInfo (disp, screen, screen->crtcs[icrtc]); fprintf(stderr, "==> %dx%d+%dx%d\n", crtc_info->x, crtc_info->y, crtc_info->width, crtc_info->height); XRRFreeCrtcInfo(crtc_info); } } XRRFreeOutputInfo (info); } XRRFreeScreenResources(screen); } void cleanup(void) { Mix_HaltMusic(); Mix_FreeMusic(music); Mix_CloseAudio(); /* free cats */ cat_instance *c, *ctmp; LIST_FOREACH_SAFE(c, &cat_list, entries, ctmp) { LIST_REMOVE(c, entries); free(c); } /* free rainbows */ rainbow_instance *r, *rtmp; LIST_FOREACH_SAFE(r, &rainbow_list, entries, rtmp) { LIST_REMOVE(r, entries); free(r); } /* free sparkles */ sparkle_instance *s, *stmp; LIST_FOREACH_SAFE(s, &sparkle_list, entries, stmp) { LIST_REMOVE(s, entries); free(s); } SDL_DestroyRenderer(renderer); SDL_Quit(); } void *ec_malloc(unsigned int size) { void *ptr = malloc(size); if (!ptr){errout("In ec_malloc -- unable to allocate memory.");} return ptr; } void errout(char *str) { if (str){fprintf(stderr,"%s\n",str);} exit(1); } void handle_args(int argc, char *argv[]) { int c, volume; char *x; //position of x in resolution (e.g. 800x600) while(1) { static struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"fullscreen", no_argument, 0, 'f'}, {"windowed", no_argument, 0, 'w'}, {"catsize", required_argument, 0, 'c'}, {"nocursor", no_argument, 0, 'a'}, {"cursor", no_argument, 0, 'b'}, {"nosound", no_argument, 0, 'e'}, {"volume", required_argument, 0, 'v'}, {"resolution", required_argument, 0, 'r'}, {"data-set", required_argument, 0, 'd'}, {"sine", no_argument, 0, 's'}, {"count", required_argument, 0, 'n'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; c = getopt_long(argc, argv, "hfwc:abev:r:d:sn:V", long_options, &option_index); /* detect the end of the options. */ if (c == -1){break;} switch (c) { case 'h': usage(argv[0]); break; case 'f': fullscreen = true; break; case 'w': fullscreen = false; break; case 'c': cat_size = atof(optarg); if (cat_size < 0.1 || cat_size > 10) { fprintf(stderr,"Size of cat is invalid, set to default.\n"); cat_size = 1; } break; case 'a': cursor = false; break; case 'b': cursor = true; break; case 'e': sound = false; break; case 'v': volume = atoi(optarg); if(volume >= 0 && volume <= 128){sound_volume = volume;} else { fprintf(stderr,"Arguments for Volume are not valid. Disabling sound.\n"); sound = false; } break; case 'r': x = strchr(optarg,'x'); if (!x){fprintf(stderr,"Argument for resolution is invalid, resolution set to default.\n"); break;} *x = '\0'; if (*(x+1) == '\0'){fprintf(stderr,"Argument for resolution is invalid, resolution set to default.\n"); break;} int dims[2] = {atoi(optarg), atoi(x+1)}; if (dims[0] >= 0 && dims[0] < 10000 && dims[1] >= 0 && dims[1] < 5000) { SCREEN_WIDTH = dims[0]; SCREEN_HEIGHT = dims[1]; } else {fprintf(stderr,"Argument for resolution is invalid, resolution set to default.\n");} break; case 'd': if (cat_dir){free(cat_dir);} cat_dir = strdup(optarg); break; case 's': sine = true; break; case 'n': cat_num = atoi(optarg); if (cat_num <= 0 || cat_num > 10000) { fprintf(stderr,"Number of cats are invalid, set to 1.\n"); cat_num = 1; } break; case 'V': printf("Copyright (C) 2020 John Antony, 2022 DiffieHellman\n" "License GPLv3: GNU GPL version 3 \n\n" "This is free software; you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n"); exit(0); case '?': /* getopt_long already printed an error message. */ break; default: exit(1); } } if (!cat_dir){cat_dir = "default";} } void handle_input(void) { while(SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: case SDL_QUIT: running = false; break; } } } void init(void) { srand(time(NULL)); SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|IMG_INIT_PNG); SDL_Window *window; if (fullscreen) { window = SDL_CreateWindow("Nyan cat", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP); /* Set screen width and height to fullscreen resolution*/ SDL_DisplayMode current; if (SDL_GetCurrentDisplayMode(0, ¤t) != 0){errout("SDL could not get display mode for screen");} SCREEN_WIDTH = current.w; SCREEN_HEIGHT = current.h; } else {window = SDL_CreateWindow("Nyan cat", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_MAXIMIZED);} if (!cursor){SDL_ShowCursor(0);} renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); load_images(); load_resource_data(); /* Divide the *_width by *_count, so we don't need to calculate width/count every time */ cat_width /= cat_count; rainbow_width /= rainbow_count; sparkle_width /= sparkle_count; SDL_SetRenderDrawColor(renderer, 0x00, 0x33, 0x66, 255); SDL_RenderClear(renderer); if (sound) { Mix_OpenAudio(44100, AUDIO_S16, 2, 256); load_music(); Mix_PlayMusic(music, 0); Mix_VolumeMusic(sound_volume); } /* init the linked lists */ LIST_INIT(&cat_list); LIST_INIT(&rainbow_list); LIST_INIT(&sparkle_list); if (cat_num == 1) { add_cat((SCREEN_WIDTH - cat_width) / 2, (SCREEN_HEIGHT - cat_height)/2); add_rainbow((SCREEN_WIDTH - rainbow_width) / 2 - OFFSET, (SCREEN_HEIGHT - rainbow_height) / 2); } else { for (int i = 0; i < cat_num; ++i) { unsigned x = rand() % (SCREEN_WIDTH - cat_width); unsigned y = rand() % (SCREEN_HEIGHT - cat_height); add_cat(x,y); add_rainbow(x - OFFSET, y); } } /* clear initial input */ while(SDL_PollEvent(&event)); /* Pre-populate with sparkles */ for (int i = 0; i < 200; ++i){update_sparkles();} } SDL_Surface* load_image(const char* path) { SDL_Surface* loadedImage = NULL; SDL_Surface* optimizedImage = NULL; loadedImage = IMG_Load(path); if(loadedImage) { SDL_PixelFormat *format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32); optimizedImage = SDL_ConvertSurface(loadedImage, format, 0); SDL_FreeSurface(loadedImage); } return optimizedImage; } void load_images() { char buffer[BUF_SZ]; SDL_Surface *cat_img, *rainbow_img, *sparkle_img; /* Load images */ snprintf(buffer, BUF_SZ, "%s/%s/cat.png", BASE_PATH, cat_dir); cat_img = load_image(buffer); snprintf(buffer, BUF_SZ, "%s/%s/rainbow.png", BASE_PATH, cat_dir); rainbow_img = load_image(buffer); snprintf(buffer, BUF_SZ, "%s/%s/sparkle.png", BASE_PATH, cat_dir); sparkle_img = load_image(buffer); /* Check everything loaded properly */ if (!cat_img||!rainbow_img||!sparkle_img){errout("Error loading images.");} /* Create textures from images */ cat_texture = SDL_CreateTextureFromSurface(renderer, cat_img); rainbow_texture = SDL_CreateTextureFromSurface(renderer, rainbow_img); sparkle_texture = SDL_CreateTextureFromSurface(renderer, sparkle_img); /* Get width and height from all the textures */ SDL_QueryTexture(cat_texture, NULL, NULL, &cat_width, &cat_height); SDL_QueryTexture(rainbow_texture, NULL, NULL, &rainbow_width, &rainbow_height); SDL_QueryTexture(sparkle_texture, NULL, NULL, &sparkle_width, &sparkle_height); } void load_music() { char buffer[BUF_SZ]; snprintf(buffer, BUF_SZ, "%s/%s/music.ogg", BASE_PATH, cat_dir); music = Mix_LoadMUS(buffer); if (!music){fprintf(stderr,"Unable to load Ogg file: %s\n", Mix_GetError());} else {Mix_HookMusicFinished(restart_music);} } void load_resource_data() { char buffer[BUF_SZ]; snprintf(buffer, BUF_SZ, "%s/%s/data", BASE_PATH, cat_dir); FILE *fp = fopen(buffer, "r"); if (!fp){errout("Error opening resource data file");} cat_count = atoi(fgets(buffer, BUF_SZ, fp)); rainbow_count = atoi(fgets(buffer, BUF_SZ, fp)); sparkle_count = atoi(fgets(buffer, BUF_SZ, fp)); if (!cat_count||!rainbow_count||!sparkle_count){errout("Error reading or parsing resource data file.");} fclose(fp); } void restart_music() { Mix_PlayMusic(music, 0); } void run() { unsigned int last_draw, draw_time; while(running) { last_draw = SDL_GetTicks(); /* it's faster to just clear the whole renderer rather than individually overwriting each sprite */ SDL_RenderClear(renderer); update_sparkles(); update_rainbows(); draw_sparkles(); draw_rainbows(); draw_cats(); handle_input(); if (sine){handle_sine();} /* display the frame */ SDL_RenderPresent(renderer); /* animation sequence increment and looping */ if (++cat_sprite >= cat_count){cat_sprite = 0;} if (++rainbow_sprite >= rainbow_count){rainbow_sprite = 0;} if (++sparkle_sprite >= sparkle_count){sparkle_sprite = 0;} /* Avoid wasting CPU & GPU by only drawing at the framerate */ draw_time = SDL_GetTicks() - last_draw; if (draw_time < (1000 / FRAMERATE)){SDL_Delay((1000 / FRAMERATE) - draw_time);} } } void usage(char* exname) { fprintf(stderr,"Usage: %s [OPTIONS]\n\ -h, --help This help message.\n\ -f, --fullscreen Enable fullscreen (default).\n\ -w, --windowed Disable fullscreen.\n\ -c, --catsize Choose size to scale cat to (between 0.1 to 10.0)\n\ -a, --nocursor Don't show the cursor (default).\n\ -b, --cursor Show the cursor.\n\ -e, --nosound Don't play sound.\n\ -v, --volume Set Volume, if enabled, from 0 - 128.\n\ -r, --resolution Argument format: x (1200x800 default).\n\ -d, --data-set Use an alternate data set. Packaged with\n\ this program by default are \"default\"\n\ and \"freedom\" sets.\n\ -s, --sine Make cat move in a sine wave.\n\ -n, --count Number of cats to spawn.\n\ -V, --version Version and copyright information.\n", exname); exit(0); } int main(int argc, char *argv[]) { // get_screen_info(); // exit(1); handle_args(argc, argv); init(); run(); cleanup(); return 0; }