{"id":2996,"date":"2023-12-19T13:40:44","date_gmt":"2023-12-19T18:40:44","guid":{"rendered":"https:\/\/www.both.org\/?p=2996"},"modified":"2023-12-19T16:17:22","modified_gmt":"2023-12-19T21:17:22","slug":"holiday-snow-on-your-terminal","status":"publish","type":"post","link":"https:\/\/www.both.org\/?p=2996","title":{"rendered":"Holiday snow on your terminal"},"content":{"rendered":"<div class=\"pld-like-dislike-wrap pld-template-1\">\r\n    <div class=\"pld-like-wrap  pld-common-wrap\">\r\n    <a href=\"javascript:void(0)\" class=\"pld-like-trigger pld-like-dislike-trigger  \" title=\"\" data-post-id=\"2996\" data-trigger-type=\"like\" data-restriction=\"cookie\" data-already-liked=\"0\">\r\n                        <i class=\"fas fa-thumbs-up\"><\/i>\r\n                <\/a>\r\n    <span class=\"pld-like-count-wrap pld-count-wrap\">    <\/span>\r\n<\/div><\/div>\n<p>I live in Minnesota and we haven&#8217;t gotten a lot of snow yet, so I figured I&#8217;d add a little &#8220;holiday cheer&#8221; by writing a program to add &#8220;gently falling snow&#8221; to my terminal instead. This program is also a good demonstration for how to update your terminal in character mode using the ncurses library.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Initialize the terminal<\/h2>\n\n\n\n<p>Before you can use any terminal-drawing routines from ncurses, your program will need to initialize the terminal. Most programs will use <code>initscr()<\/code> to set up the screen and set certain global values such as <code>COLS<\/code> for the number of columns and <code>LINES<\/code> for the number of lines, followed by <code>cbreak()<\/code> to disable buffering, and <code>noecho()<\/code> so characters typed by the user aren&#8217;t &#8220;echoed&#8221; on screen. These functions are defined in <code>curses.h<\/code>.<\/p>\n\n\n\n<p>Some programs might also call <code>intrflush(stdscr, FALSE)<\/code> to disable &#8220;flushing&#8221; the screen output if the user presses an interrupt key. You can also use <code>curs_set<\/code> to control how the cursor should be displayed; use <code>curs_set(0)<\/code> to completely hide the cursor.<\/p>\n\n\n\n<p>When the program is done, you must call <code>endwin()<\/code> to exit ncurses. Programs that simply do a thing and then end might wait for the user to press a key before returning to the command line. We can use the <code>getch()<\/code> function to return a single keystroke from the keyboard, which effectively waits for the user before taking action.<\/p>\n\n\n\n<p>The basic outline for a program that uses ncurses to clear the screen and wait for the user to press a key looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;curses.h&gt;\n\nint\nmain()\n{\n  initscr();\n  cbreak();\n  noecho();\n\n  intrflush(stdscr, FALSE);\n  curs_set(0);\n\n  getch();\n\n  endwin();\n  return 0;\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Set up the screen<\/h2>\n\n\n\n<p>To write our &#8220;falling snow&#8221; program, we&#8217;ll first need to establish the &#8220;ground&#8221; on the terminal. The <code>hline(ch,n)<\/code> function in ncurses will draw a horizontal line of n characters at the current position on the screen. To draw the line at a specific location, we can use the <code>move(y,x)<\/code> function to set the position before calling <code>hline(ch,n)<\/code> to add the characters, or we can call <code>mvhline(y,x,ch,n)<\/code> to do both at the same time.<\/p>\n\n\n\n<p>If we want to add text, such as a &#8220;Happy holidays&#8221; message, the <code>addstr(s)<\/code> function will add a string at the current screen position. We can use the similarly named <code>mvaddstr(y,x,s)<\/code> to add a string at a specific coordinate on the terminal.<\/p>\n\n\n\n<p>Screen coordinates count from zero, so the top-left coordinate on the screen is <code>0,0<\/code> and the bottom-left position on the screen is <code>LINES-1,0<\/code>. We can use these coordinates to add a horizontal line and some text to the bottom of the screen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  mvhline(LINES - 2, 0, ACS_HLINE, COLS);\n  mvaddstr(LINES - 1, 0, \"Let it snow!\");\n  refresh();<\/code><\/pre>\n\n\n\n<p>The <code>refresh()<\/code> function instructs ncurses to update the screen with the new text.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Gently falling snowflakes<\/h2>\n\n\n\n<p>To simulate the effect of a &#8220;gently falling snowflake&#8221; from the top of the screen, we&#8217;ll need to write a function called <code>drop<\/code> that draws an asterisk to represent a snowflake. Let&#8217;s define the function as <code>drop(int row, int col)<\/code> to start the snowflake at a specific coordinate. The function can continue to move the snowflake down the screen until it reaches some non-blank character, such as the border we drew with mvhline.<\/p>\n\n\n\n<p>The <code>addch(ch)<\/code> function from ncurses adds a character at the current position on the screen. Like other functions that draw to the screen, we can use the <code>mv<\/code> variant of this function to first move to a specific location before adding the character. For example, to add an asterisk at row 5 and column 3, you would use <code>mvaddch(5,3,'*')<\/code>.<\/p>\n\n\n\n<p>We can use <code>mvaddch<\/code> again and again to reposition an asterisk. By adding a slight pause between iterations, we can create the effect of a single snowflake falling to the ground:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void\ndrop(int row, int col)\n{\n  \/* drop a \"snowflake\" starting at row,col .. keep going until we run out\n     of empty space *\/\n\n  int y = row;\n\n  mvaddch(y, col, '*');\n\n  while (mvinch(y + 1, col) == ' ') {\n    usleep(10000);                     \/* 10000 microsecs = .01 secs *\/\n    mvaddch(y, col, ' ');\n    mvaddch(++y, col, '*');\n    refresh();\n  }\n}<\/code><\/pre>\n\n\n\n<p>The <code>usleep<\/code> function is defined in <code>unistd.h<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Randomize the snow<\/h2>\n\n\n\n<p>With our <code>drop<\/code> function to simulate a single snowflake, we can draw lots of snow falling one by one by using the same function across every column on the screen. But snow doesn&#8217;t fall from left to right; it&#8217;s random.<\/p>\n\n\n\n<p>A simple way to randomize how the snow falls on our screen is to generate an array of all the column coordinates, then &#8220;shuffle&#8221; the array so all the coordinates appear in random order. Initializing the array is easy; that&#8217;s just assigning a number to each member in the array.<\/p>\n\n\n\n<p>One way to randomize the array is to swap each element with another element chosen at random. The most effective way to do this is by &#8220;walking&#8221; backwards through the array and choosing another random member that comes before it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void\nrandfill(int *array, int nsize)\n{\n  srand(time(NULL));                   \/* pseudorandom but works anywhere *\/\n\n  for (int i = 0; i &lt; nsize; i++) {\n    array&#91;i] = i;\n  }\n\n  for (int i = nsize - 1; i &gt; 0; i--) {\n    swap(array, i, rand() % (i + 1));  \/* pick a random number 0..i *\/\n  }\n}<\/code><\/pre>\n\n\n\n<p>The Linux kernel provides a <code>getrandom<\/code> system call that generates a series of random bytes, which effectively produces a random number. To allow you to compile this program on other systems, I&#8217;ve used the <code>rand<\/code> function from the C standard library. This produces <em>pseudorandom<\/em> values, which aren&#8217;t completely random, but good enough for a &#8220;falling snow&#8221; program\u2014and it works everywhere.<\/p>\n\n\n\n<p>I could have written the &#8220;swap&#8221; feature inside the second loop, but I thought the code would be a little more readable if I moved that into a separate function called <code>swap<\/code> that swaps two elements of an array:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void\nswap(int *ary, int i, int j)\n{\n  \/* swap two elements i and j *\/\n  int tmp = ary&#91;i];\n  ary&#91;i] = ary&#91;j];\n  ary&#91;j] = tmp;\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Let it snow<\/h2>\n\n\n\n<p>With these functions, we can now update the main program to draw the &#8220;ground&#8221; and some text on the terminal, then simulate gently falling snow. So the program doesn&#8217;t take forever to run, this only fills up the bottom five lines with &#8220;snow.&#8221; Or if you&#8217;ve defined a really small terminal window that&#8217;s less than ten lines, the program fills up about half of the screen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;stdlib.h&gt;\n#include &lt;time.h&gt;\n\n#include &lt;curses.h&gt;\n#include &lt;unistd.h&gt;\n\n#include &lt;sys\/param.h&gt;\n\nvoid\nswap(int *ary, int i, int j)\n{\n  \/* swap two elements i and j *\/\n  int tmp = ary&#91;i];\n  ary&#91;i] = ary&#91;j];\n  ary&#91;j] = tmp;\n}\n\nvoid\nrandfill(int *array, int nsize)\n{\n  srand(time(NULL));                   \/* pseudorandom but works anywhere *\/\n\n  for (int i = 0; i &lt; nsize; i++) {\n    array&#91;i] = i;\n  }\n\n  for (int i = nsize - 1; i &gt; 0; i--) {\n    swap(array, i, rand() % (i + 1));  \/* pick a random number 0..i *\/\n  }\n}\n\nvoid\ndrop(int row, int col)\n{\n  \/* drop a \"snowflake\" starting at row,col .. keep going until we run out\n     of empty space *\/\n\n  int y = row;\n\n  mvaddch(y, col, '*');\n\n  while (mvinch(y + 1, col) == ' ') {\n    usleep(10000);                     \/* 10000 microsecs = .01 secs *\/\n    mvaddch(y, col, ' ');\n    mvaddch(++y, col, '*');\n    refresh();\n  }\n}\n\nint\nmain()\n{\n  int *colx;\n\n  initscr();\n  cbreak();\n  noecho();\n\n  intrflush(stdscr, FALSE);\n  curs_set(0);                         \/* hide cursor *\/\n\n  \/* allocate the x coords *\/\n\n  colx = calloc(sizeof(int), COLS);\n\n  if (colx == NULL) {                  \/* error *\/\n    endwin();\n    return 1;\n  }\n\n  randfill(colx, COLS);\n\n  \/* set up the screen *\/\n\n  mvhline(LINES - 2, 0, ACS_HLINE, COLS);\n  mvaddstr(LINES - 1, 0, \"Let it snow!\");\n  refresh();\n\n  \/* snow *\/\n\n  for (int row = 0; row &lt; MIN(5, LINES \/ 2); row++) {\n    for (int c = 0; c &lt; COLS; c++) {\n      drop(0, colx&#91;c]);\n    }\n  }\n\n  \/* done *\/\n\n  mvaddstr(LINES - 1, 0, \"press any key to quit . . .\");\n  getch();\n\n  endwin();\n\n  free(colx);\n  return 0;\n}<\/code><\/pre>\n\n\n\n<p>Save the program as <code>snow.c<\/code>, then compile and run it to simulate gently falling snow on your terminal:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ gcc -Wall -o snow snow.c -lncurses<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"774\" height=\"605\" src=\"https:\/\/www.both.org\/wp-content\/uploads\/2023\/12\/snow.png\" alt=\"Screenshot of a program showing gently falling snow on a Linux terminal\" class=\"wp-image-2998\"\/><figcaption class=\"wp-element-caption\">Screenshot of our program showing gently falling snow on a Linux terminal.<\/figcaption><\/figure>\n\n\n\n<p>Just in case you want to play with this little program without compiling it yourself, here&#8217;s a link to <a href=\"https:\/\/www.both.org\/downloads\/snow\" data-type=\"link\" data-id=\"https:\/\/www.both.org\/downloads\/snow\" target=\"_blank\" rel=\"noreferrer noopener\">snow<\/a>. Download it to your system and change the permissions to 555. If it&#8217;s in your path, launch it from your terminal session by typing <code><strong>snow<\/strong><\/code>. If it&#8217;s not in the path, prepend the path to the program name. Use <code><strong>.\/snow<\/strong><\/code> if it&#8217;s in the current directory.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I live in Minnesota and we haven&rsquo;t gotten a lot of snow yet, so I figured I&rsquo;d add<\/p>\n","protected":false},"author":33,"featured_media":2814,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_lmt_disableupdate":"","_lmt_disable":"","footnotes":""},"categories":[98],"tags":[147,91,144],"class_list":["post-2996","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-code","tag-fun","tag-linux","tag-ncurses"],"modified_by":"David Both","_links":{"self":[{"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/2996","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/users\/33"}],"replies":[{"embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2996"}],"version-history":[{"count":3,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/2996\/revisions"}],"predecessor-version":[{"id":3007,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/2996\/revisions\/3007"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/media\/2814"}],"wp:attachment":[{"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2996"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2996"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2996"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}