QB Express

Issue #6  ~  January 17, 2005

"A magazine by the QB community, for the QB community!"

In This Issue

From The Editor's Desk

Written by Pete

Well ahoy mateys!

Yep, that's right. Another month, another issue of QB Express!

But, actually, it hasn't even been close to a month since the last issue. It's been more like two-and-a-half weeks -- quite an impressive turnaround time for an issue of this size and quality. Sure, it might not be nearly as big as the rest of the issues, but with such a short deadline, the writers and I managed to bang out one hell of an issue here. The great collection of tutorials alone are worth the price of admission.

This month, we've got a very informative article on making IF (interactive fiction) by Na_th_an for all you text adventure fans, the second tutorial in abionnnn's series on Path Finding techniques, a little piece on "Rogue Map Storage Techniques" by Nathan1993, and the second tutorial in Stéphane Richard's wonderful series on file manipulation -- this time covering Random Access Files. We've also got our first ever FreeBasic tutorial, courtest of Nekrophidius: GUI Programming in FreeBasic. Finally, 19day is back with the second iteration of his non-BASIC but totally awesome series on PHP programming. If you're looking to learn new programming techniques, look no further than this issue of QB Express!

But that's not all. Rattrapmax6 / Kevin of x.t.r. GRAPHICS contributed big this month, giving us two brand new QB/FB comics starring a horse, as well as providing us with the gallery game of the month: Space Warp. Oz is back with a nice review of the Missing In Space by Crono of ForgedQB. And then we've got a new contributor by the name of Mike Wechsler who wrote an article entitled "Simply Put, Basically: My Interactions with QBasic, a True Story". And then there's the great big news section this issue. Even though there's been about two-and-a-half weeks since the last issue, the QB/FB community has been buzzin' and there's plenty of new stuff going on.

But I don't want to keep you waiting. There's lots of good stuff to read this month...so have at it!

Submit To QB Express

You all know the drill. This magazine can't exist without people SUBMITTING articles, editorials, tutorials, reviews, news and feedback. This is not just a solo effort by me... it's a group effort by people throughout the QB community. We publish stuff on QB, FreeBasic or just programming in general... so if you have anything to submit, or have time to write something, DO IT!

If you want to write about something, but can't think of a topic, or need inspiration, email me! I'm good at thinking of stuff to write about, and I know quite a bit about the QB community and about QB programming. If you're interested in getting your own monthly column or just want to write an article or two, by all means, do it! Anything that is submitted will be included!

I also want feedback and letters to the editor regarding this magazine. I want suggestions and critiques. What do you like? What don't you like? What could be done better? Let me know!

All submissions and feedback can be sent to pberg1@gmail.com. You can also PM me on the Pete's QB Site or QBasic News message forums. If QB Express is going to continue to be so good, YOU need to contribute!



Letter From MystikShadows

Hi Pete,

Well, you did it again Pete, absolutely brilliant issue. Interesting from start to end. I've read (1st try ;-) QB Express 5 and first thing I need to say is I'm gonna read it again ;-) you're right, it's a big one. And it's definitaly worth the read and reread(s).

I have a critic, well not quite a critic, more of a concern. You mention, in QB Express 5 that QB is dying/dead because DOS is dead. Although for the common user, I may agree (although myself, I make sure I have a DOS setup at all times because I use windows due to lack of choice because if IE was made for DOS too, I wouldn't need to install windows ;-). But I do have to say that saying that DOS is dead is an overstatement (is that a word? ;-)).

As an annectdote, you might find surprising that about a year and a 1/2 ago (maybe two), Microsoft decided to take the DOS (the one shipped with Windows ME) isolate it, put in in a setup system as release it as an update to MS-DOS 6.22. This setup had no windows, only DOS. When they announced this DOS update, they seemingly didn't expect much out of it. However, their DOS sales FAR exceeded their projections. They were quite surprised with these results, but I wasn't. Windows has it's advantages, but so does DOS. Sheer speed, with DOS extenders, Memory is less and less of an issue too.

One thing I'd like to see is for hardware builders to wake up and provide drivers for DOS (or the DOS emulators). The fact that I can't find DOS drivers for any hardware you buy today is, to me, totally senseless, it's sad to see companies, leading companies in their respective fields, sell themselves to Windows like they've been doing. It's limiting their horizon definitaly more than they seem to realize. I hope they get to read this ;-).

So no, to me DOS is far from dead. QB itself well, if everyone knew of tools like b4g (a DOS extender for QB that gives access to flat memory up to 4 Gb) well, need I say more?

Anyways, I just felt like ranting a bit about that because to me DOS will never die. Infact, the way the popular graphical OS are behaving, They're seriously playing with the faith of their product(s).

With regards,
Stephane Richard (MystikShadows)

Thanks a lot for the feedback!

The reason I say DOS is dead is because no professional software companies really support it anymore, since there's not much money to be made when almost all users are operating Windows, Mac and Linux systems. DOS software releases are few and far between these days, and limited mostly to hobbyist programmers. I think it's a shame that the great wealth of work from the past twenty years of DOS is already being forgotton. True, DOS is still hanging in there, but it's definitely on a downhill slope. It's never gonna die because it's a great legacy platform, but it's also not going to be seeing many new developments either.

Anyway, I don't think FreeBasic could have come at a better time. I love DOS and QB just as much as the next guy, but the reason why it attracted me in the first place was the ability to make programs and then share them with other people. These days if you write something complex in QB, you can't share your programs with the vast majority of computer users because they just don't know how to get them to run. I'd much rather use a slightly different platform that every Tom, Dick and Harry out there with Windows XP can get to work.

What does everybody else think? Is DOS dead? Email me and tell me what you think!


Letter From Lurah

Years ago, i finded Qb.exe in my HDD. Years before that i liked to code with GWBasic. Well, Qb was way ahead of GWBasic. It was so exciting to learn Qb and do some things with it. I made some simple games with Qb but pretty soon that thing we call life beated my interest about Qb and programming at all.

Now, years later i suddenly finded one of my old Qb programs from floppy. Floppy was broken and i couldnt load that my program. I only could see mhcs.exe on directory list.

Darn, i was pissed. After few day´s thinking i downloaded qb from internet. Feeling was awesome when i first time opened qb´s editor. It took a while to remember things and how to do them. Lol, i consider my self as a total newbie because so mutch i have forgotten.

But it was so great and positive surprise to see how popular Qb still was. Whole web-sites dedicated for Qb only. And peoples around Qb were just so great.

After all this, i was sold to Qb.exe =)

I have talked with some programmers, from Finland and other countries. Many have told that they never gona end with Qb. Of course, they are moved on "better" and poverfull languages but they gona stay and work with Qb also.

Usually the reason for this has been same. "Any other language doesnt give that same feeling when you work with it." I agree 100%.

This is probably the reason why Qb will not die yet. It will loose lot´s of users but there are still peoples that just love´s to work with it.

Maybe Qb sites will turn into some other language, maybe they keep Qb in "background" or maybe they let Qb go away totally. No one knows yet.

I have decided that if no Qb sites exist some day, ill make my own. But till that day, sites like your Pete and things like QB Express keep´s Qb alive.

So thanks Pete for all of your work for Qb. I wish i can read that QB Express also after next year and after year after that and....


By the way, sorry my bad English =)

Aikasi kuluksi ---> www.lemmikkini.net

Well thanks a lot for the great feedback! I've gotta tell you, the same thing happened to me with QB a long time ago...I programmed a few simple games for fun and had fun doing it, then on a whim one day, I logged onto the Internet and did a search for "QBasic"... And here I am today.

The QB community we've got on the Internet is really something special, and even though I don't really program in QB much anymore, I think keeping the Qmunity alive is very important. I'm glad you agree with me!

Only, instead of waiting until all the QB sites die, why don't you make your own QB site now? Why wait? QB's only gonna die faster if people wait to program in it or make their websites.


Letter From Javier


although im not really good at qbasic programming because of lack of time (im a medicine student) i really enjoy to grab this little qb4.5 fella once in a while and have some fun with.

i used to have a few ideas in the past which seem really either older or almost impossible.

i recently received an email (well, like 4 months ago or something), it was a reply of a post i made once at futuresoftware page (or something, the guys that made future library), where i stated if qb45 could be like, i dont know, using reverse code or something and then try to give it a protected mode boost. also, implementing new screen modes would be nice.

why im i saying that? i recently tried freebasic, its quite good, but, as qbfan i would like to keep qbasic if not qb45 as pure as possible, cause i read the statements that freebasic wont include (i.e. draw statement, ok, i really think would be a pain in the butt to write the code, but is part at least of old good qbasic, not only qb45) the same for the play statement. i really can't criticize since im not at programming, so technically who am i to tell you what to do, i just want this to be taken as suggestions or something.

i believe that way you can be almost sure that 90% of existing qb apps and games if not near 100% will WORK in freebasic at full speed and with almost or none glitches of any kind.

also, well, i once suggested angelo mottola, the creator of directqb to manage mode-x in his library. then again, he told me, and i really believe it, that he would have to rewrite the whole library., again, its a suggestion, cause i've seem some nice graphical modes that make a really good impression, for example: 320x400, 360x360 (not very compatible in some cards but looks really nice) and my favorite, since it gives scanline like effect, 400x300.

anyway, hope to keep in touch with the qbasic, qb45 and now fb community, cause im actually working in a program to help me make physical charts and interrogatory to my patients during my internship. its literally very basic, but i hope to look forward to make it a little more "complicated" (ie. checkboxes would be nice, hehe).


javier e. aka: XeonRebel or RicHunter

Yeah, I'd have to agree with you on FreeBasic: I would absolutely love it if QB were 100% compatible with FB. I'm talking every single command supported -- so that you can load and compile any QB program and just play it in Windows with no hassles. It's a great dream, but I doubt it will ever come true. After all, V1ctor's only one man. Supporting things like the DRAW statement takes a lot of time and energy, and there's just too much stuff going on in real life.

Still, though, FreeBasic is a giant leap in the right direction. Maybe someday we'll have a version of FB that can handle just about any QB code we throw at it. But for right now, just be happy with what we've got. :)


Have a letter for the editor? Send all your rants, raves, ideas, comments and questions to pberg1@gmail.com.

Express Poll

Every issue, QB Express holds a poll to see what QBers are thinking. The poll is located on the front page of Pete's QBasic Site, so that's where you go to vote. Make sure your voice is heard!

At what age did you start programming in QB?

Less than 600%
6-10 years721%
11-13 years1441%
14-17 years824%
17-20 years39%
21-25 years13%
26-35 years00%
35+ years13%
34 Total Votes
This turned out pretty much as expected. Most people I've met learned QB as their first language during their middle school and high school years. Not too many people learn QB as older adults, probably because older people in general don't program as much as younger folk -- and if they do, it's probably not going to be in QB.

News Briefs

News from all around the QB community, about the latest games, site updates, program releases and more!

QB Site News

Basic Network, V Planet!, Digital Blackie Experience Downtime
Nekrophidius's Digital Blackie server crashed earlier this month, bringing down many important QB / BASIC programming sites including V Planet! and all of the QB sites hosted on Nek's Digital Blackie webhosting service. Though these sites experienced several days of downtime, Nekrophidius was able to restore them in a relatively timely manner. Unfortunately, some of The Basic Network's content was lost, including back archives from the site's popular message forum. Since then, a new forum has been installed, and the site's seldom-used front end (which offered BASIC News and a few downloads and tutorials) has been scrapped. (Now visiting the site will bring you directly to the forum.)

V Planet! also experienced a bit of downtime, and its message board has been rendered inoperable since the crash. As for Digital Blackie, Nek had some bad news:
"All the old backups turned out corrupt, the most recent working backup was from late in November. Anyone who's been granted an account in December needs to contact us via either a messenger or PM on a forum, as our primary emails aren't quite working properly yet. We'll restore the account information as soon as we are able. We apologize for the inconvenience and we're working to fix everything as soon as possible. "
Apparently, several accounts (and all their data) were lost. No new info has been posted on how the recovery effort is going now, but it looks like Nek has it under control.
QBXL's New Issue
This is the third month in a row we've reported it, but the new issue of QB Accelerator actually *is* on its way...eventually. Editor SJ Zero has even designed the logo for the issue (which you can see below). Recently, SJ Zero posted the following on the QBXL.net forums:
"For all those who care, there is a 99% completed qbxl4, bigger than any other (I know -- I used the last one as a template!), and chock full of reviews, tutorials, and random stupid stuff!"
So what's the deal? Where is it? Here's hoping that it will be out by the February issue of QB Express... because this magazine is outpacing QBXL three issues to zero so far, and time keeps on tickin'...tickin'...tickin'... Let's hope that in the February issue I don't have to report again on the *upcoming* QBXL issue, and instead can rave about how great Issue #4 was and how much I'm looking forward to QBXL5.

Project News

Syn9 Releases 3D World Map Demo
In the last issue of QB Express, we brought you news that Syn9 had released a full-motion video of a beautiful new 3D engine demo he had in the works, featuring a medieval town situated on an island. On December 30th, the same day the magazine published, Syn9 released a working demo of this game, which you can download at this link. The demo was posted on the QBasic News forums, so if you'd like to give Syn9 feedback on his handiwork, that's the place to do it. You can also check out Syn9's site if you'd like to see some of his other work.
SBM Productions Releases "Hameroid" Game
On January 8th, SBM Productions posted the following message about their new game release:
I made a game...


Damn I'm proud of it. Damn you if you don't like it. I tried entering it in the GGA comp but the form returned crap in my face when I clicked submit. If this means that it didn't submit successfully then can someone enter it in for me? SBM:Niceguy?

Note :SBM is very agitated, very tired. Be nice even if you don't have to.
The Australian group has previously released a Metal Gear-style stealth game called "Spy For Hire" and the SPMake graphics editing program. You can find both of these programs at SPM Productions. Additionally, SBM released an updated version of "Hameroid.zip" that doesn't require EMS memory support (which you might be interested in if you're running Windows XP). You can get it at this address.
Kurt Kuzba's ADDGUIQB Library Released
I got the following email from Kurt Kuzba about his new ADDGUIQB Library, which we announced in the last issue. Rather than rewrite it, I'll just let him speak for himself :) --

Hi Pete;

I've got the ADDGUIQB library up on my web page.


The C source, .obj, .qlb, .lib, and QB demo app are all there in a .zip file, along with a link to my own idiot ramblings in Programmer's Notes and a link to my most recent update to the .bas demo app source code.

I plan to do some more development on the C code, adding two more button types, blank and frame, adding user definable font support, and speeding up the text font display. I may remove the existing font, and just use fonts defined in QB, adding a font designer to the demo app. This would require passing the font as a string to the print function. While slightly awkward, it would not increase the time required to process the text, even with a user defined justified font, since a pointer is a pointer is a pointer. I took care to use native mode QB pass by reference wherever possible.

Also, in the demo app, I plan to consolidate some of the functionality of the existing menus, and expand the function wrappers to make the interface more programmer friendly.

The v2 .c, obj, .qlb, and .lib files will have their own .zip file and their own demo app. Version 1 is perfectly usable as is, but there are things I can do programatically which would be better done at the .obj module level regarding registered control objects, such as knowing when you are over a text area to change the mouse cursor to a carat, or to a hand when you are over a movable object.

I don't expect progress to be quick, but I expect that there will be progress. Heck, I'm surprised I got version one finished and working bug-free. It is a learning process, to be sure, but a really, really annoying hobby as well.


Competition News

Final Round Of GGA'04 Nominations Kicks Off ... And Stalls
On January 1st, Nekrophidius officially began the final nomination round for the 2004 Gaming Gold Awards with the following message:
Sorry for the delay folks! I've just opened the second and final round of nominations for the 2004 QB Gaming Gold Awards. The second round lasts only one week, and is aimed at specific genres, game elements, and characters. Same rules apply as before. The voting period will start on January 10th. Again, sorry for the delay.

Unfortunately, now it's the 17th of January and the polls are still open and no progress has been made. Understandably, Nek is a very busy guy and just had to deal with a major server crash, but maybe he should take an hour or two and get the Gaming Golds up and running! (Come on, we're beggin' ya!)
New "3 Week Drill" Category in the QBCPC 2004/2005
I recently received word from none other than Adigun A. Polack (One of the Founders of “Aura Flow”, Continuing Developer of “Frantic Journey”, Current Developer of “Star Angelic Slugger” and webmaster of the “AAP Official Projects Squad”) about some updates to the QuickBasic Caliber Programming Compo 2004/2005 Extend Edition (now that was a mouthful):
Recently on my QuickBASIC Caliber Programming Compo 2004/2005 Extend Edition, I have already added a very special all-new breed of category for 2005 entitled the “3-Week Drill category”. Now, what this means is that it is literally a *timed* challenge where you have no more and no less than just three (3) whole weeks to code your own original project in FB, QB, or VBDOS that is based on a certain game or project that is placed as the selected target of this category. Should be real exciting, huh? ! You can check out more about what this new drill is about right here, and be ready!
So what are you waiting for? Go and enter the QBCPC 2004/2005 EE!
TAC '04 Expanded
Nekrophidius's annual December text adventure competition has been extended until the end of January. This announcement came after Na_th_an asked Nek for an extension in order to allow new entries to come in and for current entrants to improve upon their entries. Nek's response? "Consider it done then. TAC'04 officially extended until the end of January. When we have full control back again, I'll restore the website for the TAC and update it."
Frost 2K5 Remembers Compo Results
Earlier this month, SJ Zero hosted a two-day FreeBasic game programming competition -- perhaps the first ever FreeBasic compo of its type -- under the title "Frost 2K5 Remembers". The competition received three entries:
  • "Star Phalanx" by SJ Zero
  • "Scorched Planets" by Joakim
  • "3D Engine" by Xerol
Voting for the winner opened up after the contest concluded. Though no official winner has been declared yet, Joakim's "Scorched Planets" game has taken an easy lead from the votes that have been received thus far. (You can see a screen capture of this neat planetary gravity simulation to the right.) If you'd like to vote in this contest or see the current results, visit the QBXL Forums.

FreeBasic News

FreeBasic News is hot again this month, so I'm doing the same bulleted list format I did last time. I'd like to go into more detail on each of these items, but I'm getting tired and I've got class tomorrow. :P

FreeBasic Signature Wars...
There are a whole lot of new FB-related message forum signatures over at the QBasic News FreeBasic Forums these days. While most of them simply have links to some of the new FreeBasic sites, or consist of the standard FB horse logo, some are a bit more interesting. Take Mech1301 and Rockuman, for instance. Both of them have funny FB-related signatures that make statements about the new Win32 compiler. In fact, they have a strong resemblance to political cartoons. Check 'em out:


We now return you to your regularly scheduled program.

Got QB/FB news? Send it in!


Written by Pete

Every issue QB Express features a preview and exciting new screenshots from an upcoming QB game. If you would like your game featured, send in some screenshots!

Space Warp

Rattrapmax6 (Kevin), who releases programs under the brand name x.t.r. GRAPHICS, is a relative new comer to the QB scene. However, in his short time as a QB developer, he has been remarkably productive. Since he started his website in December, he has released two editions of his BA-SIC MU-SIC PLAY statement editor, his robot maze game Robo Raider and now, information about his latest project: Space Warp.

Space Warp is a self-described "space shooting action RPG for QB" where players take control of an intergalactic delivery man who is on the run from his ex-boss. The main character is a guy who dreamed his whole life of being a space pilot -- but when he finally makes it, he ends up losing cargo and gets caught in the crossfire of his enraged boss. Along the way, "You must blast his army of turret tugs while escorting people or cargo from planet to planet." Meanwhile, you've got to avoid seek-to-destroy A.I. enemies that Rattrapmax6 is "hoping will give you your flight of your life!"

Space Warp will be presented as a solid space shooter with text intermissions or animated cut-scene intermissions to move the story along. Though Space Warp's space shooting gameplay gameplay is pretty standard, with a top-view camera and standard arrow keys and spacebar controls, its story elements should set it apart from other similar games.

As far as Space Warp's graphics go, the game will feature some neat schematic drawings of the space ships (which you can see included with this article) along with animated sprites. Here's the skinny on the graphics, according to Rattrapmax6: "Text is to be animated, rockets are animated, and when you shot the opposing ship, it blows up and flames flicker past you. I'm not going to use a scrolling star field since in real space travel the stars wouldn't just pass by, their as big as planets mind." From what I've seen so far, the graphics appear to be very simple and minimalistic, using bright primary colors and simple line art, though the game is still in an early stage of development.

Rattrapmax6 has gone a step farther than most QB developers in promoting his upcoming game by releasing an animated trailer/preview program that shows the game in action and gives users a glimpse at the background story. This program was released just yesterday, and operates on the actual engine that is used in the game. If you're at all interested in this game, I suggest you check out it out.

As for when the final game will be released, Rattrapmax6 had the following to say: "I'm hoping to have it done by this summer of 2005, if I miss that deadline, it will probably push over to December. Crap happens, luck happens, and sometimes your looking at a pic of R2D2 on line, and a blue spark flies out the side of your monitor with a loud bang, leaving your screen blank. Or your mother board fries, sorry bout that, ;).. So for an exact date, that's impossible, :D. But I'm sure it will be done by this summer, maybe even sooner..."

I guess that means we can prepare for blast off in T-minus six months.

Download the Space Warp trailer or visit the x.t.r. GRAPHICS official site.

Simply Put, Basically:
My Interactions with QBasic, a True Story

Written by Michael Wechsler

He wore a black shirt with white puffy paint on it. The letters spelled out, in a scrawling hand almost impossible to read, his message to the world. "Computers, as easy as 011001…" We had all seen the other shirt which declared emphatically, "Simply Put, Basic" and we loved his crazy QBasic antics. He was a nerd, but damn proud. But before I get ahead of myself, let me explain how this all started.

The year is 1994 and I am a sixth grader as Adams Middle School. I sit in computer class unaware of what is about to transpire: my first look at QBasic. The instructor is a middle aged man named Mr. Black whose favorite pastime was defining a computer as "a dumb machine with no intelligence." This redundant phrase appeared on every test that he ever gave and as far as I know he is probably still muttering it right now.

It was a special day when our computer class took a break from playing Oregon Trail to learn the "input" and "print" commands. I still remember my first line of code:

10 PRINT "Hello world!"

Indeed, a computer truly was a dumb machine with no intelligence. But as clichéd as this code has become, it stuck with me all these years as my first taste of the power of QBasic.

Last year I had the pleasure of meeting perhaps the seminal figure in the 2004 QBasic scene: Pete Berg. I was amazed when Pete brought me to his room and showed me Pete's QBasic Website as he does to all his new acquaintances. This year, I have the distinct pleasure of living with Pete and watching him blossom and grow. For all of you rabid QB fans out there, I thought I'd share a little insight into the man, the legend, Pete.

Pete's day starts when rises before the sun to get in some early morning coding. Usually he is working on artificial intelligence but sometimes he takes a break and tackles something easier like advanced missile guidance programs. It amazes me how much he can do in QBasic alone. From there, Pete eats 4 raw eggs to build up his strength for the coming day. Next, Pete does approximately 100-200 pushups before going off to his classes. Usually he finishes his day up with some more coding and then lulls me to sleep with a gentle song from his amazing huge repertoire. I can't say what exactly Pete does after I go to bed, but I have woken up a few times to hear him furiously coding into the early morning.

One thing is for sure, it's QB for life!

You can contact Mike through me (Pete)... Yeah, he's my roommate at Ithaca College.

Missing In Space Review

Written by Oz

Missing in Space Logo


The game is basically a 'kill all the aliens before they kill you' kind of game, an 'Alien' clone. Other than that, there isn't much to say.


The graphics were rather simple, but appealing to the eye. The game is done, minus the stats bar, in primary colours, which I thought was really cool. Most games try to get out by looking flashy, but Missing In Space (MIS) went for the simplistic approach which I think did well, particularly for ForgedQB's first major release. I also think that since the graphics of the ships were fairly big, it didn't give you a lot of dodging time or space.




Rating: 50%
Not too fancy, but not very dull.






Rated: 0%


I found that maybe the distance between you and the aliens is too close, so perhaps just trimming down the image size, thus taking up less spacing, and giving you more room (mentioned in graphics section). I thought that the game speed was exceptionally fast, but it just happens that since the distance is so little, the bullets get to you faster.


Very smooth, thanks to the famous 'Rellib', by the one and only 'Relsoft'


Possibly too fast, or to big of sprites



Rated: 70%
The simplicity of the game doesn’t grind any gears, not to mention the ASM assistance.


The game, as stated is very simple. The catch that this has, that 'Alien' didn't, is the fact that you can also move upwards, and scroll in space side to side. Unfortunately, I've seen only a thousand different 'Alien' clones, with a few extra features here and there.




Rated: 50%


Not a bad game, definitely fun.



Below average, but still fun

You can download Missing In Space at ForgedQB or right here.
Visit Oz's website, AtosoftQB.

How To Make IF Games
Chapter #1

Written by Na_th_an


First of all, if you are a really beginner and got this tutorial thinking that it was a tutorial about the IF statement you are wrong.

What's this tutorial about? What's an IF?

Well, 'IF' stands for Interactive Fiction. Many people know them as 'text games', but that's not correct. An IF is 'more' than a text game, mainly for the complexity of its engine. Imagine you are reading a book. But this book is special so you can do anything you like with the things in your environment. Imagine you have a box of matches and a bag. You could examine both things, open the box of matches, take a single match from the box, light it, burn the bag and eat the box, only if the program allows you to do such nasty things. I've seen many text games in qb, and the way they are written makes the thing impossible to get more complex.

Text games outta there are this way:

CLS PRINT "You're in an empty room, carrying nothing and there's a ghoul." ' Not envolving athmosphere at all... Repeat: PRINT "You can:" PRINT "1. Run away." PRINT "2. Hit the monster." PRINT "3. Cry loudly." INPUT a SELECT CASE a CASE 1: PRINT "You cannot scape. You're died": GOTO GameOver CASE 2: PRINT "Yeah!": GOTO @£^$ CASE 3: PRINT "You're so pathetic that the monster eats you": GOTO GameOver END SELECT PRINT "Wrong number." GOTO Repeat

You see: Limited actions. But the actions are so limited only by the way the game is coded. That SELECT CASE (or IF...THEN...ELSE...ENDIFs) would be really HUGE if you had to figure out every possible action.

With a real IF engine, the games run like this:

You just exit that lumpy castle. You're facing north, seeking a single point of light meaning a little bit of civilization which can take you away your madness. You can see a wooden artifact. >> Inventory You're carrying nothing. >> Examine wooden artifact It looks like a little box. It is closed. >> Take it and open it Okay. Now you have the wooden artifact. Now it's open >> Inventory You are carrying a wooden artifact. >> Examine the artifact³ It's open, you can see inside some gems. >> Take the gems out the artifact Okay. >> Inventory You are carrying a small wooden artifact and some gems.

Imagine you had to code a thing like the bit above using the SELECT CASE technique... tsk tsk. What you need is to build a complete IF engine.


The IF engine is made up with three main parts:

There is also a fourth part which hasn't be included above because it doesn't belong to the engine, but it is different in each game. It is called "exceptions handler". These exceptions are which makes your game behave different in concrete situations. For example, when you wear some object which can be worn, for example, a ring, the engine changes the object's properties to know that you are wearing it and that's all. But let's imagine that this ring is a magic artifact which teleports you to another place. Well, that's an "exception". Get it?

To summarize it, the common actions are handled automaticly by the objects manager, the "special" actions are handled by the "exceptions handler". This part can be hard-coded or scripted, depending on the level of complexity you want to give your engine.

Well... We have lots to code. But before we write a single line, I'm gonna explain how each bit of code is designed. I spent a year figuring out this engine, and I think it is complete enough.

If you want something more complete, look for inform, which is a complete programming language only designed to make real IFs.

In this first chapter, we'll take an overview to the location manager and the parser. In the next chapter, we'll describe how the objects manager work. In a third chapter, we'll give hints on how to code them. Los Monos del Obus will release a great IF engine designed to be used with QB, FB or VB in which everything that's discussed here will apply.


To make the .BAS file shorter, all the location data will be stored in a separate file. That file will contain the following information for each location in the game:


Let's describe it:

Here is an example:

## 0 The Wind Valley You are in the Wind Valley, a small passage among two high mountains, which will lead you to Khare if you go North or back to your town if you prefer to follow your way to South. You hear the rooks hovering here and there and some coyotes. The mountains are full of dark holes. } North 1 South 2 } mountains High and dangerous, the mountains stand up with ancient majesty, guardians of ages. } rooks These awful black birds don't bring good news. } coyotes Thin and starving. You better keep out from them. } holes You remember these holes since you were a child. The old said that they were used by extrange creatures as needles, but you never believed these stories. } }

The LOCATION's manager has to read and understand that code.


The parser takes the user input and then does the following things:

Principal sinonym? verb 1, noun 1, etc...? Well, let's explain this.

First, what's the principal sinonym? Okay. Let's imagine that you have found in the game a place where there is a big closed wooden box containing many things. That box could be reffered as 'box', 'chest', 'coffin' or whatever. In the program there should be a database to look for these 'sinonyms'. When the parser finds in the user input the words 'chest' or 'coffin' it substitutes for 'box' and in this way we reduce the number of checkings that we have to do to understand the player's willing. This feature is incredibily useful in Spanish, 'cause there are things that can be named in ten different ways implying gender and number. As the engine was originally programmed for a game written in Spanish the feature is still there.

Now let's learn something about a 'phrase structure'. If you examine the imperative phrases, there are only two kinds:

As the player has to talk in imperative in an IF, we find that it's really easy to parse the natural language phrases. We only need to remember a verb, two nouns, two adjectives. For example:

'open green door': Verb: open Noun 1: door Adjective 1: green Noun 2: - Adjective 2: - 'put the big cheese into the chest': Verb: put Noun 1: cheese Adjective 1: big Noun 2: chest Adjective 2: -

If you think for a while you'll realize that we only need to know this information to understand the player's willing.

The last thing the parser does is 'substitute the pronouns'. What's that? Well, let's imagine this sequence of player's inputs:

> take the box > examine the box > open the box > take the gem from the box

It is really stupid to repeat 'the box' three times. A more natural sequence is:

> take the box > examine it > open it > take the gem from it

The parser has to remember the last noun and then substitute the word 'it'. It sounds convincing, but there is a problem. If the player enters:

> take the gem from the box.

What noun do we remember? gem or box?

That problem also exist in natural conversation. When someone says: 'Yesterday a girl was insulting my mother. I cheered her'... The question is... Who was cheered? The girl or the mother...??

The solution is to remember nothing, so when the player enters the next pronoun the parser will answer: "What do you mean?". another solution is to remember the first noun always. Whatever fits you more.

Well. Now let's explain how the parser module works. First of all, you must set up a 'dictionary' which contains every verb, noun and adjective you want to be used in your game. Keep in mind that the parser will ignore every word that isn't included in the dictionary, so be comprehensive. My engine admits up to 32768 words, so you've plenty of space to work with.

The dictionary is a text file that you can build using any text editor, for example the Windoze notepad or MS Edit.

The dictionary text file structure is as follows:

Word,id,type Word,id,type

Where word is the word (!), ID is a word ID and type is 0 if the word is a verb, 1 if the word is a noun, or 2 if the word is an adjective, for example.

The ID is useful for synonyms. If you put two words which have the same meaning, you must use the same ID for both of them. It is used, for example, in the movement verbs. 'north' and 'n' mean the same, so they must have the same ID.

There is a special section which must always be included. The Parser only recognizes IT as a pronoun, so HER and HIM must be told to be IT's synonyms, as long as THEM. This may look cheesy, but the player doesn't notice and it makes your life easier. Choose a free ID for them and include this:

it,9999,1 her,9999,1 him,9999,1 them,9999,1

We have chosen 9999 as ID, and we have to especify that they work as nouns, otherwise the parser won't work properly. After all, pronouns are substitutes for nouns, so this makes sense.


And that's all by now. See you in the next chapter!

You can contact Na_th_an through the QBasic News Forums or visit his website.

Rogue Map Storage Techniques

Written by Nathan1993

Storing maps was very simple, but it can be almost impossible to have 50 maps for rouge games. I have the solution here. I will start with the map file itself, and then explain it. I will also put comments in the map, do not do that in your map!

init 'start the init max_x, 6 'max x max_y, 6 'max y endinit 'end the init   index , 1                                   ‘the index value up_end, 3, 1                             ‘the upper exit down_end, 3, 6                        ‘downwards exit left_end , 1, 3                           ‘leftward exit right_end, 6, 3                          ‘right exit   ‘Really, this is the basis. Also, this is by NO MEANS written in stone. I am lazy, so I ‘don’t want to have to research my theories. Or my spelling!   ‘this is the actual map ‘I am making this in Microsoft word, so this is really hard. I cannot make it. MapStart ‘your map here MapEnd   ‘Seems like a scripting language, right? ‘Now you do the SAME thing with all your other maps. The only difference ‘is that you need to change the index value each time. Also, it doesn’t matter what ‘order stuff is in. also, the init is “global” stuff. ‘feel free to change this!

OK, so we have our map. I am, again, to lazy to make the code (and I wanna keep this simple thing really short). Why short? Because I wanted to. Are you questioning me? Who? Me? Yes, You! What did I do? You made this short. I wanted too...




Anyway... It’s rather simple. You just do it like a parser. If you get init, then you go to a different loop and store the things in global vars. For the max x and y. If you get that, just (like max_x) then you get another var that has the value in it. This is why there are so many commas. Just do this, (it is pretty obvious) and if you are having trouble just email me.

Bye all!

Visit Nathan1993's website or contact him


By Rattrapmax6

Rattrapmax6 brings us two brand spankin' new QB comics this month, starring a barnyard horse who just loves to program in QBasic. Check 'em out!

Rattrapmax6 QB Comic

Rattrapmax6 FB Comic

Data Structures & Algorithms
Path finding II

By abionnnn

1. Intermission

In the Last episode of DSA, we looked at two pathfinding algorithms DFS and BFS, along with queues and stacks. Now we do the fun part: actually implementing them in QB. The implementation given below is certainly not the only possible one. In fact, it's extremely inefficient in terms of memory usage! But it is short and simple enough to be understandable.

Throughout the code, I will try to give tips for those wanting to use these ideas in their implementation.

2. Map definition

This part is easy, but a simple trick which trades memory for speed (and code clarity) should be noted. Instead of testing if we go beyond the map boundaries, we can just create an impassible border. This doesn't cost too much memory, but if you prefer not to do this you will need to test if a surrounding square is beyond the array's boundaries.

Going straight for the kill:

READ MapX%, MapY% READ StrX%, StrY%, EndX%, EndY% DIM Map%(MapX%, MapY%) FOR I% = 1 TO MapY% FOR z% = 1 TO MapX% READ Map%(z%, I%) NEXT z% NEXT I%

Now for the map, we set 0 to be an impassible square, while 1 is a passable square. This choice is of course arbitrary and realistically any descriptive map could be used if you are careful later on to test for the correct possibility criterion.

An Example map:

'Map X, Map Y DATA 10,10 'Map StartX, StartY, EndX, EndY DATA 2,2,9,9 DATA 0,0,0,0,0,0,0,0,0,0 DATA 0,1,1,1,1,1,1,0,1,0 DATA 0,1,0,0,0,1,1,0,1,0 DATA 0,1,1,1,1,1,1,1,1,0 DATA 0,0,1,0,1,0,1,0,1,0 DATA 0,0,1,0,1,1,1,0,1,0 DATA 0,1,1,0,0,1,1,1,1,0 DATA 0,1,0,1,0,0,1,0,0,0 DATA 0,1,1,1,1,0,1,1,1,0 DATA 0,0,0,0,0,0,0,0,0,0

3. Depth First Search Implementation

The DFS implementation is marginally easier to understand and shorter than BFS. To save you the trouble of turning back to the original article, here is what we need for the DFS algorithm:

The stack can hold both x and y coordinates, alternatively it can hold directions that we choose but the later is slightly more involved to implement. If you want to half your memory usage for the stack though, this is definitely a route to take:

'Allocate the stack DIM X%(MapX% * MapY%), Y%(MapX% * MapY%)

There is nothing special about the already visited location array:

DIM Visited%(MapX%, MapY%)

Now that we have our ingredients, lets start baking! Errr ... lets start making! Taking this step by step:

(0) Initialisation: Empty stack, empty already visited locations.

To empty the stack, simply set the stack pointer to a value beyond the array's range (remember we invoked OPTION BASE 1, making the lower bound value 1)

SP% = 0

This is a unique state we can test. If SP% = 0, then we know the stack is empty. When we push something to the stack, we add one to SP% then add it to the array. When we pop something off the stack, we remove X%(SP%), Y%(SP%) then decrement SP% by one. This way, when we remove the last element we will end up with SP% = 0. Thus we have passed the self-consistency condition. ;)

We will not need to empty the already-visited-locations (Visited%) in this implementation since we will only use these data structures once. If you are going to do this repeatedly, you will need to give this part some thought because for large maps it may take nearly as long as the actual path finding part of the algorithm!

(1) Push the start location on to the stack, mark it as visited. (This is the current location.)


'Push the initial position on the stack SP% = SP% + 1 X%(SP%) = StrX%: Y%(SP%) = StrY% Visited%(StrX%, StrY%) = 1

(2) Check if the stack is empty. If so, terminate failing to find the goal.

We will need to start a loop now, since all steps that go to other steps come back to step 2:


Later on we can test if this condition was met:

(...) LOOP IF SP% = 0 THEN (display failure message)

(3) Check if current location is the goal. If so, terminate successfully.

We will first need the current location, such that when we first enter the loop the current location is the start location:

'Temporary reference current position CurX% = X%(SP%): CurY% = Y%(SP%)
The test for the end location is quite simple:
'Are we at the end? IF CurX% = EndX% AND CurY% = EndY% THEN EXIT DO

If we exit the DO loop this way, we'll be sure that SP% will not be zero, thus we will not display the failure message and instead plot the path!

(4) Check if there are no reachable positions from current location. If so, pop the current location off the stack, then goto step 2.

(5) For the current location, find an adjacent location which is reachable and push it onto the stack, marking it as visited.

(6) Goto step 2.

These three steps can be combined nicely in QB with a IF, ELSEIF, LOOP statement. The ordering of the choices is arbitrary, but changing it will definitely change the path you will take:

'Try going right IF Map%(CurX% + 1, CurY%) AND NOT Visited%(CurX% + 1, CurY%) THEN SP% = SP% + 1 X%(SP%) = CurX% + 1: Y%(SP%) = CurY% Visited%(CurX% + 1, CurY%) = 1 'Try going left ELSEIF Map%(CurX% - 1, CurY%) AND NOT Visited%(CurX% - 1, CurY%) THEN SP% = SP% + 1 X%(SP%) = CurX% - 1: Y%(SP%) = CurY% Visited%(CurX% - 1, CurY%) = 1 'Try going down ELSEIF Map%(CurX%, CurY% + 1) AND NOT Visited%(CurX%, CurY% + 1) THEN SP% = SP% + 1 X%(SP%) = CurX%: Y%(SP%) = CurY% + 1 Visited%(CurX%, CurY% + 1) = 1 'Try going up ELSEIF Map%(CurX%, CurY% - 1) AND NOT Visited%(CurX%, CurY% - 1) THEN SP% = SP% + 1 X%(SP%) = CurX%: Y%(SP%) = CurY% - 1 Visited%(CurX%, CurY% - 1) = 1 'Dead end ELSE SP% = SP% - 1 END IF LOOP


A smart way (at least for wide open maps) to choose which way to test first would probably be which direction would get you closer to the goal. This is called a "heuristic", since we're implying that one direction is better than another. This is not Always true. Think of this map for example:


Using a distance heuristic (the difference between your current and the goal's coordinates), implies that the smart way to go would be to the right from the start. This is obviously not true.

End of distraction

Now say that we have found the goal. So what? Where's the path that we're interested in. Well if you've been paying attention you would note that it is simply in the stack. X%() and Y%() hold the path from index 1 to index SP%. We can then take this path and do as we wish with it. In my case I decided to plot it out, along with the map!

Download the full example program for DFS here: PATHDFS.BAS

4. Breadth First Search Implementation

Rather than a stack and a bit-set (Visited%), the BFS algorithm requires the following:

The queue can be used in an array. Usually one would make the queue wrap-around, but this will not be done for simplicity. When you decide to make serious use of this algorithm, give this part some careful thought! By not wrapping around you will need to allocate enough space to hold the entire map!

To wrap around, you would basically need to MOD all add/remove operations with the maximum queue length, checking for overflows at the same time.

'Allocate the queue, big enough to avoid wrap-around problem. DIM X%(MapX% * MapY%), Y%(MapX% * MapY%)

We will also need a Front-of-Queue Pointer, and a Queue-Length Pointer. The latter is not what it's name implies, it just tells us which index of the array represents the end of the Queue.

As mentioned in the first article, we can combine the last two ingredients as follows:

'Here Parent means the following: '-1 : No parent. (Start location) ' 0 : No parent yet! (UNVISITED) ' 1 : Parent to the left ' 2 : Parent to the right ' 3 : Parent up ' 4 : Parent down DIM Parent%(MapX%, MapY%)

Thus, Parent% acts both as the array of already visited locations and the array containing the parent locations.

Cutting straight to the algorithm:

(0) Initialisation: Empty queue, empty already visited locations.

QP% = 0 'Queue Pointer QL% = 0 'Queue Length

When adding to a queue, we first increment the Queue Length and then add the coordinates to X%(QL%), X%(QL%). This is different from the stack case. When we remove from the queue, we increment QP% then read off X%(QP%), X%(QP%).

(1) Push the start location onto the queue, mark it as visited, but set it's parent location to some special value indicating that it is the start. The initial location is the current location.

'Add the initial position to the queue 'The the start has no parent. (0 means not visited, -1 means parentless) QL% = QL% + 1 X%(QL%) = StrX%: Y%(QL%) = StrY% Parent%(StrX%, StrY%) = -1

(2) Check if queue is empty. If so, terminate failing to find the goal.

The only jump done in the algorithm jumps to 2, so we will again use a DO loop:


When QP% = QL% we would have reached the end of the Queue. (since the next removal from the queue would be out of bounds!)

Testing for failures in this case is not as straight forward as before since the very last point on the queue could be the goal! But then that's all there is to it. Some time after the loop we have this:

(...) LOOP IF QP% = QL% AND (X%(QL%) <> EndX% OR Y%(QL%) <> EndY%) THEN (Display failure message)

(5) Remove the current location from the front of the queue, set current location to the item now at the front of the queue.

(3) Check if current location is the goal. If so, terminate successfully.

We will implement the loop by going from the second last step to the first loop step. This is alright if you think about it, and we only need to do this due to the definition we choose for QP% (i.e. it starts at 0 when the loop begins). Sounds complicated ... nah. Just look at the simple code below =) :

'Temporary reference current position QP% = QP% + 1 CurX% = X%(QP%): CurY% = Y%(QP%)
'Are we at the end? IF CurX% = EndX% AND CurY% = EndY% THEN EXIT DO

(4) Add each reachable location to the end of the queue, marking them as visited. For each of these added locations, make their parent location the current location.

(6) Goto step 2

This part will look surprisingly similar to the DFS one! But note that no ELSEIF's are used, since we will add ALL valid adjacent squares to the queue.

'Try going right IF Map%(CurX% + 1, CurY%) AND Parent%(CurX% + 1, CurY%) = 0 THEN QL% = QL% + 1 X%(QL%) = CurX% + 1: Y%(QL%) = CurY% Parent%(CurX% + 1, CurY%) = 1 END IF 'Try going left IF Map%(CurX% - 1, CurY%) AND Parent%(CurX% - 1, CurY%) = 0 THEN QL% = QL% + 1 X%(QL%) = CurX% - 1: Y%(QL%) = CurY% Parent%(CurX% - 1, CurY%) = 2 END IF 'Try going down IF Map%(CurX%, CurY% + 1) AND Parent%(CurX%, CurY% + 1) = 0 THEN QL% = QL% + 1 X%(QL%) = CurX%: Y%(QL%) = CurY% + 1 Parent%(CurX%, CurY% + 1) = 3 END IF 'Try going up IF Map%(CurX%, CurY% - 1) AND Parent%(CurX%, CurY% - 1) = 0 THEN QL% = QL% + 1 X%(QL%) = CurX%: Y%(QL%) = CurY% - 1 Parent%(CurX%, CurY% - 1) = 4 END IF LOOP

That gives us the goal, but how do we pry out the path??? Well it's all hidden in the Parent% array. Notice that with each turn we have noted the direction which would lead to the parent square, thus we can imply how to return from the end to the beginning! Reversing the array will give us the direction from the beginning to the end which is exactly what we are after! We can use the same space for the queue to do the backtrace part, since we don't need the queue anymore.

'Backtrace, here we start at the end and then make it back to the beginning 'Since we know which move caused the arrival to each cell we can just 'take the reverse direction. CurX% = EndX%: CurY% = EndY% 'We can make use of the space of X%, Y% now i% = 0 DO i% = i% + 1 X%(i%) = CurX%: Y%(i%) = CurY% 'Remember our definitions from before here. SELECT CASE Parent%(CurX%, CurY%) CASE 1 CurX% = CurX% - 1 CASE 2 CurX% = CurX% + 1 CASE 3 CurY% = CurY% - 1 CASE 4 CurY% = CurY% + 1 CASE -1 EXIT DO CASE ELSE EXIT DO END SELECT LOOP

In the example code, no reversal is done (it's trivial) since it will be plotted on the screen instantly. (unless you are running it on your old 286 without the turbo button! Nah, I tried it on mine and it was still too fast ;))

Download the full example program for BFS here: PATHBFS.BAS

5. Conclusion II

BFS/DFS are important to understand before moving on to better (or at least more practical) algorithms. But they are lacking in the performance department. For example, if you are making a strategy game you should seriously consider the A* algorithm instead. To be able to code it with some performance gain, you will need to use a priority queue. (e.g. a binary heap)

Well that's it for now. I will be pretty busy with University work in the months to come, but if there is demand this series will return next time with more data structures, and maybe even an algorithm or two! (hopefully A*)


Download this tutorial and both pathfinding programs: pathfind2.zip
The author can be contacted at moc.liamg@nnnnoiba (reverse it ;))

GUI Programming In FreeBasic

Written by Nekrophidius

Okay...I'm not usually one for tutorials, but since this is something I've spent quite a bit of time with now, I figured I'd share what I've learned with the QB/FB community.

First of all, I am only covering the Windows version of FB. Second of all, I'm not using any third-party libraries here, this is all API stuff. Third of all, don't bug me for a Linux version, bug Danilo about that. :)

Anyways, to start off, you'll need three headers: kernel32, user32, and gdi32. commctl32 is also rather helpful. Access to the MSDN helps a lot too, especially when you want to do stuff beyond the scope of this introductory tutorial.

A basic GUI-based program has three main parts: creating objects, a message loop, and a command procedure. Basing this example off of Blitz's original winhello.bas included with the freeBASIC package and my own FB-CD demo, we'll look at each step individually. We'll use two Subs: WinMain and WndProc. WinMain is where we'll create objects and do the message loop, and WndProc is the command procedure.

Declare Function WinMain (ByVal hInstance As Integer, ByVal hPrevInstance As Integer, szCmdLine As String, ByVal iCmdShow As Integer) As Integer

We want to be able to return a value to errorlevel. So, we enter WinMain like this:

End WinMain(GetModuleHandle(null), null, Command$, SW_NORMAL)

GetModuleHandle(null) gets the hInstance of the program calling it, which we need for virtually everything involved in a GUI, thus it'll be passed to WinMain, which is where we'll primarily use it.

In WinMain, we need to set up a few things. We're going to be creating a Window Class as well as the message loop, so we need a few variables:

DefInt a-z Function WinMain (ByVal hInstance As Integer, ByVal hPrevInstance As Integer, szCmdLine As String, ByVal iCmdShow As Integer) As Integer Dim wMsg As MSG Dim wcls As WNDCLASS

This gives us a message structure for the message loop and a window class structure for setting up a new window class.

Dim szAppName As String Dim hWnd As Unsigned Long

This will define the name of the application and define an hWnd for the window we're going to create. Keep in mind that almost everything in a GUI has an hWnd.

szAppName = "HelloWin" wcls.style = CS_HREDRAW or CS_VREDRAW wcls.lpfnWndProc = @WndProc() wcls.cbClsExtra = 0 wcls.cbWndExtra = 0 wcls.hInstance = hInstance wcls.hIcon = LoadIcon(null, IDI_APPLICATION) wcls.hCursor = LoadCursor(null, IDC_ARROW) wcls.hbrBackground = GetStockObject(WHITE_BRUSH) wcls.lpszMenuName = null wcls.lpszClassName = Sadd(szAppName)

Here, the Window class is defined. Note the use of SADD for wcls.lpszClassName and the use of @ for wcls.lpfnWndProc. lpsz is an acronym for "Long Pointer ASCII-Z String", or a long pointer to a null-terminated string. lpfn is "Long Pointer to a Function", so we need a pointer to the function WndProc.

If (RegisterClass(wcls) = false) Then MessageBox null, "This program requires Windows NT!", szAppName, MB_ICONERROR Exit Function End If

Finally, we register the class. If it succeeds, we can move on to actually create it.

hWnd = CreateWindowEx(0, szAppName, "The Hello Program", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, null, null, hInstance, null) ShowWindow hWnd, iCmdShow UpdateWindow hWnd

And that's all there is to it. Step one's over. :) Thankfully, step 2 is really short.

While (GetMessage(wMsg, null, 0, 0) <> false) TranslateMessage wMsg DispatchMessage wMsg Wend

This is the message loop. Short, eh? :) If only all of GUI coding could be this short. But now we're to the end of WinMain, so let's make sure a value is returned:

WinMain = wMsg.wParam End Function

This will return the value of wMsg.wParam to errorlevel and end the function, hence ending the program. The message loop will run until it is told otherwise by WndProc, which is what we'll look at next.

DefInt a-z Function WndProc (ByVal hWnd As UInteger, ByVal message As UInteger, ByVal wParam As UInteger, ByVal lParam As UInteger) As Integer

WndProc does not require a Declare. Note all the parameters WndProc is taking on. hWnd is the handle of the window sending the message, message is the message code itself, and wParam and lParam are general arguments which vary from message to message.

Dim rct As RECT Dim pnt As PAINTSTRUCT Dim hDC As UInteger WndProc = 0

With the exception of WndProc = 0, these are all optional pieces of data that can be used for specific messages. We'll take a look at a few common messages next, including the ones we should always be using. :)

Select Case (message)

You should always use a Select Case for this, as it is the cleanest way to do this. Of course, you could use IF...THEN, but...don't make me smack you with a rubber hose. :)


This message is received when an object gets destroyed. Generally speaking, this is usually only handled when the X of the window is clicked or the window is destroyed in another non-forceful manner. For this particular message, we can usually use these two lines of code:

PostQuitMessage 0 Exit Function

This tells the OS that the thread is to be killed. In this instance, it means that our program is going to end. Since WndProc is 0, WndProc will be exited with the value of 0, the message loop will be broken, and 0 will be returned to errorlevel.


This message is passed when the hWnd passed needs to be repainted. For this example, we're concentrating on the window itself. From winhello.bas:

hDC = BeginPaint(hWnd, pnt) GetClientRect hWnd, rct DrawText hDC,"Hello Windows from FreeBasic!", -1, rct, DT_SINGLELINE or DT_CENTER or DT_VCENTER EndPaint hWnd, pnt Exit Function

This is where gdi32 can come in very handy. What this particular piece of code does is draw text onto the window in the dead center in a default system font. Without this code, the text would not be redrawn, even if it was drawn elsewhere. It needs to be within the bounds of WM_PAINT.


This will be an extremely useful message once you start working with controls.

So what happens when we run out of messages to process? Well, obviously the message is of no importance to us, but we can't just ignore it. So, we pass it on to the default handler and end the function:

WndProc = DefWindowProc(hWnd, message, wParam, lParam) End Function

Now you're looking at all this and saying "Damn this is complex! Plus it doesn't even do anything!". Well, yes and no...what we've done was created a skeleton GUI application. In order to make it do useful things, we need to make some additions. As an example, we'll add a couple of buttons to the control, and write code to handle them.

First, make a couple of Dim Shared variables of UInteger type for our buttons' hWnd's:

Dim Shared b1hWnd As UInteger Dim Shared b2hWnd As UInteger

You'll put this where Dim Shared variables usually go. If you don't know where that is, then you're in the wrong tutorial. :)

Now, let's go back into WinMain to make us a couple of buttons. :)

b1hWnd = CreateWindowEx(0, "BUTTON", "Button 1", WS_VISIBLE Or WS_CHILD Or WS_TABSTOP, 0, 0, 100, 30, mhwnd, null, hInstance, null ) b2hWnd = CreateWindowEx(0, "BUTTON", "Button 2", WS_VISIBLE Or WS_CHILD Or WS_TABSTOP, 100, 0, 100, 30, mhwnd, null, hInstance, null )

You'll place this right after the code that created the window itself and before the ShowWindow line. This creates two buttons, 100x30 pixels in size next to each other in the upper-left corner of the window. Now, let's head back into WndProc. Remember WM_COMMAND?

Case WM_COMMAND Select Case lParam Case b1hWnd: MessageBox hWnd, "Button 1 Clicked!", "Test", 0 Case b2hWnd: MessageBox hWnd, "Button 2 Clicked!", "Test", 0 End Select

lParam holds the hWnd of the button clicked. So, we look at which one it was and respond accordingly.

Now, you might be thinking "great, but the font used in the buttons is oogly!". Well, we might be able to do something about that. :) Back to WinMain! :)

SendMessage (b1hWnd, WM_SETFONT, GetStockObject(DEFAULT_GUI_FONT),0) SendMessage (b2hWnd, WM_SETFONT, GetStockObject(DEFAULT_GUI_FONT),0)

This will go after our buttons are created and before the window is shown. These lines change the font from the system font to the GUI font, which looks much much better.

So then, what else can we do? Lots. But the rest is saved for a later tutorial. :) In the meantime, here's a couple of extra functions that will help in the future:

Function GetHighParam (param As Integer) As Integer GetHighParam = param shr 4 end function Function GetLowParam (param As Integer) As Integer GetLowParam = param And &HFFFF end function

These small functions will return the high and low word of the param passed. You'll need this when you get into more advanced controls, like scrollbar messages, which contain two pieces of data in wParam.

If you have any problems with getting any of this code to work, feel free to bug me on any of the major forums, on IRC, on MSNIM, or via email and I'll give ya a hand. GUI coding is NOT easy at first, but with some practice, anyone can pick it up. Good luck. :)

Contact Nekrophidius, or visit one of his many programming websites:
The Basic Network, V Planet!, Lost socK Software or Nek's FreeBasic Page!

File Manipulation In QBasic #2: Random Access Files

Written by Stéphane Richard (Mystikshadows)


Welcome to this second installment on file manipulation in QuickBasic and FreeBasic. In the first installment, we covered sequential file access (Quick note, Sequential file examples should work fine in FreeBasic too). Even today I can say that sequential files have their purpose. However, when it comes to handling a bigger load of data and information, sequential files quickly become annoying as you have to load the whole file and write the whole file. It becomes quite a chore just to get the row of information you're really looking for.

This is where Random Access Files come to play. With Random Access Files you read a record and write a record, not a whole file, at a time. This is what this installment will be about. Random Access Files, how you can use them, why they are better than sequential files and how you can put them to good use in your development projects. You'll find I'll be using a rather similar structure in this tutorial as I did in the Sequential File tutor because I believe it's not a bad way to go about learning. Let's get right to it shall we?


To best describe what random access files are you need to understand one basic principle. All random access files are fixed length record files. This means that for one given file, all records (or rows) of information contain the same datatypes, in the same order and of the same length. The reason these are the way they are is quite simple. It allows to calculate where we are in the file and where we can go. In comparison, if you use a fixed length sequential file (like my example in the sequential file tutor) you'd know that since each row has the same format (columns of data start at the same place) then if you count the number of rows you need to read to get to the 5th record, you'd have read in X number of characters.

In sequential files, you're the one that calculates these things. In Random access files, it's already there for you, to some point. Another advantage of random access files is that when you open the file, you can open it to be both readable and writable at the same time. As you can see, this can become quite an advantage especially when dealing with larger amounts of information.

When programming for Random Access Files, I like to use User Defined Types for this purpose. Ultimately you could use independant sets of variables, calculate the length of everything and use that, but I find User Defined Types go so well hand in hand with Random Access files.


To work with Random Access Files, the commands are different than those used for sequential files. The OPEN statement changes as well but not by much.

The OPEN Statement works as follows:


To read a record from the file you need the GET command. It's syntax is as follows:

GET #FileNumber, [RecordNumber], Variable

To write a record to a random Access File, you'll need to use the PUT command. The syntax for the PUT command is as follows:

PUT #FileNumber, [RecordNumber], Variable

To locate a given record, hence to move the record pointer around in the Random Access file, you'll need the SEEK statement. It's syntax is:

SEEK #FileNumber, Position


With all this theory at hand, I think now is a great time to throw in an example program to help sink things in. This example will create a random access file, insert 2 records, read them back in and display the records to the screen: You can download the source module here. This example should compile and run under FreeBasic as well.

' ------------------------------------------------------------- ' ContactInformation will hold data gotten from the data file ' It will also allow us to write data to the file ' ------------------------------------------------------------- TYPE ContactInformation Status AS STRING * 1 ContactID AS LONG ContactName AS STRING * 40 PhoneNumber AS STRING * 14 Email AS STRING * 70 END TYPE ' ----------------------------------- ' Variables we'll need to work with ' ----------------------------------- DIM FileHandle AS INTEGER DIM RecordNumber AS LONG DIM Contact AS ContactInformation ' ----------------------------------------------------------- ' Get next available Handle and Open the Random Access File ' If the file doesn't exist, one is created ' ----------------------------------------------------------- FileHandle = FREEFILE OPEN "TEST.DAT" FOR RANDOM AS #FileHandle LEN = LEN(Contact) ' --------------------------------------------------- ' Assuming there are no records, we'll insert a few ' --------------------------------------------------- ' --------------------------------------------- ' Fill Information into the Contact Structure ' --------------------------------------------- Contact.Status = " " Contact.ContactID = 1 Contact.ContactName = "Stephane Richard" Contact.PhoneNumber = "(123) 323-3344" Contact.Email = "srichard@adaworld.com" ' -------------------------------------- ' Insert the record into the data file ' -------------------------------------- PUT #FileHandle, , Contact ' --------------------------------------------- ' Fill Information into the Contact Structure ' --------------------------------------------- Contact.Status = " " Contact.ContactID = 2 Contact.ContactName = "Some Other Name" Contact.PhoneNumber = "(666) 555-4444" Contact.Email = "notsure@whatever.com" ' -------------------------------------- ' Insert the record into the data file ' -------------------------------------- PUT #FileHandle, , Contact ' -------------------------------------------- ' And we'll now read and display the records ' -------------------------------------------- RecordNumber = 1 GET #FileHandle, RecordNumber, Contact PRINT Contact.ContactID; " "; Contact.ContactName; " "; Contact.PhoneNumber ' ----------------------------------------------------------- ' If no record number is specified, the next one is read in ' ----------------------------------------------------------- GET #FileHandle, , Contact PRINT Contact.ContactID; " "; Contact.ContactName; " "; Contact.PhoneNumber ' ------------------------------------- ' Of course, we close the file handle ' ------------------------------------- CLOSE #FileHandle


I've commented this example program and I think it's a pretty clear example However A few notes are in order.


The next example coming up will go a few steps further. It will Allow to modify a given record, and to add new records as well. For the sake of a more complete example, I will split this example up in 2 seperate programs. The record manager itself which will allow to add, modify and delete records. and the second part will be a sample program that will create some data so we have a couple records more to work with. I will also include a routine that will pack the data file (hence do the physical deletion of records marked for delete) so you'll also have an example of that as well.

You need to keep in mind a few things as you dwell deeper into File Manipulation techniques. First off there's a certain order of things that need to be specified in order to work properly with Random Access Files.

The first of these order of things is of course:

  1. You open the file
  2. You read and/or write data to the file
  3. You close the file

The next order is about modifying and/or deleting records.

  1. You open the file
  2. You position yourself on the record that needs changing
  3. You load the record into your variable structure
  4. You edit the different variables.
  5. You reposition yourself on the record you're editing
  6. You save the information to the file (or mark the record for deletion)
  7. You close the file.

This next one is for adding a record (always at the end of the file)

  1. You open the file
  2. You create an empty data structure to be edited.
  3. You edit the data that is to be saved
  4. You determine the end of the file
  5. You write your record
  6. You close the file

NOTE: Many issues have risen in the past about how often to open a file versus how long to open the file. This is especially true for a file that may be shared by more than one use. However to me, in regular circumstances I just open the file at the beginning and close it at the end. At least for the sake of this tutorial. But if you keep variables handy to know where you should be in the file, you could just open it when you need to get a record from the file.

This second and last example of this tutorial is a much more complete application compared to the first example, it's much longer as well. You can download it here. It's a contact management program designed to show you all aspects of Random Access File management. I put more effort in this one to design something that's functional. It's main flaw is no data validation on data entry so just cooperate with what it's asking. It also has no error management as it's not the goal of this example. It covers everything you can do to a Random Access File in what I like to call a good application design where everything can be quickly changed to accomodate different types of structures. Data statements are used to position fields on the screen, this way fields can be added if needed. I put a special effort in documenting this source code too so that everything is as clear as it can.

' =========================================================================== ' NAME: Contacts Pro ' FILE NAME: RAFEx2.bas ' --------------------------------------------------------------------------- ' AUTHOR: Stephane Richard ' LICENSE: Public domain ' COPYRIGHT: Copyright (c) 2004 - Stephane Richard ' DISCLAIMER: You are free to use, distribute, copy in any way you wish. ' --------------------------------------------------------------------------- ' DESCRIPTION: This example program is the 2nd in the Random Access File ' tutorial written by the same author. It illustrates all ' functions described in the tutorial in a small but complete ' random access file management application. ' =========================================================================== ' ------------------------------- ' Sub and Function Declarations ' ------------------------------- DECLARE SUB DrawMainScreen () DECLARE SUB MainMenu () DECLARE SUB AddAContact () DECLARE SUB UpdateAContact () DECLARE SUB DeleteAContact () DECLARE SUB ListAllContacts () DECLARE SUB PackContactFile () DECLARE SUB ClearViewPrint () DECLARE SUB DisplayEmptyFields () DECLARE SUB DisplayContactFields () DECLARE SUB MoveToNext () DECLARE SUB MoveToPrevious () ' ---------------------- ' Constant Definitions ' ---------------------- CONST TRUE = -1 CONST FALSE = NOT TRUE CONST ADDCONTACT = 0 CONST UPDATECONTACT = 1 CONST DELETECONTACT = 2 CONST LOOKUPCONTACT = 3 CONST LISTCONTACTS = 4 CONST PACKDATAFILE = 5 CONST EXITCONTACTS = 6 ' ------------------------------------ ' FieldInformation User defined type ' ------------------------------------ TYPE Fieldinformation FieldNumber AS LONG FieldName AS STRING * 12 YPosition AS INTEGER XPosition AS INTEGER FieldLength AS INTEGER END TYPE ' ------------------------------------------------ ' User Defined Type used for Record Manipulation ' ------------------------------------------------ TYPE ContactInformation Status AS STRING * 1 ' Record Status Field "D" -Marked Deleted ContactID AS LONG CompanyName AS STRING * 50 ContactName AS STRING * 40 Address1 AS STRING * 60 Address2 AS STRING * 60 City AS STRING * 40 State AS STRING * 40 ZipCode AS STRING * 10 HomePhone AS STRING * 14 WorkPhone AS STRING * 14 CellPhone AS STRING * 14 PagerNumber AS STRING * 14 Email AS STRING * 60 Website AS STRING * 60 END TYPE ' ----------------------------------- ' Variables we'll need to work with ' ----------------------------------- DIM SHARED FileHandle AS INTEGER DIM SHARED RecordCount AS LONG DIM SHARED RecordNumber AS LONG DIM SHARED ContactIDCounter AS LONG DIM SHARED Contact AS ContactInformation DIM SHARED FieldInfo(1 TO 14) AS Fieldinformation DIM Counter AS INTEGER ' ------------------------------------------------- ' Read field information from the data statements ' ------------------------------------------------- FOR Counter = 1 TO 14 READ FieldInfo(Counter).FieldNumber READ FieldInfo(Counter).FieldName READ FieldInfo(Counter).YPosition READ FieldInfo(Counter).XPosition READ FieldInfo(Counter).FieldLength NEXT Counter ' ------------------------------------------------------ ' We get the next FileHandle and we open the data file ' ------------------------------------------------------ FileHandle = FREEFILE OPEN "CONTACTS.DAT" FOR RANDOM AS #FileHandle LEN = LEN(Contact) RecordNumber = 1 RecordCount = LOF(FileHandle) / LEN(Contact) ' ---------------------- ' Draw the main screen ' ---------------------- WIDTH 80, 25 COLOR 14, 1 CALL DrawMainScreen IF RecordCount > 0 THEN SEEK #FileHandle, RecordNumber CALL DisplayContactFields ELSE CALL DisplayEmptyFields END IF ' ------------------------------ ' Present the menu to the user ' ------------------------------ CALL MainMenu COLOR 7, 0 CLS CLOSE #FileHandle ' ----------------------------------------------------------- ' DATA STATEMENTS FOR FIELD POSITION AND LENGTH INFORMATION ' STRUCTURE: FieldNumber, "Field Name", Row, Col, Length ' ----------------------------------------------------------- DATA 1, "ContactID", 6, 71, 8 DATA 2, "CompanyName", 8, 29, 50 DATA 3, "ContactName", 9, 39, 40 DATA 4, "Address1", 11, 19, 60 DATA 5, "Address2", 12, 19, 60 DATA 6, "City", 13, 39, 40 DATA 7, "State", 14, 39, 40 DATA 8, "ZipCode", 15, 69, 10 DATA 9, "HomePhone", 17, 65, 14 DATA 10, "WorkPhone", 18, 65, 14 DATA 11, "CellPhone", 19, 65, 14 DATA 12, "PagerNumber", 20, 65, 14 DATA 13, "Email", 22, 19, 60 DATA 14, "Website", 23, 19, 60 ' ============================================= ' NAME: AddAContact() ' PARAMETERS: None ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' --------------------------------------------- ' DESCRIPTION: This Subroutine requests the ' information from the user ' about the contact and adds it ' to the end of the file. ' ============================================= SUB AddAContact DIM WorkContact AS ContactInformation DIM WorkString AS STRING ' ------------------------------------- ' First Step - Empty Fields on screen ' ------------------------------------- CALL DisplayEmptyFields ' -------------------------------------- ' Next we start the data entry process ' -------------------------------------- LOCATE FieldInfo(1).YPosition, FieldInfo(1).XPosition: COLOR 14, 7 INPUT Contact.ContactID LOCATE FieldInfo(1).YPosition, FieldInfo(1).XPosition: COLOR 14, 3 PRINT USING "######"; Contact.ContactID LOCATE FieldInfo(2).YPosition, FieldInfo(2).XPosition: COLOR 14, 7 INPUT Contact.CompanyName LOCATE FieldInfo(2).YPosition, FieldInfo(2).XPosition: COLOR 14, 3 PRINT Contact.CompanyName LOCATE FieldInfo(3).YPosition, FieldInfo(3).XPosition: COLOR 14, 7 INPUT Contact.ContactName LOCATE FieldInfo(3).YPosition, FieldInfo(3).XPosition: COLOR 14, 3 PRINT Contact.ContactName LOCATE FieldInfo(4).YPosition, FieldInfo(4).XPosition: COLOR 14, 7 INPUT Contact.Address1 LOCATE FieldInfo(4).YPosition, FieldInfo(4).XPosition: COLOR 14, 3 PRINT Contact.Address1 LOCATE FieldInfo(5).YPosition, FieldInfo(5).XPosition: COLOR 14, 7 INPUT Contact.Address2 LOCATE FieldInfo(5).YPosition, FieldInfo(5).XPosition: COLOR 14, 3 PRINT Contact.Address2 LOCATE FieldInfo(6).YPosition, FieldInfo(6).XPosition: COLOR 14, 7 INPUT Contact.City LOCATE FieldInfo(6).YPosition, FieldInfo(6).XPosition: COLOR 14, 3 PRINT Contact.City LOCATE FieldInfo(7).YPosition, FieldInfo(7).XPosition: COLOR 14, 7 INPUT Contact.State LOCATE FieldInfo(7).YPosition, FieldInfo(7).XPosition: COLOR 14, 3 PRINT Contact.State LOCATE FieldInfo(8).YPosition, FieldInfo(8).XPosition: COLOR 14, 7 INPUT Contact.ZipCode LOCATE FieldInfo(8).YPosition, FieldInfo(8).XPosition: COLOR 14, 3 PRINT Contact.ZipCode LOCATE FieldInfo(9).YPosition, FieldInfo(9).XPosition: COLOR 14, 7 INPUT Contact.HomePhone LOCATE FieldInfo(9).YPosition, FieldInfo(9).XPosition: COLOR 14, 3 PRINT Contact.HomePhone LOCATE FieldInfo(10).YPosition, FieldInfo(10).XPosition: COLOR 14, 7 INPUT Contact.WorkPhone LOCATE FieldInfo(10).YPosition, FieldInfo(10).XPosition: COLOR 14, 3 PRINT Contact.WorkPhone LOCATE FieldInfo(11).YPosition, FieldInfo(11).XPosition: COLOR 14, 7 INPUT Contact.CellPhone LOCATE FieldInfo(11).YPosition, FieldInfo(11).XPosition: COLOR 14, 3 PRINT Contact.CellPhone LOCATE FieldInfo(12).YPosition, FieldInfo(12).XPosition: COLOR 14, 7 INPUT Contact.PagerNumber LOCATE FieldInfo(12).YPosition, FieldInfo(12).XPosition: COLOR 14, 3 PRINT Contact.PagerNumber LOCATE FieldInfo(13).YPosition, FieldInfo(13).XPosition: COLOR 14, 7 INPUT Contact.Email LOCATE FieldInfo(13).YPosition, FieldInfo(13).XPosition: COLOR 14, 3 PRINT Contact.Email LOCATE FieldInfo(14).YPosition, FieldInfo(14).XPosition: COLOR 14, 7 INPUT Contact.Website LOCATE FieldInfo(14).YPosition, FieldInfo(14).XPosition: COLOR 14, 3 PRINT Contact.Website ' ----------------------------------- ' We add the record in the datafile ' ----------------------------------- ' SEEK #FileHandle, RecordCount PUT #FileHandle, RecordCount + 1, Contact ' ------------------------------------- ' Update RecordCount and RecordNumber ' ------------------------------------- RecordNumber = RecordCount + 1 RecordCount = LOF(FileHandle) / LEN(Contact) SEEK #FileHandle, RecordCount WorkString = "RECORD: " + STR$(RecordNumber) + " OF " + STR$(RecordCount) LOCATE 4, 79 - LEN(WorkString): COLOR 7, 1: PRINT WorkString END SUB ' ================================================= ' NAME: DeleteAContact() ' PARAMETERS: None ' RETURNS: Integer for the selected menu ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' ------------------------------------------------- ' DESCRIPTION: This Ask for confirmation to ' delete and awaits the user's input ' Then Marks the current contact for ' deletion. ' ================================================= SUB DeleteAContact DIM WorkString AS STRING LOCATE 4, 2: COLOR 0, 4: PRINT SPACE$(78); LOCATE 4, 2: COLOR 14, 4: INPUT "Are you sure you want to delete this contact? (Y/N) :", Confirm$ IF UCASE$(Confirm$) = "Y" THEN Contact.Status = "D" PUT #FileHandle, RecordNumber, Contact END IF LOCATE 4, 1: COLOR 7, 1: PRINT CHR$(186); SPACE$(78); CHR$(186) LOCATE 4, 3: COLOR 7, 1: PRINT "CURRENT CONTACT INFORMATION" WorkString = "RECORD: " + STR$(RecordNumber) + " OF " + STR$(RecordCount) LOCATE 4, 79 - LEN(WorkString): PRINT WorkString END SUB ' =============================================== ' NAME: DisplayContactFields() ' PARAMETERS: None ' ASSUMES: Contact Variable Filled ' CALLED FROM: MainMenu Subroutine ' ----------------------------------------------- ' DESCRIPTION: Empties the currently displayed ' fields to display the current ' contact information. Called ' whenever the record pointer in ' the data file is moved. ' =============================================== SUB DisplayContactFields DIM WorkString AS STRING CALL DisplayEmptyFields IF Contact.ContactID = 0 THEN GET #FileHandle, RecordNumber, Contact END IF LOCATE 4, 40 COLOR 7, 1 IF Contact.Status = "D" THEN PRINT "DELETED" ELSE PRINT " " END IF LOCATE 4, 50: COLOR 7, 1: PRINT " " WorkString = "RECORD: " + STR$(RecordNumber) + " OF " + STR$(RecordCount) LOCATE 4, 79 - LEN(WorkString): PRINT WorkString LOCATE FieldInfo(1).YPosition, FieldInfo(1).XPosition COLOR 15, 3: PRINT Contact.ContactID LOCATE FieldInfo(2).YPosition, FieldInfo(2).XPosition COLOR 15, 3: PRINT Contact.CompanyName LOCATE FieldInfo(3).YPosition, FieldInfo(3).XPosition COLOR 15, 3: PRINT Contact.ContactName LOCATE FieldInfo(4).YPosition, FieldInfo(4).XPosition COLOR 15, 3: PRINT Contact.Address1 LOCATE FieldInfo(5).YPosition, FieldInfo(5).XPosition COLOR 15, 3: PRINT Contact.Address2 LOCATE FieldInfo(6).YPosition, FieldInfo(6).XPosition COLOR 15, 3: PRINT Contact.City LOCATE FieldInfo(7).YPosition, FieldInfo(7).XPosition COLOR 15, 3: PRINT Contact.State LOCATE FieldInfo(8).YPosition, FieldInfo(8).XPosition COLOR 15, 3: PRINT Contact.ZipCode LOCATE FieldInfo(9).YPosition, FieldInfo(9).XPosition COLOR 15, 3: PRINT Contact.HomePhone LOCATE FieldInfo(10).YPosition, FieldInfo(10).XPosition COLOR 15, 3: PRINT Contact.WorkPhone LOCATE FieldInfo(11).YPosition, FieldInfo(11).XPosition COLOR 15, 3: PRINT Contact.CellPhone LOCATE FieldInfo(12).YPosition, FieldInfo(12).XPosition COLOR 15, 3: PRINT Contact.PagerNumber LOCATE FieldInfo(13).YPosition, FieldInfo(13).XPosition COLOR 15, 3: PRINT Contact.Email LOCATE FieldInfo(14).YPosition, FieldInfo(14).XPosition COLOR 15, 3: PRINT Contact.Website END SUB ' ===================================================== ' NAME: DisplayEmptyFields() ' PARAMETERS: None ' ASSUMES: Nothing ' CALLED FROM: Main Program Section ' ----------------------------------------------------- ' DESCRIPTION: This Subroutine loops through the ' defined Field definitions to display ' the set of empty fields on the screen. ' ===================================================== SUB DisplayEmptyFields DIM Counter AS INTEGER FOR Counter = 1 TO 14 LOCATE FieldInfo(Counter).YPosition, FieldInfo(Counter).XPosition COLOR 0, 3 PRINT SPACE$(FieldInfo(Counter).FieldLength) NEXT Counter END SUB ' ============================================== ' NAME: DrawMainScreen() ' PARAMETERS: None ' ASSUMES: Nothing ' CALLED FROM: Main Section of the program ' ---------------------------------------------- ' DESCRIPTION: This subroutine clears the ' screen and draws the background ' screen of the program. ' ============================================== SUB DrawMainScreen DIM Counter AS INTEGER DIM WorkString AS STRING CLS LOCATE 1, 1: COLOR 0, 5: PRINT SPACE$(80); LOCATE 1, 1: COLOR 14, 5: PRINT "Contacts Professional 1.00.000a" LOCATE 1, 49: COLOR 15, 5: PRINT "Random Access File Demonstration" LOCATE 2, 1: COLOR 0, 7: PRINT SPACE$(80); LOCATE 3, 1: COLOR 7, 1: PRINT CHR$(201); STRING$(78, CHR$(205)); CHR$(187) LOCATE 4, 1: COLOR 7, 1: PRINT CHR$(186); SPACE$(78); CHR$(186) LOCATE 4, 3: COLOR 7, 1: PRINT "CURRENT CONTACT INFORMATION" WorkString = "RECORD: " + STR$(RecordNumber) + " OF " + STR$(RecordCount) LOCATE 4, 79 - LEN(WorkString): PRINT WorkString LOCATE 5, 1: COLOR 7, 1: PRINT CHR$(204); STRING$(78, CHR$(205)); CHR$(185) FOR Counter = 6 TO 23 LOCATE Counter, 1: COLOR 7, 1: PRINT CHR$(186); SPACE$(78); CHR$(186) NEXT Counter LOCATE 7, 1: COLOR 7, 1: PRINT CHR$(199); STRING$(78, CHR$(196)); CHR$(182) LOCATE 10, 1: COLOR 7, 1: PRINT CHR$(199); STRING$(78, CHR$(196)); CHR$(182) LOCATE 16, 1: COLOR 7, 1: PRINT CHR$(199); STRING$(78, CHR$(196)); CHR$(182) LOCATE 21, 1: COLOR 7, 1: PRINT CHR$(199); STRING$(78, CHR$(196)); CHR$(182) ' ------------------- ' Draw field labels ' ------------------- LOCATE 6, 3: COLOR 7, 1: PRINT "Contact ID....:" LOCATE 8, 3: COLOR 7, 1: PRINT "Company Name..:" LOCATE 9, 3: COLOR 7, 1: PRINT "Contact Name..:" LOCATE 11, 3: COLOR 7, 1: PRINT "Address One...:" LOCATE 12, 3: COLOR 7, 1: PRINT "Address Two...:" LOCATE 13, 3: COLOR 7, 1: PRINT "City..........:" LOCATE 14, 3: COLOR 7, 1: PRINT "State.........:" LOCATE 15, 3: COLOR 7, 1: PRINT "Zip Code......:" LOCATE 17, 3: COLOR 7, 1: PRINT "Home Phone....:" LOCATE 18, 3: COLOR 7, 1: PRINT "Work Phone....:" LOCATE 19, 3: COLOR 7, 1: PRINT "Cell Phone....:" LOCATE 20, 3: COLOR 7, 1: PRINT "Pager Number..:" LOCATE 22, 3: COLOR 7, 1: PRINT "Email Address.:" LOCATE 23, 3: COLOR 7, 1: PRINT "Website URL...:" LOCATE 24, 1: COLOR 7, 1: PRINT CHR$(200); STRING$(78, CHR$(205)); CHR$(188); LOCATE 25, 1: COLOR 0, 5: PRINT SPACE$(80); LOCATE 25, 1: COLOR 7, 5: PRINT " <"; CHR$(17) + CHR$(16) + "=Menu><PgUp=Prev><PgDn=Next><Ins=New><Del=Delete><Enter=Edit>"; END SUB SUB GetContactFields END SUB ' ============================================= ' NAME: ListAllContacts() ' PARAMETERS: None ' RETURNS: Integer for the selected menu ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' --------------------------------------------- ' DESCRIPTION: This subroutine goes about ' listing all the contact data ' that is currently present in ' the data file, one after the ' other. ' ============================================= SUB ListAllContacts ' ----------------------------------------------- ' Work Variables we'll need to perform the list ' ----------------------------------------------- DIM WorkContact AS ContactInformation DIM WorkRecord AS LONG DIM WorkKey AS STRING ' ------------------------------------------------------ ' Perform the list of contacts only if we have records ' ------------------------------------------------------ IF RecordCount > 0 THEN CLS ' ---------------------------------------- ' Position ourselves on the first record ' ---------------------------------------- SEEK #FileHandle, 1 ' ------------------------------------------------ ' Loop through the records and print record data ' ------------------------------------------------ DO WHILE NOT EOF(FileHandle) GET #FileHandle, , WorkContact PRINT WorkContact.ContactID; " "; WorkContact.ContactName; " "; WorkContact.HomePhone LOOP ' --------------------- ' Await User KeyPress ' --------------------- PRINT "Press Any Key To Go Back To Contact Screen." DO WHILE WorkKey = "" WorkKey = INKEY$ LOOP ' ------------------------------------ ' Go back to the main contact screen ' ------------------------------------ CALL DrawMainScreen SEEK #FileHandle, RecordNumber CALL DisplayContactFields END IF END SUB ' ============================================= ' NAME: MainMenu() ' PARAMETERS: None ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' --------------------------------------------- ' DESCRIPTION: This Displays a main menu ' and awaits the user's input ' Calls the proper subroutine ' depending on the selected ' menu option. ' ============================================= SUB MainMenu ' --------------------------------------- ' Local variables used in this function ' --------------------------------------- DIM Menus(6) AS STRING DIM Counter AS INTEGER DIM Current AS INTEGER DIM Keyboard AS STRING ' ------------------------------------------ ' Assign the Menu Items to the Menus Array ' ------------------------------------------ Menus(0) = "[New]" Menus(1) = "[Edit]" Menus(2) = "[Delete]" Menus(3) = "[List]" Menus(4) = "[Pack Data]" Menus(5) = "[Exit]" Current = 0 DO WHILE TRUE ' ------------------------ ' Display the menu items ' ------------------------ LOCATE 2, 16 FOR Counter = 0 TO 5 IF Counter = Current THEN COLOR 7, 0 ELSE COLOR 0, 7 END IF PRINT Menus(Counter); : COLOR 0, 7: PRINT " "; NEXT Counter ' -------------------------------------------------- ' Handle the keyboard to allow for menu selection ' -------------------------------------------------- Keyboard = INKEY$ SELECT CASE Keyboard CASE CHR$(0) + CHR$(75) ' <- Left Arrow Current = Current - 1 IF Current < 0 THEN Current = 5 END IF CASE CHR$(0) + CHR$(77) ' <- Right Arrow Current = Current + 1 IF Current > 5 THEN Current = 0 END IF CASE CHR$(0) + CHR$(73) ' <- Page Up CALL MoveToPrevious CASE CHR$(0) + CHR$(81) ' <- Page Down CALL MoveToNext CASE CHR$(13) ' --------------------------------------------- ' Call appropriate subroutine based on choice ' --------------------------------------------- SELECT CASE Current CASE 0 CALL AddAContact CASE 1 CALL UpdateAContact CASE 2 CALL DeleteAContact CASE 3 CALL ListAllContacts CASE 4 CALL PackContactFile CASE 5 CanExit = TRUE EXIT DO END SELECT END SELECT LOOP END SUB ' ================================================= ' NAME: MoveToNext() ' PARAMETERS: None ' ASSUMES: DataFile for #FileHandle Opened ' CALLED FROM: MainMenu Subroutine ' ------------------------------------------------- ' DESCRIPTION: Adds 1 to the current RecordNumber ' and adjust to the end of file if ' needed, then loads Contact Info ' from the current record and calls ' DisplayContactFields to show the ' loaded contact information. ' ================================================= SUB MoveToNext RecordNumber = RecordNumber + 1 IF RecordNumber > RecordCount THEN RecordNumber = RecordCount END IF GET #FileHandle, RecordNumber, Contact CALL DisplayContactFields END SUB ' ================================================= ' NAME: MoveToPrevious() ' PARAMETERS: None ' ASSUMES: DataFile for #FileHandle Opened ' CALLED FROM: MainMenu Subroutine ' ------------------------------------------------- ' DESCRIPTION: takes 1 from RecordNumber variable ' and adjust to the start of file if ' needed, then loads Contact Info ' from the current record and calls ' DisplayContactFields to show the ' loaded contact i`nformation. ' ================================================= SUB MoveToPrevious RecordNumber = RecordNumber - 1 IF RecordNumber < 1 THEN RecordNumber = 1 END IF GET #FileHandle, RecordNumber, Contact CALL DisplayContactFields END SUB ' ============================================= ' NAME: PackContactFile() ' PARAMETERS: None ' RETURNS: Integer for the selected menu ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' --------------------------------------------- ' DESCRIPTION: This subroutine performs the ' physical deletion of records ' marked for deletion. To do so ' it will create a temporary ' file, copy all records not ' marked for Deletion to the new ' file, once done, it will erase ' the data file, and rename the ' temporary file to the official ' data file name. ' ============================================= SUB PackContactFile ' --------------------------- ' Work Variables we'll need ' --------------------------- DIM WorkContact AS ContactInformation DIM Counter AS LONG DIM SourceHandle AS INTEGER DIM DestinationHandle AS INTEGER ' ----------------------------------------- ' First we get handles and open the files ' ----------------------------------------- CLOSE #FileHandle SourceHandle = FREEFILE OPEN "CONTACTS.DAT" FOR RANDOM AS #SourceHandle LEN = LEN(WorkContact) DestinationHandle = FREEFILE OPEN "TEMP.DAT" FOR RANDOM AS #DestinationHandle LEN = LEN(WorkContact) Counter = 0 ' ---------------------------------------------------- ' Main loop to do the copying of non deleted records ' ---------------------------------------------------- DO WHILE NOT EOF(SourceHandle) GET #SourceHandle, , WorkContact IF WorkContact.Status <> "D" THEN Counter = Counter + 1 ' ----------------------------------- ' We add the record in the datafile ' ----------------------------------- SEEK #DestinationHandle, Counter PUT #DestinationHandle, Counter + 1, Contact END IF LOOP ' ------------------------ ' Close the opened files ' ------------------------ CLOSE #DestinationHandle CLOSE #SourceHandle ' ---------------------------------------------------------- ' Delete contact file and rename temp file to CONTACTS.DAT ' ---------------------------------------------------------- KILL "CONTACTS.DAT" NAME "TEMP.DAT" AS "CONTACTS.DAT" OPEN "CONTACTS.DAT" FOR RANDOM AS #FileHandle LEN = LEN(WorkContact) END SUB ' ================================================ ' NAME: UpdateAContact() ' PARAMETERS: None ' RETURNS: Integer for the selected menu ' CALLED FROM: Main Section of the program ' ASSUMES: Nothing ' ------------------------------------------------ ' DESCRIPTION: This sub start the data entry for ' fields and after saves the ' information to the file. ' ================================================ SUB UpdateAContact DIM WorkContact AS ContactInformation ' ---------------------------------------- ' First we start the data entry process ' Work from a temp stucture so if there ' are changes they are updated and shown ' ---------------------------------------- WorkContact = Contact LOCATE FieldInfo(1).YPosition, FieldInfo(1).XPosition: COLOR 14, 7 INPUT WorkContact.ContactID LOCATE FieldInfo(1).YPosition, FieldInfo(1).XPosition: COLOR 14, 3 PRINT USING "######"; Contact.ContactID LOCATE FieldInfo(2).YPosition, FieldInfo(2).XPosition: COLOR 14, 7 INPUT WorkContact.CompanyName IF (Contact.CompanyName <> WorkContact.CompanyName) AND (WorkContact.CompanyName <> "") THEN Contact.CompanyName = WorkContact.CompanyName END IF LOCATE FieldInfo(2).YPosition, FieldInfo(2).XPosition: COLOR 14, 3 PRINT Contact.CompanyName LOCATE FieldInfo(3).YPosition, FieldInfo(3).XPosition: COLOR 14, 7 INPUT WorkContact.ContactName IF (Contact.ContactName <> WorkContact.ContactName) AND (WorkContact.ContactName <> "") THEN Contact.ContactName = WorkContact.ContactName END IF LOCATE FieldInfo(3).YPosition, FieldInfo(3).XPosition: COLOR 14, 3 PRINT Contact.ContactName LOCATE FieldInfo(4).YPosition, FieldInfo(4).XPosition: COLOR 14, 7 INPUT Contact.Address1 IF (Contact.Address1 <> WorkContact.Address1) AND (WorkContact.Address1 <> "") THEN Contact.Address1 = WorkContact.Address1 END IF LOCATE FieldInfo(4).YPosition, FieldInfo(4).XPosition: COLOR 14, 3 PRINT Contact.Address1 LOCATE FieldInfo(5).YPosition, FieldInfo(5).XPosition: COLOR 14, 7 INPUT Contact.Address2 IF (Contact.Address2 <> WorkContact.Address2) AND (WorkContact.Address2 <> "") THEN Contact.Address2 = WorkContact.Address2 END IF LOCATE FieldInfo(5).YPosition, FieldInfo(5).XPosition: COLOR 14, 3 PRINT Contact.Address2 LOCATE FieldInfo(6).YPosition, FieldInfo(6).XPosition: COLOR 14, 7 INPUT Contact.City IF (Contact.City <> WorkContact.City) AND (WorkContact.City <> "") THEN Contact.City = WorkContact.City END IF LOCATE FieldInfo(6).YPosition, FieldInfo(6).XPosition: COLOR 14, 3 PRINT Contact.City LOCATE FieldInfo(7).YPosition, FieldInfo(7).XPosition: COLOR 14, 7 INPUT Contact.State IF (Contact.State <> WorkContact.State) AND (WorkContact.State <> "") THEN Contact.State = WorkContact.State END IF LOCATE FieldInfo(7).YPosition, FieldInfo(7).XPosition: COLOR 14, 3 PRINT Contact.State LOCATE FieldInfo(8).YPosition, FieldInfo(8).XPosition: COLOR 14, 7 INPUT Contact.ZipCode IF (Contact.ZipCode <> WorkContact.ZipCode) AND (WorkContact.ZipCode <> "") THEN Contact.ZipCode = WorkContact.ZipCode END IF LOCATE FieldInfo(8).YPosition, FieldInfo(8).XPosition: COLOR 14, 3 PRINT Contact.ZipCode LOCATE FieldInfo(9).YPosition, FieldInfo(9).XPosition: COLOR 14, 7 INPUT Contact.HomePhone IF (Contact.HomePhone <> WorkContact.HomePhone) AND (WorkContact.HomePhone <> "") THEN Contact.HomePhone = WorkContact.HomePhone END IF LOCATE FieldInfo(9).YPosition, FieldInfo(9).XPosition: COLOR 14, 3 PRINT Contact.HomePhone LOCATE FieldInfo(10).YPosition, FieldInfo(10).XPosition: COLOR 14, 7 INPUT Contact.WorkPhone IF (Contact.WorkPhone <> WorkContact.WorkPhone) AND (WorkContact.WorkPhone <> "") THEN Contact.WorkPhone = WorkContact.WorkPhone END IF LOCATE FieldInfo(10).YPosition, FieldInfo(10).XPosition: COLOR 14, 3 PRINT Contact.WorkPhone LOCATE FieldInfo(11).YPosition, FieldInfo(11).XPosition: COLOR 14, 7 INPUT Contact.CellPhone IF (Contact.CellPhone <> WorkContact.CellPhone) AND (WorkContact.CellPhone <> "") THEN Contact.CellPhone = WorkContact.CellPhone END IF LOCATE FieldInfo(11).YPosition, FieldInfo(11).XPosition: COLOR 14, 3 PRINT Contact.CellPhone LOCATE FieldInfo(12).YPosition, FieldInfo(12).XPosition: COLOR 14, 7 INPUT Contact.PagerNumber IF (Contact.PagerNumber <> WorkContact.PagerNumber) AND (WorkContact.PagerNumber <> "") THEN Contact.PagerNumber = WorkContact.PagerNumber END IF LOCATE FieldInfo(12).YPosition, FieldInfo(12).XPosition: COLOR 14, 3 PRINT Contact.PagerNumber LOCATE FieldInfo(13).YPosition, FieldInfo(13).XPosition: COLOR 14, 7 INPUT Contact.Email IF (Contact.Email <> WorkContact.Email) AND (WorkContact.Email <> "") THEN Contact.Email = WorkContact.Email END IF LOCATE FieldInfo(13).YPosition, FieldInfo(13).XPosition: COLOR 14, 3 PRINT Contact.Email LOCATE FieldInfo(14).YPosition, FieldInfo(14).XPosition: COLOR 14, 7 INPUT Contact.Website IF (Contact.Website <> WorkContact.Website) AND (WorkContact.Website <> "") THEN Contact.Website = WorkContact.Website END IF LOCATE FieldInfo(14).YPosition, FieldInfo(14).XPosition: COLOR 14, 3 PRINT Contact.Website ' ------------------------------------ ' We save the record in the datafile ' ------------------------------------ PUT #FileHandle, RecordNumber, Contact ' ------------------------------------- ' Update RecordCount and RecordNumber ' ------------------------------------- SEEK #FileHandle, RecordNumber END SUB



And there you have it. Hopefully, at the end of this tutorial, you'll have a good enough understanding of how Random Access Files work. A good enough understanding so you can "almost fluently" use it in your own programming projects. I used contact information in my examples, remember they could just as easily be highscore tables, list of characters (if your project has a character builder) or whatever else can be consider as tabular data or data that can be saved in a user defined type.

I hope you've enjoyed reading this tutorial at least as much as I've enjoyed writing it for you.

Download this entire tutorial as well as both example programs: RAF.rar
You can contact Stéphane at this address.

PHP Series Part Two

Written by 19day

Here's part #2 of 19day's awesome PHP series. Sure it's not BASIC, but it sure is useful! If you missed Part #1, you can find it in the last issue. Enjoy!



Arrays in PHP are powerful little buggers, and they seem to work fairly fast. They are as easy to use as vectors, but they can easily act as simple little map functions. PHP also has a few algorithmic tools for working with arrays, and a new looping control and operators.

First, I’ll show you the different ways to initialize an array.

$a1 = array(); $a2 = array(“Item1”, “Item2”, “Item3”); $a3 = array(“breakfast” => “pancake”, “lunch” => “soup”, “dinner” => “pizza”); $a4 = array(2 => array(“1+1”), 3 => array(“1+2”, “2+1”), 4 => array(“1+3”, “3+1”, “2+2”) );

Note that you don’t have to explicitly map things if you want to use the natural ordered numbering, like was shown in $a2, but then you can go on and specify a key if you like, and if there is a key conflict (either 2 specified keys, or one specified and one not), then the latest one to be specified wins.

Also note that since PHP does not use strict typing as we’ve known it, you can mix and match array key types and value types, they really are quite powerful, but then again, it can bog you down.

For each

The foreach construct was added as many times we need to iterate over all the objects in a collection. This allows us to easily go through all the records of an array (which I will hopefully discuss in a later tutorial) and then you can do something with the information.

There are two forms to the foreach control statement,

Reference Variables

References are a little confusing, as they don’t usually work as you’d expect. They aren’t like pointers, they are just aliases for other variables. Their use comes most when passing parameters, and retrieving them, by reference. To pass by reference, use the & symbol in front of the variable name in the parameter list of the function, or pass the variable with the & symbol in front. From what I’ve read, references seem not to work very well, and lead to unexpected behaviour. I’m mentioning them for completeness, but I tend not to use them myself.


From the last tutorial, you know that if you want to output the value of a variable inside a text string, you can, just:

echo “hello, your name is $firstname”;

You can do this with arrays, but you must wrap the statement in curly brackets, as such:

echo “hello, your name is {$fullname[‘firstname’]}”;

where $fullname is an array of keys firstname and lastname, and they map to what one would expect.


Unlike many of the languages I’ve worked with, arrays in PHP get their own predefined operators, and they can be fairly useful.

Outside Data

So far, we’ve only dealt with programs dealing with files and such, or just doing what they need to do and shut down. These uses are not very interesting; we need to be able to have a user to send data into a script so PHP can work with it. Luckily, it is fairly easy to get the data in.

First, we need a source for our data, so we’ll have a page where a user can enter information, it can be html or php, makes no difference, but we send the data to a php page for handling.

So how about this simple page, input.html:

<html> <body> Enter your name and favourite colour from the list: <br/> <form id="main" name="main" action="handler.php" method="get"> <input id="name" name="name" type="text"> <select name="colour"> <option value="blue">Blue</option> <option value="red">Red</option> <option value="yellow">Yellow</option> <option value="green">Green</option> </select> <input type="submit"> </form> </body> </html>

This page gives instruction to enter your name, and then pick a favourite colour from a very short list. The form sets up the action attribute to be our php page that handles the input, and the method is GET.

Now let’s say our handler.php file was the following:

<?php if(isset($_GET['name'])) { $name = $_GET['name']; } if(isset($_POST['name'])) { $name = $_POST['name']; } if(isset($_GET['colour'])) { $colour = $_GET['colour']; } if(isset($_POST['colour'])) { $colour = $_POST['colour']; } ?> <html> <body style="background-color: <?php echo $colour; ?> ;"> Hello <?php echo $name; ?>, hope you like the view. </body> </html>

Going to input.html, entering a name and picking a colour and hitting submit, takes you to handler.php, greets the person of that name, and sets the background colour of the document to be the colour chosen. In old versions of PHP, you could just use the variable sent to the PHP file from the form as if it always existed. Now, you have to explicitly get them out of the superglobal arrays _GET and _POST. If the value of the variable is set (as named in the form) then set the variable content to be the value as received from outside PHP. You can name the new variable whatever you want, but for simplicity, I just use the name as it was before. Now you can use the data.

There are two ways to send the data to the PHP file, GET and POST. As you’ll notice in the handler.php after submitting the data, the variables and values are encoded into the URL. This allows you to bookmark a page given variables entered, and can be handy. POST passes data without this visible encoding, so if you don’t want or need the data to be bookmarked or seen, then use this. Also, GET has length limits due to it being so encoded, while POST’s limits are much higher. For a message board use POST, while for a search page, one would tend to use GET, for example.

Security Considerations: It is usually a good idea to parse any incoming data to ensure that the user isn’t trying to do something they shouldn’t. Like let’s say you take a filename as input, to delete it from a particular directory. So you take their input string, and append it to a path string, and then pass that whole path in to delete. If the user is nice and only inputs filenames like “test1.txt” then you’re fine, but if they input things like “../index.html” or something, they can get to other places and do naughty things. So usually it’s best to test input as best as you can, and then only pass them to functions that will limit the damage they can do if something goes awry.

Manipulating Input/Output Strings

When you type a name with special characters, say, a quote, it gets converted in the URL, and when output, is output literally, slashes intact. Use the stripslashes function to get rid of them. addslashes exists, and is usually used to add the escape character to special characters before inserting records into a database.
urlencode - urldecode
You may want to fake GET data by having a link in the format seen when doing a GET request. urlencode encodes strings by changing specific non-alphanumeric characters to %hh where hh is the hexcode for the character. This alone may not be enough.
encodes a string changing special characters into the HTML entity, & to & and such. PHP.net suggests encoding a string using urlencode and htmlentities in concert, in that order, to combat conflicts when the data ‘looks’ like an entity.
explode comes in handy a lot, it takes two parameters, first, the token on which to explode on, and second, the string which to explode. explode(“|”, “you|know|my|name|is|simon”); returns an array, indexed at 0, with the elements “you”, “know”, “my”, “name”, “is”, “simon”. You can’t specify multiple delimiters to explode on in the same explode call (I’m guessing the best way to do that is recursively explode each chunk on each new delimiter) but the delimiter itself is a string, thus it can be longer than a character.

Regular Expressions

I was debating whether or not to do anything on this at all, since I’d have to, for completeness, try to explain what they are, how powerful they are, and how to use them in PHP. Once you’ve got a grip on them, they are quite powerful, and are usable in different programs. Regular expressions themselves, as a concept, are in the realm of computer science in general, once you know them, all you’d need to know is in what way the other tools you’d use refer to them. The grep unix tool is all about regular expressions, so if you can effectively use that, you already know more than I do.

Regular Expressions are sequences of symbols used to match against words in a regular language. A word is made up of symbols from that language’s alphabet. Let’s say we wanted to deal with a small regular language, with only two letters in the alphabet, a and b. The language, we’ll say, is {a^n b^m, n > 0, m > 0} So one of the words is ab, others include aab, abb, aaabbb, and so on. Looking at it, we can say, in English, that the words in the alphabet are some number of a’s, followed by some number of b’s, with at least one of each.

So let’s say we want to come up with a regular expression that matches this language, so that in a larger document, we want to be able to identify words that are part of the language, and others that aren’t.

You’ve probably seen patterns before in a limited context, through DOS and the wildcards. * stood for (anything), and ? stood for (character). Regular expressions under PHP have similar symbols, but they are applied to a character, character class or a sub-expression.

I’ll get some of the basic symbols out of the way first:

The answer to the question that was posed above, was already answered in defining the symbols. We wanted some number of a’s followed by some number of b’s, with at least one of each. It would seem the pattern “/a+b+/” would be appropriate.

Here’s a little page that will let you play around with regular expressions:

<?php if(isset($_GET['re'])) { $re = $_GET['re']; } if(isset($_POST['re'])) { $re = $_POST['re']; } if(isset($_GET['t'])) { $t = $_GET['t']; } if(isset($_POST['t'])) { $t = $_POST['t']; } ?> <html> <body> <form action="re.php" method="GET"> <label>Regular Expression </label><input type="text" id="re" name="re" size="20" /><br/> <label>Text to Match </label><input type="text" id="t" name="t" size="t" /><br /> <input type="submit"/> </form> <br /><br /><br /><br /><br /> <?php if (isset($t) && isset($re)) { $ret = @preg_grep(stripslashes($re), array(stripslashes($t))); if ($ret[0] == $t) { echo "match"; } else { echo "no match"; } } ?> </body> </html>

It’s a page that submits back into itself (a standard trick), it takes two strings from the form, the regular expression, and the text to match. If there is any match at all (any submatch) then it says so, otherwise not. Note that this will also match aaaabbbc. If we wanted to stop at the line (so that ONLY the fully matching input would be accepted or rejected) then this pattern would work: “/^a+b+$/” which is just the previous pattern, with the added requirement that the a’s and b’s start at the beginning of the line, and end at the end of the line.

At this point, you may be wondering, what is the point of all this. Well, at this stage, the point should be clear. Regular Expressions are fairly powerful ways of matching strings that follow a pattern. You can use them to search for things, or to validate things. Like, here’s an easy one, Canadian Postal Codes, which, being Canadian, is of relevance to me. Postal Codes are a series of 6 characters, they alternate between letters and numbers, with an optional spaces in between. Here is the regular expression that would match them: “/[A-Z][0-9][A-Z](\s)*[0-9][A-Z][0-9]/”

Here, we’ve used another symbol I haven’t mentioned before, \s. It is a symbol representing whitespace. So (\s)* represents 0 or more whitespace characters. Here are some other usefule ones:

So we could rewrite the above, with a little trouble, but as we wanted to restrict the above to just upper case letters, it’s faster that way, but [0-9] could be rewritten as simply \d. [A-Z] would have to be rewritten as [^\W\d_], that is take underscore, digits, and non-word characters, and match everything except them, so match words characters except digits and underscore, or more simply, match letters. That would match upper and lower case, we can fix that too with other options. PHP.net has some good resources on all this.

Now, let’s try a few other things, how about finding file names that begin with f, end in a number, and have the extension htm or html. One possible regular expression would be /^((f.*[0-9]\.htm)|(f.*[0-9]\.html))$/

Another thing to note is that regular expressions are greedy and will match with as much as possible (actually, I believe in PHP, you can alter this behaviour). So if you had a regular expression /a.*b/ which matches with an a, any number of any other character, then finishes with a b, then given the string a1234b1234b it will match with the whole string, and not just the first part ending in the b in the middle.

Now we should talk about the limitation of regular expressions, which are tied into the limitations of regular languages. Let’s say we had a language defined as such: is {a^n b^n, n > 0} So some number of a’s, followed by the same number of b’s. What expression could we write? Well, you can’t write one, this is a type of counting problem, sure, any number of a’s followed by any number of b’s can be handled, but in this case, the regular expression needs to ‘remember’ how many a’s it has seen so far so when it gets to the b’s, it knows when to accept or reject. This memory is contrary to regular expressions being run by finite state machines, which are conceptual little machines, with a finite number of states, that move to one state to another when it sees a single symbol as the next bit of input. Never mind if you have no idea what I’m talking about, the point simply is that regular expressions to have limitations, but they are handy none the less.

Now that you have the basics, I suggest just looking up the preg_grep, preg_match and other preg_* functions at PHP.net, they are simple to use, and the real barrier in handling them is just writing the regular expressions to use with them. Anyway, I’m hardly an expert at any of this, but I get by.

Also, I suggest, that if you can get by using other functions, like exploding a string and looking at a specific piece, or searching a string for a particular substring, then use it. The Regular Expression engine, as powerful as it is, is considerably slower than other more generic functions, so unless you actually need the power of RE’s, use something else.


This entry is a little shorter than usual, as the deadline sort of crept up on me. Next time, I think I’ll write about MySQL (basics of SQL) and how to use it in PHP.

Contact 19day at admin@SPAMMY19day[d0t]com or visit him at http://19day.com
Download a Word (.doc) version of this tutorial here.

Final Word

Well, even though this issue was a bit rushed and had only two-and-a-half weeks to come together, I think we managed to put together another winner of an issue this month! Sure it's a bit shorter than the others in terms of number of articles and news, and I didn't have time to give you guys a "Blast From The Past!" article or start another competition...(damn college eating up all my time...grumble, grumble...) -- but still, this issue is one heck of a read! Certainly nothing to sneeze at. It's 77 pages for cryin' out loud! And that's not to mention that this was probably the largest and most diverse collection of tutorials we've had in an issue yet.

But it can be even better than this. If you guys want to see an even bigger and better issue next month, you know what you need to do -- SUBMIT STUFF. We want content of all types -- as long as it's QB, FB or even just programming-related! Just send it on in. Remember, this is YOUR magazine too.

Anyway, I'd love to stay and chat, but I think it's now time for me to catch some much-needed sleep. I'll catch you next month, if not sooner.

Until next time...END.


Copyright © Pete Berg and contributors, 2005. All rights reserverd.
This design is based loosely on St.ndard Bea.er by Altherac.