GithubHelp home page GithubHelp logo

hhy5277 / command-line-guide Goto Github PK

View Code? Open in Web Editor NEW

This project forked from harrinp/command-line-guide

0.0 1.0 0.0 249 KB

Guide to making your first command line app/game

License: MIT License

command-line-guide's Introduction

Guide to making your first command line project with ncurses

Intro

Everyone's first programming experience is on some form of command line, usually a hello world in your language of choice.

int main() {
    printf("Hello World!\n");
    return EXIT_SUCCESS;
}

But eventually you grow out of it, you start to think "the command line is only for debugging" well I'm here to defend the command line, and all of its glory. Writing full-blown apps in the command line is not only fun, but its easy, and it can look damn good (assuming you're down for a retro look). Making a game or app as a command line app is quicker and easier than messing around with complex graphics libraries, or god forbid, css.

This guide is going to be a look at how you can make command line apps and games (like this one) simply and easily using the ncurses library. ncurses is a very common library for posix-compliant (typically this means Macos and Linux) command lines.

Caveats

This guide isn't going to go over anything to do with windows, ncurses supports posix compliant terminals. You may be able to get ncurses working on windows, but I offer no guarantees. Further, there are other libraries like ncurses which are for windows, namely conio.h. I won't cover it here but the principles should be similar.

Other guides

I'm not the expert, just a dev who already went through this learning process. This guide is going to be a bare-bones "get up and running" lesson, you can find a more detailed explanation of ncurses and all of its capabilities here and here.

Language choice

ncurses, like many libraries is a C library, I wrote all of these programs and examples in C, partly because that's the library language, and partly because C is beautiful. I'm a firm believer that programmers should have at least a working knowledge of the C language since it's a building block of basically everything. But I digress, you could also use C++ or something else, but you might need a different library or a connection method, my knowledge is nonexistant in this area. I recommend trying these examples in C, even if you aren't a C guru these examples should be easy to understand and expand upon. Have fun!

Example files

All example files will be included in the examples folder of this repository.

Getting started

Hello world v2

Lets redo our old hello world program and give it a fancy twist.

Make sure you include ncurses.h

#include <ncurses.h>

Now we need to initialize ncurses, and make sure we leave ncurses mode when we're done. This is done with initscr() and endwin().

int main() {
    initscr();
    printf("Hello World!\n");
    endwin();
    return EXIT_SUCCESS;
}

Now we need to update our printf with the ncurses variant, printw(). This works the same way as printf. Then we need to refresh() so that the data we've put in is shown. Then theres getch(), getch() is the ncurses function to get input as a character, but here we're using it so that the program will wait for a character before endwin(), where it clears the screen and returns to normal mode.

int main() {
    initscr();
    printw("Hello World!");
    refresh();
    getch();
    endwin();
    return EXIT_SUCCESS;
}

Compile - make sure to link ncurses with -lncurses

gcc helloWorld.c -o hello -lncurses

And run with ./hello!

Not super exciting

Fantastic, but thats not interesting. Lets spice it up. I'm going to now use mvprintw(), its the same as printw but you need to give it two coordinates, y then x, as the first two parameters.

int main() {
    initscr();
    mvprintw(5,5,"Hello World!");
    refresh();
    getch();
    endwin();
    return EXIT_SUCCESS;
}

Result:

Not super exciting

See? We've moved the hello world down 5 lines down and 5 characters over.

Terminal animation with ncurses

Now lets do something more interesting. We'll make a ball (the character 'o') and have it bounce off the walls of the terminal window.

We'll create a new file BouncingO.c, and add in two structs to hold our data.

// Stores our balls position
typedef struct _Ball {
    int x;
    int y;
} Ball;
// Stores the direction that the ball is moving
typedef struct _Direction {
    int x;
    int y;
} Direction;

Initialize with our ncurses functions

initscr();              // Start ncurses
nodelay(stdscr, TRUE);  // Don't wait for \n for getch to parse input
cbreak();               // Switch off input buffering
curs_set(FALSE);        // Don't place a cursor on the screen

Initialize our ball and window information

int height = 0;         // Window height
int width = 0;          // Window width

// initialize our ball struct and direction struct
Ball b = {
    1,1
};
Direction d = {
    1,1
};

// Get terminal window dimensions (rows and characters)
getmaxyx(stdscr, height, width);

Note: getmaxyx() does not get passed variable addresses, but it does change the variables you pass in.

And finally, make our "game" loop:

while (getch() == ERR) {
    // print ball
    mvprintw(b.y,b.x,"o");

    // move ball for next frame
    b.y += d.y;
    b.x += d.x;

    // Check for ball being outside of our window boundaries
    if (b.x == width - 1 || b.x == 0){
        // change direction
        d.x *= -1;
        b.x += d.x;
    }
    if (b.y == height - 1 || b.y == 0){
        // change direction
        d.y *= -1;
        b.y += d.y;
    }

    refresh();      // Refresh the output
    usleep(50000);  // Sleep to show output (Single frame)
    clear();        // Clear output
}

Most of this is self explanatory but its worth noting that refresh() and clear() are ncurses functions and usleep is a unistd.h function. The "game" logic of the ball is just adding the direction -1 or 1 to the ball each frame, and changing the direction when it contacts the edge.

Result:

Bouncing Ball

Fun Right?

Colors

Any self respecting game dev needs to use colors, ncurses makes this easy to do in the terminal. Because of the way that terminals work with color this has to be done with color pairs. 1 is the id of this color pair.

init_pair(1, COLOR_RED, COLOR_BLACK);  // Always start at 0
// This makes a red foreground on a black background

You then use it like this:

attron(COLOR_PAIR(1));  // Enable the color pair  
// Print something
attroff(COLOR_PAIR(1)); // Its good practice to disable it

This is nice but its hard to use easily, I find it best to abstract this process. Here's an example for a colored, multi-ball version of the bouncingO program.

This function abstracts away the color pair creation process. Basically, ncurses deals with colors as color pairs only, foreground and background. Since I only want to color the balls, I used -1 as the background (you MUST call use_default_colors() before doing this). Since I don't care which colors are which I'm just using a for loop to make all the possible color pairs with a blank background. When making your own program you may want to use just a single or a couple color pairs. You would make those like this: init_pair(ID, COLOR_RED, COLOR_WHITE); That will have a red letter on a white background. The IDs must start at 1 and go up.

void makeColorPairs(){
    for (int i = 0; i < 8; i++) {
        init_pair(i+1, i, -1);
    }

Then I use them by randomly selecting a color pair 1-8 for the balls. It looks like this after combining with randomized initial positions:

Bouncing Balls

Here are the available colors in ncurses:

COLOR_BLACK   0
COLOR_RED     1
COLOR_GREEN   2
COLOR_YELLOW  3
COLOR_BLUE    4
COLOR_MAGENTA 5
COLOR_CYAN    6
COLOR_WHITE   7

Making a larger project

Hopefully now you should be able to see how you could make a terminal project, but I'm going to run through how I made the small terminal screensaver shown above.

Repo here: Bouncing Balls Repo

When making a full-blown game my strategy is to abstract these printing functions so that the game logic is separate from the printing functions. In c this means that I create a main function that calls all of my logic and printing functions, but I keep any printing outside of my logic functions. Here's my main for the bouncing balls screensaver:

int main(int argc, char *argv[]) {
    int numBalls = 100;          // If theres an argument process it
    if (argc > 1){
        numBalls = atoi(argv[1]);
        // Theres no safeguard for bad input (because I'm lazy) don't try this at home
    }
    srand(time(NULL));
    initscr();              // Start ncurses
    nodelay(stdscr, TRUE);  // Don't wait for \n for getch to parse input
    cbreak();               // Switch off input buffering
    curs_set(FALSE);        // Don't place a cursor on the screen
    start_color();
    use_default_colors();

    makeColorPairs();       // Abstracted color pair function

    Window w = initWindow(numBalls, 50000); // Initialize data structs

    while (getch() == ERR) {
        clear();                            // Clear screen
        moveBalls(&w);                      // Movement logic
        printWindow(&w);                    // Print the balls
        usleep(w.sleepTime);                // Sleep to show screen
    }
    destroyWindow(&w);
    nodelay(stdscr, FALSE);
    nocbreak();
    endwin();
    return 0;
}

moveBalls() and printWindow() are abstracted from each other. One big benefit of this is that you can then multithread these two processes so that the game logic (or input for a game) can be processed while the print function sleeps. I did this for walled in which gave the program much better performance than it otherwise would have had.

the while (getch() == ERR){} waits for any key press and then ends the program if it gets one.

Important to this strategy is using structs to hold your data, this allows you to keep the printing and logic separate - just pass the address (a pointer) of the game data struct(s) around and manipulate/read them for logic/printing.

For this project my print function looked like this:

void printWindow(Window * w) {
    for (int i = 0; i < w->numBalls; i++) {
        attron(COLOR_PAIR(w->balls[i].colorPair));      // Applying the ball's color
        mvprintw(w->balls[i].y, w->balls[i].x, w->balls[i].avatar);
        attroff(COLOR_PAIR(w->balls[i].colorPair));     // Stop printing in this color
    }
}

And here are the structs:

typedef struct _Ball{
    // Position
    double x;
    double y;
    // How we display the ball
    char * avatar;
    // Speed (What we add to the x and y each loop iteration)
    double speedX;
    double speedY;

    int colorPair;
} Ball;

typedef struct _Window {
    int height;
    int width;
    int sleepTime;
    int numBalls;
    Ball * balls;
} Window;

Conclusion

Hopefully this guide successfully illustrated how to make games for the terminal, and good strategy for constructing them. If you have any questions or suggestions please contact me!

command-line-guide's People

Contributors

harrinp avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.