Mercurial > hg > beaglert
changeset 10:49f22e1246b2
Tank wars!
author | andrewm |
---|---|
date | Thu, 13 Nov 2014 15:58:08 +0100 |
parents | 09f03ac40fcc |
children | 517715b23df0 |
files | .cproject projects/tank_wars/game.cpp projects/tank_wars/game.h projects/tank_wars/main.cpp projects/tank_wars/render.cpp projects/tank_wars/vector_graphics.cpp projects/tank_wars/vector_graphics.h |
diffstat | 7 files changed, 809 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/.cproject Sat Nov 08 16:16:55 2014 +0100 +++ b/.cproject Thu Nov 13 15:58:08 2014 +0100 @@ -82,7 +82,7 @@ <sourceEntries> <entry excluding="audio_routines_old.S" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="core"/> <entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="include"/> - <entry excluding="basic_sensor|d-box|audio_in_FFT|filter_IIR|filter_FIR|samples|oscillator_bank|basic" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="projects"/> + <entry excluding="basic_analog_output|basic_sensor|d-box|audio_in_FFT|filter_IIR|filter_FIR|samples|oscillator_bank|basic" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="projects"/> </sourceEntries> </configuration> </storageModule> @@ -156,7 +156,7 @@ <sourceEntries> <entry excluding="audio_routines_old.S" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="core"/> <entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="include"/> - <entry excluding="basic_sensor|d-box|audio_in_FFT|filter_IIR|filter_FIR|samples|oscillator_bank|basic" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="projects"/> + <entry excluding="basic_analog_output|basic_sensor|d-box|audio_in_FFT|filter_IIR|filter_FIR|samples|oscillator_bank|basic" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="projects"/> </sourceEntries> </configuration> </storageModule>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projects/tank_wars/game.cpp Thu Nov 13 15:58:08 2014 +0100 @@ -0,0 +1,305 @@ +/* + * game.cpp + * + * Created on: Nov 10, 2014 + * Author: parallels + */ + +#include <cmath> +#include <cstdlib> +#include "vector_graphics.h" +#include "../../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; + +// 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 = randomFloat(screenHeight/2, screenHeight-5); + float player2Height = 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; + playerHasWon = 2; + } + else if((tank2X - projectilePositionX)*(tank2X - projectilePositionX) + + (tank2Y - projectilePositionY)*(tank2Y - projectilePositionY) + <= tankRadius * tankRadius) + { + projectileInMotion = false; + playerHasWon = 1; + } + else if(projectilePositionX < 0 || projectilePositionX >= screenWidth) { + // Check collision whether projectile has exited the screen to the left or right + projectileInMotion = false; + nextPlayersTurn(); + } + else if(projectilePositionY >= groundLevel[(int)floorf(projectilePositionX)]) { + // Check for projectile collision with ground + projectileInMotion = false; + 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; +} + +// 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; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projects/tank_wars/game.h Thu Nov 13 15:58:08 2014 +0100 @@ -0,0 +1,36 @@ +/* + * game.h + * + * Created on: Nov 10, 2014 + * Author: parallels + */ + +#ifndef GAME_H_ +#define GAME_H_ + +// Initialisation +void setupGame(int width, int height); +void restartGame(); + +// Update physics +void nextGameFrame(); + +// State updaters +void setTank1CannonAngle(float angle); +void setTank2CannonAngle(float angle); +void setTank1CannonStrength(float strength); +void setTank2CannonStrength(float strength); +void fireProjectile(); + +// State queries +bool gameStatusPlayer1Turn(); +bool gameStatusProjectileInMotion(); +int gameStatusWinner(); + +// Render screen; returns length of buffer used +int drawGame(float *buffer, int bufferSize); + +// Cleanup and memory release +void cleanupGame(); + +#endif /* GAME_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projects/tank_wars/main.cpp Thu Nov 13 15:58:08 2014 +0100 @@ -0,0 +1,101 @@ +/* + * main.cpp + * + * Created on: Oct 24, 2014 + * Author: parallels + */ + +#include <iostream> +#include <cstdlib> +#include <libgen.h> +#include <signal.h> +#include <getopt.h> +#include "../../include/RTAudio.h" + +extern int gScreenFramesPerSecond; + +using namespace std; + +// Handle Ctrl-C by requesting that the audio rendering stop +void interrupt_handler(int var) +{ + gShouldStop = true; +} + +// Print usage information +void usage(const char * processName) +{ + cerr << "Usage: " << processName << " [options]" << endl; + + BeagleRT_usage(); + + cerr << " --fps [-f] value: Set target frames per second\n"; + cerr << " --help [-h]: Print this menu\n"; +} + +int main(int argc, char *argv[]) +{ + RTAudioSettings settings; // Standard audio settings + + struct option customOptions[] = + { + {"help", 0, NULL, 'h'}, + {"fps", 1, NULL, 'f'}, + {NULL, 0, NULL, 0} + }; + + // Set default settings + BeagleRT_defaultSettings(&settings); + + // Parse command-line arguments + while (1) { + int c; + if ((c = BeagleRT_getopt_long(argc, argv, "hf:", customOptions, &settings)) < 0) + break; + switch (c) { + case 'f': + gScreenFramesPerSecond = atoi(optarg); + if(gScreenFramesPerSecond < 1) + gScreenFramesPerSecond = 1; + if(gScreenFramesPerSecond > 100) + gScreenFramesPerSecond = 100; + break; + case 'h': + usage(basename(argv[0])); + exit(0); + case '?': + default: + usage(basename(argv[0])); + exit(1); + } + } + + // Initialise the PRU audio device + if(BeagleRT_initAudio(&settings, 0) != 0) { + cout << "Error: unable to initialise audio" << endl; + return -1; + } + + // Start the audio device running + if(BeagleRT_startAudio()) { + cout << "Error: unable to start real-time audio" << endl; + return -1; + } + + // Set up interrupt handler to catch Control-C + signal(SIGINT, interrupt_handler); + + // Run until told to stop + while(!gShouldStop) { + usleep(100000); + } + + // Stop the audio device + BeagleRT_stopAudio(); + + // Clean up any resources allocated for audio + BeagleRT_cleanupAudio(); + + // All done! + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projects/tank_wars/render.cpp Thu Nov 13 15:58:08 2014 +0100 @@ -0,0 +1,275 @@ +/* + * render.cpp + * + * Created on: Oct 24, 2014 + * Author: parallels + */ + + +#include "../../include/RTAudio.h" +#include "../../include/Utilities.h" +#include "game.h" +#include <rtdk.h> +#include <cmath> +#include <cstdlib> +#include <time.h> + +float gFrequency; +float gPhase; +float gInverseSampleRate; +int gNumChannels; + +int gInputTank1Angle = 0; // Inputs for the cannon angles +int gInputTank2Angle = 1; +int gInputLauncher = 2; // Input for launcher FSR + +int gOutputX = 0; // Outputs for the scope +int gOutputY = 1; +int gOutputPlayer1LED = 2; +int gOutputPlayer2LED = 3; + +int gGameFramesPerSecond = 60; // How often the physics are updated +int gGameFrameInterval; // ...and in frames +int gSamplesUntilNextFrame; // Counter until next update +int gSamplesSinceFinish = 0; // How long since somebody won? +bool gGameShouldRestart = false;// Whether we need to reinitiliase the game + +// Counter for overall number of samples that have elapsed +unsigned int gSampleCounter = 0; + +// 1st-order filter and peak detector for launcher input +float gLauncherLastSample = 0; +float gLauncherFilterPole = 0.8; +float gLauncherPeakValue = 0; +float gLauncherPeakFilterPole = 0.999; +float gLauncherNoiseThreshold = 0.01 * MATRIX_MAX; +float gLauncherMinimumPeak = 0.1 * MATRIX_MAX; +bool gLauncherTriggered = false; + +// Screen update rate; affects buffer size. Actual contents of buffer +// may be smaller than this +int gScreenWidth = 512; +int gScreenHeight = 512; +int gScreenFramesPerSecond = 25; + +// Double buffer for rendering screen. Each is an interleaved buffer +// of XY data. +float *gScreenBuffer1, *gScreenBuffer2; +float *gScreenBufferWrite, *gScreenBufferRead; +int gScreenBufferMaxLength; // What is the total buffer allocated? +int gScreenBufferReadLength; // How long is the read buffer? +int gScreenBufferWriteLength; // How long is the write (next) buffer? +int gScreenBufferReadPointer; // Where are we in the read buffer now? +int gScreenBufferNextUpdateLocation; // When should we render the next buffer? +bool gScreenNextBufferReady; // Is the next buffer ready to go? + +// Auxiliary (low-priority) task for updating the screen +AuxiliaryTask gScreenUpdateTask; + +void screen_update(); + +// initialise_render() is called once before the audio rendering starts. +// Use it to perform any initialisation and allocation which is dependent +// on the period size or sample rate. +// +// userData holds an opaque pointer to a data structure that was passed +// in from the call to initAudio(). +// +// Return true on success; returning false halts the program. + +bool initialise_render(int numChannels, int numMatrixFramesPerPeriod, + int numAudioFramesPerPeriod, float matrixSampleRate, + float audioSampleRate, void *userData) +{ + srandom(time(NULL)); + + // Verify we are running with matrix enabled + if(numMatrixFramesPerPeriod*2 != numAudioFramesPerPeriod) { + rt_printf("Error: this example needs the matrix enabled, running at half audio rate\n"); + return false; + } + + // Initialise the screen buffers + gScreenBufferMaxLength = 2 * matrixSampleRate / gScreenFramesPerSecond; + gScreenBuffer1 = new float[gScreenBufferMaxLength]; + gScreenBuffer2 = new float[gScreenBufferMaxLength]; + if(gScreenBuffer1 == 0 || gScreenBuffer2 == 0) { + rt_printf("Error initialising screen buffers\n"); + return false; + } + + gScreenBufferRead = gScreenBuffer1; + gScreenBufferWrite = gScreenBuffer2; + gScreenBufferReadLength = gScreenBufferWriteLength = 0; + gScreenBufferReadPointer = 0; + gScreenBufferNextUpdateLocation = 0; + gScreenNextBufferReady = false; + + // Initialise the game + setupGame(gScreenWidth, gScreenHeight); + gGameFrameInterval = matrixSampleRate / gGameFramesPerSecond; + gSamplesUntilNextFrame = gGameFrameInterval; + + // Initialise auxiliary tasks + if((gScreenUpdateTask = createAuxiliaryTaskLoop(&screen_update, 90, + "beaglert-screen-update")) == 0) + return false; + + return true; +} + +// Swap buffers on the screen +void swap_buffers() +{ + if(gScreenBufferRead == gScreenBuffer1) { + gScreenBufferRead = gScreenBuffer2; + gScreenBufferWrite = gScreenBuffer1; + } + else { + gScreenBufferRead = gScreenBuffer1; + gScreenBufferWrite = gScreenBuffer2; + } + + gScreenBufferReadLength = gScreenBufferWriteLength; + gScreenBufferReadPointer = 0; + + // Schedule next update for 3/4 of the way through the buffer + gScreenBufferNextUpdateLocation = gScreenBufferReadLength * 0.75; + gScreenNextBufferReady = false; +} + +// render() is called regularly at the highest priority by the audio engine. +// Input and output are given from the audio hardware and the other +// ADCs and DACs (if available). If only audio is available, numMatrixFrames +// will be 0. + +void render(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut, + uint16_t *matrixIn, uint16_t *matrixOut) +{ + for(int n = 0; n < numMatrixFrames; n++) { + // First-order lowpass filter to remove noise on launch FSR + float rawSample = analogRead(gInputLauncher, n); + float launchSample = gLauncherFilterPole * gLauncherLastSample + + (1.0f - gLauncherFilterPole) * rawSample; + gLauncherLastSample = launchSample; + + // Peak-detect on launch signal + if(launchSample >= gLauncherPeakValue) { + gLauncherPeakValue = launchSample; + gLauncherTriggered = false; + } + else { + if(gLauncherPeakValue - launchSample > gLauncherNoiseThreshold && !gLauncherTriggered) { + // Detected a peak; is it big enough overall? + if(gLauncherPeakValue >= gLauncherMinimumPeak) { + gLauncherTriggered = true; + // Peak detected-- fire!! + // Set both cannon strengths but only one will + // fire depending on whose turn it is + float strength = map(gLauncherPeakValue, + gLauncherMinimumPeak, MATRIX_MAX, + 0.5f, 10.0f); + setTank1CannonStrength(strength); + setTank2CannonStrength(strength); + fireProjectile(); + } + } + + gLauncherPeakValue *= gLauncherPeakFilterPole; + } + + if(--gSamplesUntilNextFrame <= 0) { + // Update game physics and cannon angles + gSamplesUntilNextFrame = gGameFrameInterval; + + setTank1CannonAngle(map(analogRead(gInputTank1Angle, n), + 0, MATRIX_MAX, M_PI, 0)); + setTank2CannonAngle(map(analogRead(gInputTank2Angle, n), + 0, MATRIX_MAX, M_PI, 0)); + nextGameFrame(); + } + + if(gScreenBufferReadPointer >= gScreenBufferReadLength - 1 + && gScreenNextBufferReady) { + // Got to the end; swap buffers + swap_buffers(); + } + + // Push current screen buffer to the matrix output + if(gScreenBufferReadPointer < gScreenBufferReadLength - 1) { + float x = gScreenBufferRead[gScreenBufferReadPointer++]; + float y = gScreenBufferRead[gScreenBufferReadPointer++]; + + // Rescale screen coordinates to matrix ranges; invert the Y + // coordinate to go from normal screen coordinates to scope coordinates + analogWrite(gOutputX, n, constrain(map(x, 0, gScreenWidth, 0, MATRIX_MAX), 0, MATRIX_MAX)); + analogWrite(gOutputY, n, constrain(map(y, 0, gScreenHeight, MATRIX_MAX, 0), 0, MATRIX_MAX)); + } + else { + // Still not ready! Write 0 until something happens + analogWrite(gOutputX, n, 0); + analogWrite(gOutputY, n, 0); + } + + if(gameStatusWinner() != 0) { + // Blink one LED to show who won + // Blink both LEDs when projectile is in motion + uint16_t val = (gSampleCounter % 4000 > 2000) ? MATRIX_MAX : 0; + analogWrite(gOutputPlayer1LED, n, gameStatusWinner() == 1 ? val : 0); + analogWrite(gOutputPlayer2LED, n, gameStatusWinner() == 2 ? val : 0); + + // After 5 seconds, restart the game + gSamplesSinceFinish++; + if(gSamplesSinceFinish > 22050*5) + gGameShouldRestart = true; + } + else if(gameStatusProjectileInMotion()) { + // Blink both LEDs when projectile is in motion + uint16_t val = (gSampleCounter % 2000 > 1000) ? MATRIX_MAX : 0; + analogWrite(gOutputPlayer1LED, n, val); + analogWrite(gOutputPlayer2LED, n, val); + } + else if(gameStatusPlayer1Turn()) { + analogWrite(gOutputPlayer1LED, n, MATRIX_MAX); + analogWrite(gOutputPlayer2LED, n, 0); + } + else { + analogWrite(gOutputPlayer2LED, n, MATRIX_MAX); + analogWrite(gOutputPlayer1LED, n, 0); + } + + // Check if we have reached the point where we should next update + if(gScreenBufferReadPointer >= gScreenBufferNextUpdateLocation && + !gScreenNextBufferReady) { + // Update the screen at lower priority than the audio thread + scheduleAuxiliaryTask(gScreenUpdateTask); + } + + gSampleCounter++; + } +} + +void screen_update() +{ + // If we should restart, reinitialise the game + if(gGameShouldRestart) { + restartGame(); + gGameShouldRestart = false; + gSamplesSinceFinish = 0; + } + + // Render the game based on the current state + gScreenBufferWriteLength = drawGame(gScreenBufferWrite, gScreenBufferMaxLength); + + // Flag it as ready to go + gScreenNextBufferReady = true; +} + +// cleanup_render() is called once at the end, after the audio has stopped. +// Release any resources that were allocated in initialise_render(). + +void cleanup_render() +{ + // Clean up the game state + cleanupGame(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projects/tank_wars/vector_graphics.cpp Thu Nov 13 15:58:08 2014 +0100 @@ -0,0 +1,72 @@ +/* + * vector_graphics.cpp + * + * Created on: Nov 10, 2014 + * Author: parallels + */ + +#include <cmath> + +// Draw a line between two points at a specified rate in +// pixels per buffer sample. Indicate maximum available space. +// Returns space used +int renderLine(float x1, float y1, float x2, float y2, float speed, + float *buffer, int maxLength) { + // Figure out length of line and therefore how many samples + // are needed to represent it based on the speed (rounded to nearest int) + float totalLineLength = sqrtf((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1)); + int samplesNeeded = floorf(totalLineLength / speed + 0.5); + + // Now render into the buffer + int length = 0; + float scaleFactor = 1.0f / samplesNeeded; + for(int n = 0; n < samplesNeeded; n++) { + if(length >= maxLength - 1) + return length; + // X coordinate + *buffer++ = x1 + (float)n * scaleFactor * (x2 - x1); + // Y coordinate + *buffer++ = y1 + (float)n * scaleFactor * (y2 - y1); + length += 2; + } + + return length; +} + +// Draw an arc around a centre point at a specified rate of pixels +// per buffer sample. Indicate maximum available space. +// Returns space used +int renderArc(float x, float y, float radius, float thetaMin, float thetaMax, + float speed, float *buffer, int maxLength) { + // Figure out circumference of arc and therefore how many samples + // are needed to represent it based on the speed (rounded to nearest int) + float circumference = (thetaMax - thetaMin) * radius; + int samplesNeeded = floorf(circumference / speed + 0.5); + + // Now render into the buffer + int length = 0; + float scaleFactor = 1.0f / samplesNeeded; + for(int n = 0; n < samplesNeeded; n++) { + if(length >= maxLength - 1) + return length; + // Get current angle + float theta = thetaMin + (float)n * scaleFactor * (thetaMax - thetaMin); + + // Convert polar to cartesian coordinates + *buffer++ = x + radius * cosf(theta); + *buffer++ = y + radius * sinf(theta); + + length += 2; + } + + return length; +} + +// Draw a single point for a specified number of frames +void renderPoint(float x, float y, float *buffer, float length) { + while(length > 0) { + *buffer++ = x; + *buffer++ = y; + length--; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projects/tank_wars/vector_graphics.h Thu Nov 13 15:58:08 2014 +0100 @@ -0,0 +1,18 @@ +/* + * vector_graphics.h + * + * Created on: Nov 10, 2014 + * Author: parallels + */ + +#ifndef VECTOR_GRAPHICS_H_ +#define VECTOR_GRAPHICS_H_ + +int renderLine(float x1, float y1, float x2, float y2, float speed, + float *buffer, int maxLength); +int renderArc(float x, float y, float radius, float thetaMin, float thetaMax, + float speed, float *buffer, int maxLength); +void renderPoint(float x, float y, float *buffer, float length); + + +#endif /* VECTOR_GRAPHICS_H_ */