diff examples/10-Instruments/tank-wars/game.cpp @ 493:a23d74e2f6cb prerelease

Minor changes to doxygen
author Robert Jack <robert.h.jack@gmail.com>
date Tue, 21 Jun 2016 18:50:03 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/10-Instruments/tank-wars/game.cpp	Tue Jun 21 18:50:03 2016 +0100
@@ -0,0 +1,339 @@
+/*
+ * game.cpp
+ *
+ *  Created on: Nov 10, 2014
+ *      Author: parallels
+ */
+
+#include <cmath>
+#include <cstdlib>
+#include "vector_graphics.h"
+#include <Utilities.h>
+
+// Virtual screen size
+int screenWidth, screenHeight;
+
+// Basic information on the terrain and the tanks
+float *groundLevel;  // Y coordinate of the ground for each X coordinate
+float tank1X, tank1Y, tank2X, tank2Y; // Positions of the two tanks
+float tankRadius = 20;  // Radius of the tanks
+float cannonLength = 40;   // How long the cannon on each tank extends
+float gravity = 0.05;      // Strength of gravity
+
+// Current state of the game
+int playerHasWon = 0; // 1 if player 1 wins, 2 if player 2 wins, 0 if game in progress
+bool player1Turn = true;  // true if it's player 1's turn; false otherwise
+float tank1CannonAngle = M_PI/2;
+float tank2CannonAngle = M_PI/2; // Direction the tank cannons are pointing
+float tank1CannonStrength = 3;
+float tank2CannonStrength = 3; // Strength of intended projectile launch
+
+// Location of the projectile
+bool projectileInMotion = false;
+float projectilePositionX, projectilePositionY;
+float projectileVelocityX, projectileVelocityY;
+
+// Infor needed for sound rendering
+bool collisionJustOccurred = false;
+bool tankHitJustOccurred = false;
+
+// Useful utility function for generating random floating-point values
+float randomFloat(float low, float hi)
+{
+	float r = (float)random() / (float)RAND_MAX;
+	return map(r, 0, 1, low, hi);
+}
+
+// Restart the game, without reallocating memory
+void restartGame()
+{
+	float player1Height = screenHeight * 3/4; // randomFloat(screenHeight/2, screenHeight-5);
+	float player2Height = screenHeight - 5; // randomFloat(screenHeight/2, screenHeight-5);
+	for(int i = 0; i < screenWidth * 0.2; i++) {
+		groundLevel[i] = player1Height;
+	}
+	for(int i = screenWidth * 0.2; i < screenWidth * 0.8; i++) {
+		groundLevel[i] = player1Height + (player2Height - player1Height) * (i - screenWidth*0.2)/(screenWidth*0.6);
+	}
+	for(int i = screenWidth * 0.8; i < screenWidth; i++) {
+		groundLevel[i] = player2Height;
+	}
+
+	// Set the location of the two tanks so they rest on the ground at opposite sides
+	tank1X = screenWidth * 0.1;
+	tank1Y = player1Height;
+	tank2X = screenWidth * 0.9;
+	tank2Y = player2Height;
+
+	playerHasWon = 0;
+	projectileInMotion = false;
+}
+
+// Initialise the game
+void setupGame(int width, int height)
+{
+	// Set the screen size
+	screenWidth = width;
+	screenHeight = height;
+
+	// Initialize the ground level
+	groundLevel = new float[screenWidth];
+
+	restartGame();
+}
+
+// Advance the turn to the next player
+void nextPlayersTurn() {
+	player1Turn = !player1Turn;
+}
+
+
+// Move forward one frame on the game physics
+void nextGameFrame()
+{
+	if(!projectileInMotion)
+		return;
+
+	// Update position of projectile
+	projectilePositionX += projectileVelocityX;
+	projectilePositionY += projectileVelocityY;
+	projectileVelocityY += gravity;
+
+	// Check collision with tanks first: a collision with tank 1 means player 2 wins and vice-versa
+	if((tank1X - projectilePositionX)*(tank1X - projectilePositionX) +
+		(tank1Y - projectilePositionY)*(tank1Y - projectilePositionY)
+		<= tankRadius * tankRadius)
+	{
+		projectileInMotion = false;
+		collisionJustOccurred = false;
+		tankHitJustOccurred = true;
+		playerHasWon = 2;
+	}
+	else if((tank2X - projectilePositionX)*(tank2X - projectilePositionX) +
+		(tank2Y - projectilePositionY)*(tank2Y - projectilePositionY)
+		<= tankRadius * tankRadius)
+	{
+		projectileInMotion = false;
+		collisionJustOccurred = false;
+		tankHitJustOccurred = true;
+		playerHasWon = 1;
+	}
+	else if(projectilePositionX < 0 || projectilePositionX >= screenWidth) {
+		// Check collision whether projectile has exited the screen to the left or right
+		projectileInMotion = false;
+		collisionJustOccurred = true;
+		nextPlayersTurn();
+	}
+	else if(projectilePositionY >= groundLevel[(int)floorf(projectilePositionX)]) {
+		// Check for projectile collision with ground
+		projectileInMotion = false;
+		collisionJustOccurred = true;
+		nextPlayersTurn();
+	}
+}
+
+// Updates for game state
+void setTank1CannonAngle(float angle)
+{
+	tank1CannonAngle = angle;
+}
+
+void setTank2CannonAngle(float angle)
+{
+	tank2CannonAngle = angle;
+}
+
+void setTank1CannonStrength(float strength)
+{
+	tank1CannonStrength = strength;
+}
+
+void setTank2CannonStrength(float strength)
+{
+	tank2CannonStrength = strength;
+}
+
+// FIRE!
+void fireProjectile()
+{
+	// Can't fire while projectile is already moving, or if someone has won
+	if(projectileInMotion)
+		return;
+	if(playerHasWon != 0)
+		return;
+
+    if(player1Turn) {
+		projectilePositionX = tank1X + cannonLength * cosf(tank1CannonAngle);
+		projectilePositionY = tank1Y - cannonLength * sinf(tank1CannonAngle);
+		projectileVelocityX = tank1CannonStrength * cosf(tank1CannonAngle);
+		projectileVelocityY = -tank1CannonStrength * sinf(tank1CannonAngle);
+    }
+    else {
+		projectilePositionX = tank2X + cannonLength * cosf(tank2CannonAngle);
+		projectilePositionY = tank2Y - cannonLength * sinf(tank2CannonAngle);
+		projectileVelocityX = tank2CannonStrength * cosf(tank2CannonAngle);
+		projectileVelocityY = -tank2CannonStrength * sinf(tank2CannonAngle);
+    }
+
+    // GO!
+    projectileInMotion = true;
+}
+
+// Game state queries
+bool gameStatusPlayer1Turn()
+{
+	return player1Turn;
+}
+
+bool gameStatusProjectileInMotion()
+{
+	return projectileInMotion;
+}
+
+int gameStatusWinner()
+{
+	return playerHasWon;
+}
+
+bool gameStatusCollisionOccurred()
+{
+	if(collisionJustOccurred) {
+		collisionJustOccurred = false;
+		return true;
+	}
+	return false;
+}
+
+bool gameStatusTankHitOccurred()
+{
+	if(tankHitJustOccurred) {
+		tankHitJustOccurred = false;
+		return true;
+	}
+	return false;
+}
+
+
+float gameStatusProjectileHeight()
+{
+	return projectilePositionY / (float)screenHeight;
+}
+
+// Clean up any allocated memory for the game
+void cleanupGame()
+{
+	delete groundLevel;
+}
+
+// Drawing routines. Arguments are (interleaved) buffer to render
+// into, the available size, and the target for how many samples
+// to use (actual usage might vary slightly). Regardless of
+// lengthTarget, never use more than bufferSize samples.
+
+int drawGround(float *buffer, int bufferSize, int framesTarget)
+{
+	int length;
+
+	// Calculate total length of ground line, to arrive at a speed calculation
+	float totalLineLength = 0.4f*screenWidth
+							+ sqrtf(0.36f*screenWidth*screenWidth
+									+ (tank2Y-tank1Y)*(tank2Y-tank1Y));
+
+	// Speed is calculated in pixels per frame
+	float speed = totalLineLength / (float)framesTarget;
+
+	// Draw three lines: platforms for tanks and the connecting line.
+	// Eventually, render a more complex ground from the array.
+	length = renderLine(0, tank1Y, screenWidth * 0.2, tank1Y,
+						speed, buffer, bufferSize);
+	length += renderLine(screenWidth * 0.2, tank1Y, screenWidth * 0.8, tank2Y,
+						speed, &buffer[length], bufferSize - length);
+	length += renderLine(screenWidth * 0.8, tank2Y, screenWidth, tank2Y,
+						speed, &buffer[length], bufferSize - length);
+
+	return length;
+}
+
+int drawTanks(float *buffer, int bufferSize, int framesTarget)
+{
+	int length = 0;
+
+	// Calculate total length of tank lines, to arrive at a speed calculation
+	float totalLineLength = 2.0*M_PI*tankRadius + 2.0*(cannonLength - tankRadius);
+
+	// Speed is calculated in pixels per frame
+	float speed = totalLineLength / (float)framesTarget;
+
+	if(playerHasWon != 2) {
+		// Tank 1 body = semicircle + line
+		length += renderArc(tank1X, tank1Y, tankRadius, M_PI, 2.0 * M_PI,
+							speed, buffer, bufferSize);
+		length += renderLine(tank1X + tankRadius, tank1Y,
+							 tank1X - tankRadius, tank1Y,
+							speed, &buffer[length], bufferSize - length);
+		// Tank 1 cannon (line depending on angle)
+		length += renderLine(tank1X + tankRadius * cosf(tank1CannonAngle),
+			 tank1Y - tankRadius * sinf(tank1CannonAngle),
+			 tank1X + cannonLength * cosf(tank1CannonAngle),
+			 tank1Y - cannonLength * sinf(tank1CannonAngle),
+			 speed, &buffer[length], bufferSize - length);
+	}
+
+	if(playerHasWon != 1) {
+		// Same idea for tank 2
+		length += renderArc(tank2X, tank2Y, tankRadius, M_PI, 2.0 * M_PI,
+							speed, &buffer[length], bufferSize - length);
+		length += renderLine(tank2X + tankRadius, tank2Y,
+							 tank2X - tankRadius, tank2Y,
+							 speed, &buffer[length], bufferSize - length);
+		length += renderLine(tank2X + tankRadius * cosf(tank2CannonAngle),
+			 tank2Y - tankRadius * sinf(tank2CannonAngle),
+			 tank2X + cannonLength * cosf(tank2CannonAngle),
+			 tank2Y - cannonLength * sinf(tank2CannonAngle),
+			 speed, &buffer[length], bufferSize - length);
+	}
+
+	return length;
+}
+
+int drawProjectile(float *buffer, int bufferSize, int framesTarget)
+{
+	if(!projectileInMotion)
+		return 0;
+
+	// Draw a point for a specified number of frames (each containing X and Y)
+	// Return the number of items used in the buffer, which will be twice
+	// the number of frames unless the buffer is full
+
+	if(bufferSize/2 < framesTarget) {
+		renderPoint(projectilePositionX, projectilePositionY, buffer, bufferSize/2);
+		return bufferSize;
+	}
+	else {
+		renderPoint(projectilePositionX, projectilePositionY, buffer, framesTarget);
+		return framesTarget*2;
+	}
+}
+
+// Main drawing routine entry point
+int drawGame(float *buffer, int bufferSize)
+{
+	int length;
+
+	// Based on buffer size, come up with speeds for each of the elements
+	// 50% of time to ground; 30% to the tanks and 20% to the projectile
+	// Give a margin of 25% beyond so we don't run out of buffer space
+	// if things take longer to draw than we guess they will
+	const float amountToUse = 0.375; // 0.75/2 because two samples per frame
+	const float groundFraction = 0.5 * amountToUse;
+	const float tankFraction = 0.3 * amountToUse;
+	const float projectileFraction = 0.2 * amountToUse;
+
+	length = drawGround(buffer, bufferSize, bufferSize * groundFraction);
+	length += drawTanks(&buffer[length], bufferSize - length,
+						bufferSize * tankFraction);
+	length += drawProjectile(&buffer[length], bufferSize - length,
+						bufferSize * projectileFraction);
+
+	return length;
+}