From: Beth on
Randy wrote:
> Hi All,
>
> This is really for Paul Panks, but the programming technique is
> sufficiently general and interesting that I will post it here.
>
> Paul needs the ability to put status information on the screen in a
> fixed location. This is difficult to do under Windows and Linux in a
> portable fashion, so I am offering an alternate solution: multiple
> windows.
>
> Rather than attempting to put the status message at the same point on a
> screen that is scrolling by (and whose buffer is at an unknown point
> under OSes such as Linux), a cleaner solution is to open up two windows
> - one for the normal game (that scrolls) and one for the status window
> (that does not scroll, and upon which the game writes status
> information to specific spots on the screen). Because the status
> window never scrolls, it is easy to control where stuff is written to
> it (in a portable fashion) and still provide the usual user interface
> in the main game window.

This is meant to be "easier" and "cleaner"?!?

As long as you've got a "set cursor location" function available (whatever OS) then you can do it all in a perfectly "portable" manner...by allocating a text buffer and then using your own "Print" routine which works with this text buffer (and manually scrolls what needs to be scrolled, leaves alone what doesn't)...and then simply "set cursor location" to the top-left and print out the text buffer...

Not just "portable" but easily extendable to add in a buffer that's bigger than what actually fits on the screen...add in a "scrollbar" and, hey presto, you can supply a bit of "history" with the "Page Up" / "Page Down" keys or something...

Basically, allocate a few variables:

----------------------------------------

static

TextBuffer: Char[1600];
ColourBuffer: Byte[1600];

CurrChar: Uns32 := 0;

endstatic;

----------------------------------------

....where "TextBuffer" stores the characters in the scrolling area...and "ColourBuffer" keeps track of the colour information for each character...these two form the "virtual console" buffer area (note: I've decided that it's 20 lines of 80 columns, so that's 1600 characters...obviously, change the size according to the "area" you want to cover :)...and the "CurrChar" variable is used to keep track of where the "virtual cursor" is...

Then we have a simple-ish "Print" routine for this "virtual console":

----------------------------------------

Procedure Print(Message: string;
Colour: Byte);
Begin Print;

// For each character in the "message" string:
// Write character to "TextBuffer" array at "CurrChar" position;
// Write colour to "ColourBuffer" array at "CurrChar" position;
// Increment "CurrChar";
// If "CurrChar" hits ends of buffer (>1600) then
// Scroll buffer by copying bytes in both "TextBuffer" and
// "ColourBuffer" arrays back 80 bytes;
// Decrement "CurrChar" by 80;
// endif;
// endfor;

End Print;

----------------------------------------

Note that this is simple pseudo-code to just give the basic algorithm..."special characters" like a newline sequence or a tab character or whatever would need to be "handled specially" (newline: round upwards to nearest number divisible by 80, tab: Can be treated as a set number of "spaces" or rounded up to a particular column or whatever functionality you want for tab)...

[ Indeed, an alternative to providing a single "Colour" parameter is to "embed" the colour changes in the string...there's a bunch of "control characters" in ASCII that aren't relevent to our routine here - "ACK", "XON", "XOFF", etc. - so these _could_ be redefined as "change to red colour", "change to white colour", etc. control characters instead...then create an "equate" to give it a "nice name" (just like you did with "nl"...you could call these new control characters literally "red", "blue", "green" and so forth :)...then if one of the colour change characters is met then these change the "CurrColour" variable (we add this variable in to deal with that :) but don't put anything in the buffers or advance the "virtual cursor"...then when an ordinary ASCII character is found, we do put that in the buffer but put the "CurrColour" value in the colour array...this _might_ be a more convenient way of dealing with the colours, _IF_ there's a lot of "changing colours" in the middle of strings...this could be used to good effect by, for example, "colour-coding" the text...any word that is actually a recognised "noun" in the game could be cyan coloured, just to supply a "hint" to the player that this is a "game noun" (something that you could use "examine noun" or "take noun" upon :)...recognised verbs could be colour-coded yellow...and so forth...with this scheme (slightly more complicated to implement but, well, you write the _ROUTINE_ for it once and then always call the routine to handle it, of course :), the colours of words could actually be embedded in the strings themselves...perhaps not useful for Paul's game but it's one of many possible ideas :) ]

Then, we'd have our "DrawInfoBar", "DrawBuffer" and "DrawInputBox" (or whatever is required for the game...I'm assuming a basic three-way divide of the screen here :)...and these can simply "console.gotoxy()" to the required places and write out the "info bar" or the "input box"...

For the "DrawBuffer", then this is rather simple too:

----------------------------------------------

Procedure DrawBuffer;
Begin DrawBuffer;

console.gotoxy(0,2);
// for c=1 to 1600
// Write TextBuffer[c] character with
// ColourBuffer[c] colours to console;
// next c

End DrawBuffer;

----------------------------------------------

[ No actual need to "clear" anything here...in overwriting the entire area with the current buffer contents, we're implicitly clearing what was there before, of course :) ]

In order that the "Print" stuff appears immediately then the "Print" routine can end with a "DrawBuffer" call...of course, you could put this all into one routine that redraws all the three areas at the same time or something...but I'm separating it out because, well, if the "info bar" isn't changing contents then there's no actual need to keep redrawing it...a little more "efficient" to just call "DrawInfoBar" when that changes, "DrawBuffer" when that changes and "DrawInputBox" when that changes...because we're controlling the scrolling, the other two areas will stay where they are and don't need constant redrawing...also, if splitting up the routines like this, it's possible to "Print" a whole bunch of strings (a full "room description" or something :) and then "DrawBuffer" after the whole lot, rather than for each line...though, you might want to do it for each line delibrately so that you can see it scroll...

[ In fact, there's another possibility: After every character in the "Print" routine, call "DrawBuffer" and then have a very short pause...then you get that "typewriter" effect of watching it write out the text character by character...just don't make the pause too long...such an "effect" could become a bit "highly annoying" if it takes too long to print things out...and perhaps this kind of "effect" is more of a "sci-fi game" feel than a "fantasy game" style... ]

As you can tell from these various "suggestions" for variations and alternatives, the underlying point is that _YOU_ write the routines to handle this "virtual console"...so, you can basically make it work any which way you like...

Another "suggestion": Increase the buffers to a 100 lines (but only 79 characters across: The reason for "one less" will be explained in a moment :)...add in another "CurrScroll" variable (keeps track of which line is at the "top" on the actual screen from this now "bigger than the screen" buffer :)...when drawing the buffer to the console, draw 79 characters...leaving the 80th column free...in this area, we draw a "scrollbar" (based on the size of the buffer and the value of "CurrScroll" :)...and the buffer is drawn from "CurrScroll" onwards...then if the player hits "Page up" or "Page Down", the "CurrScroll" variable is altered...a little tricky to get it right because you must, of course, ensure that this all "looks correct" on the screen and still scrolls properly (e.g. make sure to "bound" the scroll variable properly so that you can't scroll off the top or off the bottom of the buffer :)...and, hey presto, you've got a "history" to your scrollable area and can page up and down to go back and re-read an earlier message to remind yourself of something that just scrolled by...

This all really isn't as complicated as it sounds...most of it is "own code" and completely under your control...the whole "virtual console" thing is perfectly "portable" (it's a "trick" to get around "direct access" not being "portable" from OS to OS: Simple, if you can't "direct access", then _don't_ "direct access" but use "indirect access" instead via your own little "virtual console" buffer...create your own "virtual console" - that you can access as directly as you like - and then just have a routine that "draws" that buffer onto the real console screen in the correct cursor locations)...

The "multiple windows" is a nice idea...but, well, it seems a lot, lot more complicated to implement than simply learning how to make a nice screen display using the "console functions"...as I say, my little "trick" is to do it all "virtually"...a couple of buffers in RAM that the program _CAN_ directly access and manipulate however it likes in a "portable" way...then just "dump" the buffer contents onto the real console at the appropriate times...

Plus, learning to do it my way here is more advisable in that this teaches a lot more than it immediately looks like it's doing...after all, if those "buffers" were storing pixels rather than characters...if a mouse pointer was moving over the top...it's the basics of "windowing" in general...text or graphical...it doesn't take too much imagination to modify the basic idea here to start adding in things like a "menubar" across the top and "floating windows" to provide "on-line help" when you press F1...or an "inventory window" when you press F2...or whatever...just a case of: "when inventory window is 'ON' then _SKIP_ over drawing those characters which this window obscures in the buffer"...

Another "suggestion": Customise the "drawing routine" a little and use darker shades of the colours for the text for the line just under the "info bar"...there, a fairly simple "drop shadow" effect...

Have you got HLA working to print UTF-8 characters directly to the console? Not difficult with "xterm", anyway, as it does it automatically...but, anyway, if that's there, then have a browse through the UNICODE character set...there are those "box drawing characters", right? That'll be nice to enclose the "input box" inside...

Also, consider, Paul, that for the "multi-player" thing, you're going to have to change the structure of the "engine" a little...basically, swap "line input" for "character input"...accept one character of input at a time and then just "concatenate" them as you go along...this is so the game doesn't "get stuck" - paused - waiting for input...it looks for an input character but if it isn't there, then it doesn't wait and carries on processing the game...

A structure where you look for a single input character...if it's not there, then just carry on...if it is there, then concatenate this new character to the "input string"...if the input character is ENTER then send the string to the "parser"...but we don't call any "line input" function which effectively pauses the game waiting for ENTER...grab one input character at a time...and if there isn't a character there when we look for one, then, no matter, we just carry on to the next "stage"...the next stage can be to look for a "multi-player message"...that is, data sent from another machine about "events" that other players do...if Player2 picks up the candlestick, then that machine will send out a "message" that contains data to let this machine know that the candlestick was picked up...alter game variables appropriately, pop up some "message" to the screen to inform the player what Player2 has just done...

But, again, don't "wait"...don't "pause"...this is what I meant by "real-time" before...you check for user input...if it's there, process that input character...if not, then it doesn't matter, move onto the next "stage"...check for "multi-player messages" coming over the network...if they are there, then process them...if not, then it doesn't matter, move onto the next "stage"...do the "game variables" need changing? If they do, then do it...if not, then it doesn't matter, move onto the next "stage"...and so on...and so forth...always "process if there's something to process...but if there isn't, then don't 'pause' or 'wait' for something to process to appear, just move onto the next 'stage'"...and then loop around and around doing all these "stages"...

And you'll effectively get something like a simple "multi-tasking" feel...the game can carry on - text about "Player2 has picked up the candlestick" appears to inform you what the other players are doing, even though you've not touched the keyboard...they just appear "asynchronously", as and when the other players do things - with or without "user input"...it's no longer "turn-based"...and, basically, for a "multi-player game" then it's going to have to be changed that way (as I said before, the point with the "multi-player game" where each player is on a separate PC is that, in effect, that _IS_ a form of "multi-threaded program"...yes, there may only be one "thread" on any one PC but the game "overall" is the _combination_ of these PCs all "co-operating" with each other...all the same stuff to worry about with "multi-threaded" programs will rear its ugly head in "multi-player mode", unfortunately...well, "multi-player" games are great fun...so I suppose it's only to be expected that there's a "price to pay" for that extra fun in the programming...there is, after all, "no such thing as a free lunch" in this life, eh? ;)...

The "multiple windows" things sounds a bit "overly complicated"...and it's not too "transferrable" because some games just wouldn't be appropriate with the interface scattered all over the desktop...though, that said, it does give "GUI windowing" abilities to a console game (you can re-arrange your game windows on the desktop into your "preferred" configuration, exploiting Windows / X itself to do the "hard work" there :)...but all that "file mapping" and sending messages and such? I don't actually think, in practice, it would be "easier" or "more convenient"...dealing with the "scrolling" yourself is, I think, less of a difficult thing to do than trying to avoid the "scrolling" by using multiple console windows...a case of "diminishing returns" or a "pyrrhic victory", so to speak...it's so much effort avoiding the "scrolling" that you really would have been better off _dealing_ with the "scrolling" issues yourself directly than trying to "side-step" them...

But, hey, that's just my opinion (though I would stress it's "educated" a little because I have written these kinds of programs before and experience suggests you'd find "virtual consoles" a whole lot simpler than "multiple console windows" and "file mapping" by a long way ;)...there is "merit" in the "multiple windows" idea...gives you a "configurable display" without any extra programming (the GUI itself allows the windows to be "re-arranged", so it does all the "hard work" there :)...it _MIGHT_ be "nice" - in "user interface" terms - to have one window for "messages", another window for "player stats" (shows inventory and the standard stuff like "Strength", "Wisdom", "Health" statistics for the player...though, that is heading towards "Role Playing Game" a little more than simply "interactive fiction"...but the dividing line is "fuzzy" on that particular score, anyway, as they are very much similar in style...just the RPG has these "statistics" and is a little more "detailed"...players can progress through "levels"...ah, start with "adventure game" and then work your way up to RPG, perhaps? ;)...

At least, I'd advise giving my "virtual console" idea a whirl first...it really will be the simpler to begin with...then try out the "multiple windows", if that sounds like a "cool" interface style for your adventure game or whatever...it's like Confucius once uttered:

"A journey of a thousand miles begins with a single step"
[ Confucius ]

So, you know, concentrate on taking that "first step"...all the rest that's involved is, well, just repeating that "first step" a few million times until the "thousand miles" is finally covered...the "first step" is always trickiest to take...every step tends to get easier after that one...so, pay attention to the "first step"...worry about the others later ;)...

Beth :)

From: Evenbit on
Frank Kotler wrote:

> This cat's sitting on a bench in Washington Square Park,
> 'bout three o'clock in the AM, you dig? Lady come up to him
> and says, "Does the crosstown bus run all night?"
>
> And he say, "... doo-dah, doo-dah..."

This reminds me of something funny...

http://nbaker.wirefire.com/FifthAvenueBus.zip
13MB Maximum Compression Zip
16MB WAV format

Uncle Josh on a Fifth Avenue Bus, Cal Stewart, 16228-B, Victor Talking
Machine Company

Played on a Victrola VV-210 89864 by the Victor Talking Machine Company
(circa 1923) [pic below]

http://nbaker.wirefire.com/S1010004.jpg
175 KB

Nathan.

From: T.M. Sommers on
Frank Kotler wrote:
>
> Okay, that helps... The memory I'm trying to access is
> around 0x40100000 - that's what mmap returns. It seems as
> if, if I map it as "MAP_PRIVATE" instead of "MAP_SHARED",
> the problem goes away...
>
> I'm doing my experimentation in Nasm - HLA syntax is just
> *too* unintuitive to me :(

Have you tried it in C? That way you can distinguish between a
bug in Linux and a bug in your assembly code.

> Here's what I've got...
>
> global _start
>
> section .data
> filename db 'mmaptest.dat', 0
>
> section .text
> _start:
> nop
>
> mov eax, 5 ; __NR_open
> mov ebx, filename
> mov ecx, 2 | 100q | 1000q ; O_DRWR, O_CREAT, O_TRUNC
> mov edx, 666q

I think this might be a problem: the 666 should be octal, not hex
(which I assume q implies). You can get a SIGBUS if you try to
write but don't have write permission, I think.

> int 80h
> or eax, eax
> js exit
>
> push byte 0
> push eax
> ; push byte 2 ; MAP_PRIVATE this works
> push byte 1 ; MAP_SHARED bus error
> push byte 7 ; PROT_READ | PROT_WRITE | PROT_EXEC
> push 1000h ; size
> push byte 0

I don't know NASM, but those 'push byte's look suspicious. I
would think you would want to push 32-bit values. I also don't
know Linux, but aren't the arguments to syscalls supposed to go
in registers?

> mov ebx, esp
> mov eax, 90 ; __NR_mmap
> int 80h
> ; call showeax ; for debugging... 0x40000000
> mov byte [eax], 'A'
>
> exit:
> mov ebx, eax
> mov eax, 1
> int 80h
>
> When I say this "works" with "MAP_PRIVATE"... it doesn't get
> the bus error - I don't actually see the 'A' in my file.
> That's expected, I guess, without "MAP_SHARED" (?)...

Yes.

> Here's my HLA "version"...

I didn't look. I understand HLA even less than I do NASM.

I am not all that familiar with the nuances of mmap(2), and I
have not read the standard, but playing with the test program
below it appears that you will get a SIGBUS if the original size
fo the file is 0, regardless of the size of the mapping. If the
original size of the file is not zero, you will get a SIGBUS if
you write beyond what has been mapped (which may be greater than
what you requested, depending on the page size). The standard
ought to spell out all this, but I'm too lazy to check (and it's
more fun to experiment).

Here is the little test program in C:

------------ mmap.c ----------------
#include <stdio.h>
#include <stdlib.h>

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
int fd = 0;
const char *fn = "mmap.out";
int error = 0;
char *map = 0;
struct stat sb;
int ch;
int open_flags = O_RDWR | O_CREAT;
int write_pos = 0;
char write_char = 'A';

/* Set to zero so user-supplied size can be stored there. */
sb.st_size = 0;

while ((ch = getopt(argc, argv, "f:s:tw:")) != -1) {
switch ( ch ) {
case 'f':
fn = optarg;
break;
case 's':
sb.st_size = strtoul(optarg, 0, 10);
break;
case 't':
open_flags |= O_TRUNC;
break;
case 'w':
write_pos = strtol(optarg, 0, 10);
break;
case '?':
default:
;
}
}

if ( (fd = open(fn, open_flags, 0666)) < 0 ) {
perror(fn);
error = 1;
}
else if ( sb.st_size == 0 && stat(fn, &sb) != 0 ) {
perror(fn);
error = 1;
}
else if ( (map = mmap(0, sb.st_size,
PROT_READ | PROT_WRITE ,
MAP_SHARED, fd, 0))
== MAP_FAILED ) {
perror(fn);
error = 1;
}

if ( !error ) {
printf("mapped %s with size %lu\n", fn, sb.st_size);
printf("writing '%c' at position %lu\n", write_char,
write_pos);
map[write_pos] = write_char;
munmap(map, sb.st_size);
}

close(fd);
return 0;
}

------------ mmap.c ----------------


--
Thomas M. Sommers -- tms(a)nj.net -- AB2SB

From: Frank Kotler on
"T.M. Sommers" wrote:
>
> Frank Kotler wrote:
> >
> > Okay, that helps... The memory I'm trying to access is
> > around 0x40100000 - that's what mmap returns. It seems as
> > if, if I map it as "MAP_PRIVATE" instead of "MAP_SHARED",
> > the problem goes away...
> >
> > I'm doing my experimentation in Nasm - HLA syntax is just
> > *too* unintuitive to me :(
>
> Have you tried it in C?

While I'm in the "unintuitive" business? :)

> That way you can distinguish between a
> bug in Linux and a bug in your assembly code.

But it doesn't sort out the bugs in my C code. Have you
*seen* my C code???

*Your* C code, OTOH, is very helpful! I assume it "works for
you". I get a segmentation fault if I run it with no
parameters, and a bus error with "tmsmmap -s 100 -w 50". I'm
not entirely sure what the parameters are "supposed" to do,
yet. I'll fool with it some more, but it looks like this
just doesn't work on 2.2.19. I'm surprised - I didn't know
it was a "new" thing.

If that turns out to be true, Paul's game - or, ummm, any
other project that wanted to communicate between processes
in this way - would have a certain "minimum requirement".
Not a problem, but I wouldn't want to do it unknowingly.

....
> > mov ecx, 2 | 100q | 1000q ; O_DRWR, O_CREAT, O_TRUNC
> > mov edx, 666q
>
> I think this might be a problem: the 666 should be octal, not hex
> (which I assume q implies).

No, "q" (or "o"... even "O") indicate octal in Nasmese. Hex
is "nnnh" or "0xnnn" or "$0nnn". "1b" is binary, although it
looks confusingly like hex. I'm apt to forget that C uses a
leading 0 to indicate octal - some assemblers take that as
hex(!) - but I do know it... I can't say as I've figured out
how to manipulate the permissions - in *any* base - but I
think "666q" is okay. Typo in ihe comment on "O_RDWR"...

> You can get a SIGBUS if you try to
> write but don't have write permission, I think.

Something to experiment with some more, anyway...

> > push byte 0
> > push eax
> > ; push byte 2 ; MAP_PRIVATE this works
> > push byte 1 ; MAP_SHARED bus error
> > push byte 7 ; PROT_READ | PROT_WRITE | PROT_EXEC
> > push 1000h ; size
> > push byte 0
>
> I don't know NASM, but those 'push byte's look suspicious. I
> would think you would want to push 32-bit values.

Yeah, that's Nasm's dumb defaults at work. Most assemblers
(I understand) see that "0" fits into signed byte, and
generate the appropriate instruction - Nasm will, too, with
the "-O" switch - but Nasm's default is to store the "0" as
four bytes, and use the "long form". If you want the signed
byte form, you've gotta say "byte". Confuses hell out of
people who aren't used to it, but it pushes 32-bits.

> I also don't
> know Linux, but aren't the arguments to syscalls supposed to go
> in registers?

Right. Unless the number of arguments gets above 5 (may vary
with newer kernels that use ebp too), the one-and-only
argument is a pointer to the real arguments. Pushing a bunch
of stuff like that is an unconventional way to put an
"arguments structure" on the stack, but if I don't screw it
up, it amounts to the same thing.

> > mov ebx, esp

.... like so...

....
> > When I say this "works" with "MAP_PRIVATE"... it doesn't get
> > the bus error - I don't actually see the 'A' in my file.
> > That's expected, I guess, without "MAP_SHARED" (?)...
>
> Yes.

That's what I was afraid of :(

> > Here's my HLA "version"...
>
> I didn't look. I understand HLA even less than I do NASM.

I'm not going to urge you to learn Nasm - much less HLA. The
only way I can figure out to "push 0" in HLA is to zero a
register and push that.

> I am not all that familiar with the nuances of mmap(2), and I
> have not read the standard, but playing with the test program
> below it appears that you will get a SIGBUS if the original size
> fo the file is 0, regardless of the size of the mapping. If the
> original size of the file is not zero, you will get a SIGBUS if
> you write beyond what has been mapped (which may be greater than
> what you requested, depending on the page size). The standard
> ought to spell out all this, but I'm too lazy to check (and it's
> more fun to experiment).

Yup. Thanks for the chemistry set!

> Here is the little test program in C:

Not that little, compared to the asm version... but it does
a lot more!

Best,
Frank
From: Frank Kotler on
"T.M. Sommers" wrote:

....
> but playing with the test program
> below it appears that you will get a SIGBUS if the original size
> fo the file is 0, regardless of the size of the mapping. If the
> original size of the file is not zero,

I see what you mean, after playing some more. If I write
something to the file - say with a text editor - before
running this, it works! Lo and behold, if I write a bunch of
"dummy" data to the file, after opening it (of course), but
before mmapping it - ta da!

Now I guess the thing is to rework the "Linux version" of
Randy's program to do that... or maybe I'll leave that to
Beth, since she defends Pascal :)

Thanks a lot for the help! Now, must sleep...

Best,
Frank