Arrays and vectors
Okay first things first. I could have used an array of CSeatSections but then you would be limited to a definite size. And I'm supposing you know it would look like this in the class:
Code:
CSeatSection *SeatSections;
And to create the section you would do this:
Code:
void CSeatSection::Create(void)
{
SeatSections=new CSeatSection[NumSeatSections];
...
}
If you don't know what new is, it's this:
CSeatSection = malloc(NumSeatSections*sizeof(CSeatSection));
But C++ has the keyword new that automatically does this for us so we don't have to use malloc anymore, but the underlying code is using malloc for it's memory allocation function.
So an integer array looks like this:
Code:
int *MyArray;
MyArray=new int[1000];
But you cannot specify a variable size array inside of a class. You must specify a size or you must create the array storage later - in other words you must use a pointer in the class and then validate the pointer later. Have you ever tried to resize an array? It's a pain in the arse. Watch.
Let's say we have an integer array called Bubba with 20 elements with each element having a value between 0 and 100.
Code:
int *Bubba;
Bubba=new int[20];
for(int i=0;i<20;i++)
{
Bubba[i]=rand() % 100;
}
Ok, but we just found out we need Bubba to have way more elements. How do we resize Bubba and yet maintain the data that was in it already? We must first create a new array with the new size, copy the contents of Bubba over to our new array, de-allocate the storage for the previous array, and then point Bubba at the new array all the while avoiding a NULL pointer situation.
And what if we want to remove an item from Bubba? Same process. What if we want to sort Bubba, or add values to Bubba, insert values in Bubba, etc., etc? It all quickly becomes a big pain in the arse.
Along comes the awesome standard template library and a class called vector. A vector is a dynamic array that supports dynamic resizing, sorting, inserting, deleting, etc., etc. And you get to access it almost exactly like an array, excluding certain cases. So here is a vector. The reason for the jiggy notation is due to the fact that it is a template class. I won't attempt to explain template classes here because there are many books out there that do a much better job at it than I would. But here is how you declare a vector.
Vectors lie in the std namespace. If you don't specify you are using the std namespace by doing:
Code:
using namespace std;
Then you must declare the vector this way using the scope resolution operator. Note that the scope resolution operator is the same on you use to declare and define functions for classes. You are stating that the function belongs to a certain namespace or object in the case of classes.
Code:
std::vector<data_type[*]> MyVector;
I put the pointer in brackets because it is not always required. There are classes and objects that allow immediate creation by just calling the constructor. All of my objects are this way. They all have a default constructor that takes 0 parameters. However some classes are not like this - for instance, DirectX COM objects cannot be instantiated because they are pointers to interfaces, therefore the vector must be a pointer to the interfaces, not the actual interface itself.
To add an item to the vector we do:
Code:
MyVector.push_back(object_of_type_data_type);
Consult a C++ book or your IDE's help file for all of the functions related to vectors. You'll find they are quite handy. Given the fact that most programming requirements often use lists, arrays, maps, etc,. the standard template library is simply a god send. You should never have to write code to do a linked list again ever in your life. The STL can take care of it. Plus the linked list you come up with will not be nearly as fast, efficient, or well-tested as the STL version. Don't reinvent the wheel - use the tools provided to you. They save you time and a lot of headaches.
Data types
Now for DWORD. It's an assembly language data type.
The following are ALWAYS true no matter what the default integer for the CPU is.
BYTE = 8 bits
WORD = 16 bits
DWORD = 32 bits
QWORD = 64 bits (I think)
Now the reason I use these. Simplicity. Back in the old DOS days and when we were all confined to 16-bit programming (the CPU ran in real mode) this was a 16-bit integer.
But since the defintion of a computer's bit depth (16, 32, 64, etc) is based on the default size of an integer all this has changed now with 32-bit CPU's and now 64-bit CPU's as well.
On a 16-bit computer, an integer is 16 bits. On a 32-bit system, 32-bits for an integer, and on a 64-bit, 64-bits for an integer.
Here is where I think some languages really screwed up. They have changed the size of integer several times in order to match the bit depth of the computer. So while unsigned int used to be 16 bits in DOS real mode, it is now 32-bits. So every time you see UINT or unsigned int, it's 32-bits. On a 64-bit OS, an integer will default to 64-bits. So you see the problem? The size of an integer is relative to the system it's running on and if the OS is in real mode or protected mode, etc. That is a huge mess.
So I opt to use the assembly language data types because they NEVER change. A BYTE is always 8 bits and so forth. This allows me as an avid assembly programmer to look at my code and IF I want to put the function into assembly or add an assembly function, I know exactly what I'm dealing with.
Now it's not all that hard anymore because everyone running Windows 98+ is using 32-bit integers. In 16-bit mode, an unsigned short was still 16-bits just like an unsigned int and their signed counterparts. Now what has happened in 32-bit mode is an unsigned short is 16-bits, and unsigned int is 32-bits, and an unsigned long is 32-bits.
I'll break down the common C data types and their assembly counterpart
16-bit system
- unsigned/signed char - BYTE - 8 bits
- unsigned/signed short - WORD - 16 bits
- unsigned/signed int - WORD - 16 bits
- unsigned/signed long - DWORD - 32 bits
- float - QWORD - 32 bits (in assembly you must use a QWORD for a float or double)
- double - QWORD - 64 bits
- long double - QWORD - 80 bits
32-bit system
- unsigned/signed char - BYTE - 8 bits
- unsigned/signed short - WORD - 16 bits
- unsigned/signed int - DWORD - 32 bits
- unsigned/signed long - DWORD - 32 bits
- float - QWORD - 32 bits (in assembly you must use a QWORD for a float or double)
- double - QWORD - 64 bits
- long double - QWORD - 80 bits
So if you see this:
Code:
DWORD x=some_value;
On a 32-bit system it's this:
Code:
unsigned long x=some_value;
Note that even though a value might be negative or positive does not change the bit depth of the data type. It just means that 1 bit is being used as the sign bit, but it still uses all the bits - however you lose quite a bit of the value range that can be represented because one of the bits represents the sign. In assembly there is no such thing as signed or unsigned - they are just BYTE, WORD, DWORD, QWORD, etc. or byte, word, doubleword, quadword.
Function return values in the code
The functions in the code I provided return false or zero IF they SUCCEED.
The functions in the code I provided return true or one IF they FAIL.
Code:
bool GetSeatInfo(DWORD ID, int Row, int Col, Seat *outSeat)
{
if(ID<SeatSections.size())
{
bool failed=SeatSections[ID]->GetSeatInfo(Row,Col,outSeat);
if(failed)
{
outSeat=NULL;
return true;
}else return false;
}
outSeat=NULL;
return true;
}
My comments
Code:
bool GetSeatInfo(DWORD ID, int Row, int Col, Seat *outSeat)
{
//Check to see if the seat section ID is valid
//Basically since the ID is the index of the CSeatSection in the
//vector, if the ID is out of range - the ID is invalid and
//the seat section does not exist
if(ID<SeatSections.size())
{
//Call the CSeatSection member GetSeatInfo
//Use CSeatSection[ID]'s GetSeatInfo to get the correct seat
//failed will be true if the seat is out of range or does not exist
//and outSeat will ALSO be NULL indicating failure
//failed will be false if the seat exists, and outSeat will be the
//requested seat info
bool failed=SeatSections[ID]->GetSeatInfo(Row,Col,outSeat);
if(failed)
{
outSeat=NULL; //Failed - something went wrong
return true; //return failure code to caller
} else return false; //Everthing went ok
}
outSeat=NULL; //if we get here, then ID was out of range
return true; //return failure to caller
}
Okay. The reason there are 2 checks here is this. There are 2 possible errors that can be created here.
1. An out of range ID could be passed to the function.
2. An out of range row or column or both could be passed to the function.
Both of these would cause the function to do some strange stuff and cause Windows to pop up some nice dialog boxes stating your code got hosed if we didn't do some error checking. Accessing an object in a vector that does not exist will cause major problems - it's akin to an array-bounds overrun or underrun. Now inside of CSeatSection the seats ARE in an array. If either row or column is out of range it will cause the offset to lie outside of the array. This will be an array bounds overrun and will crash the code.
Row*Width+Column
This is a simple formula to access row-ordered arrays. This is also useful for accessing video displays, surfaces, etc. In memory all arrays are just huge blocks of memory or small blocks of memory. They are not 2D, 3D, 4D, etc. They are 1D - like a bunch of mailboxes lined up along a highway or a huge line of numbers.
So let's do a tic tac toe board. We know tic tac toe board have 3 across and 3 down. So this will do in C:
Code:
int TicTacToeBoard[3][3];
int value=0;
for (int i=0;i<3;i++)
{
for (int j=0;j<3;j++)
{
TicTacToeBoard[i][j]=value;
value++;
}
}
Now in memory it's this:
0 1 2 3 4 5 6 7 8
Even though we specified it like this:
0 1 2
3 4 5
6 7 8
It's still linear in memory. And we already discussed the issue of forcing the compiler to do a multiply for 2D arrays. So make your arrays linear. The tic tac toe board has 9 locations. So this is the same thing:
Code:
int TicTacToeBoard[9];
int value=0;
for (int i=0;i<9;i++)
{
TicTacToeBoard[i]=value;
value++;
}
I know that doesn't require braces but I always put loops in braces for code readability. You can tell at a glance that TicTacToeBoard is being initialized inside of the loop. I like that.
Okay we still have this in memory:
0 1 2 3 4 5 6 7 8
Same thing as before. But let's say we want to find the value at TicTacToeBoard[1][1]. With a 2D array we just read the value.
Code:
int result=TicTacToeBoard[1][1];
But how do you do it in a 1D array? Simple. We know each row has 3 positions. So we take the row and multiply by the width. That gives us the right row. Now for the column, we simply add it.
So TicTacToeBoard[1][1] is really:
TicTacToeBoard[(1*3)+1) or index 4. Let's see if that works.
..R.........Column
..o....................
..w...... 0 1 2
.......................
..0.........0 1 2
..1........ 3 4 5
..2.... ....6 7 8
Yep index 4 equates to 1,1 in the grid.
So our equation to access a linear array using 2 dimensions is:
row*width+column
This only applies to row-ordered arrays. Some are column ordered in which case column*width+row is used.
GetSeatInfo is in CSeatSection and in CAuditorium
This is a design flaw, but it's needed I feel in this case. I'm trying to create a centralized interface for accessing seats. Ideally every seat request should go through CAuditorium and then the request will be passed on to CSeatSection. All of the members of CSeatSection should be private including the constructor. Then make CAuditorium a friend of CSeatSection so that it can instantiate the object. After that, no one is allowed access to CSeatSection except for CAuditorium. This guarantees that all data requests, changes, etc, will have to go through CAuditorium. This is a nice design because you the programmer can prevent you, or other programmers, from incorrectly using your classes. So CAuditorium becomes the central interface for all seat sections. Just like CSeatSection is the central interface for all seats in it's section.
I'm hiding the actual implementation of retrieving seat information and changing it. Everything that can be done must be done through CAuditorium. This is a design philosophy that I've picked up along the way and so far it has done me very well. If you notice CAuditorium is really a stupid class. All it really does is manage a vector of CSeatSections and acts as a wrapper class to a vector. That's exactly what I want.
Notice that when CAuditorium goes out of scope or ceases to exist, the compiler will create code to call the destructor. The destructor calls vector::empty() which will then call the destructor for every object in the vector. Now in the destructor for CSeatSection, notice it then de-allocates it's memory that was used for the array of seats. A nice clean shutdown and a nice centralized shut down process. This allows me to directly control how my object shuts down and when. Using this method it is easier to debug memory leaks when and if they happen. You can trace the entire shutdown process rather quickly and find the problem. This class would be even safer using the <shared_ptr> from www.boost.org. This is a 'smart' pointer that really helps you the programmer stop leaking memory. I'd look into it.
Memory leaks are those nasty critters you call tech support about when it just hosed your 6 hours of gameplay in your favorite RPG or racing game because it finally crashed the game. Memory leaks are your number one enemy and they are not as easy to avoid as you might think. Hopefully future generations of C++ will implement a 'smart' pointer that will greatly assist the programmer with these types of issues.
Translating mouse to array offset
Ok I've just given you enough information to be able to click on your grid with the mouse and calculate which seat and which section the user is clicking on.
Let's say you display one seat section on screen at a time. So 1 grid represent one array of seats. You already know row*width+column=offset_into_array right? So all you need to figure this out is where in the grid did the user click? Pretty simply really. You will know the x and y location of the mouse and all you need to do is transform that into row and column. How? Well if you know the width and height of each grid cell then this will give you the row and column of the mouse cursor, provided your grid starts at (0,0) on the screen or in the upper left corner. You can compute it for other starting points with just a bit more math, but I'll leave that to you.
Code:
int Row=MouseCursorY/HeightOfOneGridCell;
int Column=MouseCursorX/WidthOfOneGridCell;
Ok now you know the size of your grid so the following can be checked:
Code:
if (Row>HeightOfGrid || Row<0 || Column<0 || Column>WidthOfGrid)
{
//User did not click on our grid at all - clicked outside of it
}
else
{
int Offset=Row*WidthOfGrid+Column;
Seat *temp;
//Since we checked row and column and we know
//CurrentSeatSectionID is valid because we have already drawn
// the thing, the following function CANNOT fail
AuditoriumObject->GetSeatInfo(CurrentSeatSectionID,Row,Column);
//Output text relating to seat that was clicked on
//
//I'd put this output stuff in another function and call it from here
//
DrawText(x,y,temp->DayCustomer);
if (DayTime)
{
DrawText(x,y,temp->DayCustomer);
} else DrawText(x,y,temp->NightCustomer);
DrawText(x,y,temp->Price);
//...etc, etc., etc.
}
Creating 392 seats
Ok if you want 392 seats you have several choices. You can either create one seat section of 392 or you can divide this up into several sections. The choice is up to you.
Here is how you would do it:
To get 392 seats you need to decide on a width and a height. I did not provide a function to just create 392 seats since I never figured they would all be in one row. That's a helluva long row of seats. Most seats like in baseball parks, etc, all have a section, row, and column number - or at least a section and seat number.
For your purposes...identifying each seat by row and column is up to you. The code doesn't change really. Just eliminate the row and column and stick a number there. Then create a vector of that size. Then in GetSeatInfo and SetSeatInfo change the code to just use the seat number passed instead of computing the offset from the row and column of the desired seat.
To create an Auditorium with 1 section of 400 seats.
Code:
CAuditorium *TheAuditorium=new CAuditorium;
DWORD SeatSectionID=TheAuditorium->Create(20,20);
Now all future access to the seat section would be through SeatSectionID.
To get information about seat at 10,10 or seat #100.
Code:
temp Seat;
TheAuditorium->GetSeatInfo(SeatSectionID,10,10,&Seat);
Hope this helps.
I figured a lot of this grid stuff out by years of coding and years of practice. Plus I'm working on a 2D tile engine in D3D and a tile editor in MFC. Here is an example. I'm doing basically the same exact thing I just explained to you.