summaryrefslogtreecommitdiffstats
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c485
1 files changed, 485 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..f159a7e
--- /dev/null
+++ b/main.c
@@ -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(&regex, "[0-9]\\+x[0-9]\\+", 0);
+ if (regexec(&regex, 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, &current) != 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;
+ }