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_ */