gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

C/C++ > problem with scope [SOLVED]

#172018 - zelbo - Sat Jan 09, 2010 12:52 am

I'm somewhat new to c. Trying to wrap my head around pointers currently.

I'm trying to make a roguelike without using curses. So i plan to use an array to hold all the display information, then update as needed. i'm having trouble with scope and pointers, however.

running into trouble at line 41 (void drawMap(void))

any help is greatly appreciated.

do i just need to give up and use curses? even doing that, i feel like i should get a handle on how to make this work.

Code:

/* Yet Another Roguelike for the DS - By Zelbo */

#include <nds.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

// Month information
const char* month[12] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};

// Arrays for displays - map, items, creatures, fog of war
int dispMap[32][20], dispItems[32][20], dispCreatures[32][20], dispFog[32][20];

// monster class
class monster
{
   private:
   public:
   char name[15];
   int avatar; // ascii code of display char
   int cRace, cClass;
   int x, y, z;
   int maxHealth, maxMana, curHealth, curMana;
   int attack, armor, experience, level;
   bool alive;
   int state;
   
   void move(int direction)
   {
   
   
   };
};



/*
Scope and pointers ... how do i make this work?
do i need to pass pointers for console and array to the function?
*/
void drawMap(void)
{
// maybe do this part outside of the function?
// consoleSelect(&winGame0);
  for(i=0;i<32;i++)
  {
      for(i2=0;i2<20;i2++)
     {
       iprintf("%c", dispMap(i,i2));
     }
   
  }


}


//---------------------------------------------------------------------------------
int main(void)
{
// initialize variables
   monster Player;
   Player.avatar=64;
   Player.x=10;
   Player.y=10;
   int keys;
   int i, i2;
   int hours, seconds, minutes;
    unsigned long init[4]={0x123, 0x234, 0x345, 0x456}, length=4;
    init_by_array(init, length);
   touchPosition touch;

// initialize the consoles
  PrintConsole winGame0;
   PrintConsole winGame1;
   PrintConsole winGame2;
   PrintConsole winGame3;
   PrintConsole winMessage;
   PrintConsole winStats;
   PrintConsole winDebug;
   
// set screen modes
   videoSetMode(MODE_0_2D);
   videoSetModeSub(MODE_0_2D);

// assign memory banks
   vramSetBankA(VRAM_A_MAIN_BG);
   vramSetBankC(VRAM_C_SUB_BG);

// define the consoles
/*
consoleInit (PrintConsole *console,
 int layer,
 BgType type,
 BgSize size,
 int mapBase,
 int tileBase,
 bool mainDisplay,
 bool loadGraphics)
 
*/
   consoleInit(&winGame0, 0, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winGame1, 1, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winGame2, 2, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winGame3, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winMessage, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winStats, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winDebug, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, false, true);

// customize consoles
/*
void consoleSetWindow     (     PrintConsole *       console,
      int     x,
      int     y,
      int     width,
      int     height   
   )         
*/
consoleSetWindow(&winGame0, 0, 0, 32, 20);
consoleSetWindow(&winGame1, 0, 0, 32, 20);
consoleSetWindow(&winGame2, 0, 0, 32, 20);
consoleSetWindow(&winGame3, 0, 0, 32, 20);
consoleSetWindow(&winMessage, 0, 20, 32, 3);
consoleSetWindow(&winStats, 0, 23, 32, 1);
consoleSetWindow(&winDebug, 0, 0, 32, 24);

// test all consoles
consoleSelect(&winGame0);
iprintf("Game Window\n");

consoleSelect(&winMessage);
iprintf("Message Window\n");

consoleSelect(&winStats);
iprintf("Quick Stats");

consoleSelect(&winDebug);
iprintf("Debug Window\n");
     for(i2=32;i2<127;i2++)
     {
       iprintf("%c",i2);   
     }

   
   consoleSelect(&winGame2);
   iprintf("\x1b[10;20H\x1b[35m%c", Player.avatar);

consoleSelect(&winGame0);
drawMap();
   
   
// select bottom console
   consoleSelect(&bottomScreen);

   while(1) {
      // get the time inform
      time_t unixTime = time(NULL);
      struct tm* timeStruct = gmtime((const time_t *)&unixTime);
      hours = timeStruct->tm_hour;
      minutes = timeStruct->tm_min;
      seconds = timeStruct->tm_sec;
     
      //get input
      touchRead(&touch);
      scanKeys();
      keys = keysHeld();
     
      // display date and time
      iprintf("\x1b[2J%s %i %i %02i:%02i:%02i", month[timeStruct->tm_mon], timeStruct->tm_mday, timeStruct->tm_year +1900, hours, minutes, seconds); // display the current time
     
      // display pen coordinates
      iprintf("\x1b[1;0HTouch x, y = %02i, %02i\n", (touch.px/8), (touch.py/8));
     


      swiWaitForVBlank();
   }

   return 0;
}



edit: cut out some irrelevant parts, made small changes to how i'm trying to do it.


Last edited by zelbo on Sun Jan 10, 2010 11:36 pm; edited 1 time in total

#172022 - ninjalj - Sat Jan 09, 2010 11:10 pm

Quote:
Code:
iprintf("%c", dispMap(i,i2));

maybe you wanted to do:
Code:
iprintf("%c", dispMap[i][i2]);


dispMap is an array (of arrays to integers), not a pointer.

Anything that you declare outside of functions has either global scope (it is visible in all the program) or compilation-unit scope if it's declared static (it is visible only in the current object file).

#172024 - zelbo - Sun Jan 10, 2010 4:18 am

I'll never stop kicking myself for learning basic over c back in highschool.

so i changed that, and it seems to help, except i still can't get the function to do what it's supposed to. I need to get it to print the map to the game console, having trouble with that. I'm sure it's something to do with pointers, haven't quite nailed those yet. I'll keep fiddling with it, but if anyone can solve it for me, that would be sweet.

the function is at line 49, the call to it is at 164.

Code:

/* Yet Another Roguelike for the DS - By Zelbo */

#include <nds.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

// Month information
const char* month[12] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};

// Arrays for displays - map, items, creatures, fog of war
int dispMap[32][20], dispItems[32][20], dispCreatures[32][20], dispFog[32][20];
int i, i2;
// monster class
class monster
{
   private:
   public:
   char name[15];
   int avatar; // ascii code of display char
   int cRace, cClass;
   int x, y, z;
   int maxHealth, maxMana, curHealth, curMana;
   int attack, armor, experience, level;
   bool alive;
   int state;
   
   void move(int direction)
   {
   
   
   };
};


// initialize the consoles
PrintConsole winGame0;
PrintConsole winGame1;
PrintConsole winGame2;
PrintConsole winGame3;
PrintConsole winMessage;
PrintConsole winStats;
PrintConsole winDebug;
   

/*
how do i make this work?
*/
void drawMap(PrintConsole* ptr)
{
// maybe do this part outside of the function? (tried, no good)
// maybe pass the
consoleSelect(ptr);

  for(i=0;i<32;i++)
  {
      for(i2=0;i2<20;i2++)
     {
       iprintf("%c", dispMap[i][i2]);
     }
   
  }


}


//---------------------------------------------------------------------------------
int main(void)
{
// initialize variables
   monster Player;
   Player.avatar=64;
   Player.x=10;
   Player.y=10;
   int keys;
   int hours, seconds, minutes;
   touchPosition touch;


// set screen modes
   videoSetMode(MODE_0_2D);
   videoSetModeSub(MODE_0_2D);

// assign memory banks
   vramSetBankA(VRAM_A_MAIN_BG);
   vramSetBankC(VRAM_C_SUB_BG);

// define the consoles
/*
consoleInit (PrintConsole *console,
 int layer,
 BgType type,
 BgSize size,
 int mapBase,
 int tileBase,
 bool mainDisplay,
 bool loadGraphics)
 
*/
   consoleInit(&winGame0, 0, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winGame1, 1, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winGame2, 2, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winGame3, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winMessage, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winStats, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
   consoleInit(&winDebug, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, false, true);

// customize consoles
/*
void consoleSetWindow     (     PrintConsole *       console,
      int     x,
      int     y,
      int     width,
      int     height   
   )         
*/
consoleSetWindow(&winGame0, 0, 0, 32, 20);
consoleSetWindow(&winGame1, 0, 0, 32, 20);
consoleSetWindow(&winGame2, 0, 0, 32, 20);
consoleSetWindow(&winGame3, 0, 0, 32, 20);
consoleSetWindow(&winMessage, 0, 20, 32, 3);
consoleSetWindow(&winStats, 0, 23, 32, 1);
consoleSetWindow(&winDebug, 0, 0, 32, 24);

// test all consoles
consoleSelect(&winGame0);
iprintf("Game Window\n");

consoleSelect(&winMessage);
iprintf("Message Window\n");

consoleSelect(&winStats);
iprintf("Quick Stats");

consoleSelect(&winDebug);
iprintf("Debug Window\n");
     for(i2=32;i2<127;i2++)
     {
       iprintf("%c",i2);   
     }

   
   consoleSelect(&winGame2);
   iprintf("\x1b[10;20H\x1b[35m%c", Player.avatar);
   
   
   
// fill the map with something
  for(i=0;i<33;i++)
  {
   
     for(i2=0;i2<21;i2++)
     {
      dispMap[i][i2]=65;
     }
   
   
  }



consoleSelect(&winGame0);
drawMap(&winGame0);
   
   
// select bottom console
   while(1) {
      // get the time inform
      time_t unixTime = time(NULL);
      struct tm* timeStruct = gmtime((const time_t *)&unixTime);
      hours = timeStruct->tm_hour;
      minutes = timeStruct->tm_min;
      seconds = timeStruct->tm_sec;
     
      //get input
      touchRead(&touch);
      scanKeys();
      keys = keysHeld();
     
      // display date and time
      iprintf("\x1b[2J%s %i %i %02i:%02i:%02i", month[timeStruct->tm_mon], timeStruct->tm_mday, timeStruct->tm_year +1900, hours, minutes, seconds); // display the current time
     
      // display pen coordinates
      iprintf("\x1b[1;0HTouch x, y = %02i, %02i\n", (touch.px/8), (touch.py/8));
     


      swiWaitForVBlank();
   }

   return 0;
}

#172028 - Cearn - Sun Jan 10, 2010 12:07 pm

zelbo wrote:
I need to get it to print the map to the game console, having trouble with that.

The function does what it's supposed to do, but you're erasing it later inside the loop (see the '//#' comments):

zelbo wrote:

Code:
int main()
{
   //# <SNIP>

   //# Select game window and draw map
   consoleSelect(&winGame0);
   drawMap(&winGame0);
   
    // select bottom console
   while(1)
   {
      // get the time inform
      time_t unixTime = time(NULL);
      struct tm* timeStruct = gmtime((const time_t *)&unixTime);
      hours = timeStruct->tm_hour;
      minutes = timeStruct->tm_min;
      seconds = timeStruct->tm_sec;
     
      //get input
      touchRead(&touch);
      scanKeys();
      keys = keysHeld();
     
      //# Here it's still the game window, which is now cleared by the
      //# ESC[2J code.

      // display date and time
      iprintf("\x1b[2J%s %i %i %02i:%02i:%02i", month[timeStruct->tm_mon], timeStruct->tm_mday, timeStruct->tm_year +1900, hours, minutes, seconds); // display the current time
     
      // display pen coordinates
      iprintf("\x1b[1;0HTouch x, y = %02i, %02i\n", (touch.px/8), (touch.py/8));

      swiWaitForVBlank();
   }

   return 0;
}

I would say simply call the drawMap() function inside the loop, but using printf is too slow to be called like that; it takes over a frame to draw the whole map, leaving you no time to do any game logic. Here are a few alternatives:
  • Instead of printing character for character, think of the character-map as a whole string and print the entire string in a single printf call (you need a terminating '\0' in the string first, though). That should cut down on some of the overhead.
  • Use putchar() for each character instead of printf. Or puts for the whole string.
  • Don't use the console system, but manipulate the map manually.

Adittionally,
  • Do not make your loop indices global variables. Not only will this be slower than locals, it will bite you in the ass at some point when
    inside a loop you call another function that also contains a loop. The called function would then muck up the index of the caller.
  • In C, matrices are ordered [y][x], not [x][y]. Technically, something like dispMap[32][20] has width=20, height=32. If you're careful it won't matter, but it may slow things down a little because the loop accesses aren't sequential.
  • Consider whether you really need all those separate consoles. The console window isn't like Windows windows: it just gives a frame for placement and text wrapping. All your consoles actually still point to the same map (namely, 31), which could become problematic at some point. You can make due with a single console per screen and move the window as you go.

#172036 - zelbo - Sun Jan 10, 2010 11:36 pm

Awesome, thank you both. I've made a few changes, which i don't have on me at the moment, but i will probably post when i run into my next problem.

Quote:
* Instead of printing character for character, think of the character-map as a whole string and print the entire string in a single printf call (you need a terminating '\0' in the string first, though). That should cut down on some of the overhead.
I was thinking i might need to do something along those lines, i'll try to get that implemented.

Quote:
* Use putchar() for each character instead of printf. Or puts for the whole string.
Can you tell i'm new to C yet? I need to get my hands on a reference of all the functions, currently all i have is a C++ primer for non C programmers, which talks about code structure and some things like that, but what i need now is a solid command/syntax reference.

Quote:
* Don't use the console system, but manipulate the map manually.
Not sure what you mean here, could you elaborate some?

Quote:
* Do not make your loop indices global variables. Not only will this be slower than locals, it will bite you in the ass at some point when inside a loop you call another function that also contains a loop. The called function would then muck up the index of the caller.
Excellent, this is the sort of thing i need to know. Otherwise i would just global the crap out of everything to avoid having to muck around with scope and pointers.

Quote:
* In C, matrices are ordered [y][x], not [x][y]. Technically, something like dispMap[32][20] has width=20, height=32. If you're careful it won't matter, but it may slow things down a little because the loop accesses aren't sequential.
This sort of thing i find confusing and will take some adjusting, but i think i can deal. Although i'm not sure i understand what you mean by 'not sequential', i thought they were very sequential.

Quote:
* Consider whether you really need all those separate consoles. The console window isn't like Windows windows: it just gives a frame for placement and text wrapping. All your consoles actually still point to the same map (namely, 31), which could become problematic at some point. You can make due with a single console per screen and move the window as you go.
This i'm discovering, still playing around with it a bit. I thought the layers dictated their display priority, but it seems this is not the case.

I'm sure i'm going to have a ton more questions while working on this project, but for now i would say my question has been answered. Going by the "make a roguelike in 15 steps" from roguebasin, i'd say i have step three finished with room for improvement.

#172039 - Cearn - Mon Jan 11, 2010 11:44 am

zelbo wrote:
Quote:
* Use putchar() for each character instead of printf. Or puts for the whole string.
Can you tell i'm new to C yet? I need to get my hands on a reference of all the functions, currently all i have is a C++ primer for non C programmers, which talks about code structure and some things like that, but what i need now is a solid command/syntax reference.

I don't know about syntax, but for a function reference go here: http://www.cppreference.com. If you're doing this in pure C, not everything will be available, for obvious reasons. One non-obvious difference is that in C++ the headers are named <cfoo>, whereas in C you'd use <foo.h>.

zelbo wrote:
Quote:
* Don't use the console system, but manipulate the map manually.
Not sure what you mean here, could you elaborate some?

The NDS has 4 backgrounds, which can be either tilemaps or bitmaps -- mostly tilemaps. As tilemaps, you'll have a tileset and a map which says which tile should be rendered at a given position on the screen. What the console system does is provide a layer that makes it easier to print (formatted) text to a tilemap background, but underneath it's still a background. If it's really text (in particular, strings with numbers) that you need to print, use the console system. For general map interaction, it's probably easier and definitely faster to do that yourself.

See also: tonc:regbg, libnds:bg api.

zelbo wrote:
Quote:
* Do not make your loop indices global variables. Not only will this be slower than locals, it will bite you in the ass at some point when inside a loop you call another function that also contains a loop. The called function would then muck up the index of the caller.
Excellent, this is the sort of thing i need to know. Otherwise i would just global the crap out of everything to avoid having to muck around with scope and pointers.

As a rule, try to keep scope as small as possible. Locals first, then passing via function parameters, then globals if passing the same things would just become too cumbersome. Loop indices should be locals. An array of monsters is suited to be global, but when acting on an individual monster it may be best to pass a monster-pointer as a function parameter. Or a class method. It's not exactly required to do it like this though.

zelbo wrote:
Quote:
* In C, matrices are ordered [y][x], not [x][y]. Technically, something like dispMap[32][20] has width=20, height=32. If you're careful it won't matter, but it may slow things down a little because the loop accesses aren't sequential.
This sort of thing i find confusing and will take some adjusting, but i think i can deal. Although i'm not sure i understand what you mean by 'not sequential', i thought they were very sequential.

Think of it like this: elements of an array (regardless of its dimensions) are laid out as one consecutive memory block. For example, if you have array[40], the elements are ordered like this:
Code:

 0  1  2  3  4  5  6  7  8  9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39

For multi-dimensional arrays, the rule is that if you run through memory, the last index loops the fastest. Compare it to regular numbers: if you count up, the last digit (the ones) increases with every step, then the tens every 10 numbers, hundreds and so on.

The array from before can also be interpreted as a block. Since the last index is supposed to increment the fastest, the normal way to define this would be matrix[4][10], and not matrix[10][4]. Note that the array and matrix format can be used interchangeably: matrix element [y][x] is the same as array element [y*matrixWidth+x].

With 'sequential' I meant adjacent in memory. In a C matrix, the element following [0][0] is [0][1], not [1][0]. [1][0] could be quite a way away from [0][0]. Sequential accesses are often faster for a number of reasons, so it's often beneficial to order the arrays and loops accordingly. Actually, now that I think of it, because both the orders of your arrays and loops currently match, it works out nicely (though it's still customary to do it the other way).

If you want to make sure there's no index confusion, you way want to use x and y as part of the loop-names (ix and iy, for example).

zelbo wrote:
Quote:
* Consider whether you really need all those separate consoles. The console window isn't like Windows windows: it just gives a frame for placement and text wrapping. All your consoles actually still point to the same map (namely, 31), which could become problematic at some point. You can make due with a single console per screen and move the window as you go.
This i'm discovering, still playing around with it a bit. I thought the layers dictated their display priority, but it seems this is not the case.

Well, the layers also indicate priority, but that's just incidental and can be adjusted. The real thing to remember is that there are 4 backgrounds per screen. Using the same BG for multiple purposes is possible, but you have to be wary of overwriting.

Something similar applies to VRAM use. Tile memory and map memory overlap and is shared between backgrounds. For example, you're using map-base 31 for all consoles, which effectively means they all use the same map memory. Whether this is good or bad is something you'll have to decide for yourself.

#172046 - zelbo - Mon Jan 11, 2010 9:19 pm

I have so many more questions, but i think most of them will need their own thread. For now, i'm wondering if i do stop using the console system and start working with graphics, can i access the console glyphs for my sprites and maps, or will i need to convert my own graphics? i seem to remember that i would also need separate tilesets for backgrounds and sprites, is this true?

#172060 - sverx - Tue Jan 12, 2010 10:52 am

zelbo wrote:
i seem to remember that i would also need separate tilesets for backgrounds and sprites, is this true?


VRAM banks can be assigned to backgrounds OR to sprites in a given moment, so actually you need to store separate tile for BGs and sprites.

Or if I'm wrong I'd like to know :)