Hangbot - Asimov Testgame

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
User avatar
Asimov
Posts: 814
Joined: May 19th, 2012, 11:38 pm

Hangbot - Asimov Testgame

Post by Asimov » June 29th, 2013, 11:11 am

Hi all,

This is my first WIP of my game Hangbot. I have had some spectacular crashes and freezes, so be warned before you try out the code. I think I have got it stable now. I think the crashes were because I was trying to free memory already free.

Ok all this will do is show a menu. If you press key 1 a pile of letters come up. When you press a letter on your keyboard a letter should disappear from the screen.

Now this is painfully slow in debug, faster in release, but super fast if you just run the release exe after. I know my keyboard routine is not the best, and I should be using directinput or something, but I haven't got my head around the complexities yet.

Here is my current keyboard routine which returns a letter if pressed. I am using GetAsyncKeyState which can be quite slow at times. Yes I know I need to replace this with a better routine, but I am trying to learn as I go. It is hard without following a chilli tutorial LOL.

Code: Select all

#include "keyboardinput.h"

keyBoard::keyBoard()
{
	
	textreturn[0]=L"A";
	textreturn[1]=L"B";
	textreturn[2]=L"C";
	textreturn[3]=L"D";
	textreturn[4]=L"E";
	textreturn[5]=L"F";
	textreturn[6]=L"G";
	textreturn[7]=L"H";
	textreturn[8]=L"I";
	textreturn[9]=L"J";
	textreturn[10]=L"K";
	textreturn[11]=L"L";
	textreturn[12]=L"M";
	textreturn[13]=L"N";
	textreturn[14]=L"O";
	textreturn[15]=L"P";
	textreturn[16]=L"Q";
	textreturn[17]=L"R";
	textreturn[18]=L"S";
	textreturn[19]=L"T";
	textreturn[20]=L"U";
	textreturn[21]=L"V";
	textreturn[22]=L"W";
	textreturn[23]=L"X";
	textreturn[24]=L"Y";
	textreturn[25]=L"Z";
}


wstring keyBoard::key()
{
    char i;
    for(i=65;i<91;i++){
        if(GetAsyncKeyState(i)){
		   return textreturn[i-65];
        }
    }
	return L"";
}
Attachments
test.zip
(3.68 MiB) Downloaded 339 times
----> Asimov
"You know no matter how much I think I have learnt. I always end up hitting brick walls"
http://www.asimoventerprises.co.uk

User avatar
Asimov
Posts: 814
Joined: May 19th, 2012, 11:38 pm

Re: Hangbot - Asimov Testgame

Post by Asimov » June 29th, 2013, 2:44 pm

Hi all,

Well the code in my previous post is still crashing. I do notice if I disable the font routine it doesn't crash.

I don't think there is anything wrong with either routine separate, but I think they are conflicting with each other.

I suspect it has something to do with m_pd3dDevice->BeginScene(); or something. I don't know.

Everything works fine for a bit, and then my computer starts locking up, so I am doing something wrong.
----> Asimov
"You know no matter how much I think I have learnt. I always end up hitting brick walls"
http://www.asimoventerprises.co.uk

User avatar
Asimov
Posts: 814
Joined: May 19th, 2012, 11:38 pm

Re: Hangbot - Asimov Testgame

Post by Asimov » June 29th, 2013, 3:49 pm

Hi all,

Well I think I know why it is crashing, but stuck on how to put it right.

The line
D3DXCreateFont( m_pd3dDevice, 100, 0, FW_BOLD, 0, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, TEXT("Arial"), &g_Font );

is being called thousands of times and creating a font every time. so I moved it into my setfont method instead. Thinking it would run once to create the font and everything would be ok, but no it crashes straight away now.

I think it is because it is losing scope.
----> Asimov
"You know no matter how much I think I have learnt. I always end up hitting brick walls"
http://www.asimoventerprises.co.uk

User avatar
Asimov
Posts: 814
Joined: May 19th, 2012, 11:38 pm

Re: Hangbot - Asimov Testgame

Post by Asimov » June 29th, 2013, 11:28 pm

Hi all,

Well I managed to solve the font problem, but not really. To get it working I just released the font after printing to the screen. This isn't the most efficient way of doing it. Basically it is creating the font and destroying many thousands of times, instead of creating and re-using. Even though this helped the program to work I am having a similar problem with the sprite. eg look at the following code.

Code: Select all

void Sprite::Render(IDirect3DDevice9* m_pd3dDevice)
{

//D3DXCreateTextureFromFile(m_pd3dDevice, L"rustydrone3.jpg", &m_texture);


D3DXCreateTextureFromFileEx(m_pd3dDevice, L"data/rustydrone2.jpg", D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2,
	D3DX_DEFAULT, 0, D3DFMT_R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, &m_texture);
D3DXCreateSprite(m_pd3dDevice, &sprite);

Pos.x = 0.0f;
Pos.y = 0.0f;
Pos.z = 0.0f;

//sprite->SetTransform(5.0f,6,6);

// Draw The Sprite
//sprite->Begin(D3DXSPRITE_ALPHABLEND);
sprite->Begin(NULL);
sprite->Draw(m_texture, NULL, NULL, &Pos, 0xFFFFFFFF);
sprite->End();

};
The code works, and it does print to the screen, but it is recreating a sprite many thousands of times and eventually runs out of memory. So I thought I would create a new function eg

Code: Select all

void Sprite::Init(IDirect3DDevice9* m_pd3dDevice)
{
	D3DXCreateTextureFromFileEx(m_pd3dDevice, L"data/rustydrone2.jpg", D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2,
	D3DX_DEFAULT, 0, D3DFMT_R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, &m_texture);
D3DXCreateSprite(m_pd3dDevice, &sprite);
}
In theory the code in this function would only run once, and then the render code would be run continously.

Unfortunately the program now crashes. I think the render function is losing the scope of the D3DCreateSprite.

I think it is because I should set it up in the header file, but I do not know how to do this for this command.

Anybody have any ideas?
----> Asimov
"You know no matter how much I think I have learnt. I always end up hitting brick walls"
http://www.asimoventerprises.co.uk

Shaki
Posts: 104
Joined: June 13th, 2012, 12:20 am

Re: Hangbot - Asimov Testgame

Post by Shaki » June 30th, 2013, 12:51 am

Okay so I looked through and managed to fix your code, I found a very weird way that you dealt with things.

In hangbot.cpp I changed

Code: Select all

bool Hangbot::Init()
to

Code: Select all

bool Hangbot::Init()
{

gamestate = Menu;

largeFont.setFont(180);
mediumFont.setFont(30);

	if(!Game::Init())
		return false;
	else
		sprite.LoadTexture(m_pd3dDevice);
		return true;
}
I added the load sprite function to deal with your lag issues! You were loading the sprite from rom every frame and that would cause a buffer overflow, literally your game jumped to over a gig of ram when i started it previously.

The MOST important part of your program, is that you forgot to RETURN TRUE in your initialization, so the game would exit thinking that the return was false, you need to return the value.

in your winmain loop you were checking if it returned false, and because it didn't return, it assumed false, (which in binary is 0).

So the function I added for you to deal with your lag was this.

Code: Select all

void Sprite::LoadTexture(IDirect3DDevice9* m_pd3dDevice){
	HRESULT result;

	result = D3DXCreateTextureFromFileEx(m_pd3dDevice,L"rustydrone2.jpg", D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2,
	D3DX_DEFAULT, 0, D3DFMT_R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, &m_texture);

	assert(!FAILED(result));

	result = D3DXCreateSprite(m_pd3dDevice, &sprite);

	assert(!FAILED(result));

}
EDIT::

lastly, you need your render to NOT load the file every time aswell.

Code: Select all

void Sprite::Render(IDirect3DDevice9* m_pd3dDevice)
{

//D3DXCreateTextureFromFile(m_pd3dDevice, L"rustydrone3.jpg", &m_texture);

//D3DXCreateTextureFromFile(m_pd3dDevice, L"rustydrone3.jpg", &m_texture,D3DX_DEFAULT_NONPOW2);





Pos.x = 0.0f;
Pos.y = 0.0f;
Pos.z = 0.0f;

//sprite->SetTransform(5.0f,6,6);

// Draw The Sprite
//sprite->Begin(D3DXSPRITE_ALPHABLEND);
sprite->Begin(NULL);
sprite->Draw(m_texture, NULL, NULL, &Pos, 0xFFFFFFFF);
sprite->End();

};
EDIT 2:

I also think you should look in to reorganizing your files, they seem to jump all over the place :p. I know it's preference, but when I had to look through it I found myself searching. =/.

Now this I would also like to add, when you render your game, it's very important that you separate your functional code from your graphic code. This is important because if you mess up on something functional in your render function, you may not notice, and it could corrupt graphic memory.

As for your text rendering slowly, it may be caused from your

Code: Select all

D3DXCreateFont( m_pd3dDevice, size, 0, FW_BOLD, 0, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, TEXT("Arial"), &g_Font );

RECT TextRect={x,y,0,0};
in the render.

In D3D graphic processing, unless you are accessing the device using a pointer
ie:

Code: Select all

pDevice->EndScene();
It usually is only called to setup a buffer for the graphic memory. Which you can store in a d3d buffer to use later for efficiency.

Now for your font, I managed to boost your fps to literally 4,000fps by doing this with your fonts.

in your init

Code: Select all

bool Hangbot::Init()
{

gamestate = Menu;



	if(!Game::Init())
		return false;
	else
		sprite.LoadTexture(m_pd3dDevice);
		largeFont.setFont(180,m_pd3dDevice);
		mediumFont.setFont(30,m_pd3dDevice);
		letterdraw.LoadFont(m_pd3dDevice);
		return true;
}
your letterdraw.h

Code: Select all


	void LoadFont(IDirect3DDevice9* m_pd3dDevice);
letterdraw.cpp

Code: Select all

Letterdraw::Letterdraw()
	:x(25),
	y(450),
	offsetx(58),
	offsety(60)
{
	for (int i=0;i<26;i++)
	{
	testletter[i]=true;
	}
}

void Letterdraw::LoadFont(IDirect3DDevice9* m_pd3dDevice){
	letterFont.setFont(80,m_pd3dDevice);
}
font.h

Code: Select all


	void setFont(int fontsize,IDirect3DDevice9* m_pd3dDevice);
font.cpp

Code: Select all

void fontloader::setFont(int fontsize,IDirect3DDevice9* m_pd3dDevice)
{
	size=fontsize;
    D3DXCreateFont( m_pd3dDevice, size, 0, FW_BOLD, 0, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, TEXT("Arial"), &g_Font );

}
hopefully that helps lol

Shaki
Posts: 104
Joined: June 13th, 2012, 12:20 am

Re: Hangbot - Asimov Testgame

Post by Shaki » June 30th, 2013, 1:21 am

Here actually I'll re-upload the changes, I wanted to toy around with your code a bit.

You can improve a lot of things, but you were defiantly on the right track, all I did (though it may seem to be a large amount of changes) was make it so everything loaded 1 time only, and then just rendered with that loaded image, instead of re-loading everything every time.
Attachments
test.zip
(4.09 MiB) Downloaded 289 times

User avatar
Asimov
Posts: 814
Joined: May 19th, 2012, 11:38 pm

Re: Hangbot - Asimov Testgame

Post by Asimov » June 30th, 2013, 3:20 am

Hi Shaki,

Well I am not new to programming, but new to directx. Even though I followed Chillis tutorials in the past, it didn't mean I understood the engine that well. So I have been trying to start from scratch, and I wanted to use directx sprites instead of the putpixel.

I can understand why some of the code looks like frankinstein code, and that is because I didn't understand everything I was doing, and picked up bits and pieces from different sources. I would like to improve it later, when I know what I am doing.

Right I assumed bool Hangbot::Init() was only being run once and hence put my initialisation code in there heh heh. It seems it was running over and over and reloading my graphics and fonts like you said.

With the sprites and fonts, I realised that D3DXCreateTextureFromFileEx and D3DXCreateFont should only have been run once, but everytime I tried to move them into a different member function the program crashed heh heh.

The directx tutorial I followed originally had the game code in the same file as winmain. I separated this to try to simplify it, as I don't want to be messing with winmain when I am doing the game.
I don't know why the original tutorial I followed separated game.cpp and winmain.cpp, but I thought it was like chilli separating the direct3d stuff and winmain. I would change this later, but I get easily lost.

Before I downloaded your changes, I had this code I was testing

Code: Select all

#include "Sprite.h"

Sprite::Sprite()
{
}

Sprite::~Sprite()
{
	sprite->Release();
}

void Sprite::Init(IDirect3DDevice9* m_pd3dDevice)
{
	D3DXCreateTextureFromFileEx(m_pd3dDevice, L"data/rustydrone2.jpg", D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2,
	D3DX_DEFAULT, 0, D3DFMT_R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, &m_texture);
D3DXCreateSprite(m_pd3dDevice, &sprite);
}


void Sprite::Render(IDirect3DDevice9* m_pd3dDevice)
{

Pos.x = 0.0f;
Pos.y = 0.0f;
Pos.z = 0.0f;

// Draw The Sprite
//sprite->Begin(D3DXSPRITE_ALPHABLEND);
sprite->Begin(NULL);
sprite->Draw(m_texture, NULL, NULL, &Pos, 0xFFFFFFFF);
sprite->End();

};
To me it looked fine, but every time I run it, it crashed.
When I look at what you added to the sprite routine. I can see the asserts, but I can't see much difference, except that yours works, and mine doesn't LOL,

Code: Select all

void Sprite::LoadTexture(IDirect3DDevice9* m_pd3dDevice){
	HRESULT result;

	result = D3DXCreateTextureFromFileEx(m_pd3dDevice,L"rustydrone2.jpg", D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2,
	D3DX_DEFAULT, 0, D3DFMT_R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, &m_texture);

	assert(!FAILED(result));

	result = D3DXCreateSprite(m_pd3dDevice, &sprite);

	assert(!FAILED(result));

}
Thankyou for all the help. I haven't had time to dig through the code properly yet. I am sure there are bits you have added I haven't totally worked out yet.

I can see what you added is quite logical though, and it is what I wanted to do, but I am a little out of my depth.

I have lots of directx books, but I find them very difficult to learn from, and so I ploughed straight in.

BTW I have added a lot more to the code since my last post, but it is simple to implement it, now you have sorted out my problems.

Thanks a lot.

PS. You said that the code was jumping about all over the place, I think that is what you said. Do you mean the directx and winmain stuff, and what are your suggestions for improving it?

I try to keep things modular, but now I am thinking maybe keyboardinput, and letterDraw may have been ok in the same class, because they share similar functions.

PPS. I can understand why you moved

Code: Select all

void Letterdraw::LoadFont(IDirect3DDevice9* m_pd3dDevice){
	letterFont.setFont(80,m_pd3dDevice);
}
To a different function so it only runs once, but I originally put it in the constructor, because I am sure a constructor is only run once, when the class is first initialised. Am I wrong?
----> Asimov
"You know no matter how much I think I have learnt. I always end up hitting brick walls"
http://www.asimoventerprises.co.uk

Shaki
Posts: 104
Joined: June 13th, 2012, 12:20 am

Re: Hangbot - Asimov Testgame

Post by Shaki » June 30th, 2013, 7:37 am

You're right about the constructor, but you initialize your fonts before your initialize directX, so they need to be setup after directX is setup.

Seperating your classes is probably one of the best things you can do! Like you said, chili did it in his tutorials and they were very neatly made. However, you have your winmain pointing to your game, (which is a good thing to do), then your game pointing to your Hangbot. Then the Hangbot.cpp is full of code. Usually when you get everything modular, you separate them based on their function. Like, video, audio, game, that kind of thing. You do this so that your most top-level class, can run the function without if statements. You aren't wrong about anything, it's just you're mixing things in where they shouldn't be. Your rendering should be separated base on what is being rendered, ie:

Code: Select all

BeginFrame();
SpriteRender();
TextRender();
EndFrame();
~ As all different functions.

Essentially, an ideal Game::Run() would look like this

Code: Select all

GetInput();
ExecuteFunctions();
RunAudio();
RunGraphics();
Or, well something along those lines :3.

The asserts I added mainly because of a golden rule with graphic processing, check for failures! You always want to assert or check for errors on a directX device call, this helps with error checking and debugging. :p.

~And I understand what you mean by DirectX being hard as hell, the documentation SUCKS and there is very little data on it. I started making an engine for DirectX 4 days ago, and you can only find up to texturing for in-depth tutorials. I literally spent 2 days trying to figure out why my textures wouldn't render to my 3d objects, only to find out that I forgot to swap my FVF on render.

What I've been reading are the following websites for D3D:

http://www.chadvernon.com/blog/resources/directx9/
http://www.drunkenhyena.com/cgi-bin/dx9.pl
http://zophusx.byethost11.com/main.php
http://directxtutorial.com/LessonList.aspx?listid=9

For directinput I've been using:

http://msdn.microsoft.com/en-us/library ... s.85).aspx

I also found that if you go to

Code: Select all

C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Documentation\DirectX9\
you can find a small doccumetation on directX which isn't half bad.

User avatar
Asimov
Posts: 814
Joined: May 19th, 2012, 11:38 pm

Re: Hangbot - Asimov Testgame

Post by Asimov » June 30th, 2013, 9:48 am

Hi Shaki,

The more I get into this the more it gets complicated LOL. Part of the framework I have I don't understand because I got to it via a tutorial, and I am trying to get my head around how it works.

Obviously winmain is the main entry point. Then it makes an instance of Hangbot which is tGame, and then it returns, but I don't understand how any of the Game.cpp code is run. I am not totally understanding how Hangbot.cpp is run in the first place.

I assumed Hangbot::Init() would be where I put all my initialisation code. BTW it wasn't called Hangbot in the orginal tutorial heh heh. Then I thought of Hangbot::Render() as my main game loop.
Which is why I put all my code there which runs the game.

I thought I had it modular as I split up the sprite routine, the font routine.

I think I understand what you are saying. I know I have a sprite class which draws a single sprite, but I should have a class which draws all sprites which utilises the original sprite class.

I mean at the moment if I want to draw 5 sprites, I could set that up, but in Hangbot::Render I would probably have

sprite1.Render(m_pd3dDevice);
sprite2.Render(m_pd3dDevice);
sprite3.Render(m_pd3dDevice);

And I think what you are saying instead of having that. I should run a class which is called something like Drawallsprites(); and it should render all 3 sprites in one call from Hangbot::Render.

Or should I be calling it from outside Hangbot::Render.

See what a pickle I am in. At first I thought I understood what I was doing, and now I am going all blurry LOL.

Don't worry if you are too busy to answer. So far your help has been invaluable, and thanks.
You should see my code for "Missile Command" heh heh, now that would give you a headache, but at least I understood that better heh heh.

God knows what I am going to be like when I get to 3D matrixs and trying to load in x files, oh and particle effects. I think I am digging my own grave heh heh.

EDIT::
PS
I thought this line was a good idea to store picked letters LOL
if(keyinput.key().compare(L"A")==0){testletter[0]=false;pickedLetter[countit]='A';countit++;}
then I forgot that the key could repeat a miilion times a second and my array got filled up in one key press heh heh. I have to rethink that one.

Think instead of having 26 lines checking I might make an struct array, that holds the letter and the keycode, and then I can just loop through all the letters.
----> Asimov
"You know no matter how much I think I have learnt. I always end up hitting brick walls"
http://www.asimoventerprises.co.uk

User avatar
Asimov
Posts: 814
Joined: May 19th, 2012, 11:38 pm

Re: Hangbot - Asimov Testgame

Post by Asimov » June 30th, 2013, 12:34 pm

Hi Shaki,

Now I am pleased with myself.
I had this huge block of unweldy code below

Code: Select all

if(keyinput.key().compare(L"A")==0){storeletter[0]=false;pickedLetter[countit]='A';}
	if(keyinput.key().compare(L"B")==0){storeletter[1]=false;pickedLetter[countit]='B';}
	if(keyinput.key().compare(L"C")==0){storeletter[2]=false;pickedLetter[countit]='C';}
	if(keyinput.key().compare(L"D")==0){storeletter[3]=false;pickedLetter[countit]='D';}
	if(keyinput.key().compare(L"E")==0){storeletter[4]=false;pickedLetter[countit]='E';}
	if(keyinput.key().compare(L"F")==0){storeletter[5]=false;pickedLetter[countit]='F';}
	if(keyinput.key().compare(L"G")==0){storeletter[6]=false;pickedLetter[countit]='G';}
	if(keyinput.key().compare(L"H")==0){storeletter[7]=false;pickedLetter[countit]='H';}
	if(keyinput.key().compare(L"I")==0){storeletter[8]=false;pickedLetter[countit]='I';}
	if(keyinput.key().compare(L"J")==0){storeletter[9]=false;pickedLetter[countit]='J';}
	if(keyinput.key().compare(L"K")==0){storeletter[10]=false;pickedLetter[countit]='K';}
	if(keyinput.key().compare(L"L")==0){storeletter[11]=false;pickedLetter[countit]='L';}
	if(keyinput.key().compare(L"M")==0){storeletter[12]=false;pickedLetter[countit]='M';}
	if(keyinput.key().compare(L"N")==0){storeletter[13]=false;pickedLetter[countit]='N';}
	if(keyinput.key().compare(L"O")==0){storeletter[14]=false;pickedLetter[countit]='O';}
	if(keyinput.key().compare(L"P")==0){storeletter[15]=false;pickedLetter[countit]='P';}
	if(keyinput.key().compare(L"Q")==0){storeletter[16]=false;pickedLetter[countit]='Q';}
	if(keyinput.key().compare(L"R")==0){storeletter[17]=false;pickedLetter[countit]='R';}
	if(keyinput.key().compare(L"S")==0){storeletter[18]=false;pickedLetter[countit]='S';}
	if(keyinput.key().compare(L"T")==0){storeletter[19]=false;pickedLetter[countit]='T';}
	if(keyinput.key().compare(L"U")==0){storeletter[20]=false;pickedLetter[countit]='U';}
	if(keyinput.key().compare(L"V")==0){storeletter[21]=false;pickedLetter[countit]='V';}
	if(keyinput.key().compare(L"W")==0){storeletter[22]=false;pickedLetter[countit]='W';}
	if(keyinput.key().compare(L"X")==0){storeletter[23]=false;pickedLetter[countit]='X';}
	if(keyinput.key().compare(L"Y")==0){storeletter[24]=false;pickedLetter[countit]='Y';}
	if(keyinput.key().compare(L"Z")==0){storeletter[25]=false;pickedLetter[countit]='Z';}
which I have now replaced with this

Code: Select all

for (int i=0;i<26;i++)
	{
		if(keyinput.key().compare(storeletter[i].letter) ==0)
		{
			storeletter[i].testLetterBool=false;
		}
	}
Of course I had to make a struct like this

Code: Select all

struct storeLetter
{
	wstring letter;
	bool testLetterBool;
};
which is in my header file, and in my constructor I loaded it with values like

Code: Select all

int ascii=65;
	for (int i=0;i<26;i++)
	{
		storeletter[i].testLetterBool=true;
		storeletter[i].letter=(char)ascii;
		ascii++;
	}
I didn't do this just because I wanted it neater. I want to be able to store the keys pressed, and that long chunk of code was too much, when I could use a loop instead. I always intended to do that, but sometimes just to test a theory I think it is ok to do it the long way first.
----> Asimov
"You know no matter how much I think I have learnt. I always end up hitting brick walls"
http://www.asimoventerprises.co.uk

Post Reply