/*----------------------------------------------*/
// VR Pong  version 1.0	  Par Grégory Smialek	// 
// (2001)	(www.texel.fr.fm)	        //
// DirectX:"Introduction à Direct Draw" 	//
/*----------------------------------------------*/

//------ Inclusion ------//
#define INITGUID
#include <ddraw.h>
#include"ddutil.h"

//--- Informations pour la class Window----//
static char szClass[] = "VRPongClass";
static char szCaption[] = "VRPong";

//------ Objet DirectDraw ------//
LPDIRECTDRAW7 lpDD=NULL;

//------ Surfaces DirectDraw  ------//
LPDIRECTDRAWSURFACE7 background_surf=NULL;	// pour l'image de font
LPDIRECTDRAWSURFACE7 objet_surf=NULL;		// pour la ball et le stick
LPDIRECTDRAWSURFACE7 lpDDSPrimary=NULL;		// surface primaire	
LPDIRECTDRAWSURFACE7 lpDDSBack=NULL;		// BackBuffer

//------  Variables ------//
DWORD KeyColor;		// clé de couleur	

typedef struct{		// contient des infos sur la balle
	int x,y;		// position x et y
	int vx,vy;		// valeurs des vecteurs pour le déplacement
}POSITION;
POSITION ball;

int xpong=320;		// position du stick en x
int ypong=400;		// position du stick en y

int speed=1;		// vitesse de la ball
double ballstep;	// distance à déplacé entre chaque image de la ball
int stickspeed=10;	// vitesse du stick
int stickstep;		// distance à déplacé entre chaque image du stick


//------ chaîne contenant les messages d'erreur ------//
const char *ErrStr=NULL;	

//------ Messages d'erreur  ------//
const char Err_Reg_Class[]			= "Error Registering Window Class";
const char Err_Create_Win[]			= "Error Creating Window";
const char Err_DirectDrawCreate[]	= "DirectDrawCreate FAILED";
const char Err_Query[]				= "QueryInterface FAILED";
const char Err_Coop[]				= "SetCooperativeLevel FAILED";
const char Err_CreateSurf[]			= "CreateSurface FAILED";
const char Err_LoadBMP[]			= "Error Loading Image";
const char Err_DispMode[]			= "Error Setting Display Mode";
const char Err_LoadImage[]			= "Error Loading Image";


void Cleanup();
void collision();
void avance();
void key_test();
LRESULT CALLBACK WindowProc(HWND hWnd, unsigned uMsg, WPARAM wParam, LPARAM lParam);
BOOL load_images();
static BOOL Initialisation(HINSTANCE hInstance, int nCmdShow);
void render_frame();

/********************************************************/
//----------------------- main -------------------------//
/********************************************************/
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
	MSG msg;     // message de la queue

	// déclaration et initialisation des constante pour le compteur
	DWORD temps_maintenant;     // temps courant
	DWORD  temps_fps=14;		// ms par image
	DWORD temps_suivant=0;		// temps pour rendre la prochaîne image
	DWORD temps_precedent=0;	// temps de l'image précédente image
	double temps_ecoule;		// temps écoulé depuis le dernier rendu
	double temps_echelle=0.05;	// échelle pour pouvoir utiliser une valeur entière pour speed

	// initialisation de la position de la balle
	ball.x=20;		
	ball.y=20;
	ball.vx=5;
	ball.vy=5;

	// initialisation de l'application
    if (!Initialisation(hInstance, nCmdShow)) {
		Cleanup();return FALSE;
	}

	// on sauve l'heure actuelle dans suivant pour que temps_maintenant soit < que temps_suivant
	temps_suivant=GetTickCount();

	// sauvegarde du temps de la dernière frame
	temps_precedent=temps_suivant;

	while (1) { 
		temps_maintenant=GetTickCount();		// quelle heure est-il ?
		// message? 
		if (PeekMessage( &msg, NULL, 0, 0, PM_REMOVE)) { 
			if (msg.message==WM_QUIT)	// échap? 
				break; 
			// dispatch le message 
			TranslateMessage(&msg);
			DispatchMessage(&msg); 
		} else {
			// Si il est temps d'afficher la nouvelle frame 
			if (temps_maintenant>temps_suivant) { 
				temps_ecoule=(temps_maintenant-temps_precedent)*temps_echelle;
				temps_precedent=temps_maintenant;	// pour le prochain passage dans la boucle

				ballstep=speed*temps_ecoule;	    // pas de déplacement mis à jours /temps
				stickstep=(int) (stickspeed*temps_ecoule);	// idem pour le stick
				key_test();			// déplace le stick si flèche pressée
			//	render_frame();		// remet à jours l'image à l'écran
				collision();		// décecte les collisions
				avance();			// déplace la balle

				temps_suivant = temps_maintenant + temps_fps; // pour la prochaine boucle
			}
				render_frame();		// remet à jours l'image à l'écran
		}
	}
	// message final 
    return (msg.wParam);
}

/*********************************************************/
//------ Initialize de l'application et de DirectX ------//
/*********************************************************/
static BOOL Initialisation(HINSTANCE hInstance, int nCmdShow)
{
    WNDCLASS                    wc;
    HRESULT                     ddrval;
 
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC) WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(DWORD);
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szClass;
    if (!RegisterClass(&wc)) {
		ErrStr=Err_Reg_Class;
        return FALSE;
	}

    // creation et affichage de la fenêtre
    HWND hWnd = CreateWindow(szClass,szCaption,WS_VISIBLE|WS_POPUP,0,0,									// haut
						GetSystemMetrics(SM_CXSCREEN),	// obtiet la résolution du bureau de 
						GetSystemMetrics(SM_CYSCREEN),	// Windows (plus esthétique )
                        NULL,NULL,hInstance,NULL);		
    if (!hWnd) 
		{ErrStr=Err_Create_Win;return FALSE;}
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);


    // Creation de l'objet DirectDraw
	if(DirectDrawCreateEx(NULL,(LPVOID*)&lpDD,IID_IDirectDraw7, NULL)!=DD_OK)
		{ErrStr=Err_DirectDrawCreate;return FALSE;}

    //  Niveau de coopération
    if (lpDD->SetCooperativeLevel(hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN )!= DD_OK)
		{ErrStr=Err_Coop;return FALSE;}

	// Mode d'affichage
	if (lpDD->SetDisplayMode( 640, 480, 16, 0, 0)!=DD_OK)
		{ErrStr=Err_DispMode;return FALSE;}

    // Creation de la surface primaire et d'un back buffer
    DDSURFACEDESC2 ddsd;
	DDSCAPS2 ddscaps;
	ZeroMemory(&ddsd,sizeof(ddsd));
	ZeroMemory(&ddscaps,sizeof(ddscaps));
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                          DDSCAPS_FLIP | 
                          DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
	if (ddrval!=DD_OK) {
		ErrStr=Err_CreateSurf;
		return FALSE;
	}

	// Mise en place du back buffer
	ddscaps.dwCaps=DDSCAPS_BACKBUFFER ;
	ddrval=lpDDSPrimary->GetAttachedSurface(&ddscaps,&lpDDSBack);
	if (ddrval!=DD_OK) {
		ErrStr=Err_CreateSurf;
		return FALSE;
	}

	// Chargement des images
	if (!load_images()) 
		return FALSE;


	return TRUE;
}

/*************************************************************************/
//------ Chargement des images et définition de la clef de couleur ------//
/*************************************************************************/
BOOL load_images(){
	// fond d'écran puis stick et ball
	if ( ( background_surf = DDLoadBitmap( lpDD, "land.bmp", 0, 0) ) == NULL )
		{ErrStr=Err_LoadImage;return FALSE;}
	if ( ( objet_surf = DDLoadBitmap( lpDD, "pong.bmp", 0, 0) ) == NULL )
		{ErrStr=Err_LoadImage;return FALSE;}

	DDPIXELFORMAT ddpf;
	ddpf.dwSize=sizeof(ddpf);
	lpDDSPrimary->GetPixelFormat(&ddpf);

	KeyColor=ddpf.dwGBitMask;
	DDCOLORKEY key;
	key.dwColorSpaceLowValue= KeyColor;
	key.dwColorSpaceHighValue=KeyColor;
	objet_surf->SetColorKey(DDCKEY_SRCBLT,&key);
	return TRUE;
}


/**********************************************************/
//---------- Fonction d'éffacement des objets ------------//
/**********************************************************/
void Cleanup()
{
	// surfaces
	background_surf->Release();
	objet_surf->Release();
	lpDDSPrimary->Release();

	// objet DirectDraw 
	lpDD->Release();

	// affichage des erreurs éventuelles
	if (ErrStr) {
		MessageBox(NULL, ErrStr, szCaption, MB_OK|MB_ICONWARNING);
		ErrStr=NULL;
	}
}

/************************************************************/
//------------------- Affichage d'une Frame ----------------//
/************************************************************/
void render_frame()
{
	RECT rct;	// rectangle pour la balle et le stick
	// transfer des surfaces plan par plan
	lpDDSBack->Blt(NULL,background_surf,NULL,DDBLT_WAIT,NULL);

	HDC hdc;
	if (DD_OK==lpDDSBack->GetDC(&hdc)) {
		SetTextColor(hdc,RGB(255,0,0));
		SetBkMode(hdc,TRANSPARENT); 
		TextOut(hdc,290,25,"VRpong",6);

	
		SetTextColor(hdc,RGB(0,255,0));
		SetBkMode(hdc,OPAQUE); 
		SetBkColor(hdc,RGB(0,0,0)); 
		TextOut(hdc,260,440,"Echap pour quitter!",19);
		lpDDSBack->ReleaseDC(hdc);
	}


	rct.left=0;
	rct.top=0;
	rct.right=40;
	rct.bottom=8;
	lpDDSBack->BltFast(xpong,ypong,objet_surf,&rct,DDBLTFAST_WAIT|DDBLTFAST_SRCCOLORKEY);
	rct.left=0;
	rct.top=9;
	rct.right=15;
	rct.bottom=23;
	lpDDSBack->BltFast(ball.x,ball.y,objet_surf,&rct,DDBLTFAST_WAIT|DDBLTFAST_SRCCOLORKEY);

	

	// on invers la surface primaire avec le BackBuffer
	lpDDSPrimary->Flip(0,DDFLIP_WAIT); 
}

/****************************************************************/
//-------------------- Boucle de  Messages Windows -------------//
/****************************************************************/
LRESULT CALLBACK WindowProc(HWND hWnd, unsigned uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_DESTROY:
			Cleanup();
            PostQuitMessage(0);
            break;
        case WM_KEYDOWN: 
            switch (wParam) 
            { 
				case VK_ESCAPE:
					DestroyWindow(hWnd);
					break;
                default: 
                    break; 
            }
        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
	return 0L;
}

/************************************************************/
//---------------collision de la ball-----------------------//
/************************************************************/
void collision(){
	if(ball.x>=625 && (ball.vx>=0))			// la balle dépasse l'écran à doite
		ball.vx=-ball.vx;			// on invers la composante x du vecteur déplacement
	else if(ball.y<=0 && (ball.vy<=0))		// la balle dépasse l'écran en haut
		ball.vy=-ball.vy;			// on invers la composante y du vecteur déplacement
	else if(ball.x<=0 && (ball.vx<=0))		// la balle dépasse l'écran à gauche
		ball.vx=-ball.vx;			// on invers la composante x du vecteur déplacement
	else if(ball.y>=465 && (ball.vy>=0))	// la balle dépasse l'écran en bas
		ball.vy=-ball.vy;			// on invers la composante y du vecteur déplacement
	else if( ball.x+7<=(xpong+40) && ball.x+7>=xpong && ball.y+7>=ypong && ball.y+7<=(ypong+8) ){
		ball.vy=-ball.vy;	// la balle à touchée le stick donc...
	}
}

/************************************************************/
//-------avance: déplace la balle d'un pas -----------------//
/************************************************************/
void avance(){
	ball.x = (int)(ball.x + (ballstep*ball.vx));	// on ajoute le vecteur déplacement 		
	ball.y = (int)(ball.y + (ballstep*ball.vy));	// proportionnellement au pas (donc le tps)
}

/******************************************************/
//--------------teste le clavier----------------------//
/******************************************************/
void key_test(){
		if(GetAsyncKeyState(VK_LEFT)){			// flèche gauche est pressé
			if (xpong>0)	xpong-=stickstep;	// on veille à ne pas dépasser l'écran
			if (xpong<0)	xpong=0;
		}
		if(GetAsyncKeyState(VK_RIGHT)){			//flèche droite est pressé
			if (xpong<600)	xpong+=stickstep;	// on veille à ne pas dépasser l'écran
			if (xpong>600)	xpong=600;
		}
};