using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
//using System.Runtime.InteropServices;
//using System.Threading;
using System.IO;
// Code and comments written / collated by Jason King (jason.king@profox.co.uk), Jan, Feb and Mar 2002.
// Many thanks to Jerzy Peter and Peter Stephens who contributed to answers regarding
// playing sounds using winmm.dll.
// Thanks also to the guys who tried to help with the background of images problem.
namespace Asteroids
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.ComponentModel.IContainer components;
private System.Windows.Forms.Timer timer1;
private Asteroids.PlayingField display;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
this.BackColor = Color.Black;
display = new PlayingField(timer1, new Point(0,0),this.Size);
Controls.Add(display);
timer1.Enabled = true;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.timer1 = new System.Windows.Forms.Timer(this.components);
//
// timer1
//
this.timer1.Interval = 40;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 15);
this.ClientSize = new System.Drawing.Size(704, 495);
this.KeyPreview = true;
this.Name = "Form1";
this.Text = "Form1";
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyDown);
this.Resize += new System.EventHandler(this.Form1_Resize);
this.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.Form1_KeyPress);
this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyUp);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void timer1_Tick(object sender, System.EventArgs e)
{
// these next lines are now redundant as the playingfield now listens for the tick event
// for (int i=0; i<=display.playerArray.GetLength(0)-1; i++)
// {
// if (display.playerArray[i]!=null)
// if (display.playerArray[i].make_move())
// {
// display.playerArray[i] = null;
// }
//
// }
//
// display.Refresh();
// display.check_collision();
}
private void Form1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
// these are some special keys - N to make a new ship, P to pause the game, A to make a new sheet of asteroids
switch (e.KeyCode)
{
case (Keys.N):
if (display.theShip==null)
display.makeShip();
break;
case (Keys.P):
timer1.Enabled = !timer1.Enabled;
break;
case (Keys.A):
display.makeAsteroids();
break;
}
// forward all other key presses to the keyboard handling routines
if (display.theShip!=null)
display.theShip.setKeys(e);
}
private void Form1_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
{
// forward all the key releases to the keyhandling routines
if (display.theShip!=null)
display.theShip.releaseKeys(e);
}
private void Form1_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
// a special key press - this is to cope with the rapid tapping of the fire key
if (Char.ToString(e.KeyChar) == "k")
if (display.theShip!=null)
display.theShip.singleShot();
}
private void Form1_Resize(object sender, System.EventArgs e)
{
if (this.display!=null)
this.display.Size = this.Size;
}
#region some code to test copying of files using file.copy versus filestream writing
// private void button1_Click_3(object sender, System.EventArgs e)
// {
// // it can be seen from the tests performed here that there is little
// // difference between copying files using file.copy
// // and copying the contents of a file stream to a new file.
//
// int numOfCopies = 100;
// timer1.Enabled = false;
// DateTime t1 = System.DateTime.Now;
// for(int i=0;i<=numOfCopies;i++)
// File.Copy("c:\\temp\\player_missile.wav", "c:\\temp\\p" + i.ToString()+".wav");
// DateTime t2 = System.DateTime.Now;
// TimeSpan snout = t2-t1;
// MessageBox.Show("Files:"+snout.TotalSeconds.ToString());
// DateTime t3 = System.DateTime.Now;
// FileStream myFileStream = new FileStream("c:\\temp\\player_missile.wav",FileMode.Open, FileAccess.Read );
// byte[] buffer = new Byte[myFileStream.Length];
// myFileStream.Read(buffer,0,(int)myFileStream.Length);
// FileStream myOutStream;
// for (int j=0; j<=numOfCopies;j++)
// {
// myOutStream = new FileStream("c:\\temp\\p" + j.ToString() + ".wav",FileMode.Create, FileAccess.Write);
// myOutStream.Write(buffer,0,(int)myFileStream.Length);
// myOutStream.Close();
// }
// DateTime t4 = System.DateTime.Now;
// TimeSpan snout2 = t4-t3;
// MessageBox.Show("Stream:" + snout2.TotalSeconds.ToString());
// }
#endregion
}
public class Player : UserControl
{
public int score = 0;
public Point[] shapeArray; // used for defining the shape that will be drawn - also used for collision detection
public int incX, incY; // the amount to offset this player by when it moves
public Color myColor; // the default colour for this player. This may be overriden in the animate methods
public PlayingField myField; // a reference to the control that will draw the player
public Region myRegion; // a region used in the collision detection. Or not. May not be used anymore.
public GraphicsPath myPath; // the graphics path used for collision detection
public object[,] animationArray; // a 2D array filled with [GraphicsPath, Pen | Brush]
public string[] destroysClass; // a string array that defines which classes this instance can destroy when a collision is detected
public string className; // use in conjunctin with destroysClass for collision detection
public string lastKeyPressed;
public Color[] possibleColors; // an array of colours that can be used for randomising the colour of the instance
public Player myOwner; // used by missiles for determining who gets score allocated in the event of a collision with a scorable object. Somewhat redundant in this code as there is only ever one player, however, this does allow for UFOs to fire missiles that can destroy asteroids, or for a second player at the same time
public int MyIndexInOwnerArray = 0; // used by the playingfield to determine which object to release
protected string createSound, killSound; //filenames
protected string soundPath = "d:\\my documents\\Visual Studio Projects\\asteroids\\Asteroids\\"; // root path to the sound files
public Player(PlayingField thePlayingField)
{
myField = thePlayingField;
//register this as a listener or subscriber to the tick event of the playingfield clock
myField.Timer.Tick += new System.EventHandler(this.game_Tick);
}
public virtual void launchMissile()
{
}
public virtual void singleShot()
{
}
//Define an event to be raised when this instance is ready for destruction
public event DeathHandler OnDeath;
public delegate void DeathHandler(Player thePlayer, System.EventArgs e);
private void game_Tick(object sender, System.EventArgs e)
{
this.make_move();
this.animate();
}
public virtual void animate()
{
// first up, make a copy of the shape array.
Point[] tempPoints = new Point[this.shapeArray.GetLength(0)];
Array.Copy(this.shapeArray,tempPoints,this.shapeArray.GetLength(0));
// now translate this copy from its origins of 0,0 (relative to itself, not the screen)
// to where this object actually is on the screen
for (int i=0; i<=this.shapeArray.GetLength(0)-1; i++)
tempPoints[i].Offset(this.Location.X, this.Location.Y);
// now create the graphics path for this object and stick it into
// the animation array along with a brush or pen for filling / drawing with
Brush myBrush = new SolidBrush(myColor);
myPath = new GraphicsPath();
myPath.AddPolygon(tempPoints);
animationArray = new Object[1,2];
animationArray[0,0] = myPath;
animationArray[0,1] = myBrush;
}
public virtual void playMySound(string filename)
{
myField.PlaySound(soundPath + filename);
}
public virtual void setKeys(KeyEventArgs e)
{
}
public virtual void releaseKeys(KeyEventArgs e)
{
}
public virtual void killMe(Player owner)
{
// this is crucial - if you don't unsubscribe as a listener, then the clock
// will retain a reference to this instance
myField.Timer.Tick -= new System.EventHandler(this.game_Tick);
if (OnDeath !=null)
OnDeath(this, new System.EventArgs());
}
public virtual void killMe()
{
myField.Timer.Tick -= new System.EventHandler(this.game_Tick);
}
public virtual void beforeDeath(Player owner)
{
}
public virtual void make_move()
{
int x = this.Location.X + incX;
int y = this.Location.Y + incY;
if (myField.bounce) // pinball mode - all players bounce of edges of playing field
{
if (x <= 0)
// we are hitting the left hand edge of the screen
// so we need to reverse the x direction
{
this.incX *=-1; // this bit reverses x direction
this.incY -=1; // this bit just changes the angle slightly
}
if (x+this.Width >= myField.Width)
{
this.incX *=-1;
this.incY +=1;
}
if (y <= 0)
{
this.incY *=-1;
this.incX +=1;
}
if (y + this.Width >= myField.Height)
{
this.incY *=-1;
this.incX -=1;
}
}
else // wrap around mode - go off one side and back on the other side
{
// now make sure that the new location fits on the screen -
// if it goes off one side of the screen, it should come back
// on the other.
if (x + this.Width < 0)
x = myField.Width;
if (x > myField.Width)
x = 1;; //0 - (int)this.Width/2;
if (y + this.Height < 0)
y = myField.Height;
if (y > myField.Height)
y = 1;//0 - (int)this.Height/2;
}
this.Location = new Point(x,y);
}
}
public class particle : Player
{
public int timeOut; // = 3000;
public int fader = 255;
public int timeoutCounter = 0;
public particle(Point myPoint, PlayingField theField): base(theField)
{
timeOut = (int)(theField.randomGenerator.NextDouble() * 5000) + 1;
Color[] possibleColors;
possibleColors = new Color[15]
{
Color.BlueViolet,
Color.Crimson,
Color.Red,
Color.Yellow,
Color.ForestGreen,
Color.Azure,
Color.Chartreuse,
Color.Gold,
Color.DeepPink,
Color.Azure,
Color.DarkGoldenrod,
Color.DarkSalmon,
Color.DarkTurquoise,
Color.Purple,
Color.Lavender };
possibleColors = new Color[11]
{
Color.DarkGreen,
Color.DarkSeaGreen,
Color.Green,
Color.LawnGreen,
Color.LightGreen,
Color.ForestGreen,
Color.LimeGreen,
Color.Lime,
Color.SpringGreen,
Color.PaleGreen,
Color.SeaGreen
};
// Lime is the colour that really shows up against a black background
possibleColors = new Color[3]
{
Color.Red,
Color.Yellow,
Color.Red
};
// Color.LimeGreen,
// Color.LimeGreen,
// Color.LimeGreen,
// Color.LimeGreen,
// Color.LightGreen,
// Color.LimeGreen
// };
//
int colorIndex = (int)(myField.randomGenerator.NextDouble() * (possibleColors.GetLength(0)-1)) + 1;
myColor = Color.FromArgb(254, possibleColors[colorIndex]);
this.Location = myPoint;
//myColor = Color.Lime;
this.className = "particle";
this.destroysClass = new string[1]{" "};
}
public override void make_move()
{
this.incX = (int)(myField.randomGenerator.NextDouble() * 2) + 1;
this.incY = (int)(myField.randomGenerator.NextDouble() * 2) + 1;
// this.incY = -1;
if ((int)(myField.randomGenerator.NextDouble() * 10) + 1 >5)
this.incY = this.incY * - 1;
if ((int)(myField.randomGenerator.NextDouble() * 10) + 1 >5)
this.incX = this.incX * -1;
base.make_move();
}
public override void animate()
{
fader -=2;
timeoutCounter += this.myField.Timer.Interval;
if ( (fader < 0) | (timeoutCounter >= this.timeOut))
{
this.killMe(this);
return;
}
Brush myBrush = new SolidBrush(Color.FromArgb(fader,myColor));
myPath = new GraphicsPath();
this.myPath.AddEllipse(this.Location.X,this.Location.Y,3,3);
animationArray = new Object[1,2];
animationArray[0,0] = myPath;
animationArray[0,1] = myBrush;
}
}
public class Asteroid : Player
{
private int spawnsOnDeath; // how many asteroids does this create when it is destroyed - big asteroids produce 3 mediums, medium asteroids produce 2 smalls, smalls produce no asteroids at all (but a nice sparkly particle effect!)
private int fader = 0;
private int invincibleCounter = 0; // used to determine how long an asteroid is impervious to other asteroids during suicide mode
public Asteroid(Point startPoint, int Xinc, int Yinc, PlayingField Field, int spawn):base(Field)
{
switch (spawn)
{
case (3):
this.killSound = "bigasteroid.wav";
break;
case (2):
this.killSound = "mediumasteroid.wav";
break;
case (1):
this.killSound = "smallasteroid.wav";
break;
}
spawnsOnDeath = spawn;
className = "Asteroid";
destroysClass = new String[3];
destroysClass[0] = "Ship";
destroysClass[1] = "Missile";
if (myField.suicide)
destroysClass[2] = "Asteroid"; //this means that an asteroid may destroy another asteroid
else
destroysClass[2] = " "; // just a cludge to avoid having to redefine the array to be just 2 elements
myField = Field;
this.Location = startPoint;
// the x and y increments are random. By adding (3-spawn) to the increments, we force the smaller asteroids
// to tend towards being faster than bigger asteroids, though not necessarily. We need not do this at all
this.incX = Xinc + (3-spawn);
this.incY = Yinc + (3-spawn);
// big asteroids have a score of 10, mediums have 20, smalls have 30
this.score = (4-spawn) * 10;
// just make sure that all the randomness doesn't result in a stationary asteroid
// it has happened!
if ((this.incX ==0) & (this.incY==0))
{
//MessageBox.Show("Magic stationary asteroid");
this.incX = this.incY = 1;
}
possibleColors = new Color[15]
{
Color.BlueViolet,
Color.Crimson,
Color.Red,
Color.Yellow,
Color.Lime,
Color.Azure,
Color.Chartreuse,
Color.Gold,
Color.DeepPink,
Color.Azure,
Color.DarkGoldenrod,
Color.DarkSalmon,
Color.DarkTurquoise,
Color.Purple,
Color.Lavender };
int colorIndex = (int)(myField.randomGenerator.NextDouble() * (possibleColors.GetLength(0)-1)) + 1;
myColor = Color.FromArgb(200, possibleColors[colorIndex]);
//use the colorIndex as a radom amount to offset the asteroids points by for some variation
colorIndex = colorIndex * 2;
shapeArray = new Point[15];
shapeArray[0] = new Point(colorIndex+30,10);
shapeArray[1] = new Point(50+colorIndex,20+colorIndex);
shapeArray[2] = new Point(90+colorIndex,15+colorIndex);
shapeArray[3] = new Point(100+colorIndex,23+colorIndex);
shapeArray[4] = new Point(100+colorIndex,40+colorIndex);
shapeArray[5] = new Point(92+colorIndex,55+colorIndex);
shapeArray[6] = new Point(100+colorIndex,64+colorIndex);
shapeArray[7] = new Point(85+colorIndex,85+colorIndex);
shapeArray[8] = new Point(50+colorIndex,90+colorIndex);
shapeArray[9] = new Point(30+colorIndex,81+colorIndex);
shapeArray[10] = new Point(20+colorIndex,70+colorIndex);
shapeArray[11] = new Point(30+colorIndex,50+colorIndex);
shapeArray[12] = new Point(8+colorIndex,38+colorIndex);
shapeArray[13] = new Point(10+colorIndex,20+colorIndex);
shapeArray[14] = new Point(20+colorIndex,15+colorIndex);
// now perform a random rotation on the points for variety
// for the rotation angle, we'll just use the x coordinate,
// actually, now we have a random generator, lets use that
// change the size depending how many asteroids this asteroid will spawn
float sizeFactor = (float)spawnsOnDeath/3;
this.Size = new Size((int)(this.Width*sizeFactor),(int)(this.Height*sizeFactor));
Matrix myMatrix = new Matrix();
myMatrix.Scale(sizeFactor,sizeFactor);
Point asteroidCenter = new Point((int)this.Width/2, (int)this.Height/2);
myMatrix.RotateAt((int)(myField.randomGenerator.NextDouble()*360), asteroidCenter, MatrixOrder.Append);
myMatrix.TransformPoints(shapeArray);
}
public override void make_move()
{
base.make_move();
if (myField.suicide) // if we are in suicide mode (asteroids may destroy each other)
// so make sure that each asteroid is invincible for about 3 seconds
// this allows an asteroid to spawn its children without them immediately
// killing each other. It gives em time to move away from each other.
{
if (this.invincibleCounter<=3000)
{
invincibleCounter +=this.myField.Timer.Interval;
this.destroysClass[2] = " ";
}
if (invincibleCounter >= 3000)
this.destroysClass[2] = "Asteroid";
}
}
public override void beforeDeath(Player collidedWith)
{
this.playMySound(killSound);
// when a fatal collision is detected, the object that this instance collides with
// is passed in (as you can see above). This allows this instance to increment the
// the score of whoever owned the thing this is collided with (try reading that again).
// if the other party (missile?) in this collision has an owner (human player?)
// then increments its score
if (collidedWith.myOwner!=null)
collidedWith.myOwner.score += this.score;
int Xinc, Yinc;
// get our ramdom directions
if (spawnsOnDeath > 1) // spawnsOnDeath is 1, then this is a small asteroid which does not spawn any more
{
for (int i=1; i<= spawnsOnDeath; i++)
{
Xinc = (int)(4-spawnsOnDeath + myField.randomGenerator.NextDouble() * 4);
Yinc = (int)(4-spawnsOnDeath + myField.randomGenerator.NextDouble() * 4);
// make sure we have some that go right to left and bottom to top
if (myField.randomGenerator.NextDouble() > 0.5)
Xinc = Xinc * -1;
if (myField.randomGenerator.NextDouble() > 0.5)
Yinc = Yinc * -1;
myField.addPlayer(new Asteroid(this.Location,Xinc, Yinc, myField, spawnsOnDeath-1));
}
}
else
// add some particles when the last asteroid is killed
myField.spawnParticles(10,this.Location);
}
}
public class Missile:Player
{
protected Point StartPoint;
protected int distanceMoved;
//private SoundPlayer player;
public Missile(Point startPoint, int Xinc, int Yinc, PlayingField Field, Player owner):base(Field)
{
createSound = "player_missile.wav";
// if (createSound==null)
// createSound ="player_missile.wav";
//PlaySound(soundPath + createSound, 0, PlaySoundFlag.Filename|PlaySoundFlag.Asynchronously);
myOwner = owner;
StartPoint = startPoint;
destroysClass = new String[1];
//destroysClass[0] = "Ship";
destroysClass[0] = "Asteroid";
className = "Missile";
this.Size = new Size(6,6);
myField = Field;
this.Location = startPoint;
this.incX = Xinc * 2;
this.incY = Yinc * 2;
shapeArray = new Point[4];
shapeArray[0] = new Point(3,0);
shapeArray[1] = new Point(6,3);
shapeArray[2] = new Point(3,6);
shapeArray[3] = new Point(0,3);
myColor = Color.Red;
playMySound(createSound);
//myField.spawnParticles(2,this.Location);
//myField.Controls.Add(this);
}
public Missile(Point startPoint, PlayingField Field, Player owner):base(Field)
{
myOwner = owner;
StartPoint = startPoint;
myField = Field;
this.Location = startPoint;
}
public override void make_move()
{
base.make_move();
int x = Location.X - StartPoint.X;
int y = Location.Y - StartPoint.Y;
this.distanceMoved = (x*x) + (y*y);
if (this.distanceMoved>60000)
//if (System.Math.Pow((double)this.distanceMoved,(double)0.5) > 250)
{
this.beforeDeath(myOwner);
this.killMe(myOwner);
}
}
public override void beforeDeath(Player collidedWith)
// let's create a harmless explosion
{
myField.addPlayer(new MExplosion(this.Location, myField, myOwner,true));
}
}
public class MExplosion : Missile
{
private int animations=0;
private int maxAnimations=20;
private Matrix myMatrix = new Matrix();
private int fader=255;
private bool colorToggle = true;
public MExplosion(Point startPoint, PlayingField Field, Player owner, bool isHarmless) : base(startPoint, Field, owner)
{
createSound ="bigasteroid.wav";
//Field.player.PlaySound(soundPath + this.createSound);
playMySound(createSound);
destroysClass = new String[1];
if (isHarmless)
{
destroysClass[0] = " ";
className = "harmlessMExplosion";
}
else
{
destroysClass[0] = "Asteroid";
className = "MExplosion";
}
this.Size = new Size(30,30);
shapeArray = new Point[6];
shapeArray[0] = new Point(-10,-20);
shapeArray[1] = new Point(10,-20);
shapeArray[2] = new Point(20,0);
shapeArray[3] = new Point(10,20);
shapeArray[4] = new Point(-10,20);
shapeArray[5] = new Point(-20,0);
myMatrix.Reset();
// scale the matrix down so that we can shrink the shape defined above
// just cos I want it a bit smaller
myMatrix.Scale(0.6f,0.6f);
myMatrix.TransformPoints(shapeArray);
myMatrix.Reset();
}
public override void make_move()
{ /* override this method so that the explosion doesn't go anywhere */ }
public override void animate()
{
myColor = Color.FromArgb(fader,Color.Yellow);
animations++;
fader-=10;
//this next bit grows a hexagon
myMatrix.Reset();
myMatrix.Scale(1.05f,1.05f, MatrixOrder.Append);
myMatrix.TransformPoints(shapeArray);
// this bit grows a couple of circles
int innerCircle = (int)(animations/2+15);
int outerCircle = innerCircle + 10;
//this one is the edge of the circle
GraphicsPath myCircle = new GraphicsPath();
myCircle.AddEllipse(this.Location.X-innerCircle,this.Location.Y-innerCircle,innerCircle * 2,innerCircle * 2);
//Brush b1 = new SolidBrush(Color.FromArgb(fader,Color.Blue));
Pen p1 = new Pen(Color.FromArgb(fader,Color.Red),4);
//this one is the filled in portion
GraphicsPath myCircle2 = new GraphicsPath();
//myCircle2.AddEllipse(this.Location.X-outerCircle,this.Location.Y-outerCircle,outerCircle * 2,outerCircle * 2);
myCircle2.AddEllipse(this.Location.X-innerCircle,this.Location.Y-innerCircle,innerCircle * 2,innerCircle * 2);
Brush b2 = new SolidBrush(Color.FromArgb(fader,Color.Gold));
myColor = Color.FromArgb(fader,Color.Gold);
//Pen p2 = new Pen(Color.FromArgb(fader,Color.Yellow),2);
animationArray = new Object[2,2];
animationArray[0,0] = myCircle;
animationArray[0,1] = p1;
animationArray[1,0] = myCircle2;
animationArray[1,1] = b2;
//return ((animations%maxAnimations==0));
if (animations%maxAnimations==0 | fader<=0)
{
this.beforeDeath(myOwner);
this.killMe(myOwner);
}
}
public override void beforeDeath(Player collidedWith)
{
// if we don't override this method, it inherits from missile
// and thus spawns another explosion
}
}
public class Ship:Player
{
public int rotationAngle = 0;
public Point shipCenter;
public Point[] shipTarget;
private Point[] origShape;
public static int myScore = 0;
public bool[] controllers;
public int fireDelayCounter = 0;
public int singleShotCounter = 0;
public int singleShotFireDelay = 8;
// fireDelayCounter is incremented every call to make move
// which is governed by the time - originally set to 40ms = 25 ticks per second
// therefore fireDelayCounter is incremented 25 times per second
// so to fire twice in one second, we need a delay of about 12.5
// but is int so make it 12
public int fireDelay = 12;
public bool incSingleShotCounter = false;
public int invincibleCounter = 0;
private enum controls {anticlockwise = 0, clockwise = 1, thrust = 2, brake = 3, fire = 4};
public Ship(Point startPoint, int Xinc, int Yinc, PlayingField Field):base(Field)
{
controllers = new Boolean[5]{false,false,false,false,false};
score = Ship.myScore;
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Ship_KeyDown);
// this next point is used to calculate line equations for the missiles
// that the ship fires. As the ship rotates, we also rotate this point
// and this allows us to work out the gradient (the x increase and y increase)
// for the missiles' movements
destroysClass = new String[1];
destroysClass[0] = " ";
className = " Ship";// this will be set after 5 seconds
// although we only need one extra point to work out gradients for
// when new missiles are launched, it needs to be in an array
// in order to use the matrix.rotatePoints methods
shipTarget = new Point[1];
shipTarget[0] = new Point(20,0);
this.Size = new Size(40,40);
myField = Field;
this.Location = startPoint;
this.incX = 0;
this.incY = 0;
shapeArray = new Point[4];
shapeArray[0] = new Point(20,5);
shapeArray[1] = new Point(40,40);
shapeArray[2] = new Point(20,25);
shapeArray[3] = new Point(0,40);
origShape = new Point[shapeArray.GetLength(0)];
Array.Copy(shapeArray,origShape,shapeArray.GetLength(0));
shipCenter = new Point((int)(ClientRectangle.Width / 2), (int)(ClientRectangle.Height/2));
myColor = Color.DarkCyan;
}
public override void beforeDeath(Player collidedWith)
{
Ship.myScore = score;
myField.addPlayer(new MExplosion(this.Location, myField, myOwner,true));
}
public override void setKeys(KeyEventArgs e)
{
if (e.KeyCode == Keys.Z)
{
controllers[(int)controls.anticlockwise] = true;
lastKeyPressed = "anticlock";
}
if (e.KeyCode == Keys.X)
{
controllers[(int)controls.clockwise] = true;
lastKeyPressed = "clock";
}
if (e.KeyCode == Keys.K)
controllers[(int)controls.fire] = true;
if (e.KeyCode == Keys.J)
controllers[(int)controls.thrust] = true;
if (e.KeyCode == Keys.L)
controllers[(int)controls.brake] = true;
}
private void Ship_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
}
public override void launchMissile()
{
if (fireDelayCounter%fireDelay==0)
{
fireDelayCounter = fireDelayCounter%fireDelay;
//work out the incX and incY for the new missile
int incX = shipTarget[0].X - shapeArray[0].X;
int incY = shipTarget[0].Y - shapeArray[0].Y;
int createX = shapeArray[0].X + Location.X;
int createY = shapeArray[0].Y + Location.Y;
Point createAt = new Point(createX,createY);
// create the missile and tell the playing field of its existence
myField.addPlayer(new Missile(createAt,incX,incY,myField, this));
// myField.addPlayer(new Missile(createAt,incX-1,incY-1,myField, this));
// myField.addPlayer(new Missile(createAt,incX+1,incY+1,myField, this));
}
}
public override void make_move()
{
//ship should be invincible for about 5 seconds after being created
if (this.invincibleCounter<=5000)
{
invincibleCounter +=this.myField.Timer.Interval;
this.myColor = Color.FromArgb(125,Color.White);
}
if (invincibleCounter >= 5000)
{
this.myColor = Color.DarkGoldenrod;
this.className = "Ship";
}
fireDelayCounter++;
if (incSingleShotCounter)
singleShotCounter++;
// turn off the counter when the delay has expired
if (singleShotCounter%singleShotFireDelay==0)
{
incSingleShotCounter = false;
singleShotCounter = 0;
}
base.make_move();
// if (this.incX<=-1)
// this.incX++;
// if (this.incX>=1)
// this.incX--;
// if (this.incY>=1)
// this.incY--;
// if (this.incY<=-1)
// this.incY++;
if (controllers[(int)controls.anticlockwise] & controllers[(int)controls.clockwise])
{
// we need to work out which key gets precedence
// we will use the lastKeyPressed property
if (this.lastKeyPressed=="anticlock")
rotationAngle -=5;
else
rotationAngle +=5;
}
else
{
if (controllers[(int)controls.anticlockwise])
rotationAngle -=5;
if (controllers[(int)controls.clockwise])
rotationAngle +=5;
}
if (controllers[(int)controls.fire])
launchMissile(); // don't override the delay - only override the delay for a keypress event (as opposed to keydown)
if (controllers[(int)controls.thrust])
{
//work out the gradient and change the xinc and yinc accordingly
this.incX = shipTarget[0].X - shapeArray[0].X;
this.incY = shipTarget[0].Y - shapeArray[0].Y;
}
if (controllers[(int)controls.brake])
{
this.incX = 0;
this.incY = 0;
}
Matrix myMatrix = new Matrix();
myMatrix.RotateAt(rotationAngle, shipCenter, MatrixOrder.Append);
Array.Copy(origShape,shapeArray,shapeArray.GetLength(0));
myMatrix.TransformPoints(shapeArray);
shipTarget[0] = new Point(20,0);
myMatrix.TransformPoints(shipTarget);
}
public override void singleShot()
{
// when a shot is fired, reset a counter, and do not allow another shot
// until a certain time has elasped
if (singleShotCounter%fireDelay==0)
{
singleShotCounter = 0; //reset the counter
incSingleShotCounter = true;
//work out the incX and incY for the new missile
int incX = shipTarget[0].X - shapeArray[0].X;
int incY = shipTarget[0].Y - shapeArray[0].Y;
int createX = shapeArray[0].X + Location.X;
int createY = shapeArray[0].Y + Location.Y;
Point createAt = new Point(createX,createY);
// create the missile and tell the playing field of its existence
myField.addPlayer(new Missile(createAt,incX,incY,myField, this));
// myField.addPlayer(new Missile(createAt,incX-1,incY-1,myField, this));
// myField.addPlayer(new Missile(createAt,incX+1,incY+1,myField, this));
controllers[(int)controls.fire] = false;
}
}
public override void releaseKeys(KeyEventArgs e)
{
switch (e.KeyCode)
{
case (Keys.Z):
controllers[(int)controls.anticlockwise] = false;
if (lastKeyPressed == "anticlock")
lastKeyPressed = "";
break;
case (Keys.X):
controllers[(int)controls.clockwise] = false;
if (lastKeyPressed == "clock")
lastKeyPressed = "";
break;
case (Keys.K):
controllers[(int)controls.fire] = false;
break;
case (Keys.J):
controllers[(int)controls.thrust] = false;
break;
case (Keys.L):
controllers[(int)controls.brake] = false;
break;
}
}
}
public class PlayingField : UserControl
{
public Player[] playerArray;
public Player theShip;
public Random randomGenerator;
public SoundPlayer[] soundPlayers = new SoundPlayer[6];
public System.Windows.Forms.Timer Timer;
public int asteroidsRemaining = 0;
public int playerLivesRemaining = 2;
private int numOfAsteroidsForNewScreen = 0;
private bool isGameOver = false;
public bool bounce, suicide;
public bool optimiseCollisionDetection = true;
//private bool shouldCheckCollision = true;
public PlayingField(System.Windows.Forms.Timer theTimer, Point myLocation, Size mySize)
{
this.Location = myLocation;
this.Size = mySize;
// create an array of sound channels
for (int i = 0; i<=soundPlayers.GetLength(0)-1; i++)
soundPlayers[i] = new SoundPlayer();
// add the passed in timer so that other objects can see it
Timer = theTimer;
// now, rather than put some code in the tick event of the timer,
// we will register this playingfield as a listener (or subscriber)
// for the clock's tick event. We will tell it call this game_Tick event
// when it ticks.
Timer.Tick += new System.EventHandler(this.game_Tick);
// play around with the rendering settings to try and get better performance
this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
// this.SetStyle(ControlStyles.DoubleBuffer, true); - this line is actually quicker than the one preceding it, but it is not as smooth
//this array stores references to all the items on the playing field - except the score
playerArray = new Player[5];
//this random generator is used by various classes mainly for positioning of an instance on the screen
randomGenerator = new Random();
this.startGame();
}
public void startGame()
{
for (int i=0; i<=playerArray.GetLength(0)-1; i++)
{
if (playerArray[i]!=null)
playerArray[i].killMe(); // just pass 'this' in - it doesn't matter in this case what it is
playerArray[i] = null;
}
Ship.myScore = 0;
this.asteroidsRemaining = 0;
this.numOfAsteroidsForNewScreen = 0;
this.playerLivesRemaining = 3;
this.makeShip();
this.makeAsteroids();
this.isGameOver = false;
Timer.Enabled = true;
}
public void makeShip()
{
Point startPoint = new Point((int)(this.Width/2)-20,(int)(this.Height/2)-20);
this.theShip = new Ship(startPoint,0,0, this);
this.addPlayer(this.theShip);
}
private void game_Tick(object sender, System.EventArgs e)
{
// for (int z=1; z<=3;z++)
// this.spawnParticles(1,new Point((int)(this.Width/2)-20,(int)(this.Height/2)-20));
//
this.Refresh();
if (this.playerLivesRemaining == 0 & (!this.isGameOver))
{
this.gameOver();
}
else
{
if (!this.isGameOver)
this.check_collision();
else
this.startGame();
}
}
public void spawnParticles(int numOfParticles, Point myLocation)
{
for (int j=1;j<=numOfParticles;j++)
{
//Point jkl = new Point((int)this.Width/2, (int)this.Height/2);
this.addPlayer(new particle(myLocation,this));
}
}
public void makeAsteroids()
{
numOfAsteroidsForNewScreen++;
// randomly enter pinball / dodgem mode
if (this.randomGenerator.Next(0,9) == 1)
{
this.bounce = true;
}
else
{
this.bounce = false;
}
if (this.randomGenerator.Next(0,9) == 1)
{
this.suicide = true;
}
else
{
this.suicide = false;
}
int x, y, incX, incY;
Point startPoint;
for (int i=1; i<=numOfAsteroidsForNewScreen; i++)
{
//get our random start points
x = (int)(this.randomGenerator.NextDouble() * this.Width);
y = (int)(this.randomGenerator.NextDouble() * this.Height);
// if the start points are within a square that bounds the center of the screen
// where the ship is supposed to be
// better still, don't go against the centre, go against where the ship is at
// this time
int middleX, middleY;
//just to be safe lets check the ship exists
if (this.theShip !=null)
{
middleX = theShip.Location.X;
middleY = theShip.Location.Y;
}
else
{
middleX = (int)this.Width/2;
middleY = (int)this.Height/2;
}
if ( ((x > middleX - 400) & (x < middleX + 400)) & ((y > middleY-400) & (y < middleY + 400)) )
{
x = x - 400;
y = y - 400;
}
if (x<=0)
x = 1;
if (x>=this.Width)
x = this.Width-100;
if (y<=0)
y = 1;
if (y>=this.Height)
y = this.Height - 100;
// get our ramdom directions
incX = (int)(1 + this.randomGenerator.NextDouble() * 4);
incY = (int)(1 + this.randomGenerator.NextDouble() * 4);
// make sure we have some that go right to left and bottom to top
if (this.randomGenerator.NextDouble() > 0.5)
incX = incX * -1;
if (this.randomGenerator.NextDouble() > 0.5)
incY = incY * -1;
startPoint = new Point(x,y);
this.addPlayer(new Asteroid(startPoint,incX, incY, this, 3));
}
PlaySound("d:\\My Documents\\Visual Studio Projects\\asteroids\\Asteroids\\acid bass drum.wav");
}
public virtual void PlaySound(string filename)
{
for (int i=0; i<=soundPlayers.GetLength(0)-1;i++)
if (soundPlayers[i].isReady)
{
// we could reach each file into an array of bytes
// using filestream object. Then each time that file is requested
// we could squirt out a new file from the array
// and thus effectively copying the original file.
// This may work out quicker than the file.copy method
// as this way the source file for the sound is only read
// once.
FileStream myFileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
string myTempFile = "d:\\temp\\mysound" + i.ToString() + ".wav";
if (File.Exists(myTempFile))
File.Delete(myTempFile);
File.Copy(filename,myTempFile);
soundPlayers[i].PlaySound(myTempFile);
//soundPlayers[i].Player.FileName = filename;
// ThreadStart myStart = new ThreadStart(soundPlayers[i].PlaySound);
// Thread myThread = new Thread(myStart);
// myThread.Start();
break;
}
}
public void addPlayer(Player newPlayer)
{
// we need to redimension the playerArray
// to do this, we need to clone the playerArray
// and then redefine it to be one element bigger
// and then copy the old elements back, and add the new.
// If anyone knows a simpler way of redimensioning an array, please let me know jason.king@profox.co.uk
// To be efficient, we could reuse any null elements we find
int length = playerArray.GetLength(0);
bool shouldAdd = true;
if (newPlayer.className=="Asteroid")
{
this.asteroidsRemaining++;
}
// if (newPlayer.className=="Ship")
// {
// this.playerLivesRemaining--;
// }
for (int i = 0; i<=length-1; i++)
{
if (playerArray[i]==null)
{
playerArray[i] = newPlayer;
playerArray[i].MyIndexInOwnerArray = i;
// now register this playing field as a listener (subscriber) to
// the new player's OnDeath event
playerArray[i].OnDeath += new Player.DeathHandler(this.player_Dies);
//we don't need to add a new element
shouldAdd = false;
break; //escape out of loop as there may be more than one
// null and we don't want to add the player more than once
}
}
if (shouldAdd)
{
//create a clone of the original - we could use clone or copyto
Player[] temp = new Player[length];
playerArray.CopyTo(temp,0);
playerArray = new Player[length+1];
temp.CopyTo(playerArray,0);
playerArray[length] = newPlayer;
playerArray[length].MyIndexInOwnerArray = length;
playerArray[length].OnDeath += new Player.DeathHandler(this.player_Dies);
}
}
private void gameOver()
{
this.Timer.Enabled = false;
// do some score table stuff here.
MessageBox.Show("Your score is " + Ship.myScore.ToString());
this.isGameOver = true;
// for (int i=0; i<=playerArray.GetLength(0)-1; i++)
// {
// playerArray[i] = null;
// }
this.Timer.Enabled = true;
}
public void player_Dies(Player source, System.EventArgs e)
{
int theIndex = source.MyIndexInOwnerArray;
this.playerArray[theIndex]=null;
switch (source.className)
{
case ("Ship"):
this.playerLivesRemaining--; //this is done when a ship is added to the screen
this.theShip=null;
// if (this.playerLivesRemaining < 0)
// this.gameOver();
// else
// {
// Thread.Sleep(500);
// this.makeShip();
// }
if (this.playerLivesRemaining > 0)
{
this.makeShip();
}
break;
case ("Asteroid"):
this.asteroidsRemaining--;
if (this.asteroidsRemaining <= 0)
this.makeAsteroids();
break;
}
}
public void setPlayers(Player[] thePlayers)
{
playerArray = new Player[thePlayers.GetLength(0)];
Array.Copy(thePlayers,playerArray,thePlayers.GetLength(0));
}
public void check_collision()
{
for (int i=0; i<=playerArray.GetLength(0)-1; i++)
{
bool killI = false;
/* step through each player
* and check that its coordinates against each other player.
* If they intersect, remove one, two or none of the players
* depending on their destroysClass listing
*/
if ((playerArray[i]!=null))
{
Rectangle iRec = new Rectangle(playerArray[i].Location,playerArray[i].Size);
for (int j=0; j<=playerArray.GetLength(0)-1; j++)
{
if ((i==j) | (playerArray[j]==null))
continue; // ignore the rest of the code and loop round - no point checking an object against itself
// check to see if each is a valid target for the other
Rectangle jRec = new Rectangle(playerArray[j].Location,playerArray[j].Size);
bool killJ = false;
bool canKillJ = isValidTarget(playerArray[i].destroysClass,playerArray[j].className);
bool canKillI = isValidTarget(playerArray[j].destroysClass,playerArray[i].className);
// if neither are valid targets, loop round, otherwise, check to see if they collide
if (canKillJ | canKillI)
{
Rectangle ijRec = Rectangle.Intersect(iRec,jRec);
if (!ijRec.IsEmpty | !this.optimiseCollisionDetection)
{
#region Check objects intersect by checking points against graphics paths
if (playerArray[j]!=null)
{
Point pointToCheck;
foreach(Point p in playerArray[j].shapeArray)
{
// convert p into screen coordinates
int pX = p.X + playerArray[j].Location.X;
int pY = p.Y + playerArray[j].Location.Y;
// note - using PointToScreen was not working as expected
pointToCheck = new Point(pX,pY);
// we need the null check here since any asteroids we have added
// may not have been drawn yet and hence will not have their regions set up yet
if (playerArray[i].myPath!=null)
{
if(playerArray[i].myPath.IsVisible(pointToCheck))
{
killI = canKillI;
killJ = canKillJ;
// now we need to check which of the two
// are destroyed (it may be just one, both or neither)
#region Check if classname appears in target list
/*
foreach(string target in playerArray[i].destroysClass)
{
if (playerArray[j].className==target)
// playerArray[j] is killed
{
killJ = true;
break;
}
}
foreach(string target in playerArray[j].destroysClass)
{
if (playerArray[i].className==target)
// playerArray[i] is killed
{
killI = true;
break;
}
}
*/
#endregion
break;
}
}
}
}
#endregion
}
}
else
continue;
if (killJ)
playerArray[j].beforeDeath(playerArray[i]);
if (killI)
playerArray[i].beforeDeath(playerArray[j]);
if (killJ)
playerArray[j].killMe(playerArray[i]);
if (killI)
{
playerArray[i].killMe(playerArray[j]);
break;
}
}
}
}
}
public bool isValidTarget(string[] targetList, string searchFor)
{
bool retval = false;
foreach(string target in targetList)
{
if (searchFor==target)
{
retval = true;
break;
}
}
return retval;
}
protected override void OnPaint(PaintEventArgs pe)
{
SolidBrush brush = new SolidBrush(Color.FromArgb(128,Color.Aquamarine));
int theScore = 0;
if (this.theShip!=null)
{
theScore = this.theShip.score ;
Font myFont = new Font("Arial",14, FontStyle.Bold | FontStyle.Italic);
StringFormat drawFormat = new StringFormat();
drawFormat.FormatFlags = StringFormatFlags.DirectionRightToLeft;
Point myPoint = new Point(this.Width-10,10);
pe.Graphics.DrawString( this.playerLivesRemaining.ToString()+ " - " + theScore.ToString(),myFont,brush,myPoint,drawFormat);
}
for (int i=0; i<=playerArray.GetLength(0)-1; i++)
{
// it is likely that there are elements in the player array that are null (destroyed asteroids etc)
// in which case loop around and move on to the next element
if (playerArray[i]==null)
continue;
if (playerArray[i].animationArray!=null)
{
// step through an animation array and use its paths and shapes to draw to the screen
for (int k=0; k<=playerArray[i].animationArray.GetLength(0) - 1; k++)
{
if ((playerArray[i].animationArray[k,1] is Brush))
pe.Graphics.FillPath((Brush)playerArray[i].animationArray[k,1],(GraphicsPath)playerArray[i].animationArray[k,0]);
else
pe.Graphics.DrawPath((Pen)playerArray[i].animationArray[k,1], (GraphicsPath)playerArray[i].animationArray[k,0]);
}
}
}
}
}
public class SoundPlayer : UserControl
{
private System.ComponentModel.IContainer components;
//public AxMCI.AxMMControl Player; - beta2 code
public AxMediaPlayer.AxMediaPlayer Player; // released version code
public bool isReady = true;
public SoundPlayer()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));
//this.Player = new AxMCI.AxMMControl(); // beta2 code
this.Player = new AxMediaPlayer.AxMediaPlayer();
// This next line is either casting the activeX control to some handy type
// or perhaps glueing an interface to it. Maybe. I don't know. Too highbrow for me. Can anyone explain?
((System.ComponentModel.ISupportInitialize)(this.Player)).BeginInit();
this.Player.Enabled = true;
this.Player.Name = "Player";
this.Player.OcxState = ((System.Windows.Forms.AxHost.State)(resources.GetObject("Player.OcxState")));
this.Player.TabIndex = 0;
this.Player.Visible = false;
//this.Player.Done += new AxMCI.DmciEvents_DoneEventHandler(this.Player_Done); beta2 code
this.Player.PlayStateChange += new AxMediaPlayer._MediaPlayerEvents_PlayStateChangeEventHandler(Player_Done);
this.Controls.AddRange(new System.Windows.Forms.Control[] {this.Player});
((System.ComponentModel.ISupportInitialize)(this.Player)).EndInit();
this.Player.AutoStart = true; // release version code
/* beta2 code
this.Player.DeviceType = "WaveAudio";
this.Player.Wait = true;
this.Player.Shareable = false;
*/
this.Player.SendPlayStateChangeEvents = true; // release code version
//this.Player.Notify = true; // beta2 code
}
// private void Player_Done(object sender, AxMCI.DmciEvents_DoneEvent e) - beta2 code
private void Player_Done(object sender, AxMediaPlayer._MediaPlayerEvents_PlayStateChangeEvent e)
{
#region Released version of C#
if ( ((AxMediaPlayer.AxMediaPlayer)(sender)).PlayState == MediaPlayer.MPPlayStateConstants.mpStopped)
{
// in order for the File.Delete to work, we need to force the player to release the file
// Without documentation to help, I am forced to make it error with an empty filename
// Need to store the old filename before setting the new one to ""
string filename = this.Player.FileName;
this.Player.Open("");
File.Delete(filename);
this.isReady = true;
}
#endregion
#region beta2 code
//this.Player.Command = "Close"; beta2 code
//File.Delete(this.Player.FileName); //beta2 code
// this.Player.FileName = ""; // beta2 code
/*
if (e.notifyCode==1)
this.isReady = true;
else
this.isReady = false
*/
#endregion
}
public void PlaySound(string fileName)
{
if (this.isReady)
{
this.isReady = false;
/* beta2 code
if (fileName!=this.Player.FileName)
this.Player.FileName = fileName;
this.Player.Command = "Close";
this.Player.Command = "Open";
this.Player.Command = "Play";
*/
// release code version
this.Player.Open(fileName);
}
}
public void killPlayer()
{
//this.Player.Command = "Close"; //beta code version
this.Dispose(true);
}
}
public class SoundThread
{
public string soundFile;
public PlayBlah soundFlags;
public SoundThread(string fileName)//, PlayBlah flags)
{
this.soundFile = fileName;
this.soundFlags = PlayBlah.Filename|PlayBlah.Asynchronously;
}
public enum PlayBlah
{
Synchronously = 0x0000,
Asynchronously = 0x0001,
NoDefault = 0x0002,
Memory = 0x0004,
Loop = 0x0008,
NoStop = 0x0010,
NoWait = 0x00002000,
Alias = 0x00010000,
AliasId = 0x00110000,
Filename = 0x00020000,
Resource = 0x00040004,
Purge = 0x0040,
Application = 0x0080
}
// [DllImport("winmm.dll")]
// public static extern int PlaySound(String pszSound, int hmod,PlayBlah fdwSound);
public void Play()
{
// PlaySound(this.soundFile,0,this.soundFlags);
}
}
}