'=========================================================================== ' Subject: MODULAR PROGRAMMING Date: 05-01-99 (16:19) ' Author: Walt Decker Code: Text ' Origin: DamlerBenz@aol.com Packet: FAQS.ABC '=========================================================================== Modular programming is a concept that is little understood in BASIC programming. This may be because many have only QB1.1 or have a pirated copy of QB4.5/QBX or have not really taken the time to read the QB4.5/QBX documentation. Modular programming is not an execution concept but a development concept. Most modern programming use it, and it can be a powerful tool of used correctly. Modular programming is quite usefull and is really easier than programming one main module with a large number of SUBS. There are, however, a few things that must be understood. First, there are a number of BASIC statements that do not work across modules. These are: 1. DIM SHARED 2. CONST 3. DATA 4. TYPE ... END TYPE 5. '$DYNAMIC 6. '$STATIC 7. DEF FN... END DEF Second, each module must have its own SUB/FUNCTION declarations provided any SUBs or FUNCIONs within the module reference other SUBs or FUNCTIONs. Third, each module can have its own named common statements plus named common statements in other modules plus blank common statments referenced in the main module but not its own blank common statements. Fourth, it requires a little preplanning, which is probably the hardest part. Normally, it's best to start by defining those procedures that will be common to all modules, such as mouse support, keyboard support, message display, and user input queries. When those are worked out, one can then begin building modules that are more specific to the problem and at the same time use the more general modules. Fifth, there is no support for modular programming in QB1.1. There is one other item of which, although not of great importantance, one should be aware. Each module can have its own OPTION BASE. In other words, one module can have OPTION BASE 0 and another can have OPTION BASE 1. The reason it is not really important is that arrays can be DIMed to any dimension one wants. For example: I = -10 J = 50 DIM Array(I TO J) DIM Array1(J TO I) DIM Array2(J TO I, I TO J) BASIC is perfectly happy with all three. Although the OPTION BASE is not really important, if it is used it should, but does not have to be, consistent in all modules. Being consistant makes it easier to debug and maintain. Since a module is really a stand alone BASIC program, it can be developed independantly of all other modules that will go into the final application. That means one can develope the module in the IDE, test it in the IDE, compile and link it in the IDE and run it from DOS or Windows. That means before it even gets into the final app it is almost, but not quite bullet proof. The reason it is almost bullet proof is that there are always a few minor modifications that must be made to smoothly integrate it into the app. Now to look at the outline of a modularly programmed application. First, you have the main module or the entry point of the application. It may be as simple as the following: DEFINT A-Z OPTION BASE 1 DECLARE SUB PrintIt (Prnt$) CALL PrintIt("Hello! How are you") END Sub PrintIt is in a module by itself and could look something like this: DEFINT A-Z OPTION BASE 0 CONST BackColor = 7 CONST ForeColor = 4 END SUB PrintIt (Mess$) ScreenCenter = 40 MessLen = LEN(Mess$) \ 2 LOCATE 10, ScreenCenter - MessLen COLOR ForeColor, BackColor PRINT Mess$ END SUB Notice that CONST ForeColor and BackColor are defined in the second module but not the first. If you added in the first module PRINT ForeColor, BackColor, the result would be 0 0. Those constants are not available to the main module, but since they are defined in the support module they are available to all SUBs and FUNCTIONs within it. Now, go back to the list of BASIC statements that do not work across modules: 1. DIM SHARED 2. CONST 3. DATA 4. TYPE ... END TYPE 5. '$DYNAMIC 6. '$STATIC 7. DEF FN... END DEF There will be two modules. Each of the modules will have exactly the same declarations except that the parameters will not be the same. This works because the modules themselves are encapsulated, that is, the declarations are LOCAL TO THE MODULE. The modules do not care what the requirements of each other are. They are concerned only with their own requirements. As a matter of fact, the two modules can even have the same line lables/numbers althought that is not a good idea. The only real requirement is that the modules do not have SUBs/FUNCTIONs with the same name. MAIN.BAS DEFINT A-Z OPTION BASE 1 TYPE Rodent Lb AS INTEGER Rb AS INTEGER Mx AS INTEGER My AS INTEGER END TYPE DEF FNTrim$ (a$) = RTRIM$(LTRIM$(a$)) DIM SHARED Jason AS STRING DIM SHARED X AS INTEGER, Y AS INTEGER CONST Background = 7 CONST Foreground = 4 DATA "THIS IS A TEST" DIM Mice AS Rodent END MOD1.BAS DEFINT A-Z OPTION BASE 1 TYPE Rodent X AS SINGLE Y AS SINGLE S AS STRING * 10 T AS STRING * 20 END TYPE DEF FNTrim$ (a$) FOR Itrim = 1 LEN A$ J$ = MID$(a$, Itrim, 1) J = ASC(J$) IF J < 32 OR J > 126 THEN GOTO SKIP.TRIM B$ = B$ + J$ SKIP.TRIM: ' NEXT Itrim FNTrim$ = B$ END DEF CONST Background = 2 CONST Foreground = 14 DATA "HELLO THERE" DIM SHARED Jason AS STRING * 10 DIM SHARED X AS SINGLE, Y AS DOUBLE DIM SHARED Mice AS Rodent END Sure as there is life on this rock, someone will take a look at MAIN.BAS and MOD1.BAS and say "Hey, you can't do that. You have two functions named FNTrim$. You said you can't have two functions of the same name in two different modules." That's true. However, there are two things going here. First, although DEF FNis a function, it is not a function procedure whereas FUNCTION Trim$ is defined as a function procedure. Second, FNTrim$ is a variable name, not a function itself. The "FN" is reserved for DEF FN. Since one module can not refer to an "FN" in another module, the use of the variable name FNTrim$ is perfectly legal. A module can, however, refer to a FUNCTION procedure in another module. So, one can't have the same FUNCTION name in two different modules. As it turns out, it is perfectly legal to have FNTrim$ and FUNCTION Trim$ in the same module. The reason, they are two distinct variable names. Theoretically a module is a program unit that can do its thing without interaction with, or knowledge of the data structures of, any other module. If that was really the case, one could have a group of EXEs CHAINed together and everyting would work well. In practice, that is seldom the case. Modules are designed to interact, ie, pass data back and forth. Since DIM SHARED does not work, how does data pass between modules. There are two methods. They can be used either singly or together. Those methods are by passing data through an argument list or by using some form of COMMON or both. Everyone is familiar with argument lists so that will not be discussed, except for a couple of details. Communication implies that the modules that communicate know something about the data requirements and data structures of each other. For example, one cannot pass reals to a SUB or FUNCTION that is expecting integers (except in QBX where the BYVAL modifier is accepted and when passing data to an external FUNCTION or SUB written in another language). So, when passing data through an argument list, the data type, position and number of items apply. As an illustration, take a look at MAIN.BAS and MOD1.BAS. MAIN.BAS MOD1.BAS DEFINT A - Z DEFINT A - Z OPTION BASE 1 OPTION BASE 1 TYPE Rodent TYPE Rodent Lb AS INTEGER X AS SINGLE Rb AS INTEGER Y AS SINGLE Mx AS INTEGER S AS STRING * 10 My AS INTEGER T AS STRING * 20 END TYPE END TYPE DIM Mouse AS RODENT DIM Mouse AS RODENT CALL T1(mouse) CALL T3 END END SUB T1(A AS RODENT) SUB T2(A AS RODENT) A.Lb = 1 A.X = 3.14159 A.Rb = 2 A.S = "This is a Test" CALL T2(A) END SUB END SUB SUB T4(A AS RODENT) SUB T3 A.Mx = 57 DIM Mice AS RODENT A.My = 22 Mice.Y = 0.057824 END SUB Mice.T = "HAVE A GOOD DAY" CALL T4(Mice) END SUB When MAIN.BAS passes data to SUB T1 everything is fine because the data passed matches the description of A AS RODENT. When T1 passes data to T2 an error will occur because the description of the data passed does not match MOD1.BASs description of RODENT even though the names are the same. When MAIN.BAS references SUB T3, there is no error because no data is passed; however, when T3 passes data to T4 an error occurs because the descriptions do not match. To get around this, there must be a description in each module of the TYPE ... END TYPE templets referenced. For example: DEFINT A - Z DEFINT A - Z OPTION BASE 1 OPTION BASE 1 TYPE Rodent TYPE Rodent Lb AS INTEGER X AS SINGLE Rb AS INTEGER Y AS SINGLE Mx AS INTEGER S AS STRING * 10 My AS INTEGER T AS STRING * 20 END TYPE END TYPE TYPE Other TYPE Other X AS SINGLE Lb AS INTEGER Y AS SINGLE Rb AS INTEGER S AS STRING * 10 Mx AS INTEGER T AS STRING * 20 My AS INTEGER END TYPE END TYPE By changing the requirements in the respective modules to AS OTHER, no errors will result. However, it would be better to change the names of the TYPE ... END TYPE structures to match the data types. The second method used to pass data back and forth between modules is the use of COMMON. In the applications seen on AOL, this method seems to be little used. At this point, a digresseion must be made to add a caveat to item number 5. That item stated that QB1.1 does not support modular programming. In the context of this discussion, that is correct; however, through the use of blank COMMON and CHAIN, QB1.1 does support a type of modular programming. So, QB1.1 programmers can use part of this inter-module communication discussion to perform a type of modular programming. There are two types of COMMON. These types are termed "blank" and "named." Blank COMMON takes the form: COMMON V1, V2(), V3$, V4 AS STRING * 10, V5! COMMON V6$(), V7 AS Rodent, V8#, V9() AS STRING * 10 COMMON V10#(), V11 AS INTEGER Named COMMON takes the form: COMMON /a/ V1, V2(), V3$, V4 AS STRING * 10, V5! COMMON /B/ V6$(), V7 AS Rodent, V8#, V9() AS STRING * 10 COMMON /C/ V10#(), V11 AS INTEGER In both forms, arrays are automatically of type DYNAMIC, therefore they are moved out of DGROUP, and they are dimensiond outside of the COMMON declaration. So, what does COMMON do? It sets aside an area of memory that is "universal" to all modules within the application. In otherwords, the modules can use the variables declared in COMMON to pass data between them. The reason the word "universal" is in quotes is that, in reality, the data is available to SUBs and FUNCTIONs only if the SHARED attribute is also present. If the SHARED attribute is not present, the data contained in the COMMON area is available to the main body (the part that makes the declarations) only. With that said, take a look at the following: MAIN.BAS MOD1.BAS DEFINT A - Z DEFINT A - Z OPTION BASE 1 OPTION BASE 1 COMMON A, B, C COMMON SHARED A, B, C COMMON SHARED D, E, F COMMON SHARED G, H, I COMMON SHARED Q$, C$ COMMON Question$, Answer$ END END The above illustrates two facets of COMMON: 1. All common need not be SHARED and the SHARED attribute in one module need not match the SHARED attribute in another. This property allows the programmer some latitude in using variable names in SUBs and FUNCTIONs without affecting the data contained in the COMMON environment. 2. COMMON is NOT variable name sensitive. However, as with an argument list, the data type, position, and number of objects is important. In other words COMMON A, B, C and COMMON AZ, AX, AY are equivalent but COMMON A, B, C and COMMON A!, B, C, D is not. This is true for both blank COMMON and named COMMON. Although variable names are unimportant, to facilitate debugging and maintenance it is good practice to keep them consistent across modules. It was stated above that an individual module cannot have its own blank COMMON blocks. There is, as with most programming, a caveat to that statement. If it is the first reference to blank COMMON, an individual module may have its own. An individual module may also increase the number of blank common blocks at the bottom or decrease the number at the bottom, but it cannot decrease or increase at the top or in the middle. To illustrate: MAIN.BAS MOD1.BAS MOD2.BAS DEFINT A - Z DEFINT A - Z DEFINT A - Z OPTION BASE 1 OPTION BASE 1 OPTION BASE 1 COMMON A, B, C COMMON A, B, C COMMON D, E, F, G The above is legal structure. The following, however, is not. MAIN.BAS MOD1.BAS MOD2.BAS DEFINT A - Z DEFINT A - Z DEFINT A - Z OPTION BASE 1 OPTION BASE 1 OPTION BASE 1 COMMON AB, AC, AD COMMON A, B, C COMMON D, E, F, G COMMON DD, DE, FF, GG The COMMON in MOD1.BAS decreased the number of blocks for its purposes from the bottom. The top block still matches the top block of the entry point MAIN.BAS. MOD2.BAS, however, has decreased the number of blocks from the top. Therefore, the top block does not match and an error will occur. This is true for named common provided there is more than one block with the same name. As previously stated the form for named COMMON is: COMMON (SHARED)/ blockname / variablelist Shared is optional. The rules for named common are the same as for blank common with two exceptions. 1. Each module can have its own set of named common blocks. 2. Named common does not work across executables. Executables are distinct for modules and application. The modules are all the stand-alone OBJs that comprise an executable and the application is a group of distinct executables (*.EXEs) that work in harmony to acomplish a task. So, take a look at how named common may be used to communicate between modules. MAIN.BAS DEFINT A-Z OPTION BASE 1 TYPE Rodent Lb AS INTEGER Rb AS INTEGER Mx AS INTEGER My AS INTEGER show AS INTEGER END TYPE COMMON /Meese/ Mice AS Rodent Init = 0 CALL Mouse(Init, Mice) CALL Track END MOD1.BAS DEFINT A-Z OPTION BASE 1 TYPE Rodent Lb AS INTEGER Rb AS INTEGER Mx AS INTEGER My AS INTEGER show AS INTEGER END TYPE COMMON SHARED /Meese/ Mice AS Rodent COMMON SHARED /Coord/ Row(), Col(), Lx, Ly COMMON SHARED /Colr/ DrwColor END SUB Track REDIM Row(10000), Col(10000) IF Mice.show = 0 THEN CALL Mouse(1, Mice) Lx = Mice.Mx Ly = Mice.My Count = 1 Row(Count) = Mice.My Col(Count) = Mice.Mx DO CALL Mouse(3, Mice) IF Mice.Mx <> Lx OR Mice.My <> Ly THEN Count = Count + 1 Row(Count) = Mice.My Col(Count) = Mice.Mx END IF LOOP UNTIL Mice.Lb OR Mice.Rb OR Count >= 10000 Mice.show = 0 CALL Mouse(2, Mice) CALL DrawLine(Count) ERASE Row, Col END SUB MOD2.BAS DEFINT A-Z OPTION BASE 1 TYPE Rodent Lb AS INTEGER Rb AS INTEGER Mx AS INTEGER My AS INTEGER show AS INTEGER END TYPE COMMON SHARED /Meese/ Mice AS Rodent COMMON SHARED /Coord/ Row(), Col(), Lx, Ly COMMON SHARED /Colr/ DrwColor END SUB DrawLine (NumPnts) PSET (Col(1), Row(1), DrwColor FOR I = 1 TO NumPnts) LINE -(Row(I), Col(I)), DrwColor NEXT I Mice.Mx = Lx Mice.My = Ly CALL Mouse(4, Mice) END SUB Notice that MAIN.BAS does very little. It appears to initialize a mouse handler, then passes the work off to another module. Since all three modules use the record variable Mice, a copy of its template must be in each module. Data is passed both by argument list and named COMMON. Now, someone might ask, "Why is Mice being passed in the arugument list if it is in a common block?" Since there is no module shown that has SUB Mouse in it, one can assume that Mice is not defined as common in the module containing SUB Mouse. However, a copy of its description must be in its module. Further, it could be that SUB Mouse is in a general purpose library that has no common blocks in it. In either case, Mice must be passed in the argument list of SUB Mouse. There is another thing that should be noticed. In MOD1.BAS and MOD2.BAS the only program lines are in SUBs. There are no program lines in their body, only declarations. The reason is that there is no way to get to the body of the two modules. Neither of them is the applications' entry point. MAIN.BAS is. The only program lines that can be accessed in the body of the two modules is an error handler. Theoretically, QB4.5 allows local error handlers in SUBs and FUNCTIONs. However, I've never been able to get it to work. Local error handlers work quite nicely in PDS7.x, however. There are several additional points that must be made concerning COMMON. 1. Once a COMMON block is defined, it cannot be undefined and the data contained therein is not erased except in the case of arrays which, since they are automatically dynamic, can be deallocated. Therefore, the data contained in COMMON can be accessed and changed by any module that references it. This can present debugging difficulties if care is not exercised in both defining the COMMON and in its use. 2. The STATIC attribute (used in SUBs and FUNCTIONs) declares the variables in the SUB or FUNCTION as LOCAL. It also sets up a data block inwhich the SUB/FUNCTION stores data that can be used the next time the SUB/FUNCTION is called. If one of the variable names used in the procedure is one that is also declared COMMON, the STATIC attribute takes precidence and the data in COMMON is not changed. To illustrate: MAIN.BAS MOD1.BAS DEFINT A - Z DEFINT A - Z OPTION BASE 1 OPTION BASE 1 COMMON /Dummy/Statik COMMON SHARED /Dummy/Statik END CALL Init.Static SUB Init.Static PRINT Statik; Statik = 25 CALL Cng.Static END END SUB MOD2.BAS DEFINT A-Z OPTION BASE 1 COMMON SHARED /Dummy/ Statik END SUB Cng.Static STATIC Statik = 255 END SUB Because all the variables in SUB Cng.Static are declared STATIC a local data area is set up in memory that only SUB Cng.Static can access. The variable Statik is made local to SUB Cng.Static and is placed in the local data area without affecting the value of the COMMON Statik variable. 3. COMMON and DIM SHARED may be used in the same module; however, the DIM SHARED variable may not have a name that is already declared as COMMON. The reason is that DIM SHARED is local to the module, but COMMON is global to the application. To attempt to do so produces a duplicate definition error. On the other hand, individual SUBs within a module may declare a COMMON variable as SHARED. This property gives the programmer much greater flexibility in deciding where and how COMMON variables are changed. For example: MAIN.BAS DEFINT A-Z OPTION BASE 1 COMMON /Dummy/ Variable.A, Variable.B CALL Init.Varb PRINT Variable.A, Variable.B, Variable.C END MOD1.BAS DEFINT A-Z OPTION BASE 1 COMMON /Dummy/ Variable.A, Variable.B, Variable.C DIM SHARED Variable.A <-- Produces "Duplicate definition error" END SUB Init.Varb SHARED Variable.A, Variable.B Variable.A = 100 Variable.B = 255 Variable.C = 10 CALL Cng.Varb END SUB SUB Cng.Varb SHARED Variable.C Variable.A = 255 Variable.B = 100 Variable.C = 25 END SUB Because Init.Varb declares Variable.A and Variable.B as SHARED, it can affect the values in the COMMON but Variable.C is considered a local variable and its COMMON value does not change. In SUB Cng.Varb, Varaible.A and Variable.B are considered local variables whereas Variable.C is declared as SHARED. Therefore, when the three variables are printed the result is 100, 255, and 25 NOT 255, 100, and 10. It is difficult to present specific guidelines for modular programming, simply because each person has a different programming style and each program is relatively unique. Possibly the best advice is for the programmer to look at the problem to be attacked, determine its needs, then build the modules around those needs. As one becomes more familiar with the modular programming concept, in fact with programming per se, that approach becomes easier. To me, modular programming is similar to writing a letter or a research document. In a letter or research document there is the heading, the body of the document, and the closing. Most applications do the same thing. They start by declaring variables and presenting some type of introduction, perform their assigned task, then end, either by presenting the user with the option to do it over again or just quit. All applications have one thing in common. That one thing is some type of input/output system. It can be as simple as PRINT A + B or as complex as that used by a word processor. Therefore, an I/O system could be one module. Since most of you who are reading this are into game programming, let's see if we can define some general modules for a game. Most games have some type of introduction screen. That screen can be as complex as those in Civilization, Myst, Riven, and countless others, or as simple as a menu to start the game or quit. If the game has an intro screen it will normally present a menu screen also. So, we have, in effect, identified two seperate modules, an intro module and a menu module. Depending on the complexity of the menu there may be others associated with the menu module. For example a menu may look something like this: OPTIONS PLAY GAME LOAD GAME Save GAME CONFIGURE (Characters / Weapons) HELP CREDITS QUIT PLAY GAME is a module, LOAD/SAVE GAME is a module, HELP is a module, and CREDITS is a module. With modular programming, all of them can be designed as overlays. The GAME module itself may require a specialized I/O system. Depending on the type of game, the GAME module may have other modules associated with it. The game "Missile Command" has a specialized I/O system but nothing else. All the screens are handled within the game module itself. On the other hand, a game that requires a map may have a module designed specifically to read the map data and generate the map. The game may also require the user to type words or phrases and therefore would require some type of keyboard parser and a keyword editor. So, two, and perhaps three (depending on design and requirements) additonal modules are identified. Of course, the more complex the game, the more modules that may be identified. In outline form, the modules may appear something like this: MENU.BAS INTRO.BAS LOADSAVE.BAS CONFIG.BAS HELP.BAS CREDITS.BAS GAME.BAS MAP.BAS PARSEKBD.BAS KEYWORD.BAS GAMEIO.BAS Configured like this, each module indented one place could be an overlay. MAP.BAS, PARSEKBD.BAS, KEYWORD.BAS, and GAMEIO.BAS would make up part of the GAME.BAS overlay. In other words, MENU.BAS would make up the memory resident skeleton application, and everything else would be loaded into memory as needed. So how do we put this together to make an application? For the QB1.1 programmer it is not as easy as it is for the QB4.5/QBX programmer, but it can be done. This first part will demonstrate a possible solution for the QB1.1 programmer. The second part will demonstrate a possible solution for the QB4.5/QBX programmer. QB1.1: Take a look at this outline for a possible game: MENU.BAS INTRO.BAS LOADSAVE.BAS CONFIG.BAS HELP.BAS CREDITS.BAS GAME.BAS MAP.BAS PARSEKBD.BAS KEYWORD.BAS GAMEIO.BAS Each of the above is a completely seperate BAS that must run in the QBASIC environment. The QB1.1 programmer would start by programming INTRO.BAS first, then MENU.BAS, LOADSAVE.BAS, CONFIG.BAS, HELP.BAS, and CREDITS.BAS. Each of the BAS files may look something like the following: INTRO.BAS ....program lines, SUBs/FUNCTIONs CHAIN "MENU.BAS" END MENU.BAS COMMON Chars, Weapons, Chapter <--- used for configuration COMMON SaveLoad <--- used to make a decision IF SAVELOAD = 1 THEN CHAIN "GAME.BAS" .... program lines to present choices SELECT CASE CHOICE$ CASE "SAVE", "LOAD" IF CHOICE$ = "LOAD" THEN SAVELOAD = 1 CHAIN "SAVELOAD.BAS" CASE "CONFIG" CHAIN "CONFIG.BAS" CASE "HELP" CHAIN "HELP.BAS" CASE "CREDITS" CHAIN "CREDITS.BAS" CASE "GAME" CHAIN "GAME.BAS" END SELECT END CONFIG.BAS COMMON Chars, Weapons, Chapter ...... program lines to configure game CHAIN "MENU.BAS" END HELP.BAS ...... program lines to show help CHAIN "MENU.BAS" END CREDITS.BAS ...... program lines to show credits CHAIN "MENU.BAS" END SAVELOAD.BAS COMMON Chars, Weapons, Chapter <--- used for configuration COMMON SaveLoad <--- used to make a decision IF SAVELOAD = 1 THEN CALL GetFile.List(GameFile$) CALL GetFile(GameFile$) CHAIN "MENU.BAS" END IF CALL GetFile.Name(GameFile$) CALL SaveFile(GameFile$) CHAIN "MENU.BAS" END Depending on the type of game, GAME.BAS may chain to MAP.BAS to load the proper screen or episode, or MAP.BAS may be converted to SUB MAP, to load the proper screen or episode. PARSEKBD.BAS, KEYWORD.BAS, and GAMEIO.BAS will be converted to SUB PARSEKBD, SUB KEYWORD, and SUB GAMEIO. At the end of the game or episode, GAME.BAS should chain back to MENU.BAS. For the QB4.5/QBX programmer the modular programming process is different. Here is the game outline again: MENU.BAS INTRO.BAS LOADSAVE.BAS CONFIG.BAS HELP.BAS CREDITS.BAS GAME.BAS MAP.BAS PARSEKBD.BAS KEYWORD.BAS GAMEIO.BAS MENU.BAS is the application kernal, or the resident part assuming that overlays are used. So, the application may look like this: MENU.BAS COMMON Chars, Weapons, Chapter <--- used for configuration CALL INTRO <--- SUB INTRO is in INTRO.BAS TOP:' .... program lines to set up menu choices SELECT CASE CHOICE$ CASE "SAVE", "LOAD" IF CHOICE$ = "LOAD" CALL LOAD <--- SUB LOAD is in SAVELOAD.BAS CALL GAME <--- SUB GAME is in GAME.BAS ELSE CALL SAVE <--- SUB SAVE is in SAVELOAD.BAS END IF CASE "CONFIG" CALL CONFIG <--- SUB CONFIG is in CONFIG.BAS CASE "HELP" CALL HELP <--- SUB HELP is in HELP.BAS CASE "CREDITS" CALL CREDITS <--- SUB CREDITS is in CREDITS.BAS CASE "GAME" CALL GAME <--- SUB GAME is in GAME.BAS END SELECT GOTO TOP END SAVELOAD.BAS, CONFIG.BAS, HELP.BAS, and CREDITS.BAS are programmed basically the same as in QB1.1. GAME.BAS may be quite different. GAME.BAS COMMON Chars, Weapons, Chapter <--- used for configuration END SUB GAME CALL MAP <--- SUB MAP is in MAP.BAS .... Game play lines CALL GAMEIO <--- May be in GAME.BAS or GAMEIO.BAS CALL PARSEKBD <--- May be in PARSEKBD.BAS or GAME.BAS CALL KEYWORD <--- May be in KEYWORD.BAS or GAME.GAS END SUB The next question is, "How do you do this?" The easiest way is from withing the QB4.5/QBX environment. Go into QB using the /L switch. This is in case you want to use INTERRUPT/INTERRUPTX/ABSOLUTE. Start by programming MENU.BAS first. It may look something like this: MENU.BAS DEFINT A-Z OPTION BASE 1 DECLARE SUB INTRO () COMMON /Config/ Weapons(), Characters(), Chapter, Save CALL INTRO .... Code to display menu and select menu item END Save MENU.BAS and select NEW from the QB4.5/QBX FILES menu Program INTRO.BAS. It might look something like this: INTRO.BAS DEFINT A-Z OPTION BASE 1 END SUB INTRO .... Program lines to display introduction screen END SUB Save INTRO.BAS. From the FILES menu, select OPEN then select MENU.BAS. When MENU.BAS is loaded, go back to the FILES menu and select LOAD. Select INTRO.BAS and AS MODULE (should be the default). INTRO.BAS will be loaded. You will be able to see it and MENU.BAS if you press F2. Press F5 to run in the IDE just to make sure it will work. You can also edit both MENU.BAS and INTRO.BAS from the IDE at this time. Once the file has run, go to the FILES menu and select SAVE ALL. QB will produce a *.MAK file that will be read when you load MENU.BAS. This *.MAK file will also load INTRO.BAS when you load MENU.BAS. Follow the above procedure for each module you create. When you are finished creating all the modules you need, you can go to the RUN menu, select MAKE EXE, and all the files will be compiled and linked into one EXE. This method will not produce overlays. To create overlays set up a batch file for compiling all the *.BAS files at one time and with the same options. You can get a list of compile options from the BC command line. Next, either set up a response file for LINK to read or execute LINK from the command line. Some LINK options you may want are /NOI /NOE /NOD /HIGH. You can get a list of LINK options by issuing the command LINK /HE. >From the command line LINK may look something like the following: LINK Object Modules [.OBJ]: /NOI /NOD /NOE:QBRUN45.EXE Object Modules [.OBJ] MENU + (SAVELOAD) + (HELP) + (CREDITS) + (INTRO)+ Object Modules [.OBJ] (GAME + MAP + PARSEKBD + GAMEIO) MapFile [NUL] Libraries [.LIB] QB.LIB + QBRUN45.LIB The *.OBJ files in parentheses are designated as overlays. Once that is completed, there will be one EXE named MENU. That will be the application kernal (the part that is in memory while the application is running). The others will be loaded only when called and unloaded when finished. And that is MODULAR PROGRAMMING in a nutshell.