+ Reply to Thread
Results 1 to 4 of 4

Thread: C++ ncurses help

  1. #1
    kbjradmin's Avatar
    kbjradmin is offline x10 Elder kbjradmin is an unknown quantity at this point
    Join Date
    Feb 2008
    Location
    Washington State, USA
    Posts
    512

    C++ ncurses help

    i am teaching myself C++ and as a project to get used to the language i am making a simple dice game using ncurses. i got everything to work up to the point where i tried to put the individual dice in their own windows. there is no error occurring, but the window isn't showing up. please help.

    Code:
    /*****************************************************************************
    * 
    * dice.cpp
    * Author: James Brumond
    * Date Created: 24 October, 2009
    * 
    * A simple dice game.
    * 
    *****************************************************************************/
    
    #include <string>
    #include <string.h>
    #include <iostream>
    #include <sstream>
    #include <ctime>
    #include <cstdlib>
    #include <stdio.h>
    #include <stdlib.h>
    #include <ncurses.h>
    
    using namespace std;
    
    // function prototypes
    void endProg(int);
    void startCurses();
    WINDOW *createNewWin(int, int, int, int);
    
    // initialize windows
    WINDOW *wins [5] = { createNewWin(5, 9, 0, 10), createNewWin(5, 9, 0, 20), createNewWin(5, 9, 0, 30), createNewWin(5, 9, 0, 40),
                createNewWin(5, 9, 0, 50) };
    
    // the die class
    class Die {
        private:
            int side;
            int value;
            int values [6];
            const char* display;
            const char* displays [6];
        public:
            Die(unsigned int);
            void roll();
            void set(int);
            int getValue();
            const char* getDisplay();
            int win;
            void draw();
    };
    
    // initialize the die
    // set all of the die's values, display texts, and the window where the die
    // outputs data to
    Die::Die(unsigned int window) {
        // when die is a one
        values[0] = 1;
        displays[0] = (char*)"\n         \n    o    \n         \n\n  Die %d", (window + 1);
        
        // when die is two
        values[1] = 2;
        displays[1] = (char*)"\n  o      \n         \n      o  \n\n  Die %d", (window + 1);
        
        // when die is three
        values[2] = 3;
        displays[2] = (char*)"\n  o      \n    o    \n      o  \n\n  Die %d", (window + 1);
        
        // when die is four
        values[3] = 4;
        displays[3] = (char*)"\n  o   o  \n         \n  o   o  \n\n  Die %d", (window + 1);
        
        // when die is five
        values[4] = 5;
        displays[4] = (char*)"\n  o   o  \n    o    \n  o   o  \n\n  Die %d", (window + 1);
        
        // when die is six
        values[5] = 6;
        displays[5] = (char*)"\n  o   o  \n  o   o  \n  o   o  \n\n  Die %d", (window + 1);
        
        // set values
        side = 0;
        value = values[side];
        display = displays[side];
        win = window;
    }
    
    // takes a number between 0 and 5
    // sets the die's value and display according to 
    void Die::set(int newSide) {
        if (newSide > 5 || newSide < 0) {
            cout << "void Die::set(int) must recieve a value between 0 and 5." << endl;
            cout << "fatal error: aborting..." << endl;
            endProg(1);
        }
        else {
            side = newSide;
            value = values[side];
            display = displays[side];
        }
    }
    
    // roll the die
    // uses srand with the current time for seed and rand % 6 to get
    // a random value between 0 and 5
    void Die::roll() {
        srand(time(0));
        side = rand() % 6;
        set(side);
    }
    
    // get the numerical value of the die
    int Die::getValue() {
        return value;
    }
    
    // get the text to be displayed for the die
    const char* Die::getDisplay() {
        return display;
    }
    
    void Die::draw() {
        Die::roll();
        wprintw(wins[win], Die::getDisplay());
        wborder(wins[win], '|', '|', '-', '-', '+', '+', '+', '+');
        
    }
    
    // end curses and exit the program
    void endProg(int status) {
        try {
            endwin();
        }
        catch(...) {  }
        exit(status);
    }
    
    // start the curses library
    void startCurses() {
        initscr();
        cbreak();
        keypad(stdscr, true);
    }
    
    // create a new window with a border
    WINDOW *createNewWin(int height, int width, int y, int x) {
        WINDOW *localWin;
        localWin = newwin(height, width, y, x);
        wborder(localWin, '|', '|', '-', '-', '+', '+', '+', '+');
        wrefresh(localWin);
        return localWin;
    }
    
    // main entry point of the program
    int main() {
        startCurses();
        Die dice [5] = { Die(0), Die(1), Die(2), Die(3), Die(4) };
        dice[0].roll();
        dice[0].draw();
        while (0 == 0) {
            char ch = getch();
            if (ch == 'q') {
                break;
            }
            dice[0].roll();
            dice[0].draw();
        }
        endProg(0);
    }
    Last edited by kbjradmin; 10-24-2009 at 05:51 PM.

  2. #2
    misson is offline x10 Spammer misson is a jewel in the rough
    Join Date
    Mar 2008
    Location
    Libertatia
    Posts
    2,506

    Re: C++ ncurses help

    Quote Originally Posted by kbjradmin View Post
    i am teaching myself C++ and as a project to get used to the language i am making a simple dice game using ncurses. i got everything to work up to the point where i tried to put the individual dice in their own windows. there is no error occurring, but the window isn't showing up. please help.
    Learn to use whatever debugger you have available. Break on Die::draw() and you'll see that wins[win] is NULL. Keep reading for an explanation.

    Quote Originally Posted by kbjradmin View Post
    Code:
    // initialize windows
    WINDOW *wins [5] = { createNewWin(5, 9, 0, 10), createNewWin(5, 9, 0, 20), createNewWin(5, 9, 0, 30), createNewWin(5, 9, 0, 40),
                createNewWin(5, 9, 0, 50) };
    Initializing globals happens before main() is invoked, which means the createNewWin calls happen before initscr() is called. Either move the initialization of wins[] to within main() or go fully OO. The former is easier, the latter purer and more instructive. It's also more complex. First you apply the Resource Acquisition Is Initialization (RAII) principle--create a curses class that handles curses initialization; creating an instance of the curses class calls the curses initialization functions. Since you only want to initialize curses once, make the curses class a singleton. Then write a Curses::Window class (the "Curses::" refers to a namespace, not an outer class) that gets the curses instance in the Curses::Window constructor. The first window that gets created causes curses to be initialized. If no window is created, curses isn't initialized.

    In any case, globals are evil. Try to find another way.

    Magic numbers are also evil. "5", representing the number of dice, is scattered all over. If you changed the number of dice, you might miss an instance. The simplest solution is to define a global constant that sets the number of dice.

    Code:
    const int diceCount=5;
    Another solution is to write a macro that calculates the length of an array:
    Code:
    #define $LENGTH(arr) (sizeof(arr) / sizeof(*arr))
    WINDOW *wins [5];
    
    class Die {
        public:
            Die(unsigned int=0);
    ...
        Dice dice[$LENGTH(wins)];
        // start from 1 since dice[0] is already initialized to Dice(0)
        for (int i=1; i < $LENGTH(wins); ++i) {
            dice[i] = Dice(i);
        }
    Note that $LENGTH won't work with pointers.

    Quote Originally Posted by kbjradmin View Post
    Code:
        displays[0] = (char*)"\n         \n    o    \n         \n\n  Die %d", (window + 1);
    I don't know what you think this line does, but it doesn't do it. The comma operator causes the left side to be evaluated, then the right. The value of the comma is the value of the right side (the left side's value is discarded). The line is equivalent to:
    Quote Originally Posted by kbjradmin View Post
    Code:
        displays[0] = (char*)"\n         \n    o    \n         \n\n  Die %d";
        window + 1;
    Note that the comma operator has lowest precedence. You can overload the comma, but that's generally a bad idea.

    Quote Originally Posted by kbjradmin View Post
    Code:
    void Die::roll() {
        srand(time(0));
        side = rand() % 6;
    You only need to seed a random number generator once. Seeding it more than once will have a pronounced affect on the quality of the numbers it produces. Of course, since we're dealing with rand(), it's not like they're quality to begin with.


    Quote Originally Posted by kbjradmin View Post
    Code:
    void Die::draw() {
        Die::roll();
        wprintw(wins[win], Die::getDisplay());
        wborder(wins[win], '|', '|', '-', '-', '+', '+', '+', '+');
        
    }
    ...
            dice[0].roll();
            dice[0].draw();
    roll() gets called twice with each iteration of the loop. Since the call to roll() within draw() is almost certainly wrong, remove that one.

    You never refresh the window in Die::draw so it gets redrawn on the screen.

    Having to redraw the frame is messy. One solution is to draw the frame in a parent and draw the dice face in a child. Another is to make the frame a part of the face.

    Quote Originally Posted by kbjradmin View Post
    Code:
        while (0 == 0) {
    This works fine. I just wanted to mention the clearer alternatives:
    Code:
    while (true) {...}
    for (;;) {...}
    Both of those are idiomatic forever loops. Someone reading the code knows it's intentional. With other forever conditions (such as 0 == 0), there's a chance, however small, that the loop condition is in error. Some people go so far as to "#define forever for (;;)".

    Instead of a forever loop, you could move the character test to the loop condition:
    Code:
        Die dice [$LENGTH(wins)] = { Die(0), Die(1), Die(2), Die(3), Die(4) };
        do {
            for (int i=0; i<$LENGTH(wins); ++i) {
                dice[i].roll();
                dice[i].draw();
            }
        } while ('q' != getch());

    Quote Originally Posted by kbjradmin View Post
    Code:
        while (0 == 0) {
            char ch = getch();
    getch() gets a character from the default window, stdscr, which is returned by initscr() (unless there's an error). This ends up covering your other windows. There are various solutions: use wgetch(), resize stdscr with wresize(), make the dice windows subwindows of stdscr using subwin() or derwin() rather than newwin(). I recommend the last.

    Quote Originally Posted by kbjradmin View Post
    Code:
    void Die::set(int newSide) {
        if (newSide > 5 || newSide < 0) {
            cout << "void Die::set(int) must recieve a value between 0 and 5." << endl;
            cout << "fatal error: aborting..." << endl;
            endProg(1);
        }
    ...
    void endProg(int status) {
        try {
            endwin();
        }
        catch(...) {  }
        exit(status);
    }
    This is backwards. Die::set should throw an exception. Catch that in main outside the "while" loop, print the error. You use exceptions because the time when an error happens isn't the best time to handle the error. Maybe the function up the call chain from Die::set has a way of fixing the error; by exiting, you prevent recovery. Since uncaught exceptions will cause the program to exit, there's also no need to explicitly call exit().

    endwin is from a C library; it won't throw exceptions.


    One other issue: a Die is tightly coupled to displaying itself, but the wins and dice arrays are uncoupled; this is backwards. Separate the model (the stuff you'd write if you didn't need to interact with a user) from the view (how the model is displayed). You don't need to go full MVC in this case.
    Be sure to read all pages linked in this post; they have further information that should prove useful. When asking for help, make sure you follow Eric Raymond's and Jon Skeet's guidelines for prompt, accurate responses. Please answer any questions I ask; they're not rhetorical (probably). Any posted code is intended as illustrative example, rather than a solution to your problem to be copied without alteration. Study it to learn how to write your own solution.
    Misson, not Mission.

  3. #3
    kbjradmin's Avatar
    kbjradmin is offline x10 Elder kbjradmin is an unknown quantity at this point
    Join Date
    Feb 2008
    Location
    Washington State, USA
    Posts
    512

    Re: C++ ncurses help

    thank you misson, i will work on this some more and if i have problems or don't understand something in your post, i'll post back.



    edit:

    I just wanted to mention that i did get the windows working. now i get to actually make it into a game...

    anyway, thanks for the help, misson. here is my new code if you're interested.

    Code:
    /*****************************************************************************
    * 
    * dice.cpp
    * Author: James Brumond <kbjr14@gmail.com>
    * Date Created: 24 October, 2009
    * 
    * A simple dice game.
    * 
    *****************************************************************************/
    
    #include <string>
    #include <string.h>
    #include <iostream>
    #include <sstream>
    #include <ctime>
    #include <cstdlib>
    #include <stdio.h>
    #include <stdlib.h>
    #include <ncurses.h>
    
    using namespace std;
    
    // global definitions
    const int diceCount = 5;
    
    
    
    /*****************************************************************************
    *   START THE CONTROL CLASS
    *****************************************************************************/
    
    
    
    class Control {
    	private:
    	public:
    		Control();
    		void endProg(int status);
    } control;
    
    Control::Control() {
    	return;
    }
    
    // end curses and exit the program
    void Control::endProg(int status) {
    	exit(status);
    }
    
    
    
    /*****************************************************************************
    *   END THE CONTROL CLASS
    *****************************************************************************/
    
    
    
    /*****************************************************************************
    *   START THE CURSES HANDLING CLASS
    *****************************************************************************/
    
    class CursesHandle {
    	private:	
    		WINDOW *windows [20];
    		int windowCount;
    	public:
    		CursesHandle();
    		~CursesHandle();
    		WINDOW *createNewWin(int, int, int, int);
    } curses;
    
    // initialize curses and the window array
    CursesHandle::CursesHandle() {
    	initscr();
    	cbreak();
    	keypad(stdscr, true);
    	curs_set(0);
    	windows[0] = *&stdscr;
    	windowCount = 1;
    	return;
    }
    
    // stop curses
    CursesHandle::~CursesHandle() {
    	curs_set(1);
    	endwin();
    }
    
    // create a new window and add a reference to it to the
    // CursesHandle.windows array
    WINDOW *CursesHandle::createNewWin(int height, int width, int y, int x) {
    	WINDOW *localWin;
    	localWin = newwin(height, width, y, x);
    	wrefresh(localWin);
    	windows[windowCount] = *&localWin;
    	windowCount++;
    	return *&localWin;
    }
    
    /*****************************************************************************
    *   END THE CURSES HANDLING CLASS
    *****************************************************************************/
    
    
    
    /*****************************************************************************
    *   START THE DIE CLASS
    *****************************************************************************/
    
    
    
    class Die {
    	private:
    		int side;
    		int value;
    		int values [6];
    		const char* display [6];
    		const char* displays [6][6];
    		WINDOW *win;
    		bool held;
    	public:
    		Die(unsigned int=0);
    		void roll();
    		void set(int);
    		int getValue();
    		const char** getDisplay();
    		void draw();
    		bool isHeld();
    		void toggleHold();
    		void setHold(bool);
    };
    
    // initialize the die
    // set all of the die's values, display texts, and the window where the die
    // outputs data to
    Die::Die(unsigned int die) {
    	// build the title string
    	const char* title;
    	switch (die) {
    		case 0: return; break;
    		case 1: title = " Die One "; break;
    		case 2: title = " Die Two "; break;
    		case 3: title = "Die Three"; break;
    		case 4: title = " Die Four"; break;
    		case 5: title = " Die Five"; break;
    	}
    	
    	// when die is a one
    	values[0] = 1;
    	displays[0][0] = (char*)"+-------+";
    	displays[0][1] = (char*)"|       |";
    	displays[0][2] = (char*)"|   o   |";
    	displays[0][3] = (char*)"|       |";
    	displays[0][4] = (char*)"+-------+";
    	displays[0][5] = (char*)title;
    	
    	// when die is two
    	values[1] = 2;
    	displays[1][0] = (char*)"+-------+"; 
    	displays[1][1] = (char*)"| o     |";
    	displays[1][2] = (char*)"|       |";
    	displays[1][3] = (char*)"|     o |";
    	displays[1][4] = (char*)"+-------+";
    	displays[1][5] = (char*)title;
    	
    	// when die is three
    	values[2] = 3;
    	displays[2][0] = (char*)"+-------+";
    	displays[2][1] = (char*)"| o     |";
    	displays[2][2] = (char*)"|   o   |";
    	displays[2][3] = (char*)"|     o |"; 
    	displays[2][4] = (char*)"+-------+";
    	displays[2][5] = (char*)title;
    	
    	// when die is four
    	values[3] = 4;
    	displays[3][0] = (char*)"+-------+";
    	displays[3][1] = (char*)"| o   o |";
    	displays[3][2] = (char*)"|       |";
    	displays[3][3] = (char*)"| o   o |";
    	displays[3][4] = (char*)"+-------+";
    	displays[3][5] = (char*)title;
    	
    	// when die is five
    	values[4] = 5;
    	displays[4][0] = (char*)"+-------+";
    	displays[4][1] = (char*)"| o   o |";
    	displays[4][2] = (char*)"|   o   |";
    	displays[4][3] = (char*)"| o   o |";
    	displays[4][4] = (char*)"+-------+";
    	displays[4][5] = (char*)title;
    	
    	// when die is six
    	values[5] = 6;
    	displays[5][0] = (char*)"+-------+";
    	displays[5][1] = (char*)"| o   o |";
    	displays[5][2] = (char*)"| o   o |";
    	displays[5][3] = (char*)"| o   o |";
    	displays[5][4] = (char*)"+-------+";
    	displays[5][5] = (char*)title;
    	
    	// set values
    	side = 0;
    	value = values[side];
    	for (int i = 0; i < 6; i++) {
    		display[i] = displays[side][i];
    	}
    	win = curses.createNewWin(6, 9, 0, ((10 * die) - 10));
    	held = false;
    	return;
    }
    
    // takes a number between 0 and 5
    // sets the die's value and display according to 
    void Die::set(int newSide) {
    	if (newSide > 5 || newSide < 0) {
    		cout << "void Die::set(int) must recieve a value between 0 and 5." << endl;
    		cout << "fatal error: aborting..." << endl;
    		control.endProg(1);
    	}
    	else {
    		side = newSide;
    		value = values[side];
    		for (int i = 0; i < 6; i++) {
    			display[i] = displays[side][i];
    		}
    	}
    	return;
    }
    
    // roll the die
    // uses srand with the current time for seed and rand % 6 to get
    // a random value between 0 and 5
    void Die::roll() {
    	side = (rand() % 6);
    	set(side);
    	return;
    }
    
    // get the numerical value of the die
    int Die::getValue() {
    	return value;
    }
    
    // draw the die to the screen in the proper location
    void Die::draw() {
    	for (int i = 0; i < 6; i++) {
    		mvwprintw(win, i, 0, display[i]);
    	}
    	wrefresh(win);
    	return;
    }
    
    // returns whether or not the die is being held
    bool Die::isHeld() {
    	return held;
    }
    
    // toggles whether or not the die is held
    void Die::toggleHold() {
    	if (isHeld()) {
    		held = false;
    	}
    	else {
    		held = true;
    	}
    	return;
    }
    
    // set manually whether or not the die is held
    void Die::setHold(bool hold) {
    	held = hold;
    	return;
    }
    
    
    
    /*****************************************************************************
    *   END THE DIE CLASS
    *****************************************************************************/
    
    
    
    /*****************************************************************************
    *   START THE DICE CLASS
    *****************************************************************************/
    
    
    
    class Dice {
    	private:
    	public:
    		Dice();
    		Die dice [diceCount];
    		void rollAll();
    		void drawAll();
    		void rollUnheld();
    		void drawUnheld();
    		void unholdAll();
    } dice;
    
    // initialize the dice
    Dice::Dice() {
    	for (int i = 0; i < diceCount; i++) {
    		dice[i] = Die(i+1);
    	}
    }
    
    // roll all of the dice
    void Dice::rollAll() {
    	for (int i = 0; i < diceCount; i++) {
    		dice[i].roll();
    	}
    }
    
    // draw all of the dice
    void Dice::drawAll() {
    	for (int i = 0; i < diceCount; i++) {
    		dice[i].draw();
    	}
    }
    
    // roll all of the unheld dice
    void Dice::rollUnheld() {
    	for (int i = 0; i < diceCount; i++) {
    		if (! dice[i].isHeld()) {
    			dice[i].roll();
    		}
    	}
    }
    
    // draw all of the unheld dice
    void Dice::drawUnheld() {
    	for (int i = 0; i < diceCount; i++) {
    		if (! dice[i].isHeld()) {
    			dice[i].draw();
    		}
    	}
    }
    
    // unhold all of the dice
    void Dice::unholdAll() {
    	for (int i = 0; i < diceCount; i++) {
    		dice[i].setHold(false);
    	}
    }
    
    
    
    /*****************************************************************************
    *   END THE DICE CLASS
    *****************************************************************************/
    
    
    
    // main entry point of the program
    int main() {
    	// seed rand()
    	srand(time(0));
    	
    	// get user input and take action accordingly
    	// exit on keypress 'q'
    	do {
    		// draw dice to screen
    		dice.rollAll();
    		dice.drawAll();
    	} while (getch() != 'q');
    	
    	// end ncurses and exit
    	control.endProg(0);
    }
    Last edited by kbjradmin; 10-25-2009 at 04:52 PM.

  4. #4
    misson is offline x10 Spammer misson is a jewel in the rough
    Join Date
    Mar 2008
    Location
    Libertatia
    Posts
    2,506

    Re: C++ ncurses help

    That's better. You still have a few magic fives, but they're easily taken care of by adding a "Die::sides" field and using that instead. Class Control seems a needless indirection, and Die::set() should throw an exception rather than calling exit(). Better yet, make Die::set() protected so you don't need to worry about getting a bad value from outside your Die class hierarchy. Make newSide and Die::side unsigned, and replace the error check in Die::set() with a assert(newSide < sides) for a sanity check in development builds. In main(), there's also no need to call exit(); you can use a return statement.

    Now that you've done your own work, here's an illustration of some of the stuff I mentioned.

    Singleton class to initialize curses:
    Code:
    /* in Curses.h */
    namespace Curses {
        class Curses {
        public:
            static Curses& getInstance();
            ~Curses();
        private:
            Curses();
        };
    }
    
    /* in Curses.cc */
    namespace Curses {
        Curses& Curses::getInstance() {
            /* static variable "curses_" creates a single instance of Curses that exists over the lifetime
               of the process and will be destroyed when process exits.
             */
            static Curses curses_;
            return curses_;
        }
    
        Curses::Curses() {
            initscr();
            savetty();
            cbreak();
            keypad(stdscr, true);
            noecho();
        }
    
        Curses::~Curses() {
            resetty();
            endwin();
        }
    }
    Here's how it's used:
    Code:
    /* in Window.h */
    namespace Curses {
        class Window {
        public:
            Window(int x, int y, int width, int height, WINDOW* par=stdscr);
            Window(int x, int y, int width, int height, Window& par);
            virtual ~Window();
    
            Window& resize(int width, int height);
            // update window on screen
            virtual Window& draw();
            // regenerate content & redraw
            virtual Window& refresh();
            virtual Window& print(const char*);
            virtual Window& print(int i);
            virtual Window& moveCursor(int x, int y);
    
        protected:
            void init(int x, int y, WINDOW* par);
    
            WINDOW *wndw_;
            int width_, height_;
        };
    }
    
    /* in Window.cc */
        void Window::init(int x, int y, WINDOW* par) {
            Curses& c = Curses::getInstance();
            wndw_ = derwin(par, height_, width_, y, x);
            refresh();
        }
    The line: Curses& c = Curses::getInstance(); is what ensures that curses is initialized.

    The singleton implementation of Curses isn't superior to your CursesHandle, but it does have the advantage that you can't accidentally initialize curses more than once. Another advantage, one that doesn't matter in this application, is a matter of efficiency: if you don't use curses, you don't have to pay the cost of initializing it. Also, Curses doesn't pollute the global namespace with a global instance, but you could fix that in your code in various ways, such as by making CursesHandle curses into a static member of CursesHandle or by putting it in a namespace.

    The downside to Curses is that you might neglect to get the instance in a class that requires curses, but as all curses classes descend from Window in my example, this isn't a problem.

    Die is very simple:
    Code:
    /* in Die.h */
    class Die {
    public:
        Die::Die(unsigned sides=6) : sides_(sides), value_(0) {}
    
        unsigned roll();
        operator unsigned();
        unsigned sides();
        Die& setSides(unsigned sides);
    
    protected:
        unsigned sides_;
        unsigned value_;
        Random r_;
    };
    
    /* in Die.cc */
    unsigned Die::roll() {
        // add code to notify any watchers that value is changing
        return value_ = r_() % sides_;
    }
    
    Die::operator unsigned()
    { return value_ + 1; }
    
    unsigned Die::sides()
    { return sides_; }
    
    Die& Die::setSides(unsigned sides) {
        sides_ = sides;
        return *this;
    }
    Die relies on a simple wrapper around random() named Random that takes care of seeding. Displaying is handled by a DiceView class. Check out the attached source for the rest of the implementation. It's far from perfect, but should be illustrative of various techniques.
    Attached Files
    Last edited by misson; 10-26-2009 at 03:37 AM.
    Be sure to read all pages linked in this post; they have further information that should prove useful. When asking for help, make sure you follow Eric Raymond's and Jon Skeet's guidelines for prompt, accurate responses. Please answer any questions I ask; they're not rhetorical (probably). Any posted code is intended as illustrative example, rather than a solution to your problem to be copied without alteration. Study it to learn how to write your own solution.
    Misson, not Mission.

+ Reply to Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
x10hosting free hosting for the masses
dedicated servers