Intel sponsors gamedev.net search:   
Adventures in Text-modeBy Twisol      
In order of no progress to done: Red, Orange, Yellow, Blue, Light blue, Green, Light green.

Last updated 2/12/09 at 12:01 am PST

Cripes! 2.0 -- See the Class Layout

Arenamatic Code w/ cConsole


Saturday, November 29, 2008
With the help of some of the folks on IRC, I finally got some basic user input working. And it doesn't hog the CPU, either! I think the code speaks better than I can this time.

bool Game::Update(INPUT_RECORD* records, DWORD numEvents)
{
    bool retval = false;
    for (DWORD i = 0; i < numEvents; ++i)
    {
        if (records[i].EventType == KEY_EVENT)
        {
            if (records[i].Event.KeyEvent.bKeyDown)
            {
                switch (records[i].Event.KeyEvent.wVirtualKeyCode)
                {
                    case VK_UP:
                        retval = true; y -= 1; break;
                    case VK_DOWN:
                        retval = true; y += 1; break;
                    case VK_LEFT:
                        retval = true; x -= 1; break;
                    case VK_RIGHT:
                        retval = true; x += 1; break;
                }
            }

            keypress[records[i].Event.KeyEvent.wVirtualKeyCode] = (records[i].Event.KeyEvent.bKeyDown != 0);
        }
    }
    return retval;
}

int Game::Run()
{
    Draw();

    INPUT_RECORD* records = new INPUT_RECORD[1];
    DWORD numEvents = 1;
    bool redraw = false;

    while (ReadConsoleInput(hIn, records, numEvents, &numEvents))
    {
        redraw = Update(records, numEvents);
 
        if (keypress[VK_ESCAPE])
            break;
 
        if (redraw)
        {
            Draw();
            redraw = false;
        }

        GetNumberOfConsoleInputEvents(hIn, &numEvents);
        if (numEvents)
        {
            delete[] records;
            records = new INPUT_RECORD[numEvents];
        }
    }
    delete[] records;

    return 0;
}



It's very basic right now, but most of that is going to go into that Keyboard class I mentioned (currently not being used). While I was working this out, I was using a while (true) loop. That turned out to be a very bad idea, because when I opened up the Task Manager, Cripes was sucking up almost 20% of my CPU time. I figured this version out, luckily, which uses far less CPU time because ReadConsoleInput() blocks when there's no input in the buffer.

This code will definitely be changing, mostly as I move code into the Keyboard class and start utilizing it in Run(). For now I'm just glad I can kinda-sorta move the map around. :P

~Jonathan

Comments: 2 - Leave a Comment

Link



Friday, November 28, 2008
I'm not actually sure how to proceed here. User input is easy enough on its own, but I need to continually Draw() at a certain framerate as well as watch for user input (and act on it wen it comes in). I created a Keyboard class, but I'm really not sure how to do this.

I want to draw, at the moment, once every 0.25 seconds. I also want to watch for user input constantly, and move the "camera" every time a key is pressed (but only allow them to move once every so often). And I don't want to continually loop, because I think that hogs CPU; I just want to do something if there's something to do, or it's time to draw.

The only thing I've managed to do, that works in some respect, checks the console input buffer every time through the loop and sets the appropriate element in an array of bools depending on what key event comes through. Then it draws, and Draw() checks the time elapsed between the last time we drew and now, and if we're good to go, it checks the arrow key booleans and moves accordingly. The problem here is that it only works if we held the key down when Draw() actually draws. If we hit an arrow key after 50ms since the last Draw(), and let go at 100ms since the last draw, it's like we never hit the key at all - Draw() doesn't recognize that we tried to move. And it keeps looping whether we have anything to do or not.

Obviously I'm new to handling keyboard input like this. I think WaitForMultipleObjects() could help (because it can wait on an input buffer handle), but I still have no idea how to proceed. A poke in the right direction would be hugely appreciated! =\
~Jonathan

Comments: 4 - Leave a Comment

Link



Thursday, November 27, 2008
I just finished adding a wrap-around functionality to World::Draw(), with a boolean parameter to enable it. It was actually a lot simpler than I thought it would be. In the previous version, without wrapping support, I had to clip the SMALL_RECT parameter entirely down to [0, width) and [0, height). In this version, I put the clipping code in an if-statement, only to be run if the wrap flag is false. Within the loops that map each World buffer index to a temporary buffer, I calculate the indexes with modulo, and if it's a negative result, I subtract it from width/height (because it's already floored down to that range). Here's the final code:

BOOL World::Draw(SMALL_RECT& clip, COORD drawloc, bool wrap) const
{
    if (clip.Left > clip.Right || clip.Top > clip.Bottom)
        throw std::exception("Bottom/Right cannot be greater than Top/Left!");

    if (!wrap)
    {
        if (clip.Left < 0) clip.Left = 0;
        if (clip.Top < 0) clip.Top = 0;
        if (clip.Right >= width) clip.Right = width - 1;
        if (clip.Bottom >= height) clip.Bottom = height - 1;
    }

    int view_width = (clip.Right - clip.Left + 1), view_height = (clip.Bottom - clip.Top + 1);

    if (view_width == 0 || view_height == 0)
        throw std::exception("Viewing dimensions too small.");

    CHAR_INFO* buf = new CHAR_INFO[view_width*view_height];

    for (int y = 0; y < view_height; ++y)
    {
        for (int x = 0; x < view_width; ++x)
        {
            int world_x = (x + clip.Left) % width,
                world_y = (y + clip.Top) % height;

            if (world_x < 0) world_x += width;
            if (world_y < 0) world_y += height;

            buf[x+(y*view_width)].Attributes = buffer[world_x+(world_y*width)].Attributes;
            buf[x+(y*view_width)].Char = buffer[world_x+(world_y*width)].Char;
    	}
    }

    SMALL_RECT writeloc = {drawloc.X, drawloc.Y, view_width+drawloc.X-1, view_height+drawloc.Y-1};
    COORD bufsize = {view_width, view_height};
    COORD origin = {0, 0};

    BOOL retval = WriteConsoleOutput(screen, buf, bufsize, origin, &writeloc);
    delete buf;
    return retval;
}






I also noticed this time around that I had a memory leak on the last line. Originally I was just returning the return value of WriteConsoleOutput(), but I had completely forgotten to delete[] the temporary buffer I passed to it. The last three lines here solved that.

So it wasn't nearly as hard as I thought it would be. Awesome!

~Jonathan

Comments: 3 - Leave a Comment

Link



Wednesday, November 26, 2008
First things first: [insert paragraph of rage]. I just lost everything I typed out for this post because I lost my internet connection right when I hit Reply. ARGH.

*sigh* Okay... After some comments and discussion on #gamedev, I decided to get rid of World::View and the DrawView functions. I replaced them with two functions, SetScreenBuffer() and Draw(). The first sets the console screen buffer to draw to (via a HANDLE), and the second draws the World to the screen. Draw() is important here; I'm sure you can figure out in about five seconds how SetScreenBuffer works.

World::Draw() takes two parameters: a SMALL_RECT& defining the viewing rectangle, and a COORD specifying the upper-left coordinate of the view, relative to the screen buffer being drawn to. Being able to specify the screen buffer via SetScreenBuffer() can be useful in case you want to do double-buffering or something, but in this case it's useful because I created a new buffer for the console, and I want World to draw there instead of the original buffer. (Hey, look at that, the last few lines in the editor all begin with "buffer" at the beginning. Including this one! ^_^)

EDIT: I was going to add something into this past paragraph, but I didn't want to break the line of 'buffer' >_>. You can use an offscreen buffer to edit what the View gives you, too. For example, changing the background color or odd things like that. You could draw entities in at this stage too, sprites and whatnot, but in this case, I'm going to let the World handle that.
--END EDIT

Here's the code for Game::Draw(), which calls World::Draw(). It includes the older version of the code too, for comparison.
void Game::Draw()
{
    /*/
    /// Old version
    World::View worldview = world.CropView();
    SMALL_RECT drawRect = {0, 3, worldview.Width()-1, worldview.Height()+2};
    COORD origin = {0, 0};
    COORD worldsize = {worldview.Width(), worldview.Height()};

    WriteConsoleOutput(hOut, worldview.CharInfo(), worldsize, origin, &drawRect);
    /*/
    /// New version
    SMALL_RECT cliprect = {0, 0, world.Width(), world.Height()};
    COORD drawloc = {0, 3};
    world.Draw(cliprect, drawloc);
    //*/
}

The first thing I noticed was that it's much easier to specify where to draw, not to mention to see where the drawing coordinate are in the first place. The old version had them integrated into the drawRect. In the new version, everything looks much clearer, and concise too.

Incidentally, this approach makes it a bit easier to add a flag to World::Draw for wraparound views. That is, if the right edge of the map is visible near the middle of the viewing rect, the left edge adjoins to it and continues to the right, wrapping around like a cylinder or sphere. (You don't sail off the edge of a cartographical map, you return to the opposite edge)

Also note the interesting assortment of comment blocks here. Each of the comments with a * in it acts as part of a toggling structure, with the first /*/ acting as the switch. If the switch has two stars (or zero), the old version code is active. If it has only one, the new version code activates. I thought it was nifty.


~Jonathan

P.S. This post is waiting for me to send it right now, because the internet is still down. Thankfully, I saved it to a text file this time, just in case.

Comments: 2 - Leave a Comment

Link



Tuesday, November 25, 2008
Today (and partially last night) I wrote up a View class to wrap the clipped view of the World returned by World::DrawView(). It's constructible only by World (excepting the copy-ctor, which is public), taking width and height parameters to create a dynamically-allocated CHAR_INFO buffer. It overloads the [] operator and a conversion operator (because it was easy and it makes sense), and handles the buffer's memory itself.

class View
{
    private:
        CHAR_INFO* view;
        int width, height;

        View(int width, int height);
        friend const View World::DrawView(int, int, int, int) const;
    public:
        View(const View& v);
        ~View();

        View& operator=(const View& v);
        CHAR_INFO* const operator[] (int idx);
        operator const CHAR_INFO* const() const;

        int Width() const;
        int Height() const;
};




This is all I need to do to draw my world view to the screen:
void Game::Draw()
{
    World::View worldview = world.DrawView(0, 0, world.Width(), world.Height());
    SMALL_RECT drawRect = {0, 3, worldview.Width()-1, worldview.Height()+2};
    COORD origin = {0, 0};
    COORD worldsize = {worldview.Width(), worldview.Height()};

    WriteConsoleOutput(hOut, worldview, worldsize, origin, &drawRect);
}

Notice that I can pass worldview to WriteConsoleOutput? That's the conversion operator (converts to const CHAR_INFO* const). I think it looks really clean. DrawView() takes the xy coordinates of the upper-left and lower-right rectangle to view. Technically, passing the world's Width and Height puts the rect one over the edge on the right and bottom sides, but DrawView clips it down, so it's a small shortcut rather than subtracting one from each. Then again, I have to do it in the drawRect anyways...

Comments: 3 - Leave a Comment

Link


Cripes! - The Snipes clone


After doing a little more reasearch, I tried using CreateConsoleScreenBuffer() to create a separate buffer for Cripes to draw to. It turns out that separate screen buffers keep separate state, so I don't really need to worry about saving the old state. My Game class constructor handles the set-up of the console window, and here's the source code:

Game::Game()
	: hOut(CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL)),
	  hIn(GetStdHandle(STD_INPUT_HANDLE)),
	  hPrev(GetStdHandle(STD_OUTPUT_HANDLE)),
	  world(40, 25)
{
	if (hOut == INVALID_HANDLE_VALUE)
		throw std::exception("Unable to create new console buffer.");
	else if (hIn == INVALID_HANDLE_VALUE)
		throw std::exception("Unable to access console input buffer.");

	if (!SetConsoleActiveScreenBuffer(hOut))
		throw std::exception("Unable to set the active console buffer.");

	SMALL_RECT win_size = {0, 0, 39, 24};
	SetConsoleWindowInfo(hOut, true, &win_size);
	COORD buff_size = {40, 25};
	SetConsoleScreenBufferSize(hOut, buff_size);

	SetConsoleTitle("Cripes 2.0 - A Snipes Clone");

	CONSOLE_CURSOR_INFO cci;
	GetConsoleCursorInfo(hOut, &cci);
	cci.bVisible = false;
	SetConsoleCursorInfo(hOut, &cci);
}








It's actually more simple than I expected. The initializer list sets up the handles and gives our World its dimensions, and the block of if-statements makes sure they're all valid. The next block of code sets the window size, then the buffer size. You actually have to do it in that order here, otherwise we'd set a buffer smaller than the window, which Win32 doesn't like. Then we set the title, and I make the cursor invisible. Take a look:



It actually looks like a normal window filled with a solid blue color. Or it would if the icon didn't scream "CONSOLE!!" I made it that color via a world.FillColor() function I'm using for testing purposes only, so I can see if this stuff is working properly. It is.


Day-to-day Life


I just got my driver's permit. The written test I took allowed a maximum of eight mistakes. I made seven. At least I passed. I'm just a little nervous about driving for my first time... make that really nervous. :|

~Jonathan


EDIT: I don't know what happened here, but it sure looks interesting!

EDIT 2: Ah, I botched my conversion from 2D coordinates to 1D indices.

Comments: 0 - Leave a Comment

Link



Monday, November 24, 2008
Don't get all excited, I haven't gotten all that far from where I was last night. I've been working on the world view (typename changed from WorldView to World) and I think I have a far better design for it than last time. The World is constructed with a world width and height, and it doesn't care in the least what part is viewable. I think that makes more sense anyways.

World also has a DrawView method that takes the width and height of a viewing window, and the upper-left coordinate of the area to be displayed. It currently returns a CHAR_INFO pointer to a dynamically-allocated buffer, but I don't like having 'new' in one class and 'delete' in a whole different one, so I might be wrapping it into a new WorldView class representing the viewing area, with the new and delete calls both in the same logical area. Comments on that idea would be welcome.

Now, about the map generation bit. I replayed the Snipes game, downloaded from here if I didn't post that already, and I've come to the conclusion that the maze is randomly generated each run. I think I'm actually excited about that... but I really, really want to get the core elements in first.

One thing I'm not quite so keen on is the collision detection, although I think it was so horrendous before because of bad design. But yeah... back to World coding.

~Jonathan


EDIT: I can't speak for the rest of Win32 - I've never used it in depth - but Microsoft did a great job on the console functions. I just found [Get|Set]ConsoleCursorInfo functions, easily removing the annoying blinking cursor from the screen. I'm being careful to save the original values of things like the title and the cursor settings, so I can return them to normal when the program is finished. I'm not honestly sure what would happen if I left them like that when the program finished, and I had run it from cmd.exe myself.

Comments: 0 - Leave a Comment

Link



Sunday, November 23, 2008
If you haven't noticed, I seem to have some issues committing myself to any particular goal. I know plenty of you have had the same problem with projects, past or present, but it seems to be really ingrained with me. And unfortunately I'm not sure what to do about it, because it's heavily tied into my over-achieving nature. And I -like- everything I do, the problem is the jack-of-all-trades-master-of-none thing. Bleh.

Well, I've finally gotten back to Cripes, starting from scratch. Yes, I'm icing the MUD. It was more than I expected... plus VMware with Ubuntu wrecked havoc on my internet connection. Anyways, I'm putting effort into the game view first, because I know I can do the HUD just fine. That was about the only part I actually finished the first time around, although I want to run over it again later on. We'll see how it goes. Here's a list of my previously finished projects though, just because.

Finished projects:
-Console color/position manipulators
-cConsole (it's a beast, but it's reasonably done)

Unfinished projects:
-"Link's Grand Quest" - my first program ever, and according to my critics, my best work. *groan*
-Arenamatic
-eMUD
-Cripes (in progress)


~Jonathan

Comments: 0 - Leave a Comment

Link


All times are ET (US)

 
S
M
T
W
T
F
S
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
30

OPTIONS
Track this Journal

 RSS 

ARCHIVES
August, 2009
July, 2009
March, 2009
February, 2009
December, 2008
November, 2008
October, 2008
August, 2008
July, 2008
June, 2008
May, 2008
April, 2008
November, 2007
July, 2007
June, 2007
May, 2007
April, 2007
March, 2007
February, 2007
December, 2006
October, 2006
September, 2006
July, 2006
May, 2006
April, 2006