Spellcaster presents: TTTTTTTTTT HH HH EEEEEEEEEE MM MM AAAA GGGGGGGGG TT HH HH EE MMM MMM AA AA GG TT HH HH EE MM M M MM AA AA GG TT HHHHHHHHHH EEEEEE MM MM MM AAAAAAAA GG TT HH HH EE MM MM AA AA GG GGGG TT HH HH EE MM MM AA AA GG GG TT HH HH EEEEEEEEEE MM MM AA AA GGGGGGGG Issue 8 26-4-96 -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- Index: 1. Introduction 1.1. About the magazine 1.2. About the author 1.3. Distribution 1.4. Contribuitions 1.5. Hellos and greets 2. Mailroom 3. Coding sites 4. Designing a text adventure - Part I 4.1. Plot creation 4.2. Plotline 4.3. Map creation 4.4. Getting started 5. Our friend, the pointer - Part III 6. How to make a cool starfield 7. Sprites Part I - Introduction 7.1. Generating images 7.2. Killing an image 7.3. Displaying an image 7.4. Clipping 7.5. Saving to the disk 7.6. Loading from the disk 8. Graphics Part VII - Scrolling Part II 8.1. Tile Scrolling 8.2. Parallax Scrolling 8.3. Partial Scrolls 9. Hints and tips 10. Points of view 11. The adventures of Spellcaster, part 8 -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- 1. Introduction 1.1. About the magazine Welcome to number 8 of 'The Mag', brought to you, as usual, by Spellcaster, alias Diogo de Andrade. This is another big issue, so scream in joy ! :) I'm very happy !! This is the first issue to feature an article I didn't write !! That article is a little bit different from the usual articles. It's about were can you get info on programming and other stuff coding-related. It was writen by Scorpio (another portuguese dude !!), one of the people that most support 'The Mag'... He is also the person that stands all of my questions in the MOO I'm trying now... Thanks, man, for beeing there... ;) Also, a little add... I'm trying to change the logo on top, the one that says 'THE MAG', but my ANSI art sucks !! In fact, all my art sucks !!! I was born to code... :) So, if anyone out there wants to do a better logo for me, go ahead !!! NOTE- No strange codes, just plain ASCII chars... Not even ASCII-E characters, because I use (and sometimes write) this in a UNIX machine, and I don't know how to configure the simbols on the screen... :( This magazine is dedicated to all the programmers and would-be programmers out there, especially to those that can't access the Net easily to get valuable information, to those who wish to learn how to program anything, from demos to games, passing through utilities and all sort of thing your mind can think of, and to those that can't find the right information. When you read this magazine, I'll assume some things. First, I assume you have Borland's Turbo Pascal, version 6 and upwards (and TASM for the assembly tutorials). I'll also think you have a 80386 (or 386 for short; a 486 would be even better), a load of patience and a sense of humor. This last is almost essencial, because I don't receive any money for doing this, so I must have fun doing it. I will also take for certain you have the 9th grade (or equivelent). Finally, I will assume that you have the last issues of 'The Mag', and that you have grasped the concepts I tried to transmit. If you don't have the issues, you can get them by mail, writing to one of the adresses shown below (Snail mail and Email). As I stated above, this magazine will be made especially for those who don't know where to get information, or want it all in the same place, and to those who want to learn how to program, so I'll try to build knowledge, building up your skills issue by issue. If you sometimes fail to grasp some concept, don't despair; try to work it out. That's what I did... Almost everything I know was learnt from painfull experience. If you re-re-re-read the article, and still can't understand it, just drop a line, by mail, or just plain forget it. Most of the things I try to teach here aren't linked to each other (unless I say so), so if you don't understand something, skip it and go back to it some weeks later. It should be clearer for you then. Likewise, if you see any terms or words you don't understand, follow the same measures as before. Ok, as I'm earing the Net gurus and other god-like creatures talking already, I'm just going to explain why I use Pascal. For starters, Pascal is a very good language, ideal for the beginner, like BASIC (yech!), but it's powerfull enough to make top-notch programms. Also, I'll will be using assembly language in later issues, and Pascal makes it so EASY to use. Finally, if you don't like my choice of language, you can stop whining. The teory behind each article is very simple, and common with any of the main languages (C, C++, Assembly - Yes, that's true... BASIC isn't a decent language). Just one last thing... The final part of the magazine is a little story made up by my distorted mind. It's just a little humor I like to write, and it hasn't got nothing to do with programming (well, it has a little), but, as I said before, I just like to write it. 1.2. About the author Ok, so I'm a little egocentric, but tell me... If you had the trouble of writing hundreds of lines, wouldn't you like someone to know you, even by name ? My name is Diogo de Andrade, alias Spellcaster, and I'm the creator, editor and writer of this magazine. I live in a small town called Setubal, just near Lisbon, the capital of Portugal... If you don't know where it is, get an encyclopedia, and look for Europe. Then, look for Spain. Next to it, there's Portugal, and Setubal is in the middle. I'm 18 years old, and I just made it in to the university (if you do want to know, I'm in the Technical Institute of Lisbon, Portugal), so I'm not a God-Like creature, with dozens of years of practice (I only program by eight years now, and I started in a Spectrum, progressing later to an Amiga. I only program in the PC for a year or so), with a mega-computer (I own a 386SX, 16 Mhz), that wear glasses with lens that look like the bottom of a bottle (I use glasses, but only sometimes), that has his head bigger than a pumpkin (I have a normal sized head) and with an IQ of over 220 (mine is actually something like 180-190). I can program in C, C++, Pascal, Assembly and even BASIC (yech!). So, if I am a normal person, why do I spend time writing this ? Well, because I have the insane urge to write thousands of words every now and then, and while I'm at it, I may do something productive, like teaching someone. I may be young, but I know a lot about computers (how humble I am; I know, modesty isn't one of my qualities). Just one more thing, if you ever program anything, please send to me... I would love to see some work you got, maybe I could learn something with it. Also, give me a greet in your program/game/demo... I love seeing my name. 1.3. Distribution I don't really know when can I do another issue, so, there isn't a fixed space of time between two issues. General rule, I will try to do one every two weeks, maybe more, probably less (Eheheheh). This is getting to an issue every month, so, I'll think I'll change the above text... :) 'The Mag' is available by the following means: - Snail Mail : My address is below, in the Contributions seccion... Just send me a disk and tell me what issues you want, and I will send you them... - E-Mail : If you E-mail me and ask me for some issues, I will Email you back with the relevant issues attached. - BBS's : I don't know for sure what BBS's have or will have my magazine, but I will try to post it in the Skyship BBS. If you have a BBS and you want to receive 'The Mag', contact me. Skyship BBS numbers: (351)+01-3158088 (351)+01-3151435 - Internet : You can access the Spellcaster page and take the issues out of there in: http://alfa.ist.utl.pt/~l42686 - Anonymous ftp: I've put this issue of 'The Mag' on the ftp.cdrom.com site... I don't know if they'll accept it there, because that's a demo only site, and my mag doesn't cover only demos, but anyways, try it out... It has lots of source code of demos. 1.4. Contributions I as I stated before, I'm not a God... I do make mistakes, and I don't have (always) the best way of doing things. So, if you think you've spotted an error, or you have thought of a better way of doing things, let me know. I'll be happy to receive anything, even if it is just mail saying 'Keep it up'. As all human beings, I need incentive. Also, if you do like to write, please do... Send in articles, they will be welcome, and you will have the chance to see your names up in lights. They can be about anything, for a review of a book or program that can help a programmer, to a point of view or a moan. I'm specially interested in articles explaining XMS, EMS, DMA and Soundblaster/GUS. If anyone out there has a question or wants to see an article about something in particular, feel free to write... All letters will be answered, provided you give me your address. I'm also trying to start a new demo/game/utility group, and I need all sort of people, from coders (sometimes, one isn't enough), musicians (I can compose, but I'm a bit limited), graphics artists (I can't draw nothing) and spreaders... I mean, by a spreader, someone who spreads things, like this mag. If you have a BBS and you want it to include this magazine, feel free to write me... I don't have a modem, so I can only send you 'The Mag' by Email. You can also contact me personally, if you study on the IST (if you don't know what the IST is, you don't study there). I'm the freshman with the black hair and dark-brown eyes... Yes, the one that is trying to make people believe he can code !! :) My adress is: Praceta Carlos Manito Torres, n§4/6§C 2900 Set£bal Portugal Email: dgan@rnl.ist.utl l42686@alfa.ist.utl.pt And if you want to contact me on the lighter side, get into the Lost Eden talker... To do that telnet to: Alfa.Ist.Utl.Pt : Port 1414 If that server is down, try the Cital talker, in Zeus.Ci.Ua.PT : Port 6969 I'm almost always there in the afternoon... As you may have guessed already, my handle is Spellcaster (I wonder why...)... 1.5. Hellos and greets I'll say hellos and thanks to all my friend, especially for those who put up with my constant whining. Special greets go to Denthor from Asphyxia (for the excelent VGA trainers), Draeden from VLA (for assembly tutorials), Joaquim Elder Guerreiro, alias Dr.Shadow (Delta Team is still up), Joao Neves for sugestions, testing and BBS services, and all the demo groups out there. I will also send greets to everybody that responded to my mag... Thank you very much ! Super Very Special thanks go to: Ricardo 'Scorpio' Oliveira... -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- 2. Mailroom Well, I've got another letter telling me I SUCK ! Just kidding... I got a letter of someone who noticed one error in a previous issue. It was a friend of mine called Nuno, and he studies in the same school as I do, and he is also trying to major in Computer Engeneering (hope he doesn't end like me and he makes through !)... Well, so what's the screw-up, you may ask... And I will answear... In issue four, in the graphics section, in the part about circles, I told you that Pascal uses reverse-angling, that is, that the trigonometrical circle goes in a clockwise direction... Well, this is WRONG !! Pascal goes in the standart (anti-clockwise) direction... The reason that made me believe so was that in real life, usually the y axis increases the more you go upwards and decreases going downwards... But in the computer sense, this goes reverse, and that is why I screwed up... Sorry but that ! :) Thanks, NSJ ! ----------------------------------------------------------------------------- Other letter I got was from a guy who told me that he got an Heap Overflow error, when he runned the same program lot's of times... I've checked the program and realized that it was my fault ! I forget to told you, when I talked about virtual screens, back in issue 5, that you should de-allocate the pointers to the virtual pages, because they are filling the memory. So, in the end of the program, when you don't need the virtual screens anymore you should call the CloseVirt procedure: Procedure CloseVirt; Var A:Byte; Begin For A:=1 To Npages Do Begin Freemem(Virt[A],64000); VP[A]:=$A000; End; End; Sorry about that... :) -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- 3. Coding sites Ok, this is the article writen by Scorpio (as you will guess)... I, Spellcaster will make my coments between square brackets []... I am Ricardo Manuel Oliveira, aka Scorpio and I live in Portugal... [ I already told you that !! :) ] I have read all the issues of The Mag (like all of you :)) and I was very angry 'cos Spellcaster didn't know the URL of my page (Check 'THE MAG #7).. :)... He didn't know Denthor's one too, though... :) [ So, I'm no URL storing machine !! :) ] Now, more seriously: This article is a coding-resources list gathered by me... Why have I decided to share it with you all? 'Cos I'm happy right now... I have created a HTML page about my local soccer club (Vitoria Sport Clube, for portuguese people), and it appeared reviewed in a national magazine (CyberNet, for ...) So, I have felt that I have to share this joy with lots of people and I'm contributing to the growth of 'THE MAG'... Since I read it, I could see my name there, right ? [ Of course you can !! Write some more !! I like having less work to do ! ] DEMO-RELATED CODING PAGES: -------------------------- http://www.dur.ac.uk/~d405ua/demoftps.html Extensive list of demo-related FTP servers (UK) http://www.cdrom.com/pub/demos/hornet/8086 8086 Compo + Coding examples to the 8086 http://196.6.101.37:80/grants/Asphyxya/Asphyxya.html Denthor / Asphyxia 's Homepage GREAT vga-coding Tutorials!!! http://alfa.ist.utl.pt/~l42686/ Spellcaster's Homepage - 'THE MAG' (general (demo/vga)coding magazine) You're reading it [ My great page ! He forgot that 'The Mag' also haves stuff about game making and the Adventures of Spellcaster !! :) ] GENERAL-CODING PAGES: --------------------- http://www.cs.vu.nl/~jprins/tp.html Turbo Pascal Programmer's page If you're into Pascal, CHECK THIS ONE NOW!!! http://www.interlog.com/~jfanjoy/swag/swag.html SWAG Homepage - Pascal code for everybody (_LOTS_ OF CODE) http://www.fys.ruu.nl/~faber/amain.html Faber's Hotlist Assembly links http://www.cera2.com/assembly Assembly Language HotLists and Major Resources http://www.caiw.nl/~jdbruijn/cl.html Code Language Page, by Jack de Bruijn http://onyx.idbsu.edu/~jcofflan/ Joe's Assembly Language Page, by Joe :-) http://www.netrunner.net/~irvinek/asm.htm Assembly Language Sources, by Kip P. Irvine Where can I find CODE?: ----------------------- ftp://ftp.cdrom.com/pub/demos/code Democoding examples and more... a LOT more It's in USA, a bit _SLOW_ for european users during the afternoon... ftp://ftp.luth.se/pub/msdos/demos Mirror of cdrom _FAST_ for european users (_VERY_FAST_ indeed :)) (includes FTPMAIL!!!) ftp://ftp.co.iup.edu/code Mirrors cdrom's CODE dir. 'Bit slow... ftp://x2ftp.oulu.fi/pub/msdos/programming This place is huge! It'll keep you busy for a LONG time. ftp://garbo.uwasa.fi/pub/ Another BIG place... Happy searchin' Oh, yes... Check the alt.lang.* newsgroups [ And the following groups: comp.graphics.algorithms rec.games.programmer comp.programming alt.sys.ibm.pc.demos (I think this is it... Search for demos... :) ] At last, if you want more links try my bookmarks at http://wwwalu.ci.uminho.pt:8888/~si17899/bookmark.html To contact me, email me at: si17899@ci.uminho.pt [ Check it out !! :) ] Spellcaster, take the lead... Thanks Scorpio ! Feel free to write some more ! :) So, let's move on to the next article... -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- 4. Designing a text adventure - Part I This article is intended for the beginners who want to do games. I know that text adventures are definetly out, but the ideas behind a text adventure are very similar to those found in graphic adventures, so what I say here can be expanded to a graphic adventure... Note that these are my personal views on how a text adventure should be coded and designed... These articles will teach _EVERY_ step in the creation of a text adventure, from designing and plotline creation, to programming, to pointers on how to further expand the system... There can be some errors and irregularities in the code and explanations, because I'm writing this at the same time I'm coding the adventure, so I will sometimes make mistakes... If you spot one, or have any doubts, mail me ! 4.1. Plot creation This part of the article will give you some pointers on how you should start to build your text adventure... So, for me, one the more important parts of _ANY_ game is the plot... It's the first thing I do, because I believe that the other pieces of the game (playability and such) will come together, if you have a good story. In this stage you must try to be original... Because it's getting harder and harder to be original in the plot, you must be original in other things... For example, I do most of the story of the game with the idea of SUSPENSE ! I like to put the hero in the game with no idea of what he is supposed to accomplish... This is my idea... Other piece of originality is the atenction to detail... This is also very important, for it adds very much for the atmosphere of the game... So, if you follow these ideas, you can make a text adventure that is fun to play, even in these DOOM-clones filled times... Just for the record, I will state something about Doom... Cool graphics and gameplay, crappy story !! I admire the guys at ID software (technically speaking), but I would like to shoot their desing department ! :) Another important of the detail is to pay heed to the time/space in which the game occurs... If you are doing an adventure passed into outer-space, you shouldn't put an old Chevy in it... :) So, for our text adventure, I will conceive a little stupid story... So, let me think... Hum... This is hard... :) Ok, I got it! Let's imagine that the hero's uncle has just passed out (on the 25 April 1996... This is details, that make up the atmosphere), and he had sent you a letter (the letter is dated of the 23 April 1996... Again, detail) before he did so, saying that he has a treasure hidden in his mansion (that is in a high cliff, near the ocean), and if you can found it, it is yours... So, filled with lust for gold, the hero sets foot for the mansion. Crappy story, I know, but this one enables me to explain how to make a basic text adventure... Another thing I think it's very important in a game (of any kind) is the title... I don't know, but I spend a lot of time thinking of the name for a game... The name should give the user the will to explore the game further... So, one possible title for this game should could be 'The Mansion', but this doesn't call the atencion... So, you could name it 'Treasure Mansion', but this title is crappy, because the word 'treasure' has been used so many times in games and titles that has became vulgar... Other examples of words like that is 'Prince', 'Princess', 'Dragon'. Let's try another time... How about 'Evil Mansion'. It's getting better, but I think it isn't suitable for the game yet... We also have variations on the theme: 'Evil Home', 'Home of Evil', 'Evil Lair'... But still, altough they are cool, they remember a game where there is a house possessed by an evil sorcerer... And this is not the case (altough it could be)... The only problem (in this story I'm developing) in the house is that it has a couple of monsters/haunts... We could call it 'Haunted House', or 'Haunted Mansion', but 'Haunted' is one of those words, similar to 'treasure'... So, we can be a bit more inventive, and add to the atmosphere. We could say that the name of the mansion is 'Fanglore', and call the game just that: 'Fanglore'... using this name, we've added to the atmosphere (because we added the detail of the name of the house), and we gave a name to the game that make the user not know what is he going to see... :) So, the process of creating the great text-adventure 'Fanglore' is underway... :) 4.2. Plotline Next in the list (for me) is the plotline and map creation. These two steps should be made simultaneosly... The plotline is the course the game should follow normally... In Fanglore, the plotline is: Going to Fanglore -> Finding the treasure -> Escape the house A game can have several endings, and different soluttions for the game, and that increases the complexity of the plotline (and the overall interest of the game)... But the way of doing this is the same. Each part of the plotline is a different step of the game. Now, every step of the game has different objectives (though the game has only one objective). For example, the first step 'Going to Fanglore' has his own objective, that is, to enter the mansion. Also, every step of the game has a reason of existing... The basic reason is the completion of the game, but you must explain, that is, you must know what put the character(s) in that particular position. The first step of Fanglore exists because the taxi left us at the door of the mansion, that is closed for years, and you don't have the key. Also, very step of the game have a way to completing it's objective, and that also needs to be defined. Again, in the first step, the way of getting into Fanglore is to find a shovel and knock down the door... You also have to take in account possible problems and a way to overcome them... The problems in a text adventure are not only made of a difficult puzzle to resolve; monsters and other similar things also have it's effects... In the first part there aren't any problems, but in the second part, there is a room that is filled with gas, and you have to use a gas mask to go past that room, and to find the mask, you must go to the kitchen of the house and open the oven... That is were the mask is... So, scematically: - Going to Fanglore Objective ? To get into Fanglore. Why ? Because the cab left us at the door and we don't have the key. How ? Get the shovel Knock down door with it (the door opens) The other steps of the plotline: - Finding the treasure Objective ? Find the treasure. Why ? Because that is the point of the game. How ? Go to kitchen Open oven Get gas mask Wear mask Go past the gas room Find the library Push bookshelf (you found a secret passage) Go through passage You found the vault with the treasure Other problems ? Monsters in some rooms, that can kill you. You can kill them, if you have the sword (that is in the main room)... - Escape the house Objective ? Get out of Fanglore. Why ? Because the passage has closed when you passed through it, and you are stuck... How ? Push a brick in the wall (reveals another secret passage) Go through passage So, the plotline is complete... You should also create a map while you are doing the plotline... 4.3. Map creation As said, the map should be made at the some time as the plotline. There isn't a standart way of doing the map... The ideia is this: In the map, you should setup were are going to be the exits of a room (the relantion between the various rooms) and you should also number all the rooms. The first draft for a map is shown in picture Map01.Pcx. It has six rooms: 1. Kitchen 2. Main Hall 3. Gate 4. Gas Room 5. Library 6. Treasure Room For the plotline, you already know the importance of each of these rooms. The yellow line connecting rooms mean that there is an exit from one to another. If there isn't a line, there isn't an exit. The orange dotted line means a conditional exit, that is, an exit that is only open at certain times, or when you have made something (for example, the exit from room 3 to room 2 only opens when you break down the door). An arrow on the end of a exitline means an unidirectional passage... For example, you can go east from room 5 to room 6, but you can't go west back to room 5. But, this doesn't give much to explore, and almost all players like to search dozens of rooms... It is part of the detail... For example, in a mansion should have a dinning room, and a great garden... Well, these aren't present in the draft. And a mansion should also have bedrooms, and Fanglore as we designed doesn't have them. So, you make a more detailed map... This is in Map02.Pcx. It has 22 rooms. The rooms that are in green are the garden-rooms, and the others are the mansion-rooms. We've also added the dinning room also. But we didn't added the bedroom. Well, if you want, you can ommit certain details, but you have to add others. In the case of bedrooms, you can say that there is a stair in the Main Hall, that takes you to the upper floor, but the stair is blocked, so you can't go that way... So, the map is now complete. 4.4. Getting started So, let's get started in the coding department... We first should get the room data. I've made a little program that enables you to type in the room data (with lame source code). Each room is a record that has the exits info, and the description of the room. So: Type RoomType=Record Desc:Array[1..10] of String[79]; North,South,East,West:Byte; End; The description of the room is an array of strings, with only 79 chars per string because the screen only has enough space for 80 lines, and you must take in acount the return character. You don't need to use the complete 79 columns, neither the 10 lines of text. You just have to type in a line with a single '*' after the last line of text. If you want to use the 10 lines, the '*' is left out. As you have various rooms, you should have an array with the various rooms: Const NumberRooms=22; Var Rooms:Array[1..NumberRooms] of RoomType; The fields North, South, East and West are a number that indicates to what room you should go if you go in that direction. A zero there indicates that there isn't an exit in that direction. For example, if Rooms[1].North=2 then, if you go north in room 1 you will go to room 2. Fanglore will use a file called Room.Dat to store the room data. So, to end this issue's article in text adventure, here is the procedure to read the room data: Procedure ReadRoomData; Var F:Text; A,B:Byte; Flag:Boolean; Begin { Prepares the text file for accessing } Assign(F,'Room.Dat'); Reset(F); { For every room in the game } For A:=1 To NumberRooms Do Begin { Clear the room's description } For B:=1 To 10 Do Rooms[A].Desc[B]:=''; { Read the description of the room } Flag:=True; B:=1; While Flag Do Begin Readln(F,Rooms[A].Desc[B]); If (B=10) Or (Rooms[A].Desc[B]='*') Then Flag:=False; Inc(B); End; { Read exit data } Readln(F,Rooms[A].North); Readln(F,Rooms[A].South); Readln(F,Rooms[A].East); Readln(F,Rooms[A].West); End; Close(F); End; The procedure is self-explainatory, with the exceptions of a few points. If you are wondering why do we clear the rooms description before we load, if there isn't anything there, this is the answear: Pascal, when it creates a variable, puts it anywhere in memory it can fit, but it can be something there, and Pascal doesn't erase it... It is good programming pratice to clear the comtent of a variable before using it (jee, I sound like one of my teachers). Second of all, I don't know if I already showed you what does the Inc keyword. The Inc keyword increases the variable given by one (if no other parameter is specified). So, Inc(B) is equal to B:=B+1, and Inc(B,2) is equal to B:=B+2... Well, the call to this procedure should be made in a procedure called something like Init, which sets up all variables and prepares the game to be played. So, procedure Init is (for the moment, for there are other stuff to initialize) like this: Procedure Init; Begin ReadRoomData; End; And the Init procedure should be called from the main program. So, the main program should be like this, for the meanwhile: Begin Init; End. This all is already typed in file FangLore.Pas... NOTE: Any file generated with the RoomGen program can be altered by hand, that is, you can load it to any ASCII editor and edit any errors and alterations you want to make... Don't forget to save as ASCII the altered file ! So, in next issue, I will tell you how to build the phrase parser and some of the basic verbs... Until then, try to do it by yourself... :) -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- 5. Our friend, the pointer - Part III Well, this is the last of the articles on pointers... Nobody liked them, so I won't waste my time with them anymore... :( In this part I will discuss dynamic structures to the highest degree (I'm such a liar !! :) ). So, let's assume you want to do a database that stores the names and phonenumbers of all your friends (if you are like me, you have _LOTS_ of friends... :))) ). You could do it in the tradicional manner: Type FriendRec=Record Name:String; Phone:String; End; Var Friends:Array[1..100] Of FriendRec; But this would impose a limit of 100 friends, and you would spend your precious variable memory... Even if you increased the size of the array, you would have a limit of 128 friends, because you have 2 strings, that ocuppy 256 bytes each, and: 2*256*128=65536, that is the maximum memory you can allocate for variables... That is no good... You should be able to add phonenumbers 'till you run out of memory... You could use the method teached in number one, using static pointers, but that only enables to use 65536 bytes of memory, and that would only solve the problem of lack of variable memory. You can use a variant of method 2, that it is what I'm going to teach you. First, define this: Type PFriend=^FriendRec; FriendRec=Record Name:String; Phone:String; Next:PFriend; End; Var Friends:PFriend; Ok, you did understand it, did you ? Think about this... What we are doing is to create a structure that points to a place in memory where a structure of type FriendRec is allocated. In the FriendRec structure it is defined another pointer that points to another structure of type FriendRec, and so forth... Look at the scheme below... In the beggining, variable Friends points to a random position in memory: Friends --> ? Then, you allocate the structure associated with variable Friends... To do so, you use the New command... So, you do: New(Friends); The structure will change and become something like this: Friends --> Name Phone Next --> ? To access now the name and phonenumber of the first friend, you just do: Friends^.Name:='Diogo Andrade'; Friends^.Phone:='555-1355'; Writeln(Friends^.Name); You should make always: Friends^.Next:=NIL; I will explain the reason why later. And if you want to create another structure, that is, another friend ? Yes, you do: New(Friends^.Next); The structure would look something like this: Friends --> Name --> Name Phone | Phone Next ---| Next --> ? So, the fields would be accessed by: Friends^.Next^.Name Friends^.Next^.Phone Friends^.Next^.Next But, if you increase the number of structures, it would become impossible to access them all... So, you use a little trick: you assign an extra variable to the first record, and then you go through all of them, like this: Procedure GoLast; Var Tmp:PFriends; Begin Tmp:=Friends; While Tmp^.Next<>NIL Do Begin Tmp:=Tmp^.Next; End; ..... { Tmp now points to the last structure } ..... End; Now do you understand what does the NIL value is needed for ? To know what is the last... :)) I know this sounds complicated at first, but with pratice it will come to you as natural as 2+2=5... :) Just a few notes... 1) Never loose the pointer to the first position in memory, or else you can't access that address... As the matter of fact, you shouldn't loose _ANY_ pointer, or else they will become lost pieces of memory you can no longer access... 2) Don't forget to deallocate the pointers when you end the program, or else you will loose the memory on which they are allocated... They don't just go away like variables in a Pascal program... They need to be deallocated with the Dispose keyword... Well, I think this wraps it up... As I said earlier, this is the last of the pointer articles, so I'm going to start a new series in next issue... I don't know what it will be, but I think I'm going to make it about assembly... Stay tuned... Spellcaster out... :) -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- 6. How too make a cool starfield This article is about creating a starfield... Remember the starfield program I gave in the Graphics article, last issue ? Well, that program SUCKED big time ! :) As I don't like beeing regarded as a bad programmer (altough I am :) ), here is an article on about how to create a starfield... Well, starfields are funny... They are beutifull and exciting... And this text is sucking ! :) Now, really, the problem of the starfield in issue 7 is that it spent too much of the processor (it wasn't efficient, because it moved the entire screen, when it only needed to move a few pixels) and it was unreallistic, because we all know that stars that are further away from us move slower that stars close to us... So, let's get down to business... One of the reasons why the starfield of last issue's was so slow was that you scrolled _ALL_ the pixels of the screen, that is, you did 64000 moves !! That's ridiculous, if you think that the screen only had 1000 stars, that is, you could move all the stars using only 1000 memory moves... That is 64 times faster ! So, the ideia is this... You keep track of the coordinates of all the stars, and you move them wherever you want... Ok, so one star is a record: Type Star=Record X,Y:Integer; Color:Byte; End; And lots of stars are an array: Var Stars:Array[1..1000] Of Star; Then, you must initialize the stars, the graphics and the palette... We will use the Mode13h unit, that was given the last issue: Procedure Init; Var A:Word; Begin InitGraph; { Sets up 16 grays... } For A:=0 To 15 Do SetColor(A,A*4,A*4,A*4); { Sets up stars } For A:=1 To 1000 Do Begin Stars[A].X:=Random(320); Stars[A].Y:=Random(200); Stars[A].Color:=Random(15)+1; End; End; Note: If you are wondering why Stars[A].Color:=Random(15)+1, know this: 0 <= Random(x) < x ; but, in this example: 0 <= Random(15) < 15 ; but a star shouldn't get color 0, so we add one to Random(15) and we get: 1 <= Random(15)+1 < 16... So we get the colors we want... :) Having done this, let's move on to more theory... The ideia is this: 1) Clear Stars 2) Move Stars 3) Draw Stars 4) Go back to step 1 So, how do you do this, you may ask... Well, like this... Step 1: You just draw the stars in their positions, but in color 0 (or whatever is the color of your background). Remember to only clear the stars before you move them, or else you don't clear them, you'll just draw them in color 0 elsewhere ! Procedure ClearStars; Var A:Word; Begin For A:=1 To 1000 Do PutPixel(Stars[A].X,Stars[A].Y,0,VGA); End; Step 2: You should give the ability to move the stars to every direction you may want... As this is a bidimensional starfield, you have 2 incrementation variables. The ideia is to sum to the coordinates of the stars a value given to the procedure. Procedure MoveStars(Ix,Iy:Integer); Var A:Integer; Begin For A:=1 To 1000 Do Begin Stars[A].X:=Stars[A].X+Ix; Stars[A].Y:=Stars[A].Y+Iy; End; End; Now, let's assume you want to move the stars left... You just do MoveStars(-1,0); Easy, isn't it ? Wrong ! If you do like this, you can get an error... For example, let's assume: Stars[1].X:=0; Ix:=-1; If you execute the MoveStars procedure, you will get an error, because Stars[1].X is a variable of type Word, that doesn't allow negative numbers! The best way to fix this is to make the X and Y fields of the Star's record variables of type Integer and check if their are out of bounds... If they are, you wrap it around the screen. Wrap around the screen is a process that means that is a star has coordinates (0,10) and it is scrolled left, a new star will appear at (319,10)... So, the new procedure is like this: Procedure MoveStars(Ix,Iy:Integer); Var A:Integer; Begin For A:=1 To 1000 Do Begin Stars[A].X:=Stars[A].X+Ix; Stars[A].Y:=Stars[A].Y+Iy; If Stars[A].X<0 Then Stars[A].X:=319; If Stars[A].X>319 Then Stars[A].X:=0; If Stars[A].Y<0 Then Stars[A].Y:=199; If Stars[A].Y>199 Then Stars[A].Y:=0; End; End; Step 3: Well this should be simple enough... You just draw the stars ! :) Procedure DrawStars; Var A:Word; Begin For A:=1 To 1000 Do PutPixel(Stars[A].X,Stars[A].Y,Stars[A].Color,VGA); End; Well, this is easy enough, right ? The whole program is here: Program Starfield; Uses Mode13h; Type Star=Record X,Y:Integer; Color:Byte; End; Var Stars:Array[1..1000] Of Star; A:Integer; Procedure Init; Var A:Word; Begin InitGraph; { Sets up 16 grays... } For A:=0 To 15 Do SetColor(A,A*4,A*4,A*4); { Sets up stars } For A:=1 To 1000 Do Begin Stars[A].X:=Random(320); Stars[A].Y:=Random(200); Stars[A].Color:=Random(15)+1; End; End; Procedure ClearStars; Var A:Word; Begin For A:=1 To 1000 Do PutPixel(Stars[A].X,Stars[A].Y,0,VGA); End; Procedure MoveStars(Ix,Iy:Integer); Var A:Integer; Begin For A:=1 To 1000 Do Begin Stars[A].X:=Stars[A].X+Ix; Stars[A].Y:=Stars[A].Y+Iy; If Stars[A].X<0 Then Stars[A].X:=319; If Stars[A].X>319 Then Stars[A].X:=0; If Stars[A].Y<0 Then Stars[A].Y:=199; If Stars[A].Y>199 Then Stars[A].Y:=0; End; End; Procedure DrawStars; Var A:Word; Begin For A:=1 To 1000 Do PutPixel(Stars[A].X,Stars[A].Y,Stars[A].Color,VGA); End; Procedure MoveAround(Ix,Iy:Integer); Begin ClearStars; MoveStars(Ix,Iy); DrawStars; End; Begin Init; For A:=1 To 100 Do MoveAround(-1,0); For A:=1 To 100 Do MoveAround(-1,1); For A:=1 To 100 Do MoveAround(0,1); For A:=1 To 100 Do MoveAround(1,1); For A:=1 To 100 Do MoveAround(1,0); For A:=1 To 100 Do MoveAround(1,-1); For A:=1 To 100 Do MoveAround(0,-1); For A:=1 To 100 Do MoveAround(-1,-1); Closegraph; End. So, if you run this, you realize that this FLICKER A LOT !! And that anoying ! Ok, to mend this, you should use virtual screens... The ideia is the same, differing only that you clear and draw the stars in the virtual screen and then you copy the virtual screen to the VGA screen... That will avoid the flicker... Program: Program VirtualStarfield; Uses Mode13h; Type Star=Record X,Y:Integer; Color:Byte; End; Var Stars:Array[1..1000] Of Star; A:Integer; Procedure Init; Var A:Word; Begin InitGraph; InitVirt; Cls(0,VP[1]); { Sets up 16 grays... } For A:=0 To 15 Do SetColor(A,A*4,A*4,A*4); { Sets up stars } For A:=1 To 1000 Do Begin Stars[A].X:=Random(320); Stars[A].Y:=Random(200); Stars[A].Color:=Random(15)+1; End; End; Procedure ClearStars; Var A:Word; Begin For A:=1 To 1000 Do PutPixel(Stars[A].X,Stars[A].Y,0,VP[1]); End; Procedure MoveStars(Ix,Iy:Integer); Var A:Integer; Begin For A:=1 To 1000 Do Begin Stars[A].X:=Stars[A].X+Ix; Stars[A].Y:=Stars[A].Y+Iy; If Stars[A].X<0 Then Stars[A].X:=319; If Stars[A].X>319 Then Stars[A].X:=0; If Stars[A].Y<0 Then Stars[A].Y:=199; If Stars[A].Y>199 Then Stars[A].Y:=0; End; End; Procedure DrawStars; Var A:Word; Begin For A:=1 To 1000 Do PutPixel(Stars[A].X,Stars[A].Y,Stars[A].Color,VP[1]); End; Procedure MoveAround(Ix,Iy:Integer); Begin ClearStars; MoveStars(Ix,Iy); DrawStars; WaitVbl; CopyPage(VP[1],VGA); End; Begin Init; For A:=1 To 100 Do MoveAround(-1,0); For A:=1 To 100 Do MoveAround(-1,1); For A:=1 To 100 Do MoveAround(0,1); For A:=1 To 100 Do MoveAround(1,1); For A:=1 To 100 Do MoveAround(1,0); For A:=1 To 100 Do MoveAround(1,-1); For A:=1 To 100 Do MoveAround(0,-1); For A:=1 To 100 Do MoveAround(-1,-1); Closegraph; CloseVirt; End. If you are smart, you already realized that this is slow... Again! Why ? Because you are moving 64000 bytes again, because of the virtual screen! So, what's the solution for the slowness ? Well, in my computer, there isn't !! But, in a good computer, you can try testing for the Vertical Retrace and clear and draw only when the electron beam is going up the screen... But, to clear and draw in a single retrace, you should avoid computing the movement of the stars, so you should use two arrays to store the before and after positions. Now, let's get to the real fun stuff... It was about time !!! This is getting to be the most boring article in 'The Mag'... As the matter of fact, this the most boring article of any mag! Ok, multi-speed scrolling! Well, stars that are at a greater distance from us tend to move slower relatively to us. So, you must compute the movement of the stars bearing in consideration the distance... But, because you don't have to add another variable that stores the distance, you can use the color variable, because a star that is farther from us is naturally less bright, so, the nearest the color is to 0, the slower it will move. The only thing that must be changed in the program is the procedure that computes the stars, the MoveStars procedure. It will became something like this: Procedure MoveStars(Ix,Iy:Integer); Var A:Integer; Begin For A:=1 To 1000 Do Begin Stars[A].X:=Stars[A].X+Ix*(Stars[A].Color Div 4); Stars[A].Y:=Stars[A].Y+Iy*(Stars[A].Color Div 4); If Stars[A].X<0 Then Stars[A].X:=319; If Stars[A].X>319 Then Stars[A].X:=0; If Stars[A].Y<0 Then Stars[A].Y:=199; If Stars[A].Y>199 Then Stars[A].Y:=0; End; End; You add to the coordinate of the star a value computed taking in acount the distance, that is, the brightness of the star... This gives 4 different speeds (16 different brightnesses DIV 4)... You could improve to use real values, instead of integer, and then have a greater flexibility... So, get coding and improve this code, because, altough it is better than last issue's code it just plain _SUCKS_ ! :) Have fun... :) -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- 7. Sprites Part I - Introduction This is the first of a series of articles that teaches you how to create and use your vey own Sprite Engine... Sprites are a very important part in coding games and some demos... They are the vital part of games like R-Type, Street-Fighter and all games like that. This will be more like a image handling tutorial, but sprites are just that, images that move (or not) on the screen. In the end of this first part, you will be able to get an image from the screen, how to save and load images from disk, and to put an image in the screen. 7.1. Generating images This will teach you how to get an image from the screen. First of all, we need to know were to store it... The first think to come to mind is an array, but that isn't suitable to store an image, because we want to store images of different sizes, and an array has a fixed sized... So we need a structure whose size can be defined at run-time, rather than at compile time... So, who do you gonna call ? POINTERS !!! So, get 'The Mag', issue 7, for a complete explanation on how this particular way of handling pointers will work. So, the ideia is this: 1) Allocate the required space for the image 2) Get the image Let's start with number one... So, to allocate the space, we use the GetMem keyword, but we need to know how much space we want to allocate. Let's assume you are trying to make the GetImage procedure: Procedure GetImage(x1,y1,x2,y2:Word;Var Img:Pointer;Where:Word); This would get the image from (x1,y1) to (x2,y2) of page Where and store it in Img... Well, to store a pixel, you need a byte, well, to store a rectangular piece of image, you need x*y pixels, where x and y are the horizontal and vertical sizes, respectivelly. But, we also need to store the x and y sizes of the image, because we will need them to draw the image (to interpret the data store in the pointer, because that data won't be organized in a rectangule, but it will be stored as a line of bytes) and we need them also to deallocate the image, because we must know the size of the pointer to deallocate him... So, as the x and y are variables of type word, we need 2 bytes for each, so we need 4 bytes for both. So, let's make the procedure: Procedure GetImage(x1,y1,x2,y2:Word;Var Img:Pointer;Where:Word); Var Dx,Dy:Word; A,B:Word; Segm,Offs:Word; Begin { Allocate memory } Dx:=Abs(x2-x1)+1; Dy:=Abs(y2-y1)+1; GetMem(Img,Dx*Dy+4); { Gets the segment and offset of pointer, to access the } { data indexed... } Segm:=Seg(Img^); Offs:=Ofs(Img^); { Stores x and y sizes of image } Move(Dx,Mem[Segm:Offs],2); Move(Dy,Mem[Segm:Offs+2],2); { Store the image } Offs:=Offs+4; For A:=y1 to y2 Do For B:=x1 to x2 Do Begin Mem[Segm:Offs]:=GetPixel(B,A,Where); Inc(Offs); End; End; Abs is a function that gives the absolute value of a number: Abs(5) = 5 Abs(-5) = 5 GetPixel is a function defined in the Mode13h unit (given with this issue of 'The Mag'). It takes three parameters (x,y and Where), and it gives out the color number of the pixel at (x,y) of page Where. So, we have the procedure that gets an image and store it... As in every pointer, we need to deallocate it at some moment in time (even at the end of the program). So, before I forget, how to... 7.2. Killing an image This is very simple... You just analise the x and y size of an image and deallocate the space given to the pointer. Code: Procedure KillImage(Var Img:Pointer); Var Dx,Dy:Word; Segm,Offs:Word; Begin { Get X and Y size of image } Segm:=Seg(Img^); Offs:=Ofs(Img^); Move(Mem[Segm:Offs],Dx,2); Move(Mem[Segm:Offs+2],Dy,2); { Clear pointer } FreeMem(Img,Dx*Dy+4); End; Easy, isn't it ? Let's move on... It is no use getting an image if you can't show it... 7.3. Displaying an image Well, to display an image, you must go to the pointer and dump the data onto the screen... That's easy enough to do, and there are some ways to speed up that process. Let's image a 4x4 image, stored in thr Img pointer, and we want to place that image at some coordinates, (x,y). Img -> 04 00 04 00 FF FA A0 01 02 03 04 05 06 00 C0 30 20 10 EE 02 ----- ----- ----------- ----------- ----------- ----------- | | | | | | | | | | |--------| | | | | | | | | | |-----------|----- FF FA A0 01 | | | | |----- 02 03 04 05 | | | | 06 00 C0 30 __| | | | 21 10 EE 02 -----| | Y Size | X size Did you understand the above scheme ? We must translate a linear batch of bytes into a rectangular one... So: Procedure PutImage(X,Y:Integer;Var Img:Pointer;Where:Word); Var Dx,Dy:Word; A,B:Word; Segm,Offs:Word; Begin { Get X and Y size of image } Segm:=Seg(Img^); Offs:=Ofs(Img^); Move(Mem[Segm:Offs],Dx,2); Move(Mem[Segm:Offs+2],Dy,2); { Draw the picture } Offs:=Offs+4; For A:=Y To Y+Dy-1 Do For B:=X To X+Dx-1 Do Begin PutPixel(B,A,Mem[Segm:Offs],Where); Inc(Offs); End; End; Yupi !! We have a PutImage procedure... But let's improve it... If you look closely at the scheme, you know that in you can transfer a line of the image at one, using the move command... So replace: For A:=Y To Y+Dy-1 Do For B:=X To X+Dx-1 Do Begin PutPixel(B,A,Mem[Segm:Offs],Where); Inc(Offs); End; With: For A:=Y To Y+Dy-1 Do Begin Move(Mem[Segm:Offs],Mem[Where:A*320+X],Dx); Offs:=Offs+Dx; End; There it is... A fast (Pascal thinking) routine to put images on the screen... Try to put an image in coordinates in a way the image goes off the screen... As you may see, the image wraps around the screen in the X direction. Well, this won't do for most games, so we must introduce: 7.4. Clipping Well, the ideia behind clipping is quite simple... You verify if the coordinates of a certain pixel are out of the screen, and if they are, you simply don't place it... This is obviosly slower than the normal PutImage, and you can't put the entire line all at once in the screen, but sometimes it's more usefull... So, you define the variables: MinX=0; MaxX=319; MinY=0; MaxY=199; This is the drawing window... Making this definitions you gain flexibility, because you gain the ability to clip the images to a part of the screen, not only to all of the screen. So, adapting the first PutImage procedure: Procedure PutImage_C(X,Y:Integer;Var Img:Pointer;Where:Word); Var Dx,Dy:Word; A,B:Word; Segm,Offs:Word; Begin { Get X and Y size of image } Segm:=Seg(Img^); Offs:=Ofs(Img^); Move(Mem[Segm:Offs],Dx,2); Move(Mem[Segm:Offs+2],Dy,2); { Draw the picture } Offs:=Offs+4; A:=Y; While (A<=Y+DY-1) And (A=MinX) And (Y>=MinY) Then PutPixel(B,A,Mem[Segm:Offs],Where); Inc(Offs); Inc(B); End; Inc(A); End; End; We use a While cicle instead of a For cicle, because if you think a bit, notice that if an X value is larger than the maximum X value permited, then all the X values of that line are also out of bounds, so no use in testing them... And the same for the Y values, so the use the while cicles to test it. So, inside the loops, we only need to test if the coordinates are bigger than the minimum values... You should choose carefully which one of the routines (with or without clipping) to use in your program, because the routine without clipping is a lot faster and is most usefull if certain cases... 7.5. Saving to the disk Sometimes, you can't generate all the images you want in your programs, so you must load them from disk. But, to load to the disk, you have to save it first... To do so, you have to choose a format. I usually use a quite simple one: four bytes with the x and y sizes, and then the bitmap information... This way you can save and load directly from and to the pointer. So, let's make the procedure: Procedure SaveImage(Var F:File;Img:Pointer); Var Dx,Dy:Word; Segm,Offs:Word; Begin { Get X and Y size of image } Segm:=Seg(Img^); Offs:=Ofs(Img^); Move(Mem[Segm:Offs],Dx,2); Move(Mem[Segm:Offs+2],Dy,2); { Save the data } BlockWrite(F,Img^,Dx*Dy+4); End; We assume the parameter F (of type file) is already an opened file for writing. Why don't we open the file to write and close it within the procedure ? Well, because you may want to save more than one picture in a file, and if you did so, you only would be able to save one image. Now, let's pass on to: 7.6. Loading from the disk I'll don't even bother explaining the loading procedure... It is _SO_ simple !! You read the size, and you get the memory for the pointer... Then, you just shoot the data to the pointer! Simple, isn't it ? Procedure LoadImage(Var F:File;Var Img:Pointer); Var Dx,Dy:Word; Segm,Offs:Word; Begin { Get X and Y size of image } BlockRead(F,Dx,2); BlockRead(F,Dy,2); { Get the memory } GetMem(Img,Dx*Dy+4); { Store X and Y sizes } Segm:=Seg(Img^); Offs:=Ofs(Img^); Move(Dx,Mem[Segm:Offs],2); Move(Dy,Mem[Segm:Offs+2],2); { Store the image } Offs:=Offs+4; BlockWrite(F,Mem[Segm:Offs],Dx*Dy); End; So, here ends the first part of the sprites tutorial... Hope you enjoyed. Just a note: I've put all the procedures given in this issue in an unit called Images. That unit will be further expanded in the next issues, so make sure you update it... -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- 8. Graphics Part VI - Scrolling Part II As you may have gussed already, this is the continuation of last issue's graphics seccion... In this issue, we will delve into other forms of scrolling, more complex ones... Not more complicated, just more complex. 8.1. Tile scrolling You know the Ultima games ? (of course you know them ! Everybody knows them !!)... Well, they use this type of scrolling... What's the ideia ? Think about this: imagine you wanted to do a game and you wanted to scroll around in a playfield made up of 10 full screens... Well, you know a screen uses 64000 bytes, so 10 screens would use 640000 bytes ! That almost all base memory ! And a game with 10 screens isn't that big !! One of my old games had a playfield with almost 500 screens !!! But, if you think for a while you will come to the conclusion that in so many screens some things are going to be repeated... Imagine that the playfield is a grassland, with some trees... Well, that's were the concept of tilling comes to action... You must have some sort of a map and some graphics... The map will tell which graphics go to which places... So, like a puzzle, you build the image of small tiles. I'll only explain this method in a simple way, because I'm intending to do a whole article on them soon, because there is so much to know about them... So, let's put this into practice... You already know how to read and display images, so you must only to use following procedure to display a certain part of the map: Procedure DispGeo(X,Y,DX,DY,Where:Word); Var A,B:Word; Begin For A:=0 To DX-1 Do For B:=0 To DY-1 Do PutImage(A*16,B*16,GeoImages[GeoMap[X+A,Y+B]]^,Where); End; Looks confusing ? It isn't... I'll explain... This procedure displays in coordinates (0,0) of the page choosen by the Where parameter the zone of the map that goes from (X,Y) to (X+DX,Y+DY). Don't confuse the x and y coordinates of the screen (or virtual page) with the x and y coordinates of the map. The map should be stored in the GeoMap array (this can be loaded or generated by the program), and the images (the tiles) should be loaded to the GeoImages array of pointers... This can be done with the following procedure: Procedure LoadGeoImages(Filename:String); Var F:File; Index:Word; Begin Assign(F,Filename); Reset(F,1); Index:=1; While Not Eof(F) Do Begin LoadImage(F,GeoImages[Index]); Inc(Index); End; End; This loads all images available in a file to an array called GeoImages. In the example I'm making, the tiles are 16x16 pixels... That is the reason of the multiplication for 16 in the DispGeo procedure. An example of a procedure that generates a GeoMap is: Procedure GenGeoMap; Var A,B:Word; Begin For A:=0 To GeoMapSizeX Do For B:=0 To GeoMapSizeY Do GeoMap[A,B]:=Random(5)+1; End; This fills the GeoMap with random tiles, ranging from 1 to 5. Now, to scroll around this map, you just have to call the DispGeo procedure with different X and Y values (don't alter the Dx and Dy values, or else you'll get funny results on the screen). There is a test program that uses the above procedure with this mag... It is called ScrlGeo.Pas... It uses the file ScrlGeo.Img that stores the images of the trees. So, the basics to get you going is taught... Use this knowledge wisely, my child... :) 7.2. Parallax scrolling Some computers (like the Amiga) have built-in circuits that handle parallax scrolling, but in the PC, you have to code yourself your parallax engine... But, what is parallax scrolling... Well, parallax scrolling is similar to what I have done with the starfield effect in this issue (see article 6). Objects that are further away from us appear to move slower, so you must scroll them at different times... What I'm going to teach is a combination of two scrolls... For example, check the Prllx01.Pas program... This scrolls a piece of land, with some water on the sides... It uses the theory of scrolling of the last issue. Now, check the Prllx02.Pas program... It is similar in most respects, differing only in the fact that it scrolls clouds... Now, let's combine the two together ! As we want to make parallax scrolling, you must make sure that the clouds move faster than the land, so let's do it like this: while the land scrolls one pixel, the clouds scroll two pixels ! Then, for every frame, we just have to combine the two scrolls... To do so, we draw the land and then we draw the clouds, making sure that only the white parts of the cloud are drawn, not the black parts. To do so, we have to check each pixel of the image to put on top... So, we have to modify the CopyPage procedure... Here is the new one (the T stands for Transparency): Procedure CopyPage_T(From,Too:Word); Var Offs:Word; Begin For Offs:=0 To 63999 Do If Mem[From:Offs]<>0 Then Mem[Too:Offs]:=Mem[From:Offs]; End; Then, you just alter the procedure that calls the scrolls, in a way that it scrolls two times the clouds before scrolling once the land... And voil , you have parallax scrolling... It's all in the Prllx03.Pas file... This is _VERY_SLOW_, but it is only to demonstrate... In future issues I will teach you how to do this in assembler and that will _REALLY_ speed up that thing... 7.3. Partial scrolls This final part talks about partial scrolls... That's an easy concept to grasp... To partially scroll anything, independent from the direction, the ideia is to move one line (or part of a line) at a time... I'll give you the procedures that do this, and you dissect them as you will: Procedure ScrollUp(x1,y1,x2,y2,Where:Word); Var A:Word; Begin For A:=y1 To y2 Do Move(Mem[Where:A*320+x1],Mem[Where:(A-1)*320+x1],x2-x1); End; Procedure ScrollDown(x1,y1,x2,y2,Where:Word); Var A:Word; Begin For A:=y2 DownTo y1 Do Move(Mem[Where:A*320+x1],Mem[Where:(A+1)*320+x1],x2-x1); End; Procedure ScrollLeft(x1,y1,x2,y2,Where:Word); Var A:Word; Begin For A:=y1 To y1 Do Move(Mem[Where:A*320+x1],Mem[Where:A*320+x1-1],x2-x1); End; Procedure ScrollRigth(x1,y1,x2,y2,Where:Word); Var A:Word; Begin For A:=y1 To y1 Do Move(Mem[Where:A*320+x1],Mem[Where:A*320+x1+1],x2-x1); End; I think this is fairly simple to understand... The ScrollLeft and ScrollRight procedures haven't changed much, but in full screen scrolls we could move the entire screen up and down with just one intruction, but with partial scrolls we must move it line by line... Well, this is the end of the scrolling saga... I think I've covered almost everything, except tile-scrolling, that will be discussed in detail a future issue... -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- 9. Hints and Tips * - Begginners tip ** - Medium tip *** - Advanced tip - Logic operations on ilogic operands (**) Well, I'm loosing my brain thinking about something to put here... I can't remember nothing... Ok, I remember something... Did you knew you could do logical operation such as AND, OR and NOT with operands of any kind ? You may ask, how does this work ???? Well, like this... Imagine the internal (binary) representation of the number 41: 41 = 00101001 ; Then you ANDit with number 1: 1 = 00000001 ; The result is: 00000001 ; He makes the logic corresponding with the logic tables I have given you... Similiarly: OR: NOT: 00101001 NOT 00101001 = OR 00000010 11010110 = 00101010 This is quite usefull sometimes, because logic operations are much faster than aritmetic options... There are quite nifty tricks to do with this, but I'll leave that for other issue... :) -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x- 10. Points of View Ooff !! I'm tired... Almost 80000 bytes this one !! That's 15000 more than the second larger issue... Well, what can I say in this issue's point of view ? I really don't know... I'll satrt by telling what I'm going to put in the next issue... I'll continue the Sprites series (talk about transparency, animation and movement), the Graphics Series (I'll talk about text in mode 13h)... I'll also do part II of the 'Building a text adventure' tutorial (talking about the parser and basic commands). I'll also start a tutorial on 'How to make a program 100% in assembler'... And I'm thinking of making another article on effects coding... I think that is going to be another _LARGE_ issue ! :) I don't know when is issue 9 be ready, because I'm going into projects time at the university (couple of works in assembly, one in Modula 2 and another in C)... And also, I must study a bit (if I want to pass the semester alive :) Also, I'm working on a game (won't tell you anything about it !! It's a secret), I'm making a talker and a MUD, and also a demo (all I'm going to say about this one here is the name: Into The Shadows !! Neat, isn't it ?). I don't know were I'll get the time to do this all, but I'm going to try... Well, this points of view are sucking big time !! As all this issue... Ok, I'll leave you with a question ? Am I beginning to be more serious in 'The Mag' ? Am I growing old and less funnier ? Hope not... :) But I feel I'm not as a comediant as I used to be... Maybe I've run out of jokes... Or maybe I'm just tired (after all, 80000 bytes !!!)... So, to give you some humor, here's a couple of jokes: How many Microsoft programmers are needed to screw a lightbulb ? None !! They just change the standart ! :)))) 'Who the hell in General Failure and why is he reading my disk ???' -x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x 11. The adventures of Spellcaster, the rebel programmer of year 2018. Episode 8 - Suicide The upgrading of our jetcar was going well... We were trying to hook up a Durallium block to the front of it, and to do so we needed to increase the power of the engine. - Has anyone ever told you that you are insane ? - Deathripper asked me. - Yep... - I answered, without taking my eyes of the drone that was lifting the heavy block. - Am I cool or what ? - No, you are insane... You are a suicidal maniac... - Well, suicide is quite fun, if you look at it with a nice perspective - I replied, with a grim look in my face and a dark tone of voice. - As the matter of fact, I'm the crazy one, going after this psicho's plans like this... - Karl muttered to himself, kicking a bolt and looking at his sister, that was working on the engine of the craft. - Well, this "psycho", as you called me can't code jack-shit, - I said, looking at Kristie with hatred in his eyes, remembering what she said about him about a week ago. - but I was the one that made this thing possible... - Oh, let's all kneel at the feet of the Great Lord Spellcaster, Master of Assembly Coding and Ruler of Gourad Shading !! - Excalibur said, ironically. - Shut the hell up, Kristie... - I shouted, my voice echoing in the cold closed room. - You shouldn't talk of what you don't know... - I said, with a cold emontionless voice. - Why do you stand up to this piece of shit, Karl ??!!! - She asked, waving her arms frantically. - Well... He's got a point... All we ever do is say that he can't code, altough the only thing we used so far to destroy Comptel is his virus... We are beeing unfair... - You are ?! - I asked, surprised. - Yes, of course you are... - I continued, trying to hide the surprise in my voice. - WHAT ?! - Excalibur shouted. - ARE YOU PAYING HEED TO A GUY THAT LOOKS LIKE HE HAS COME FROM THE VISUAL BASIC KINDERGARTEN ?!!! - Her voice then changed to a more friendly tone, yet filled with anger. - Well, in that case I'm OUT of your stupid and suicidal plan... She got up from beneath the jetcar and she opened the door of the garage. The sunlight hurted my eyes, and I look at her perfect body's shadow, thinking of the reasons that make me fight with her, when I knew, deep in my heart that I loved her. She swang her head, making her black hair fly in the wind, and she look directly at me... I could be wrong, but in her eyes there was much more than just hatred... I just didn't knew what it was... Then she turned over again and she left the garage. - Well, Gundsen, it looks like we don't have a pilot for the jetcar... - I said in a sad voice. - Don't worry... She'll be back... She always does... And after all, her hate for William Gates is so deep in her that she can forget any other hatred. I stood there in silent for awhile. Then I broke the silence with a question: - Why does she hate Gates so much ? - I know why... But only she can tell you... The garage fell in silence again... I looked at Gundsen, and his eyes were staring at me, as he could read my thoughs. He said, smilling: - No, I don't think she hates you at all... And saying this, he also leaved the garage and I was left alone with my thoughs, wondering if my feelings for Kristie were indeed so deep and visible as it seemed... See you in the next issue Diogo "SpellCaster" Andrade