diff options
Diffstat (limited to 'main.c')
-rw-r--r-- | main.c | 485 |
1 files changed, 485 insertions, 0 deletions
@@ -0,0 +1,485 @@ + /* nyancat in GNU C + Copyright © 2011-2013, 2017 John Anthony + Copyright © 2021, 2023-2024 DiffieHellman + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +#include <SDL2/SDL.h> +#include <SDL2/SDL_image.h> +#include <SDL2/SDL_mixer.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <string.h> +#include <getopt.h> +#include <stdint.h> +#include <math.h> +#include <stdio.h> +#include <X11/extensions/Xrandr.h> +#include <regex.h> +#include "main.h" +#include "nyan.h" +#include "globals.h" + +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 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 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, 'u'}, + {"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'}, + {"bounce", no_argument, 0, 'b'}, + {"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:auev:r:d:sbn: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 'u': + 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': + /* check that the optarg is in the correct <num>x<num> format to guarantee correct parsing */ + regex_t regex; + regcomp(®ex, "[0-9]\\+x[0-9]\\+", 0); + if (regexec(®ex, optarg, 0, NULL, 0) != 0){fprintf(stderr,"Argument for resolution is invalid, resolution set to default.\n"); break;} + + x = strchr(optarg,'x'); + *x = '\0'; + + int dims[2] = {atoi(optarg), atoi(x+1)}; + /* todo: poll the total resolution of all screens with xrandr and don't allow larger to keep window smaller than the display and support screens larger than 32K */ + if (dims[0] >= 200 && dims[0] <= 30720 && dims[1] >= 200 && dims[1] <= 17280) + { + 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 'b': + bounce = 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("nyancat (GNU C version)\n" + "Copyright © 2011-2013, 2017 John Anthony\n" + "Copyright © 2021, 2023-2024 DiffieHellman\n" + "License (part) GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl-3.0.html>\n" + "License (part) AGPLv3+: GNU Affero GPL version 3 or later <https://gnu.org/licenses/agpl-3.0.html>\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,1,1, &cat_list); + add_rainbow((screen_width - rainbow_width) / 2 - OFFSET, (screen_height - rainbow_height) / 2, &rainbow_list); + } + 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,1,1, &cat_list); + add_rainbow(x - OFFSET, y, &rainbow_list); + } + } + + + /* clear initial input */ + while(SDL_PollEvent(&event)); + + /* Pre-populate with sparkles */ + for (int i = 0; i < 200; ++i){update_sparkles(&sparkle_list);} + +} + +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(&sparkle_list); + update_rainbows(&cat_list, &rainbow_list); + draw_sparkles(&sparkle_list); + draw_rainbows(&rainbow_list); + draw_cats(&cat_list); + + handle_input(); + + if (sine){handle_sine(&cat_list);} + if (bounce){handle_bounce(&cat_list);} + + /* 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: <width>x<height> (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\ + -o, --bounce Bounce cat around.\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; + } |