{"id":6607,"date":"2024-10-12T00:01:38","date_gmt":"2024-10-12T07:01:38","guid":{"rendered":"https:\/\/c-for-dummies.com\/blog\/?p=6607"},"modified":"2024-10-05T10:57:59","modified_gmt":"2024-10-05T17:57:59","slug":"more-terminal-screen-manipulation","status":"publish","type":"post","link":"https:\/\/c-for-dummies.com\/blog\/?p=6607","title":{"rendered":"More Terminal Screen Manipulation"},"content":{"rendered":"<p>Bouncing a cursor on the screen is a fun programming exercise, and you can use common C library techniques and ANSI escape sequences to make it happen in a terminal window, as covered in <a href=\"https:\/\/c-for-dummies.com\/blog\/?p=6595\">last week&#8217;s Lesson<\/a>. At this point, most programmers would be content and leave well enough alone. Not me!<br \/>\n<!--more--><br \/>\nThe first, and major, change I made to the code is to turn the single asterisk into a line of asterisks &mdash; a snake. So instead of a single asterisk bouncing around the terminal screen, a conga line of asterisks bounces around the screen. The effect looks like a snake trying to escape.<\/p>\n<h3><a href=\"https:\/\/github.com\/dangookin\/C-For-Dummies-Blog\/blob\/master\/2024_10_12-Lesson-a.c\" rel=\"noopener\" target=\"_blank\">2024_10_12-Lesson-a.c<\/a><\/h3>\n<pre class=\"screen\">\r\n#include &lt;stdio.h&gt;\r\n#include &lt;time.h&gt;\r\n#include &lt;unistd.h&gt;\r\n#include &lt;sys\/ioctl.h&gt;\r\n\r\n#define home() printf(\"\\e[H\")\r\n#define clear() printf(\"\\e[H\\e[2J\")\r\n\r\n<span class=\"comments\">\/* check for a key waiting *\/<\/span>\r\nint kbhit(void)\r\n{\r\n    int k;\r\n\r\n    ioctl(STDIN_FILENO,FIONREAD,&amp;k);\r\n\r\n    return(k);\r\n}\r\n\r\n<span class=\"comments\">\/* set the cursor to position x (columns)\r\n   and y (rows ) and set character c *\/<\/span>\r\nvoid putat(x,y,c)\r\n{\r\n    printf(\"\\e[%d;%dH%c\",y,x,c);\r\n}\r\n\r\n<span class=\"comments\">\/* pause for m milliseconds *\/<\/span>\r\nvoid delay(int m)\r\n{\r\n    long pause;\r\n    clock_t now,then;\r\n\r\n    pause = m*(CLOCKS_PER_SEC\/1000);\r\n    now = then = clock();\r\n    while( (now-then) &lt; pause )\r\n        now = clock();\r\n}\r\n\r\n<span class=\"comments\">\/* set an asterisk in the center of the screen *\/<\/span>\r\nint main()\r\n{\r\n    int rows,columns,vert,horz,a;\r\n    const int length = 7;\r\n    struct winsize w;\r\n    struct coord {\r\n        int y;\r\n        int x;\r\n    } tail[length];\r\n    char buffer[BUFSIZ];\r\n\r\n    <span class=\"comments\">\/* obtain the window size *\/<\/span>\r\n    ioctl(STDOUT_FILENO, TIOCGWINSZ, &amp;w);\r\n    rows = w.ws_row;\r\n    columns = w.ws_col;\r\n\r\n    <span class=\"comments\">\/* remove line buffering *\/<\/span>\r\n    setvbuf(stdout,buffer,_IONBF,BUFSIZ);\r\n\r\n    <span class=\"comments\">\/* initialize *\/<\/span>\r\n    clear();\r\n    puts(\"Press Enter to end\");\r\n    vert=1;\r\n    horz=1;\r\n    for( a=0; a&lt;length; a++ )\r\n    {\r\n        tail[a].y = columns\/2;\r\n        tail[a].x = rows\/2;\r\n    }\r\n\r\n    <span class=\"comments\">\/* bounce the asterisk *\/<\/span>\r\n    while(1)\r\n    {\r\n        <span class=\"comments\">\/* end the loop on key press *\/<\/span>\r\n        if( kbhit() )\r\n        {\r\n            getchar();\r\n            break;\r\n        }\r\n        <span class=\"comments\">\/* output the snake *\/<\/span>\r\n        putat( tail[0].y, tail[0].x, ' ');    <span class=\"comments\">\/* erase the tail *\/<\/span>\r\n        for( a=1; a&lt;length; a++ )\r\n            putat( tail[a].y, tail[a].x, '*');\r\n        <span class=\"comments\">\/* update the snake *\/<\/span>\r\n        for( a=0; a&lt;length-1; a++ )\r\n        {\r\n            tail[a].y = tail[a+1].y;\r\n            tail[a].x = tail[a+1].x;\r\n        }\r\n        <span class=\"comments\">\/* move the head *\/<\/span>\r\n        tail[length-1].x += horz;\r\n        tail[length-1].y += vert;\r\n        delay(125);                <span class=\"comments\">\/* 1\/8 sec. delay *\/<\/span>\r\n        <span class=\"comments\">\/* check bounds *\/<\/span>\r\n        if( tail[length-1].y==columns-1 || tail[length-1].y==1 )\r\n            vert=-vert;            <span class=\"comments\">\/* switch directions *\/<\/span>\r\n        if( tail[length-1].x==rows-1 || tail[length-1].x==2 )\r\n            horz=-horz;            <span class=\"comments\">\/* switch directions *\/<\/span>\r\n    }\r\n    <span class=\"comments\">\/* set the cursor and say goodbye *\/<\/span>\r\n    putat(1,rows-1,'B');\r\n    puts(\"ye!\");\r\n\r\n    return 0;\r\n}<\/pre>\n<p>This code requires a massive update to the <em>main()<\/em> function to account for plotting multiple asterisks in a line. To accomplish this task, the <em>coord<\/em> structure is created, which contains members <code>y<\/code> and <code>x<\/code> for rows and columns. This structure is declared as an array, <code>tail<\/code>, with a given <code>length<\/code> defined as a constant integer.<\/p>\n<p>The entire &#8220;snake&#8221; is initialized to the center screen position in a <em>for<\/em> loop:<\/p>\n<p><code>for( a=0; a&lt;length; a++ )<br \/>\n{<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;tail[a].y = columns\/2;<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;tail[a].x = rows\/2;<br \/>\n}<\/code><\/p>\n<p>Within the endless <em>while<\/em> loop, an asterisk is set at every position in the tail structure array:<\/p>\n<p><code>for( a=1; a&lt;length; a++ )<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;putat( tail[a].y, tail[a].x, '*');<\/code><\/p>\n<p>Then the snake&#8217;s position is updated, copying each array element from the head (the last element, <code>tail[length-1]<\/code>) back down to the tail (<code>tail[0]<\/code>):<\/p>\n<p><code>for( a=0; a&lt;length-1; a++ )<br \/>\n{<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;tail[a].y = tail[a+1].y;<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;tail[a].x = tail[a+1].x;<br \/>\n}<\/code><\/p>\n<p>Finally, the head is moved according to the direction variables <code>horz<\/code> and <code>vert<\/code>:<\/p>\n<p><code>tail[length-1].x += horz;<br \/>\ntail[length-1].y += vert;<\/code><\/p>\n<p>A delay occurs, followed by <em>if<\/em> statements to check the boundaries and reverse the <code>horz<\/code> and <code>vert<\/code> direction variables if necessary.<\/p>\n<p>Figure 1 illustrates the results. You can change the snake&#8217;s length by resetting the <code>length<\/code> variable, though as a constant it can&#8217;t be adjusted as the code runs.<\/p>\n<div id=\"attachment_6609\" style=\"width: 650px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-6609\" src=\"https:\/\/c-for-dummies.com\/blog\/wp-content\/uploads\/2024\/10\/1012-snake.gif\" alt=\"\" width=\"640\" height=\"427\" class=\"size-full wp-image-6609\" \/><p id=\"caption-attachment-6609\" class=\"wp-caption-text\">Figure 1. The snake!<\/p><\/div>\n<p>Not content with my messing around, I tried another update to the code, one to add color. This change is done by updating only the <em>putat()<\/em> function, shown here:<\/p>\n<pre class=\"screen\">\r\n<span class=\"comments\">\/* set the cursor to position x (columns)\r\n   and y (rows ) and set character c *\/<\/span>\r\nvoid putat(x,y,c)\r\n{\r\n    int color;\r\n\r\n    color = x\/10;\r\n    switch(color)\r\n    {\r\n        case 0:            <span class=\"comments\">\/* red *\/<\/span>\r\n            printf(\"\\e[31m\\e[%d;%dH%c\",y,x,c);\r\n            break;\r\n        case 1:            <span class=\"comments\">\/* green *\/<\/span>\r\n            printf(\"\\e[32m\\e[%d;%dH%c\",y,x,c);\r\n            break;\r\n        case 2:            <span class=\"comments\">\/* yellow *\/<\/span>\r\n            printf(\"\\e[33m\\e[%d;%dH%c\",y,x,c);\r\n            break;\r\n        case 3:            <span class=\"comments\">\/* blue *\/<\/span>\r\n            printf(\"\\e[34m\\e[%d;%dH%c\",y,x,c);\r\n            break;\r\n        case 4:            <span class=\"comments\">\/* magenta *\/<\/span>\r\n            printf(\"\\e[35m\\e[%d;%dH%c\",y,x,c);\r\n            break;\r\n        case 5:            <span class=\"comments\">\/* cyan *\/<\/span>\r\n            printf(\"\\e[36m\\e[%d;%dH%c\",y,x,c);\r\n            break;\r\n        default:\r\n            printf(\"\\e[37m\\e[%d;%dH%c\",y,x,c);\r\n    }\r\n}<\/pre>\n<p>The column position is divided by ten &mdash; which is a guess, assuming an 80-column screen. Using the screen&#8217;s actual width would be a better determination. These ANSI color codes were covered in an <a href=\"https:\/\/c-for-dummies.com\/blog\/?p=5270\">earlier blog post<\/a>.<\/p>\n<p>You can obtain the full code for this update <a href=\"https:\/\/github.com\/dangookin\/C-For-Dummies-Blog\/blob\/master\/2024_10_12-Lesson-b.c\" rel=\"noopener\" target=\"_blank\">from GitHub<\/a>. Its output looks like Figure 1, but with colors applied as the snake horizontally traverses the screen. (The GIF I made doesn&#8217;t capture the colors adequately.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Once I realized I could play with the cursor on the terminal screen, I just went nuts! <a href=\"https:\/\/c-for-dummies.com\/blog\/?p=6607\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-6607","post","type-post","status-publish","format-standard","hentry","category-main"],"_links":{"self":[{"href":"https:\/\/c-for-dummies.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/6607","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/c-for-dummies.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/c-for-dummies.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/c-for-dummies.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/c-for-dummies.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=6607"}],"version-history":[{"count":5,"href":"https:\/\/c-for-dummies.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/6607\/revisions"}],"predecessor-version":[{"id":6620,"href":"https:\/\/c-for-dummies.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/6607\/revisions\/6620"}],"wp:attachment":[{"href":"https:\/\/c-for-dummies.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=6607"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/c-for-dummies.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=6607"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/c-for-dummies.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=6607"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}