Add score fonts / start AI paddle
[pong.git] / game.cpp
1         /*This source code copyrighted by Lazy Foo' Productions (2004-2020)
2
3 and may not be redistributed without written permission.*/
4
5 //Using SDL, SDL_image, standard IO, vectors, and strings
6 #include <SDL2/SDL.h>
7 #include <SDL2/SDL_image.h>
8 #include <SDL2/SDL_ttf.h>
9 #include <stdio.h>
10 #include <string>
11
12 //Screen dimension constants
13 const int SCREEN_WIDTH = 640;
14 const int SCREEN_HEIGHT = 480;
15 static int player1Score = 0;
16 static int player2Score = 0;
17
18 //Texture wrapper class
19 class LTexture
20 {
21         public:
22                 //Initializes variables
23                 LTexture();
24
25                 //Deallocates memory
26                 ~LTexture();
27
28                 //Loads image at specified path
29                 bool loadFromFile( std::string path );
30                 
31                 //Creates image from font string
32                 bool loadFromRenderedText( std::string textureText, SDL_Color textColor );
33
34                 //Deallocates texture
35                 void free();
36
37                 //Set color modulation
38                 void setColor( Uint8 red, Uint8 green, Uint8 blue );
39
40                 //Set blending
41                 void setBlendMode( SDL_BlendMode blending );
42
43                 //Set alpha modulation
44                 void setAlpha( Uint8 alpha );
45                 
46                 //Renders texture at given point
47                 void render( int x, int y, SDL_Rect* clip = NULL, double angle = 0.0, SDL_Point* center = NULL, SDL_RendererFlip flip = SDL_FLIP_NONE );
48                 //Gets image dimensions
49                 int getWidth();
50                 int getHeight();
51
52         private:
53                 //The actual hardware texture
54                 SDL_Texture* mTexture;
55
56                 //Image dimensions
57                 int mWidth;
58                 int mHeight;
59 };
60
61 //A circle stucture
62 struct Circle
63 {
64   int x, y;
65   int r;
66 };
67
68 //The dot that will move around on the screen
69 class Dot
70 {
71     public:
72
73                 //Maximum axis velocity of the dot
74                 static const int DOT_VEL = 2.5;
75
76                 //Initializes the variables
77     Dot(float x, float y, float velX, float velY, Uint8 w, Uint8 h);
78
79                 //Moves the dot
80                 void move( SDL_Rect& paddle1 );
81
82                 //Shows the dot on the screen
83                 void render();
84
85                 void reset();
86
87                 Circle& getCollider();
88
89     private:
90                 //The X and Y offsets of the dot
91                 float mPosX, mPosY;
92
93                 //The velocity of the dot
94                 float mVelX, mVelY;
95                 //The dimensions of the dot
96                 
97           int mWidth;
98           int mHeight;
99
100           Circle mCollider;
101
102           void shiftColliders();
103 };
104
105 class Paddle
106 {
107     public:
108
109                 //Maximum axis velocity of the dot
110                 static const Uint8 PADDLE_VEL = 10;
111
112                 //Initializes the variables
113     Paddle(Uint16 x, Uint16 y, Uint8 velX, Uint8 velY, Uint8 w, Uint8 h);
114
115                 //Takes key presses and adjusts the dot's velocity
116                 void handleEvent( SDL_Event& e );
117
118                 void move();
119                 void render();
120
121                 SDL_Rect& getCollider();
122
123     private:
124     
125           SDL_Rect p;
126           
127           int8_t mVelX;
128           int8_t mVelY;
129 };
130 //Starts up SDL and creates window
131 bool init();
132
133 //Loads media
134 bool loadMedia();
135
136 //Frees media and shuts down SDL
137 void close();
138
139 //The window we'll be rendering to
140 SDL_Window* gWindow = NULL;
141
142 //The window renderer
143 SDL_Renderer* gRenderer = NULL;
144
145 //Circle/Box collision detector
146 bool checkCollision( Circle& a, SDL_Rect& b );
147
148 //Scene textures
149 LTexture gDotTexture;
150
151 // Score textures
152 LTexture tPlayerOneScore;
153 LTexture tPlayerTwoScore;
154
155 TTF_Font *gFont = NULL;
156
157 LTexture::LTexture()
158 {
159         //Initialize
160         mTexture = NULL;
161         mWidth = 0;
162         mHeight = 0;
163 }
164
165 LTexture::~LTexture()
166 {
167         //Deallocate
168         free();
169 }
170
171 bool LTexture::loadFromRenderedText( std::string textureText, SDL_Color textColor )
172 {
173   // Get rid of preexisting texture
174   free();
175   // Render text surface
176   SDL_Surface* textSurface = TTF_RenderText_Solid( gFont, textureText.c_str(), textColor );
177   if( textSurface == NULL )
178   {
179     printf( "Unable to render text surface! SDL_ttf Error: %s\n", TTF_GetError() );
180   }
181   else
182   {
183     //Create texture from surface pixels
184     mTexture = SDL_CreateTextureFromSurface( gRenderer, textSurface );
185     if( mTexture == NULL )
186     {
187       printf( "Unable to create texture from rendered text! SDL Error: %s\n", SDL_GetError() );
188     }
189     else
190     {
191       //Get image dimensions
192       mWidth = textSurface->w;
193       mHeight = textSurface->h;
194     }
195     // Get rid of old surface
196     SDL_FreeSurface( textSurface );
197   }
198   // Return success
199   return mTexture != NULL;
200 }
201
202 bool LTexture::loadFromFile( std::string path )
203 {
204         //Get rid of preexisting texture
205         free();
206
207         //The final texture
208         SDL_Texture* newTexture = NULL;
209
210         //Load image at specified path
211         SDL_Surface* loadedSurface = IMG_Load( path.c_str() );
212         if( loadedSurface == NULL )
213         {
214                 printf( "Unable to load image %s! SDL_image Error: %s\n", path.c_str(), IMG_GetError() );
215         }
216         else
217         {
218                 //Color key image
219                 SDL_SetColorKey( loadedSurface, SDL_TRUE, SDL_MapRGB( loadedSurface->format, 0, 0xFF, 0xFF ) );
220
221                 //Create texture from surface pixels
222         newTexture = SDL_CreateTextureFromSurface( gRenderer, loadedSurface );
223                 if( newTexture == NULL )
224                 {
225                         printf( "Unable to create texture from %s! SDL Error: %s\n", path.c_str(), SDL_GetError() );
226                 }
227                 else
228                 {
229                         //Get image dimensions
230                         mWidth = loadedSurface->w;
231                         mHeight = loadedSurface->h;
232                 }
233
234                 //Get rid of old loaded surface
235                 SDL_FreeSurface( loadedSurface );
236         }
237
238         //Return success
239         mTexture = newTexture;
240         return mTexture != NULL;
241 }
242
243 #if defined(_SDL_TTF_H) || defined(SDL_TTF_H)
244 bool LTexture::loadFromRenderedText( player1Score, SDL_Color textColor )
245 {
246         //Get rid of preexisting texture
247         free();
248
249         //Render text surface
250         SDL_Surface* textSurface = TTF_RenderText_Solid( gFont, textureText.c_str(), textColor );
251         if( textSurface != NULL )
252         {
253                 //Create texture from surface pixels
254         mTexture = SDL_CreateTextureFromSurface( gRenderer, textSurface );
255                 if( mTexture == NULL )
256                 {
257                         printf( "Unable to create texture from rendered text! SDL Error: %s\n", SDL_GetError() );
258                 }
259                 else
260                 {
261                         //Get image dimensions
262                         mWidth = textSurface->w;
263                         mHeight = textSurface->h;
264                 }
265
266                 //Get rid of old surface
267                 SDL_FreeSurface( textSurface );
268         }
269         else
270         {
271                 printf( "Unable to render text surface! SDL_ttf Error: %s\n", TTF_GetError() );
272         }
273
274         
275         //Return success
276         return mTexture != NULL;
277 }
278 #endif
279
280 void LTexture::free()
281 {
282         //Free texture if it exists
283         if( mTexture != NULL )
284         {
285                 SDL_DestroyTexture( mTexture );
286                 mTexture = NULL;
287                 mWidth = 0;
288                 mHeight = 0;
289         }
290 }
291
292 void LTexture::setColor( Uint8 red, Uint8 green, Uint8 blue )
293 {
294         //Modulate texture rgb
295         if(SDL_SetTextureColorMod( mTexture, red, green, blue ) < 0)
296         {
297           printf("Error setting dolor for texture: %s", SDL_GetError());
298         }
299 }
300
301 void LTexture::setBlendMode( SDL_BlendMode blending )
302 {
303         //Set blending function
304         SDL_SetTextureBlendMode( mTexture, blending );
305 }
306                 
307 void LTexture::setAlpha( Uint8 alpha )
308 {
309         //Modulate texture alpha
310         SDL_SetTextureAlphaMod( mTexture, alpha );
311 }
312
313 void LTexture::render( int x, int y, SDL_Rect* clip, double angle, SDL_Point* center, SDL_RendererFlip flip )
314 {
315         //Set rendering space and render to screen
316         SDL_Rect renderQuad = { x, y, mWidth, mHeight };
317
318         //Set clip rendering dimensions
319         if( clip != NULL )
320         {
321                 renderQuad.w = clip->w;
322                 renderQuad.h = clip->h;
323         }
324
325         //Render to screen
326         SDL_RenderCopyEx( gRenderer, mTexture, clip, &renderQuad, angle, center, flip );
327 }
328
329 int LTexture::getWidth()
330 {
331         return mWidth;
332 }
333
334 int LTexture::getHeight()
335 {
336         return mHeight;
337 }
338
339 Dot::Dot(float x, float y, float velX, float velY, Uint8 w, Uint8 h)
340 {
341     //Initialize the offsets
342     mPosX = x;
343     mPosY = y;
344
345     //Initialize the velocity
346     mVelX = velX;
347     mVelY = velY;
348
349     mCollider.r = w / 2;
350
351     mWidth = w;
352     mHeight = h;
353
354     shiftColliders();
355 }
356
357 void Dot::move( SDL_Rect& paddle1)
358 {
359   //Move the dot left or right
360
361   if (checkCollision(mCollider, paddle1))
362   {
363         float amount = (mPosY - (paddle1.y + (paddle1.h/2))) / 10;
364         mVelX = -mVelX;
365         mVelY = amount;
366         printf("amount: %f, mVelX: %f, mVelY: %f paddle1.y: %d, paddle1.h: %d, mPosY: %f\n", 
367                 amount, mVelX, mVelY, paddle1.y, paddle1.h, mPosY);
368   }
369
370   //If the dot collided or went too far to the left or right
371   if( ( mPosX - mCollider.r < 0 ) || ( mPosX + mCollider.r > SCREEN_WIDTH ) )
372   {
373     printf("Score\n"); 
374     reset();
375   }
376
377   //If the dot collided or went too far up or down
378   if( ( mPosY - mCollider.r < 0 ) || ( mPosY + mCollider.r > SCREEN_HEIGHT ) )
379   {
380     //Move back
381     mVelY = -mVelY; 
382     shiftColliders();
383   }
384
385   mPosX += mVelX;
386   shiftColliders();
387   //Move the dot up or down
388   mPosY += mVelY;
389   shiftColliders();
390 }
391
392 void Dot::reset() 
393 {
394         mPosX = SCREEN_WIDTH/2;
395         mPosY = SCREEN_HEIGHT/2;
396         mVelX = -(DOT_VEL);
397         mVelY = 1;
398         mWidth = 10;
399         mHeight = 10;
400 }
401
402 void Dot::render()
403 {
404   //Show the dot
405   gDotTexture.render( mPosX - mCollider.r, mPosY - mCollider.r );
406 }
407
408 void Dot::shiftColliders()
409 {
410         //Align collider to center of dot
411         mCollider.x = mPosX;
412         mCollider.y = mPosY;
413 }
414
415 double distanceSquared( int x1, int y1, int x2, int y2 )
416 {
417   int deltaX = x2 - x1;
418   int deltaY = y2 - y1;
419   return deltaX*deltaX + deltaY*deltaY;
420 }
421
422 bool checkCollision( Circle& a, SDL_Rect& b )
423 {
424   //Closest point on collision box
425   int cX, cY;
426
427   //Find closest x offset
428   if( a.x < b.x )
429   {
430     cX = b.x;
431   }
432   else if( a.x > b.x + b.w )
433   {
434     cX = b.x + b.w;
435   }
436   else
437   {
438     cX = a.x;
439   }
440
441   //Find closest y offset
442   if( a.y < b.y )
443   {
444     cY = b.y;
445   }
446   else if( a.y > b.y + b.h )
447   {
448     cY = b.y + b.h;
449   }
450   else
451   {
452     cY = a.y;
453   }
454
455   //If the closest point is inside the circle
456   if( distanceSquared( a.x, a.y, cX, cY ) < a.r * a.r )
457   {
458     //This box and the circle have collided
459     return true;
460   }
461
462   //If the shapes have not collided
463   return false;
464 }
465
466 Paddle::Paddle(Uint16 x, Uint16 y, Uint8 velX, Uint8 velY, Uint8 w, Uint8 h)
467 {
468     //Initialize the offsets
469     p.x = x;
470     p.y = y;
471     p.w = w;
472     p.h = h;
473
474     //Initialize the velocity
475     mVelX = velX;
476     mVelY = velY;
477 }
478
479 SDL_Rect& Paddle::getCollider()
480 {
481   return p;
482 }
483
484 void Paddle::handleEvent( SDL_Event& e )
485 {
486     //If a key was pressed
487         if( e.type == SDL_KEYDOWN && e.key.repeat == 0 )
488     {
489         //Adjust the velocity
490         switch( e.key.keysym.sym )
491         {
492             case SDLK_UP: {
493               mVelY -= PADDLE_VEL;
494               break;
495             }
496             case SDLK_DOWN: {
497               mVelY += PADDLE_VEL; 
498               break;
499             }
500         }
501     }
502     //If a key was released
503     else if( e.type == SDL_KEYUP && e.key.repeat == 0 )
504     {
505         //Adjust the velocity
506         switch( e.key.keysym.sym )
507         {
508             case SDLK_UP: mVelY += PADDLE_VEL; break;
509             case SDLK_DOWN: mVelY -= PADDLE_VEL; break;
510         }
511     }
512 }
513
514 void Paddle::move()
515 {
516     //Move the dot up or down
517     p.y += mVelY;
518
519     //If the dot went too far up or down
520     if( ( p.y < 0 ) || ( p.y + p.h > SCREEN_HEIGHT ) )
521     {
522         //Move back
523         p.y -= mVelY;
524     }
525 }
526
527 void Paddle::render()
528 {
529     //Show the dot
530     SDL_RenderFillRect(gRenderer, &p);
531 }
532
533 bool init()
534 {
535         //Initialization flag
536         bool success = true;
537
538         //Initialize SDL
539         if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
540         {
541                 printf( "SDL could not initialize! SDL Error: %s\n", SDL_GetError() );
542                 success = false;
543         }
544         else
545         {
546                 //Set texture filtering to linear
547                 if( !SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1" ) )
548                 {
549                         printf( "Warning: Linear texture filtering not enabled!" );
550                 }
551
552                 //Create window
553                 gWindow = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
554                 if( gWindow == NULL )
555                 {
556                         printf( "Window could not be created! SDL Error: %s\n", SDL_GetError() );
557                         success = false;
558                 }
559                 else
560                 {
561                         //Create vsynced renderer for window
562                         gRenderer = SDL_CreateRenderer( gWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );
563                         if( gRenderer == NULL )
564                         {
565                                 printf( "Renderer could not be created! SDL Error: %s\n", SDL_GetError() );
566                                 success = false;
567                         }
568                         else
569                         {
570                                 //Initialize renderer color
571                                 SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
572                                 //Initialize PNG loading
573                                 int imgFlags = IMG_INIT_PNG;
574                                 if( !( IMG_Init( imgFlags ) & imgFlags ) )
575                                 {
576                                         printf( "SDL_image could not initialize! SDL_image Error: %s\n", IMG_GetError() );
577                                         success = false;
578                                 }
579
580         // Initialize SDL_ttf
581         if( TTF_Init() == -1 )
582         {
583           printf( "SDL_ttf could not initialize! SDL_ttf Error: %s\n", TTF_GetError() );
584           success = false;
585         }
586                         }
587                 }
588         }
589
590         return success;
591 }
592
593 bool loadMedia()
594 {
595         //Loading success flag
596         bool success = true;
597
598         // Open the font
599         gFont = TTF_OpenFont("lazy.ttf", 20);
600
601         if( gFont == NULL )
602         {
603     printf( "Failed to load lazy font! SDL_ttf Error: %s\n", TTF_GetError() );
604     success = false;
605   }
606   else
607   {
608     // Render text
609     SDL_Color textColor = { 0xFF, 0xFF, 0xFF };
610     if( !tPlayerOneScore.loadFromRenderedText( std::to_string(player1Score), textColor ) || 
611                 !tPlayerTwoScore.loadFromRenderedText( std::to_string(player2Score), textColor ))
612     {
613       printf( "Failed to render text texture!\n" );
614       success = false;
615     }
616   }
617
618         //Load dot texture
619         if( !gDotTexture.loadFromFile( "dot.bmp" ) )
620         {
621                 printf( "Failed to load dot texture!\n" );
622                 success = false;
623         }
624
625         return success;
626 }
627
628 void close()
629 {
630         //Free loaded images
631         gDotTexture.free();
632         tPlayerOneScore.free();
633         tPlayerTwoScore.free();
634
635         TTF_CloseFont( gFont );
636         gFont = NULL;
637
638         //Destroy window        
639         SDL_DestroyRenderer( gRenderer );
640         SDL_DestroyWindow( gWindow );
641         gWindow = NULL;
642         gRenderer = NULL;
643
644         //Quit SDL subsystems
645         TTF_Quit();
646         IMG_Quit();
647         SDL_Quit();
648 }
649
650 int main( int argc, char* args[] )
651 {
652         //Start up SDL and create window
653         if( !init() )
654         {
655                 printf( "Failed to initialize!\n" );
656         }
657         else
658         {
659                 //Load media
660                 if( !loadMedia() )
661                 {
662                         printf( "Failed to load media!\n" );
663                 }
664                 else
665                 {       
666                         //Main loop flag
667                         bool quit = false;
668
669                         //Event handler
670                         SDL_Event e;
671
672                         //The dot that will be moving around on the screen
673                         Paddle paddle = Paddle(10, SCREEN_HEIGHT/2, 0, 0, 8, 40);
674                         Paddle ai = Paddle(SCREEN_WIDTH-10, SCREEN_HEIGHT/2, 0, 0, 8, 40);
675
676                         Dot ball = Dot(SCREEN_WIDTH/2, SCREEN_HEIGHT/2, -(ball.DOT_VEL), 1, 10, 10);
677
678                         //While application is running
679                         while( !quit )
680                         {
681                                 //Handle events on queue
682                                 while( SDL_PollEvent( &e ) != 0 )
683                                 {
684                                         //User requests quit
685                                         if( e.type == SDL_QUIT )
686                                         {
687                                                 quit = true;
688                                         }
689
690                                         //Handle input for the dot
691                                         paddle.handleEvent( e );
692                                 }
693
694                                 //Move the dot
695                                 paddle.move();
696                                 ai.move();
697                                 ball.move(paddle.getCollider());
698
699                                 //Clear screen
700                                 SDL_SetRenderDrawColor( gRenderer, 0x00, 0x00, 0x00, 0xFF );
701                                 SDL_RenderClear( gRenderer );
702
703                                 // Try to render score text
704                                 tPlayerOneScore.render(SCREEN_WIDTH/4, 40);
705
706                                 //Render middle line
707                                 SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
708                                 SDL_RenderDrawLine(gRenderer, SCREEN_WIDTH/2, 0, SCREEN_WIDTH/2, SCREEN_HEIGHT);
709
710                                 //Render objects
711                                 paddle.render();
712                                 ai.render();
713                                 ball.render();
714
715                                 //Update screen
716                                 SDL_RenderPresent( gRenderer );
717                         }
718                 }
719         }
720
721         //Free resources and close SDL
722         close();
723
724         return 0;
725 }