using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using csAsteroidsBot.Properties;
namespace csAsteroidsBot
{
// System.Windows.Forms.Form && multithreading && debugging needs this attribute.
// -> Stop the debugger from calling ToString() on breakpoint hit.
[System.Diagnostics.DebuggerDisplay("Form1")]
public partial class Form1 : Form
{
Socket socket = null;
EndPoint endpoint = null;
Queue<String> logQueue = new Queue<String>();
Thread botThread = null;
bool doExit = false;
bool doPause = false;
FramePacket frame = new FramePacket();
KeysPacket keys = new KeysPacket();
int prevKeysPacketsIndex = 0;
KeysPacket[] prevKeysPackets = new KeysPacket[NUM_PREVIOUS_KEYSPACKETS_MAX];
int gPing = 0;
int gLostFrames = 0;
String gDebugLabelText = "";
Vector2D gDebugVectorBlue = new Vector2D();
Vector2D gDebugVectorYellow1 = new Vector2D();
Vector2D gDebugVectorYellow2 = new Vector2D();
Vector2D gDebugRectYellow1 = new Vector2D();
Vector2D gDebugRectRed1 = new Vector2D();
public UInt32 gameFrameTime = 0;
byte rotationTableIndex = 0;
GState[] gStates = new GState[NUM_GSTATES_MAX];
int gStateIndex = 0;
GState gStateToDraw = null;
bool playing = false;
Vector2D deathShot = new Vector2D();
GObject aimedObject = new GObject();
int shotsFired = 0;
public const int SCREEN_X = 1024;
public const int SCREEN_Y = 768;
// gleichzeitig vorhandene Asteroiden: 26
// (ab dann entsteht immer nur ein kleinerer statt zwei bei Abschuss eines größeren)
public const int NUM_ASTEROIDS_MAX = 30;
// gleichzeitige eigene Schüsse: 4
// gleichzeitige Schüsse des UFO: 2
public const int NUM_SHOTS_MAX = 10;
public const int NUM_PREVIOUS_KEYSPACKETS_MAX = 3;
public const int PANIC_DISTANCE_PRESS_HYPERSPACE = 30 * 30;
public const int NUM_GSTATES_MAX = 10;
public Form1()
{
InitializeComponent();
log("application start");
checkBoxBotActivated.Checked = true;
checkBoxOnly5Minutes.Checked = false;
checkBoxVisualize.Checked = false;
for (int i = 0; i < NUM_PREVIOUS_KEYSPACKETS_MAX; i++)
{
prevKeysPackets[i] = new KeysPacket();
}
for (int i = 0; i < NUM_GSTATES_MAX; i++)
{
gStates[i] = new GState();
}
}
private void Form1_Load(object sender, EventArgs e)
{
String[] args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{
textBoxIPAddress.Text = args[1];
buttonConnect_Click(null, null);
}
else
{
textBoxIPAddress.Text = Settings.Default.DefaultIPAddress;
}
}
public void log(String s)
{
if (logQueue.Count < 10)
{
logQueue.Enqueue(s + "\r\n");
}
}
public void storeKeysToPreviousKeys()
{
KeysPacket k1 = keys;
KeysPacket k2 = prevKeysPackets[prevKeysPacketsIndex];
k2.keys = k1.keys;
k2.ping = k1.ping;
prevKeysPacketsIndex++;
if (prevKeysPacketsIndex == NUM_PREVIOUS_KEYSPACKETS_MAX)
{
prevKeysPacketsIndex = 0;
}
}
public KeysPacket getPrevKeysPacket(int age)
{
int index = prevKeysPacketsIndex - age;
if (index < 0) index = NUM_PREVIOUS_KEYSPACKETS_MAX - 1 - age;
return prevKeysPackets[index];
}
public float getAutoAimAllowance(int distanceToObjectSquared)
{
return (float)(Math.Sqrt(distanceToObjectSquared) / getShotVector().getLength());
}
public float getAutoAimAllowance(float distanceToObjectSquared)
{
return (float)(Math.Sqrt(distanceToObjectSquared) / getShotVector().getLength());
}
public Vector2D getShotVector()
{
Vector2D vShot = new Vector2D(rotationTable[rotationTableIndex].shotVectorX, rotationTable[rotationTableIndex].shotVectorY);
//if (vShot.X > 0) vShot.X *= 63.0F / 8.0F;
//else vShot.X *= 64.0F / 8.0F;
//if (vShot.Y > 0) vShot.Y *= 63.0F / 8.0F;
//else vShot.Y *= 64.0F / 8.0F;
return vShot;
}
public static Vector2D wrapScreen(Vector2D v)
{
while (v.X < -512) v.X += 1024; // normalize to -512 ... 511
while (v.X > 511) v.X -= 1024;
while (v.Y < -384) v.Y += 768; // normalize to -384 ... 383
while (v.Y > 383) v.Y -= 768;
return v;
}
public static Vector2D wrapScreenAbsolut(Vector2D v)
{
// Weil der Bildschirm 4:3-Format hat, werden von dem technisch möglichen Koordinatenbereich
// für die y-Achse nur die Werte 128 bis 895 genutzt, während die x-Koordinaten Werte zwischen
// 0 und 1023 annehmen. Größere Zahlen sind rechts beziehungsweise oben.
// -> Die linke untere Ecke ist bei (0, 128), die rechte obere bei (1023, 895).
if (v.X < 0) v.X += 1023;
if (v.X > 1023) v.X -= 1023;
if (v.Y < 128) v.Y += (895 - 128);
if (v.Y > 895) v.Y -= (895 - 128);
return v;
}
public static float wrapScreenAbsolutX(float x)
{
if (x < 0) x += 1023;
if (x > 1023) x -= 1023;
return x;
}
public static float wrapScreenAbsolutY(float y)
{
if (y < 128) y += (895 - 128);
if (y > 895) y -= (895 - 128);
return y;
}
public static float wrapScreenX(float x)
{
while (x < -512) x += 1024; // normalize to -512 ... 511
while (x > 511) x -= 1024;
return x;
}
public static int wrapScreenX(int x)
{
while (x < -512) x += 1024; // normalize to -512 ... 511
while (x > 511) x -= 1024;
return x;
}
public static float wrapScreenY(float y)
{
while (y < -384) y += 768; // normalize to -384 ... 383
while (y > 383) y -= 768;
return y;
}
public static int wrapScreenY(int y)
{
while (y < -384) y += 768; // normalize to -384 ... 383
while (y > 383) y -= 768;
return y;
}
public FramePacket receivePacket()
{
try
{
while (true)
{
//bool dataAvailable = socket.Poll(-1, SelectMode.SelectRead);
bool dataAvailable = socket.Poll(10000000, SelectMode.SelectRead);
if (!dataAvailable) log("no data available");
byte[] receiveBytes = new byte[socket.Available];
int received = socket.ReceiveFrom(receiveBytes, ref endpoint);
if (received != 1026)
{
String s = "" + Convert.ToChar(receiveBytes[0]) +
Convert.ToChar(receiveBytes[1]) +
Convert.ToChar(receiveBytes[2]) +
Convert.ToChar(receiveBytes[3]);
if (s.Equals("busy"))
{
log("server send <busy>");
doExit = true;
}
else if (s.Equals("game"))
{
log("server send <game over>");
doExit = true;
}
else
{
log("ReceivePacket() got " + received + " bytes ??? : " + s + "...");
}
}
bool moreDataAvailable = socket.Poll(0, SelectMode.SelectRead);
if (moreDataAvailable == false)
{
return FramePacket.FromByteArray(receiveBytes);
}
else
{
log("moreDataAvailable");
}
}
}
catch (Exception e)
{
log("Exception in ReceivePacket(): " + e.Message);
terminateThreadCloseSocket(100);
}
return null;
}
public void SendPacket(KeysPacket packet)
{
byte[] byteCommand = packet.ToByteArray();
socket.SendTo(byteCommand, byteCommand.Length, SocketFlags.None, endpoint);
}
public void terminateThreadCloseSocket(int msTimeout)
{
doExit = true;
if (botThread != null)
{
botThread.Join(msTimeout);
}
if (socket != null)
{
socket.Close(msTimeout);
log("disconnected");
}
}
// ////////////////////////////////////////////////////////
#region form1 event handler
// ////////////////////////////////////////////////////////
private void buttonConnect_Click(object sender, EventArgs e)
{
try
{
terminateThreadCloseSocket(200);
doExit = false;
doPause = false;
String userInput = textBoxIPAddress.Text;
int serverPort = Settings.Default.MameUDPPort;
IPAddress serverIP = null;
if (!IPAddress.TryParse(userInput, out serverIP))
{
serverIP = Dns.GetHostAddresses(userInput)[0];
}
if (serverIP == null)
{
log("server hostname/IP address not valid: " + userInput);
return;
}
log("try connect to: " + userInput + " [" + serverIP.ToString() + "] " + "at port: " + serverPort);
endpoint = new IPEndPoint(serverIP, serverPort);
EndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 0);
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.ReceiveBufferSize = 1026;
socket.Blocking = false;
socket.Bind(localEndPoint);
log("connect OK");
ThreadStart threadStart = new ThreadStart(run);
//ThreadStart threadStart = new ThreadStart(run2);
//ThreadStart threadStart = new ThreadStart(writeRotationTableLogFile);
botThread = new Thread(threadStart);
botThread.Start();
botThread.Priority = ThreadPriority.AboveNormal;
gameFrameTime = 0;
}
catch (Exception ex)
{
log(ex.Message);
MessageBox.Show("" + ex.Message, "rockerBot - Connect Error");
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
terminateThreadCloseSocket(60);
System.IO.StreamWriter file = new System.IO.StreamWriter("outConsole.txt");
String str = "" + textBox1.Text;
file.WriteLine(str);
file.Close();
}
private void timer1_Tick(object sender, EventArgs e)
{
while (logQueue.Count > 0)
{
textBox1.AppendText(logQueue.Dequeue());
}
labelLatency.Text = "latency: " + gPing + " lostFrames: " + gLostFrames;
labelDebug01.Text = "frame: " + gameFrameTime;
labelDebug02.Text = "" + gDebugLabelText;
uint m = gameFrameTime / (60 * 60);
uint s = (gameFrameTime / 60) % 60;
labelDebug03.Text = "time: " + m + ":" + (s < 10 ? "0" : String.Empty) + s;
drawGState(gStateToDraw);
}
private void buttonDisconnect_Click(object sender, EventArgs e)
{
terminateThreadCloseSocket(100);
}
private void Form1_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)Keys.Escape)
{
Application.Exit();
}
}
private void checkBoxBotActivated_CheckedChanged(object sender, EventArgs e)
{
doPause = !doPause;
}
private void numericUpDownRefreshMs_ValueChanged(object sender, EventArgs e)
{
timer1.Interval = (int)numericUpDownRefreshMs.Value;
}
#endregion
// ////////////////////////////////////////////////////////
public class Vector2D
{
public float X; // speed vector x
public float Y; // speed vector y
public Vector2D()
{
X = 0;
Y = 0;
}
public Vector2D(Vector2D other)
{
X = other.X;
Y = other.Y;
}
public Vector2D(int x, int y)
{
X = x;
Y = y;
}
public Vector2D(float x, float y)
{
X = x;
Y = y;
}
public Vector2D(float rotation)
{
// Convert rotation to radians.
// -> Multiply by PI/180 to convert degrees to radians.
X = (float)(Math.Cos(rotation * Math.PI / 180.0));
Y = (float)(Math.Sin(rotation * Math.PI / 180.0));
}
public Vector2D getDeltaFrom(float x, float y)
{
Vector2D v = new Vector2D();
float dX = wrapScreenX(x - X);
float dY = wrapScreenY(y - Y);
v.X = dX;
v.Y = dY;
return v;
}
public Vector2D getDeltaFrom(Vector2D other)
{
return getDeltaFrom(other.X, other.Y);
}
public float getLength()
{
return (float)Math.Sqrt(X * X + Y * Y);
}
public Vector2D getNormalized()
{
float temp = 1.0F / getLength();
return new Vector2D(X * temp, Y * temp);
}
public Vector2D normalize()
{
float temp = 1.0F / getLength();
X *= temp;
Y *= temp;
return this;
}
// return a scalar value (single number), which for "unit vectors" is equal
// to the cosine of the angle between this vector and the input vector.
// for non-unit vectors, it is equal to the length of each multiplied by the cosine.
public float getDotProduct(Vector2D other)
{
return (X * other.X) + (Y * other.Y);
}
// rotates the vector around a center by an amount of degrees
public void rotateBy(float degrees, Vector2D center)
{
degrees *= (float)Math.PI / 180.0F;
float cs = (float)Math.Cos(degrees);
float sn = (float)Math.Sin(degrees);
X -= center.X;
Y -= center.Y;
X = X * cs - Y * sn;
Y = X * sn + Y * cs;
X += center.X;
Y += center.Y;
}
// calculates the angle of this vector in grad in the trigonometric sense.
// returns a value between 0 and 360.
float getAngleTrig()
{
if (X == 0)
return Y < 0 ? 270 : 90;
else
if (Y == 0)
return X < 0 ? 180 : 0;
if (Y > 0)
if (X > 0)
return (float)(Math.Atan(Y / X) * 180.0F / Math.PI);
else
return (float)(180.0 - Math.Atan(Y / -X) * 180.0F / Math.PI);
else
if (X > 0)
return (float)(360.0 - Math.Atan(-Y / X) * 180.0F / Math.PI);
else
return (float)(180.0 + Math.Atan(-Y / -X) * 180.0F / Math.PI);
}
// calculates the angle between this vector and another one in grad.
// returns a value between 0 and 90.
public float getAngleWith(Vector2D other)
{
double tmp = X * other.X + Y * other.Y;
if (tmp == 0.0)
return 90.0F;
tmp = tmp / Math.Sqrt((X * X + Y * Y) * (other.X * other.X + other.Y * other.Y));
if (tmp < 0.0)
tmp = -tmp;
return (float)(Math.Atan(Math.Sqrt(1 - tmp * tmp) / tmp) * 180.0 / Math.PI);
}
public Vector2D add(Vector2D other)
{
X += other.X;
Y += other.Y;
return this;
}
public Vector2D sub(Vector2D other)
{
X -= other.X;
Y -= other.Y;
return this;
}
public Vector2D mul(Vector2D other)
{
X *= other.X;
Y *= other.Y;
return this;
}
public Vector2D scalarMul(float f)
{
Vector2D v = new Vector2D();
v.X = X * f;
v.Y = Y * f;
return v;
}
public Vector2D div(Vector2D other)
{
X /= other.X;
Y /= other.Y;
return this;
}
public bool equlas(Vector2D other, float roundingError)
{
if (X + roundingError >= other.X &&
X - roundingError <= other.X &&
Y + roundingError >= other.Y &&
Y - roundingError <= other.Y)
{
return true;
}
return false;
}
public override string ToString()
{
return "X:" + X + " Y:" + Y;
}
}
public class FramePacket
{
public UInt16[] vectorram = new UInt16[513];
public char frameno; // wird bei jedem Frame inkrementiert
public char ping; // Der Server schickt das letzte empfangene ping-Byte zurück
public static FramePacket FromByteArray(byte[] array)
{
if (array.Length != 1026) return null;
FramePacket np = new FramePacket();
for (int i = 0; i < 513; i++)
{
// Damit ich später nicht über einen Pointer auf das Vector-Ram zugreifen
// muss, kopiere ich die Daten in ein ushort Array um.
np.vectorram[i] = (ushort)(array[2 * i] + array[(2 * i) + 1] * 256);
}
np.frameno = (char)array[1024];
np.ping = (char)array[1025];
return np;
}
public override string ToString()
{
return "frame:" + frameno + " ping:" + ping;
}
}
public class KeysPacket
{
const int KEY_HYPERSPACE = 1;
const int KEY_FIRE = 2;
const int KEY_THRUST = 4;
const int KEY_RIGHT = 8;
const int KEY_LEFT = 16;
public int keys;
public char ping; // wird vom Server bei nächster Gelegenheit zurückgeschickt. Für Latenzmessung.
public KeysPacket()
{
keys = '@';
ping = (char)0;
}
public byte[] ToByteArray()
{
byte[] bytes = new byte[8];
bytes[0] = (byte)'c';
bytes[1] = (byte)'t';
bytes[2] = (byte)'m';
bytes[3] = (byte)'a';
bytes[4] = (byte)'m';
bytes[5] = (byte)'e';
bytes[6] = (byte)keys;
bytes[7] = (byte)ping;
return bytes;
}
public void clear()
{
keys = '@';
}
public void hyperspace(bool b)
{
if (b)
keys |= KEY_HYPERSPACE;
else
keys &= ~KEY_HYPERSPACE;
}
public void fire(bool b)
{
if (b)
{
keys |= KEY_FIRE;
}
else
{
keys &= ~KEY_FIRE;
}
}
public void thrust(bool b)
{
if (b)
keys |= KEY_THRUST;
else
keys &= ~KEY_THRUST;
}
public void left(bool b)
{
if (b)
{
keys |= KEY_LEFT;
right(false);
}
else
keys &= ~KEY_LEFT;
}
public void right(bool b)
{
if (b)
{
keys |= KEY_RIGHT;
left(false);
}
else
keys &= ~KEY_RIGHT;
}
public bool isLeftPressed()
{
return (keys & KEY_LEFT) != 0;
}
public bool isRightPressed()
{
return (keys & KEY_RIGHT) != 0;
}
public bool isFirePressed()
{
return (keys & KEY_FIRE) != 0;
}
public bool isHyperspacePressed()
{
return (keys & KEY_HYPERSPACE) != 0;
}
public bool isThrustPressed()
{
return (keys & KEY_THRUST) != 0;
}
public override string ToString()
{
String s = "no keys";
if (isLeftPressed()) s = "left";
if (isRightPressed()) s = "right";
if (isFirePressed()) s += " fire";
return s;
}
}
public enum E_GOBJECT_TYPE
{
Unknown,
Asteriod,
Saucer,
Shot,
Ship
}
public enum E_GOBJECT_SUBTYPE
{
Unknown,
Shape_A,
Shape_B,
Shape_C,
Shape_D
}
public enum E_GOBJECT_SIZE
{
Unknown,
Small,
Mid,
Big
}
public enum E_GOBJECT_TRACKINGSTATE
{
Unknown,
WaitFrame01,
WaitFrame02,
WaitFrame03,
WaitFrame04,
WaitFrame05,
WaitFrame06,
WaitFrame07,
WaitFrame08,
WaitFrame09,
WaitFrame10,
Tracked
}
public class GObject
{
private Vector2D pos = new Vector2D();
public Vector2D Pos
{
get { return pos; }
set { pos = wrapScreen(value); }
}
private Vector2D vel = new Vector2D();
public Vector2D Vel
{
get { return vel; }
set { vel = wrapScreen(value); }
}
private E_GOBJECT_TYPE type;
public E_GOBJECT_TYPE Type
{
get { return type; }
set { type = value; }
}
private E_GOBJECT_SUBTYPE subType;
public E_GOBJECT_SUBTYPE SubType
{
get { return subType; }
set { subType = value; }
}
private E_GOBJECT_SIZE size;
public E_GOBJECT_SIZE Size
{
get { return size; }
set { size = value; }
}
private E_GOBJECT_TRACKINGSTATE trackingState;
public E_GOBJECT_TRACKINGSTATE TrackingState
{
get { return trackingState; }
set
{
if (value < E_GOBJECT_TRACKINGSTATE.Unknown) trackingState = E_GOBJECT_TRACKINGSTATE.Unknown;
else if (value <= E_GOBJECT_TRACKINGSTATE.Tracked) trackingState = value;
else trackingState = E_GOBJECT_TRACKINGSTATE.Tracked;
}
}
private int framesUntilHit;
//public int FramesUntilHit
//{
// get { return framesUntilHit; }
// set
// {
// if (value >= 0 && framesUntilHit != 0)
// framesUntilHit = value;
// }
//}
public int FramesUntilHit
{
get { return framesUntilHit; }
set
{
if (value >= 0)
framesUntilHit = value;
else
framesUntilHit = 0;
}
}
private int shotsTaken;
public int ShotsTaken
{
get { return shotsTaken; }
set { shotsTaken = value; }
}
private Vector2D estimatedHitPos = new Vector2D();
public Vector2D EstimatedHitPos
{
get { return estimatedHitPos; }
set { estimatedHitPos = wrapScreenAbsolut(value); }
}
private Vector2D initialPos = new Vector2D();
public Vector2D InitialPos
{
get { return initialPos; }
set { initialPos = value; }
}
private int framesAlive;
public int FramesAlive
{
get { return framesAlive; }
set { framesAlive = value; }
}
private int panicFactor;
public int PanicFactor
{
get { return panicFactor; }
set { panicFactor = value; }
}
private float distanceToShip;
public float DistanceToShip
{
get { return distanceToShip; }
set { distanceToShip = value; }
}
private string text;
public string Text
{
get { return text; }
set { text = value; }
}
public GObject()
{
clear();
}
public GObject clone()
{
GObject o = new GObject();
o.Pos = pos;
o.Vel = vel;
o.Type = type;
o.SubType = subType;
o.Size = size;
o.TrackingState = trackingState;
o.FramesUntilHit = framesUntilHit;
o.ShotsTaken = shotsTaken;
o.EstimatedHitPos = estimatedHitPos;
o.InitialPos = initialPos;
o.framesAlive = framesAlive;
o.PanicFactor = panicFactor;
o.DistanceToShip = distanceToShip;
o.Text = text;
return o;
}
public bool equals(GObject other, int frame)
{
float roundingError = 0.0f;
Vector2D newPos = new Vector2D(pos);
newPos.add(vel.scalarMul(frame));
newPos.X = (float)Math.Floor(newPos.X);
newPos.Y = (float)Math.Floor(newPos.Y);
wrapScreenAbsolut(newPos);
if (trackingState == E_GOBJECT_TRACKINGSTATE.Tracked)
{
roundingError = 2.0f;
}
else
{
roundingError = 10.0f;
}
if (type == E_GOBJECT_TYPE.Asteriod)
if (other.Type == E_GOBJECT_TYPE.Asteriod)
if (subType == other.SubType)
if (size == other.Size)
if (newPos.equlas(other.Pos, roundingError))
return true;
if (type == E_GOBJECT_TYPE.Saucer)
if (other.Type == E_GOBJECT_TYPE.Saucer)
return true;
if (type == E_GOBJECT_TYPE.Shot)
if (other.Type == E_GOBJECT_TYPE.Shot)
if (newPos.equlas(other.Pos, roundingError))
return true;
else
return false;
return false;
}
public void clear()
{
pos.X = 0;
pos.Y = 0;
vel.X = 0;
vel.Y = 0;
type = E_GOBJECT_TYPE.Unknown;
subType = E_GOBJECT_SUBTYPE.Unknown;
size = E_GOBJECT_SIZE.Unknown;
trackingState = E_GOBJECT_TRACKINGSTATE.Unknown;
framesUntilHit = 0;
shotsTaken = 0;
estimatedHitPos.X = 0;
estimatedHitPos.Y = 0;
initialPos.X = 0;
initialPos.Y = 0;
framesAlive = 0;
panicFactor = 0;
distanceToShip = 0;
text = "";
}
public override String ToString()
{
return "" + type + " " + subType + " " + size + " " +
" x:" + pos.X + " y:" + pos.Y + " vx:" + vel.X + " vy:" + vel.Y +
"fAlive:" + framesAlive + " fUntilHit:" + framesUntilHit + " " + text;
}
public void setAsAsteroid(int xPos, int yPos, int shape, int scale)
{
type = E_GOBJECT_TYPE.Asteriod;
pos.X = xPos;
pos.Y = yPos;
switch (shape)
{
case 1: subType = E_GOBJECT_SUBTYPE.Shape_A; break;
case 2: subType = E_GOBJECT_SUBTYPE.Shape_B; break;
case 3: subType = E_GOBJECT_SUBTYPE.Shape_C; break;
case 4: subType = E_GOBJECT_SUBTYPE.Shape_D; break;
default: subType = E_GOBJECT_SUBTYPE.Unknown; break;
}
switch (scale)
{
case 0: size = E_GOBJECT_SIZE.Big; break;
case 15: size = E_GOBJECT_SIZE.Mid; break;
case 14: size = E_GOBJECT_SIZE.Small; break;
default: size = E_GOBJECT_SIZE.Unknown; break;
}
}
public void setAsSaucer(int xPos, int yPos, int scale)
{
type = E_GOBJECT_TYPE.Saucer;
pos.X = xPos;
pos.Y = yPos;
switch (scale)
{
case 15: size = E_GOBJECT_SIZE.Big; break;
case 14: size = E_GOBJECT_SIZE.Small; break;
default: size = E_GOBJECT_SIZE.Unknown; break;
}
}
public void setAsShot(int xPos, int yPos)
{
type = E_GOBJECT_TYPE.Shot;
pos.X = xPos;
pos.Y = yPos;
}
}
public class GState
{
public GObject[] asteroids = new GObject[NUM_ASTEROIDS_MAX];
public int asteroidsCount = 0;
public GObject[] shots = new GObject[NUM_SHOTS_MAX];
public int shotsCount = 0;
public GObject saucer = new GObject();
public bool saucerPresent = false;
public GObject ship = new GObject();
public bool shipPresent = false;
public int shipDX = 0;
public int shipDY = 0;
public UInt32 frameNumber = 0;
public int asteroidsAndExplosionsOnScreen = 0;
public GState()
{
for (int i = 0; i < asteroids.Length; i++)
{
asteroids[i] = new GObject();
}
for (int i = 0; i < shots.Length; i++)
{
shots[i] = new GObject();
}
}
public void clear()
{
for (int i = 0; i < asteroids.Length; i++)
{
asteroids[i].clear();
}
asteroidsCount = 0;
for (int i = 0; i < shots.Length; i++)
{
shots[i].clear();
}
shotsCount = 0;
saucer.clear();
saucerPresent = false;
ship.clear();
shipPresent = false;
shipDX = 0;
shipDY = 0;
frameNumber = 0;
asteroidsAndExplosionsOnScreen = 0;
}
public GState clone()
{
GState s = new GState();
s.asteroidsCount = asteroidsCount;
for (int i = 0; i < asteroidsCount; i++)
{
s.asteroids[i] = asteroids[i].clone();
}
s.shotsCount = shotsCount;
for (int i = 0; i < shotsCount; i++)
{
s.shots[i] = shots[i].clone();
}
s.frameNumber = frameNumber;
s.saucer = saucer.clone();
s.saucerPresent = saucerPresent;
s.ship = ship.clone();
s.shipDX = shipDX;
s.shipDY = shipDY;
s.shipPresent = shipPresent;
return s;
}
public override string ToString()
{
return "frame:" + frameNumber + " astCnt:" + asteroidsCount + " shotCnt:" + shotsCount;
}
}
public GState getCurrentGState()
{
return gStates[gStateIndex];
}
public GState getLastGState()
{
int last = gStateIndex - 1;
if (last < 0) last = NUM_GSTATES_MAX - 1;
return gStates[last];
}
public GState getOldestGState()
{
int oldest = gStateIndex + 1;
if (oldest == NUM_GSTATES_MAX) oldest = 0;
return gStates[oldest];
}
public void incrementGStateIndex()
{
gStateIndex++;
if (gStateIndex == NUM_GSTATES_MAX) gStateIndex = 0;
}
public void storeFrameToGState(GState s)
{
ushort[] vector_ram = frame.vectorram;
int dx = 0, dy = 0;
int sf = 0;
int vx = 0, vy = 0, vz = 0, vs = 0;
int v1x = 0;
int v1y = 0;
int shipdetect = 0;
// reset state status
s.clear();
s.frameNumber = gameFrameTime;
if (frame.vectorram[0] != 0xe001 && frame.vectorram[0] != 0xe201)
{
// fatal error. first comand is always JMPL
log("unexpected command received: " + vector_ram[0]);
return;
}
int pc = 1;
for (; pc < 513; )
{
int op = vector_ram[pc] >> 12;
switch (op)
{
case 0xa: // LABS
vy = vector_ram[pc] & 0x3ff;
vx = vector_ram[pc + 1] & 0x3ff;
vs = vector_ram[pc + 1] >> 12;
break;
case 0xb: // HALT
return;
case 0xc: // JSRL
switch (vector_ram[pc] & 0xfff)
{
case 0x8f3:
s.asteroids[s.asteroidsCount++].setAsAsteroid(vx, vy, 1, vs);
s.asteroidsAndExplosionsOnScreen++;
break;
case 0x8ff:
s.asteroids[s.asteroidsCount++].setAsAsteroid(vx, vy, 2, vs);
s.asteroidsAndExplosionsOnScreen++;
break;
case 0x90d:
s.asteroids[s.asteroidsCount++].setAsAsteroid(vx, vy, 3, vs);
s.asteroidsAndExplosionsOnScreen++;
break;
case 0x91a:
s.asteroids[s.asteroidsCount++].setAsAsteroid(vx, vy, 4, vs);
s.asteroidsAndExplosionsOnScreen++;
break;
case 0x929:
s.saucerPresent = true;
s.saucer.setAsSaucer(vx, vy, vs);
break;
case 0x880: //SUB_EXPLOSION_XXL:
s.asteroidsAndExplosionsOnScreen++;
break;
case 0x896: //SUB_EXPLOSION_XL:
s.asteroidsAndExplosionsOnScreen++;
break;
case 0x8B5: //SUB_EXPLOSION_L:
s.asteroidsAndExplosionsOnScreen++;
break;
case 0x8D0: //SUB_EXPLOSION_S:
s.asteroidsAndExplosionsOnScreen++;
break;
}
break;
case 0xd: // RTSL
return;
case 0xe: // JMPL
/*
pc = vector_ram[pc] & 0xfff;
break;
*/
return;
case 0xf: // SVEC
/*
dy = vector_ram[pc] & 0x300;
if ((vector_ram[pc] & 0x400) != 0)
dy = -dy;
dx = (vector_ram[pc] & 3) << 8;
if ((vector_ram[pc] & 4) != 0)
dx = -dx;
scale = (((vector_ram[pc] & 8) >> 2) | ((vector_ram[pc] & 0x800) >> 11)) + 2;
vz = (vector_ram[pc] & 0xf0) >> 4;
*/
break;
default:
dy = vector_ram[pc] & 0x3ff;
if ((vector_ram[pc] & 0x400) != 0)
dy = -dy;
dx = vector_ram[pc + 1] & 0x3ff;
if ((vector_ram[pc + 1] & 0x400) != 0)
dx = -dx;
sf = op;
vz = vector_ram[pc + 1] >> 12;
if (dx == 0 && dy == 0 && vz == 15)
s.shots[s.shotsCount++].setAsShot(vx, vy);
if (op == 6 && vz == 12 && dx != 0 && dy != 0)
{
switch (shipdetect)
{
case 0:
v1x = dx;
v1y = dy;
++shipdetect;
break;
case 1:
s.shipPresent = true;
s.ship.Pos.X = vx;
s.ship.Pos.Y = vy;
s.shipDX = v1x - dx;
s.shipDY = v1y - dy;
++shipdetect;
break;
}
}
else if (shipdetect == 1)
shipdetect = 0;
break;
}
if (op <= 0xa)
++pc;
if (op != 0xe) // JMPL
++pc;
}
}
/// <summary>
/// tries to track GObjects on screen
/// </summary>
/// <param name="s1">the new state to be updated</param>
/// <param name="s2">the old state. stays untouched, only used for calculation</param>
public void updateTracking(GState s1, GState s2)
{
bool[] alreadyTrackedAsteroids = new bool[s1.asteroidsCount];
bool[] alreadyTrackedShots = new bool[s1.shotsCount];
for (int i = 0; i < s2.asteroidsCount; i++)
{
GObject a2 = s2.asteroids[i];
for (int k = 0; k < s1.asteroidsCount; k++)
{
GObject a1 = s1.asteroids[k];
if (!alreadyTrackedAsteroids[k] && a2.equals(a1, 1))
{
alreadyTrackedAsteroids[k] = true;
a1.TrackingState = a2.TrackingState + 1;
a1.FramesUntilHit = a2.FramesUntilHit - 1;
a1.ShotsTaken = a2.ShotsTaken;
a1.EstimatedHitPos.X = a2.EstimatedHitPos.X;
a1.EstimatedHitPos.Y = a2.EstimatedHitPos.Y;
a1.InitialPos.X = a2.InitialPos.X;
a1.InitialPos.Y = a2.InitialPos.Y;
a1.FramesAlive = a2.FramesAlive + 1;
a1.Text = a2.Text;
Vector2D v = new Vector2D(a1.InitialPos);
a1.Vel = v.getDeltaFrom(a1.Pos);
if ((a1.Vel.X == 0 && a1.Vel.Y == 0) || a1.FramesAlive == 0)
{
//log("initialPos == currentPos");
//log("old: " + a2.ToString());
//log("new: " + a1.ToString());
a1.Vel.X = a2.Vel.X;
a1.Vel.Y = a2.Vel.Y;
a1.FramesAlive = 0;
}
else
{
a1.Vel.X /= (float)a1.FramesAlive;
a1.Vel.Y /= (float)a1.FramesAlive;
if (a1.TrackingState == E_GOBJECT_TRACKINGSTATE.Tracked)
{
if (!a1.Vel.equlas(a2.Vel, 2.0f))
{
//log("!a1.Vel.equlas(a2.Vel, 2.0f)");
a1.Vel.X = a2.Vel.X;
a1.Vel.Y = a2.Vel.Y;
a1.InitialPos.X = a1.Pos.X;
a1.InitialPos.Y = a1.Pos.Y;
a1.FramesAlive = 0;
}
}
}
a1.DistanceToShip = a1.Pos.getDeltaFrom(s1.ship.Pos).getLength();
break;
}
}
}
for (int k = 0; k < s1.asteroidsCount; k++)
{
GObject a1 = s1.asteroids[k];
if (a1.FramesAlive == 0)
{
a1.InitialPos.X = a1.Pos.X;
a1.InitialPos.Y = a1.Pos.Y;
}
}
if (s1.asteroidsCount == 1)
{
GObject a1 = s1.asteroids[0];
if (a1.FramesUntilHit == 5)
{
//log("reSync Velocity:" + a1.ToString());
a1.InitialPos.X = a1.Pos.X;
a1.InitialPos.Y = a1.Pos.Y;
a1.FramesAlive = 0;
}
}
for (int i = 0; i < s2.shotsCount; i++)
{
GObject a2 = s2.shots[i];
for (int k = 0; k < s1.shotsCount; k++)
{
GObject a1 = s1.shots[k];
if (!alreadyTrackedShots[k] && a2.equals(a1, 1))
{
alreadyTrackedShots[k] = true;
a1.TrackingState = a2.TrackingState + 1;
a1.FramesUntilHit = a2.FramesUntilHit - 1;
a1.ShotsTaken = a2.ShotsTaken;
a1.EstimatedHitPos.X = a2.EstimatedHitPos.X;
a1.EstimatedHitPos.Y = a2.EstimatedHitPos.Y;
a1.InitialPos.X = a2.InitialPos.X;
a1.InitialPos.Y = a2.InitialPos.Y;
a1.FramesAlive = a2.FramesAlive + 1;
a1.Text = a2.Text;
Vector2D v = new Vector2D(a1.InitialPos);
a1.Vel = v.getDeltaFrom(a1.Pos);
if ((a1.Vel.X == 0 && a1.Vel.Y == 0) || a1.FramesAlive == 0)
{
a1.Vel.X = a2.Vel.X;
a1.Vel.Y = a2.Vel.Y;
}
else
{
a1.Vel.X /= (float)a1.FramesAlive;
a1.Vel.Y /= (float)a1.FramesAlive;
if (a1.TrackingState == E_GOBJECT_TRACKINGSTATE.Tracked)
{
if (!a1.Vel.equlas(a2.Vel, 2.0f))
{
a1.Vel.X = a2.Vel.X;
a1.Vel.Y = a2.Vel.Y;
}
}
}
a1.DistanceToShip = a1.Pos.getDeltaFrom(s1.ship.Pos).getLength();
break;
}
}
}
for (int k = 0; k < s1.shotsCount; k++)
{
GObject a1 = s1.shots[k];
if (a1.TrackingState == E_GOBJECT_TRACKINGSTATE.Unknown)
{
if (a1.FramesAlive == 0)
{
a1.InitialPos.X = a1.Pos.X;
a1.InitialPos.Y = a1.Pos.Y;
}
float shipSize = 25.0f;
if (a1.Pos.X - shipSize < s1.ship.Pos.X && a1.Pos.X + shipSize > s1.ship.Pos.X)
{
if (a1.Pos.Y - shipSize < s1.ship.Pos.Y && a1.Pos.Y + shipSize > s1.ship.Pos.Y)
{
a1.FramesUntilHit = 69;
a1.EstimatedHitPos.X = deathShot.X;
a1.EstimatedHitPos.Y = deathShot.Y;
//log("new own Shot detected");
}
}
}
}
if (s2.saucerPresent && s1.saucerPresent)
{
s1.saucer.TrackingState = s2.saucer.TrackingState + 1;
s1.saucer.FramesUntilHit = s2.saucer.FramesUntilHit - 1;
s1.saucer.ShotsTaken = s2.saucer.ShotsTaken;
s1.saucer.EstimatedHitPos.X = s2.saucer.EstimatedHitPos.X;
s1.saucer.EstimatedHitPos.Y = s2.saucer.EstimatedHitPos.Y;
s1.saucer.Text = s2.saucer.Text;
Vector2D v = new Vector2D(s2.saucer.Pos);
s1.saucer.Vel = v.getDeltaFrom(s1.saucer.Pos);
s1.saucer.Vel.add(s2.saucer.Vel);
s1.saucer.Vel.X /= 2.0f;
s1.saucer.Vel.Y /= 2.0f;
s1.saucer.DistanceToShip = s1.saucer.Pos.getDeltaFrom(s1.ship.Pos).getLength();
if (s1.saucer.TrackingState == E_GOBJECT_TRACKINGSTATE.Tracked)
{
if (!s1.saucer.Vel.equlas(s2.saucer.Vel, 2.0f))
{
s1.saucer.TrackingState = E_GOBJECT_TRACKINGSTATE.Unknown;
s1.saucer.FramesUntilHit = 0;
s1.saucer.ShotsTaken = 0;
s1.saucer.EstimatedHitPos.X = 0;
s1.saucer.EstimatedHitPos.Y = 0;
}
}
}
if (s2.shipPresent && s1.shipPresent)
{
Vector2D v = new Vector2D(s2.ship.Pos);
s1.ship.Vel = v.getDeltaFrom(s1.ship.Pos);
s1.ship.Vel.add(s2.ship.Vel);
s1.ship.Vel.X /= 2.0f;
s1.ship.Vel.Y /= 2.0f;
//log("shipV:" + s1.ship.Vel.ToString());
}
}
/// <summary>
/// extrapolate GState source by frame Frames
/// </summary>
/// <param name="source">base of extrapolation. stays untouched</param>
/// <param name="extrapolated">the result (GState is to be provided by caller)</param>
/// <param name="frame">how many frames to look forward/backward in time</param>
/// <returns></returns>
public GState getExtrapolatedGstate(GState source, ref GState extrapolated, int frame)
{
if (frame == 0) frame = 1;
extrapolated.asteroidsCount = source.asteroidsCount;
extrapolated.frameNumber = source.frameNumber + (uint)frame;
extrapolated.saucerPresent = source.saucerPresent;
extrapolated.shipDX = source.shipDX;
extrapolated.shipDY = source.shipDY;
extrapolated.shipPresent = source.shipPresent;
extrapolated.shotsCount = source.shotsCount;
extrapolated.ship.Pos.X = source.ship.Pos.X + source.ship.Vel.X * frame;
extrapolated.ship.Pos.Y = source.ship.Pos.Y + source.ship.Vel.Y * frame;
extrapolated.ship.Vel.X = source.ship.Vel.X;
extrapolated.ship.Vel.Y = source.ship.Vel.Y;
extrapolated.ship.DistanceToShip = source.ship.Pos.getDeltaFrom(source.ship.Pos).getLength();
extrapolated.ship.FramesUntilHit = source.ship.FramesUntilHit - frame;
extrapolated.ship.ShotsTaken = source.ship.ShotsTaken;
extrapolated.ship.EstimatedHitPos.X = source.ship.EstimatedHitPos.X;
extrapolated.ship.EstimatedHitPos.Y = source.ship.EstimatedHitPos.Y;
extrapolated.ship.Size = source.ship.Size;
extrapolated.ship.SubType = source.ship.SubType;
extrapolated.ship.TrackingState = source.ship.TrackingState;
extrapolated.ship.Type = source.ship.Type;
extrapolated.saucer.Pos.X = source.saucer.Pos.X + source.saucer.Vel.X * frame;
extrapolated.saucer.Pos.Y = source.saucer.Pos.Y + source.saucer.Vel.Y * frame;
extrapolated.saucer.Vel.X = source.saucer.Vel.X;
extrapolated.saucer.Vel.Y = source.saucer.Vel.Y;
extrapolated.saucer.DistanceToShip = source.saucer.Pos.getDeltaFrom(source.ship.Pos).getLength();
extrapolated.saucer.FramesUntilHit = source.saucer.FramesUntilHit - frame;
extrapolated.saucer.ShotsTaken = source.saucer.ShotsTaken;
extrapolated.saucer.EstimatedHitPos.X = source.saucer.EstimatedHitPos.X;
extrapolated.saucer.EstimatedHitPos.Y = source.saucer.EstimatedHitPos.Y;
extrapolated.saucer.Size = source.saucer.Size;
extrapolated.saucer.SubType = source.saucer.SubType;
extrapolated.saucer.TrackingState = source.saucer.TrackingState;
extrapolated.saucer.Type = source.saucer.Type;
for (int i = 0; i < source.asteroidsCount; i++)
{
extrapolated.asteroids[i].Pos.X = source.asteroids[i].Pos.X + source.asteroids[i].Vel.X * frame;
extrapolated.asteroids[i].Pos.Y = source.asteroids[i].Pos.Y + source.asteroids[i].Vel.Y * frame;
extrapolated.asteroids[i].Vel.X = source.asteroids[i].Vel.X;
extrapolated.asteroids[i].Vel.Y = source.asteroids[i].Vel.Y;
extrapolated.asteroids[i].DistanceToShip = source.asteroids[i].Pos.getDeltaFrom(source.ship.Pos).getLength();
extrapolated.asteroids[i].FramesUntilHit = source.asteroids[i].FramesUntilHit - frame;
extrapolated.asteroids[i].ShotsTaken = source.asteroids[i].ShotsTaken;
extrapolated.asteroids[i].EstimatedHitPos.X = source.asteroids[i].EstimatedHitPos.X;
extrapolated.asteroids[i].EstimatedHitPos.Y = source.asteroids[i].EstimatedHitPos.Y;
extrapolated.asteroids[i].InitialPos.X = source.asteroids[i].InitialPos.X;
extrapolated.asteroids[i].InitialPos.Y = source.asteroids[i].InitialPos.Y;
extrapolated.asteroids[i].FramesAlive = source.asteroids[i].FramesAlive + frame;
extrapolated.asteroids[i].Size = source.asteroids[i].Size;
extrapolated.asteroids[i].SubType = source.asteroids[i].SubType;
extrapolated.asteroids[i].TrackingState = source.asteroids[i].TrackingState;
extrapolated.asteroids[i].Type = source.asteroids[i].Type;
}
for (int i = 0; i < source.shotsCount; i++)
{
extrapolated.shots[i].Pos.X = source.shots[i].Pos.X + source.shots[i].Vel.X * frame;
extrapolated.shots[i].Pos.Y = source.shots[i].Pos.Y + source.shots[i].Vel.Y * frame;
extrapolated.shots[i].Vel.X = source.shots[i].Vel.X;
extrapolated.shots[i].Vel.Y = source.shots[i].Vel.Y;
extrapolated.shots[i].DistanceToShip = source.shots[i].Pos.getDeltaFrom(source.ship.Pos).getLength();
extrapolated.shots[i].FramesUntilHit = source.shots[i].FramesUntilHit - frame;
extrapolated.shots[i].ShotsTaken = source.shots[i].ShotsTaken;
extrapolated.shots[i].EstimatedHitPos.X = source.shots[i].EstimatedHitPos.X;
extrapolated.shots[i].EstimatedHitPos.Y = source.shots[i].EstimatedHitPos.Y;
extrapolated.shots[i].Size = source.shots[i].Size;
extrapolated.shots[i].SubType = source.shots[i].SubType;
extrapolated.shots[i].TrackingState = source.shots[i].TrackingState;
extrapolated.shots[i].Type = source.shots[i].Type;
}
return extrapolated;
}
public float getGObjectPixelSize(GObject o)
{
float size = 5.0F;
if (o.Type == E_GOBJECT_TYPE.Asteriod)
{
if (o.Size == E_GOBJECT_SIZE.Big) size = 31.0F;
else if (o.Size == E_GOBJECT_SIZE.Mid) size = 15.0F;
else if (o.Size == E_GOBJECT_SIZE.Small) size = 7.0F;
}
else if (o.Type == E_GOBJECT_TYPE.Saucer)
{
if (o.Size == E_GOBJECT_SIZE.Big) size = 12.0F;
else if (o.Size == E_GOBJECT_SIZE.Small) size = 7.0f;
}
return size;
}
uint framesSinceLastResync = 0;
public byte reSyncShotRotation(GState s)
{
byte newRotationTableIndex = rotationTableIndex;
byte oldRotationTableIndex = rotationTableIndex;
int initialShotPosX = 0;
int initialShotPosY = 0;
bool doReSync = false;
bool initialShotFound = false;
if (s.shipDX == 0 && s.shipDY == 0)
{
return rotationTableIndex;
}
if (keys.isLeftPressed())
{
// because we sent left, the received DX/DY should be rotIndex -1
byte oldIndex = (byte)(rotationTableIndex - (byte)1);
int expectedDX = rotationTable[oldIndex].shipDX;
int expectedDY = rotationTable[oldIndex].shipDY;
if ((s.shipDX != expectedDX) || (s.shipDY != expectedDY))
{
//log("reSync (left was pressed)");
//log("old rotIndex was:" + oldIndex);
//log("current rotIndex:" + rotationTableIndex);
//log("received shipDX:" + s.shipDX + " shipDY:" + s.shipDY);
//log("expected DX:" + expectedDX + " DY:" + expectedDY);
doReSync = true;
}
}
else if (keys.isRightPressed())
{
// because we sent right, the received DX/DY should be rotIndex +1
byte oldIndex = (byte)(rotationTableIndex + (byte)1);
int expectedDX = rotationTable[oldIndex].shipDX;
int expectedDY = rotationTable[oldIndex].shipDY;
if ((s.shipDX != expectedDX) || (s.shipDY != expectedDY))
{
//log("reSync (right was pressed)");
//log("old rotIndex was:" + oldIndex);
//log("current rotIndex:" + rotationTableIndex);
//log("received shipDX:" + s.shipDX + " shipDY:" + s.shipDY);
//log("expected DX:" + expectedDX + " DY:" + expectedDY);
doReSync = true;
}
}
if (!doReSync)
{
//log("reSync ok for rotIndex:" + oldRotationTableIndex);
return oldRotationTableIndex;
}
for (int i = 0; i < s.shotsCount; i++)
{
initialShotPosX = (int) Math.Round(s.shots[i].Pos.X - s.ship.Pos.X);
initialShotPosY = (int) Math.Round(s.shots[i].Pos.Y - s.ship.Pos.Y);
if (initialShotPosX >= -20 && initialShotPosX <= 20)
{
if (initialShotPosY >= -20 && initialShotPosY <= 20)
{
initialShotFound = true;
break;
}
}
}
if (!initialShotFound)
{
//log("reSync no closeShot found");
for (byte i = 0; i < 128; i++)
{
newRotationTableIndex = (byte)(oldRotationTableIndex + i);
if (rotationTable[newRotationTableIndex].shipDX == s.shipDX &&
rotationTable[newRotationTableIndex].shipDY == s.shipDY)
{
log("reSync by dx/dy +" + (i + 1) + " framesSinceLastResync: " + (gameFrameTime - framesSinceLastResync));
framesSinceLastResync = gameFrameTime;
if (keys.isLeftPressed()) newRotationTableIndex++;
if (keys.isRightPressed()) newRotationTableIndex--;
return newRotationTableIndex;
}
newRotationTableIndex = (byte)(oldRotationTableIndex - i);
if (rotationTable[newRotationTableIndex].shipDX == s.shipDX &&
rotationTable[newRotationTableIndex].shipDY == s.shipDY)
{
log("reSync by dx/dy " + (-i - 1) + " framesSinceLastResync: " + (gameFrameTime - framesSinceLastResync));
framesSinceLastResync = gameFrameTime;
if (keys.isLeftPressed()) newRotationTableIndex++;
if (keys.isRightPressed()) newRotationTableIndex--;
return newRotationTableIndex;
}
}
}
else
{
//log("reSync closeShot found");
for (byte i = 0; i < 128; i++)
{
newRotationTableIndex = (byte)(oldRotationTableIndex + i);
if (rotationTable[newRotationTableIndex].shipDX == s.shipDX &&
rotationTable[newRotationTableIndex].shipDY == s.shipDY &&
rotationTable[newRotationTableIndex].shotPosX == initialShotPosX &&
rotationTable[newRotationTableIndex].shotPosY == initialShotPosY)
{
log("reSync by dx/dy and closeShot +" + (i + 1) + " framesSinceLastResync: " + (gameFrameTime - framesSinceLastResync));
framesSinceLastResync = gameFrameTime;
if (keys.isLeftPressed()) newRotationTableIndex++;
if (keys.isRightPressed()) newRotationTableIndex--;
return newRotationTableIndex;
}
newRotationTableIndex = (byte)(oldRotationTableIndex - i);
if (rotationTable[newRotationTableIndex].shipDX == s.shipDX &&
rotationTable[newRotationTableIndex].shipDY == s.shipDY &&
rotationTable[newRotationTableIndex].shotPosX == initialShotPosX &&
rotationTable[newRotationTableIndex].shotPosY == initialShotPosY)
{
log("reSync by dx/dy and closeShot " + (-i - 1) + " framesSinceLastResync: " + (gameFrameTime - framesSinceLastResync));
framesSinceLastResync = gameFrameTime;
if (keys.isLeftPressed()) newRotationTableIndex++;
if (keys.isRightPressed()) newRotationTableIndex--;
return newRotationTableIndex;
}
}
}
log("reSync out of range");
return oldRotationTableIndex;
}
public void run()
{
char prevframe = (char)0;
GState s = getCurrentGState();
GObject killedObject = new GObject();
int canShoot = 0;
int canHyperjump = 0;
float min_dist = 0x7fffffff;
float min_dx = 0;
float min_dy = 0;
float panicDistance = min_dist;
bool doShoot = false;
int minShotFrame = 0x7fffffff;
int shotFrame = 0x7fffffff;
Vector2D vShot = null;
Vector2D vShotFrame = new Vector2D();
Vector2D vHitFrame = new Vector2D();
int minPanicFactor = 0x7fffffff;
bool collidingObject = false;
bool doAim = false;
float minHitFrames = 0x7fffffff;
int ownShotsOnScreen = 0;
int frameCountCurrentLevel = 0;
int frameCountPrepareLevel = 0;
Vector2D ast = new Vector2D();
Vector2D shot = new Vector2D();
GObject a = new GObject();
Vector2D futurePos = new Vector2D();
bool trippleShotState = false;
while (!doExit)
{
if (doPause)
{
Thread.Sleep(10);
continue;
}
++keys.ping;
if (keys.ping > 255) keys.ping = (char)0;
if (keys.isLeftPressed())
{
//log("sending left oldRot:" + rotationTableIndex);
rotationTableIndex++;
//log(" newRot:" + rotationTableIndex);
}
if (keys.isRightPressed())
{
//log("sending right oldRot:" + rotationTableIndex);
rotationTableIndex--;
//log(" newRot:" + rotationTableIndex);
}
if (keys.isHyperspacePressed())
{
KeysPacket kp = getPrevKeysPacket(1);
if (kp.isLeftPressed())
rotationTableIndex--;
if (kp.isRightPressed())
rotationTableIndex++;
}
if (keys.isFirePressed())
{
killedObject.FramesUntilHit = minShotFrame;
killedObject.ShotsTaken++;
killedObject.EstimatedHitPos.X = vHitFrame.X;
killedObject.EstimatedHitPos.Y = vHitFrame.Y;
deathShot.X = vShotFrame.X;
deathShot.Y = vShotFrame.Y;
//log("sending shot: estimated hit: " + deathShot.ToString());
shotsFired++;
//gDebugLabelText = "shotsFired: " + shotsFired;
}
SendPacket(keys);
storeKeysToPreviousKeys();
frame = receivePacket();
if (frame == null)
{
log("*** no frame received ***");
Thread.Sleep(0);
continue;
}
prevframe++;
if (prevframe > 255) prevframe = (char)0;
if (playing) gameFrameTime++;
if (frame.frameno != prevframe || frame.ping != keys.ping)
{
int ping = keys.ping - frame.ping;
if (ping < 0) ping += 256;
int lostframes = frame.frameno - prevframe;
if (lostframes < 0) lostframes += 256;
prevframe = frame.frameno;
if (playing) gameFrameTime += (UInt32)lostframes;
gPing = ping;
gLostFrames += lostframes;
//if (ping != 0) log("ping:" + ping);
//if (lostframes != 0) log("lostFrames:" + lostframes + " totalLostFrames:" + gLostFrames);
}
GState current = getCurrentGState();
GState last = getLastGState();
storeFrameToGState(current);
//log("asteroidsAndExposionsOnScreen:" + current.asteroidsAndExplosionsOnScreen);
rotationTableIndex = reSyncShotRotation(current);
updateTracking(current, last);
keys.clear();
s = current;
gStateToDraw = current;
if (s.asteroidsCount > 0)
{
frameCountCurrentLevel++;
}
else if (!s.saucerPresent && frameCountCurrentLevel > 300)
{
log("screen cleared in " + frameCountCurrentLevel + " frames");
frameCountCurrentLevel = 0;
frameCountPrepareLevel = 0;
}
if (!s.shipPresent)
{
frameCountCurrentLevel = 0;
continue;
}
playing = true;
if (checkBoxOnly5Minutes.Checked)
if (gameFrameTime > 18000)
continue;
min_dist = 0x7fffffff;
min_dx = 0;
min_dy = 0;
panicDistance = 0x7fffffff;
doShoot = false;
shotFrame = 0x7fffffff;
vShot = getShotVector();
minShotFrame = 0x7fffffff;
minPanicFactor = 0x7fffffff;
collidingObject = false;
minHitFrames = 0x7fffffff;
trippleShotState = false;
for (int i = 0; i < s.asteroidsCount + 1; i++)
{
if (i == s.asteroidsCount)
if (s.saucerPresent)
a = s.saucer;
else
continue;
else
a = s.asteroids[i];
float dx = a.Pos.X - s.ship.Pos.X;
float dy = a.Pos.Y - s.ship.Pos.Y;
dx = wrapScreenX(dx);
dy = wrapScreenY(dy);
float dist = dx * dx + dy * dy;
bool worthAiming = false;
float theta = getGObjectPixelSize(a);
if (a.TrackingState == E_GOBJECT_TRACKINGSTATE.Tracked || dist < 20000)
{
if (a.FramesUntilHit == 0)
{
worthAiming = true;
}
else if (a.Size == E_GOBJECT_SIZE.Big &&
a.ShotsTaken < 3 &&
a.Type != E_GOBJECT_TYPE.Saucer &&
ownShotsOnScreen < 3)
{
theta -= 10.0f;
worthAiming = true;
}
else if (a.Size == E_GOBJECT_SIZE.Mid &&
a.ShotsTaken < 3 &&
a.Type != E_GOBJECT_TYPE.Saucer &&
ownShotsOnScreen < 3)
{
theta -= 5.0f;
worthAiming = true;
}
}
if (worthAiming)
{
for (int frames = 0; frames < 68; frames++)
{
ast.X = a.Pos.X + a.Vel.X * frames;
ast.Y = a.Pos.Y + a.Vel.Y * frames;
wrapScreenAbsolut(ast);
if (a.PanicFactor == 0)
{
float boundingBox = theta + 25.0f;
if (ast.X - boundingBox < s.ship.Pos.X && ast.X + boundingBox > s.ship.Pos.X)
{
if (ast.Y - boundingBox < s.ship.Pos.Y && ast.Y + boundingBox > s.ship.Pos.Y)
{
a.Text = "COLLIDE";
a.PanicFactor = frames;
}
}
}
ast.X = a.Pos.X + a.Vel.X * frames;
ast.Y = a.Pos.Y + a.Vel.Y * frames;
shot.X = s.ship.Pos.X + vShot.X * frames;
shot.Y = s.ship.Pos.Y + vShot.Y * frames;
wrapScreenAbsolut(ast);
wrapScreenAbsolut(shot);
if (ast.X - theta < shot.X && ast.X + theta > shot.X)
{
if (ast.Y - theta < shot.Y && ast.Y + theta > shot.Y)
{
if (s.asteroidsAndExplosionsOnScreen < 24 ||
a.Size == E_GOBJECT_SIZE.Small ||
a.Type == E_GOBJECT_TYPE.Saucer ||
a.PanicFactor != 0)
{
doShoot = true;
shotFrame = frames + 1;
vShotFrame.X = shot.X;
vShotFrame.Y = shot.Y;
vHitFrame.X = ast.X;
vHitFrame.Y = ast.Y;
break;
}
}
}
}
}
float shipRot = rotationTable[rotationTableIndex].shotRotation;
shipRot += 180.0f;
float allowance = getAutoAimAllowance(dist);
futurePos.X = a.Pos.X + a.Vel.X * allowance;
futurePos.Y = a.Pos.Y + a.Vel.Y * allowance;
float futureDX = futurePos.X - s.ship.Pos.X;
float futureDY = futurePos.Y - s.ship.Pos.Y;
futureDX = wrapScreenX(futureDX);
futureDY = wrapScreenY(futureDY);
float astRot = (float)(Math.Atan2(futureDY, futureDX) * 180.0 / Math.PI);
astRot += 180.0f;
float angel = Math.Abs(astRot - shipRot);
float framesToRotate = angel / (3 * 360.0f / 256.0f);
float futureDistanceToShip = futurePos.getDeltaFrom(s.ship.Pos).getLength();
float framesShotWillFly = futureDistanceToShip / vShot.getLength();
float flyFactor = 5.0f - ownShotsOnScreen;
if (flyFactor < 1) flyFactor = 1.0f;
//float hitFrames = framesToRotate + framesShotWillFly / flyFactor;
float hitFrames = framesToRotate + framesShotWillFly;
if (shotFrame < minShotFrame)
{
minShotFrame = shotFrame;
killedObject = a;
if (frameCountCurrentLevel > 80)
{
if (killedObject.Size == E_GOBJECT_SIZE.Big ||
killedObject.Size == E_GOBJECT_SIZE.Mid)
{
if (a.ShotsTaken == 0 || a.ShotsTaken == 1)
{
trippleShotState = true;
}
}
}
}
doAim = false;
if (worthAiming)
{
if (a.PanicFactor != 0 && a.PanicFactor < minPanicFactor)
{
minPanicFactor = a.PanicFactor;
doAim = true;
collidingObject = true;
}
else if (collidingObject == false && hitFrames < minHitFrames)
{
minHitFrames = hitFrames;
doAim = true;
}
}
if (doAim)
{
aimedObject = a;
min_dist = dist;
min_dx = dx;
min_dy = dy;
float aimFactor = getAutoAimAllowance(min_dist);
min_dx += a.Vel.X * aimFactor;
min_dy += a.Vel.Y * aimFactor;
}
if (dist < 3000)
{
if (a.Size == E_GOBJECT_SIZE.Big) panicDistance = dist - 40 * 40;
else if (a.Size == E_GOBJECT_SIZE.Mid) panicDistance = dist - 20 * 20;
else if (a.Size == E_GOBJECT_SIZE.Small) panicDistance = dist - 8 * 8;
}
}
ownShotsOnScreen = 0;
for (int i = 0; i < s.shotsCount; ++i)
{
a = s.shots[i];
float dx = a.Pos.X - s.ship.Pos.X;
float dy = a.Pos.Y - s.ship.Pos.Y;
dx = wrapScreenX(dx);
dy = wrapScreenY(dy);
float dist = dx * dx + dy * dy; // squared distance to object
//log("x:" + a.x + " y:" + a.y);
if (a.FramesUntilHit != 0) ownShotsOnScreen++;
if (a.TrackingState == E_GOBJECT_TRACKINGSTATE.Tracked)
{
for (int frames = 0; frames < 72; frames++)
{
float x = a.Pos.X + a.Vel.X * frames;
float y = a.Pos.Y + a.Vel.Y * frames;
//x = wrapScreenX(x);
//y = wrapScreenY(y);
float shipSize = 20.0f;
if (x - shipSize < s.ship.Pos.X && x + shipSize > s.ship.Pos.X)
{
if (y - shipSize < s.ship.Pos.Y && y + shipSize > s.ship.Pos.Y)
{
a.Text = "COLLIDE";
a.PanicFactor = 100;
break;
}
}
}
}
if (dist < PANIC_DISTANCE_PRESS_HYPERSPACE && a.PanicFactor == 100)
{
panicDistance = dist;
//log("incomming shot: hyperjump");
}
}
gDebugVectorBlue.X = vShot.X;
gDebugVectorBlue.Y = vShot.Y;
float crossProduct = s.shipDX * min_dy - s.shipDY * min_dx;
float worthTurning = 0; // min_dist / 4;
if ((s.asteroidsCount > 0) || (s.saucerPresent))
{
if (crossProduct > worthTurning)
{
keys.left(true);
}
else if (crossProduct < -worthTurning)
{
keys.right(true);
}
}
if (canHyperjump == 1) canHyperjump = 2;
else if (canHyperjump == 2) canHyperjump = 0;
if (panicDistance < PANIC_DISTANCE_PRESS_HYPERSPACE && canHyperjump == 0)
{
canHyperjump = 1;
keys.hyperspace(true);
keys.left(false);
keys.right(false);
keys.thrust(false);
}
if (ownShotsOnScreen >= 4)
{
doShoot = false;
}
gDebugLabelText = "ownShots: " + ownShotsOnScreen;
if (canShoot == 1) canShoot = 2;
else if (canShoot == 2) canShoot = 0;
if (doShoot && canShoot == 0)
{
canShoot = 1;
keys.fire(true);
//log("frame:" + gameFrameTime + "\t shot");
}
incrementGStateIndex();
}
}
public void drawGState(GState s)
{
if (!checkBoxVisualize.Checked)
{
return;
}
if (s == null)
{
return;
}
Graphics g = panel.CreateGraphics();
// Weil der Bildschirm 4:3-Format hat, werden von dem technisch möglichen Koordinatenbereich
// für die y-Achse nur die Werte 128 bis 895 genutzt, während die x-Koordinaten Werte zwischen
// 0 und 1023 annehmen. Größere Zahlen sind rechts beziehungsweise oben.
// -> Die linke untere Ecke ist bei (0, 128), die rechte obere bei (1023, 895).
int top = 895;
int bottom = 128;
int left = 0;
int right = 1023;
float scalerX = ((right - left) + 1) / panel.Size.Width;
float scalerY = ((bottom - top) + 1) / panel.Size.Height;
Pen black = new Pen(Color.Black);
Pen red = new Pen(Color.Red);
Pen green = new Pen(Color.Green);
Pen blue = new Pen(Color.Blue);
Pen yellow = new Pen(Color.Yellow);
Pen orange = new Pen(Color.Orange);
Pen pink = new Pen(Color.Pink);
Pen cyan = new Pen(Color.Cyan);
Brush blackBrush = Brushes.Black;
Brush orangeBrush = Brushes.Orange;
Brush pinkBrush = Brushes.Pink;
Brush cyanBrush = Brushes.Cyan;
Font font8 = new Font("Arial", 8);
Font font16 = new Font("Arial", 16);
g.Clear(Color.DarkGray);
if (s.shipPresent)
{
float x = (s.ship.Pos.X / scalerX) - 5;
float y = ((s.ship.Pos.Y - top) / scalerY) - 5;
g.DrawEllipse(blue, x, y, 10, 10);
float velocityX = x + s.ship.Vel.X * 10;
float velocityY = y + (-s.ship.Vel.Y * 10);
g.DrawLine(red, x, y, velocityX, velocityY);
g.DrawString(s.ship.Text, font8, blackBrush, x + 20, y);
}
if (s.saucerPresent)
{
float x = s.saucer.Pos.X / scalerX;
float y = (s.saucer.Pos.Y - top) / scalerY;
float size = 8.0f;
if (s.saucer.Size == E_GOBJECT_SIZE.Big) size = 16;
if (s.saucer.FramesUntilHit == 0)
g.FillRectangle(blackBrush, x - size / 2, y - size / 2, size, size);
else
g.FillRectangle(orangeBrush, x - size / 2, y - size / 2, size, size);
float hitX = s.saucer.EstimatedHitPos.X / scalerX;
float hitY = (s.saucer.EstimatedHitPos.Y - top) / scalerY;
if (hitX != 0 && hitY != 0)
g.FillRectangle(pinkBrush, hitX - size / 2, hitY - size / 2, size, size);
float velocityX = x + s.saucer.Vel.X * 10;
float velocityY = y + (-s.saucer.Vel.Y * 10);
g.DrawLine(red, x, y, velocityX, velocityY);
g.DrawString(s.saucer.Text, font8, blackBrush, x + 20, y);
}
for (int i = 0; i < s.asteroidsCount; i++)
{
float x = (s.asteroids[i].Pos.X / scalerX);
float y = (s.asteroids[i].Pos.Y - top) / scalerY;
float size = 8.0f;
if (s.asteroids[i].Size == E_GOBJECT_SIZE.Big) size = 35.0f;
if (s.asteroids[i].Size == E_GOBJECT_SIZE.Mid) size = 18.0f;
if (s.asteroids[i].FramesUntilHit == 0)
g.DrawEllipse(black, x - size / 2, y - size / 2, size, size);
else
g.DrawEllipse(orange, x - size / 2, y - size / 2, size, size);
float hitX = (s.asteroids[i].EstimatedHitPos.X / scalerX);
float hitY = (s.asteroids[i].EstimatedHitPos.Y - top) / scalerY;
if (hitX != 0 && hitY != 0)
{
g.DrawEllipse(pink, hitX - size / 2, hitY - size / 2, size, size);
//s.asteroids[i].Text = "hitX:" + s.asteroids[i].EstimatedHitPos.X + "\r\nhitY:" + s.asteroids[i].EstimatedHitPos.Y;
}
float velocityX = x + s.asteroids[i].Vel.X * 10;
float velocityY = y + (-s.asteroids[i].Vel.Y * 10);
g.DrawLine(red, x, y, velocityX, velocityY);
g.DrawString(s.asteroids[i].Text, font8, blackBrush, x + 20, y);
//g.DrawString("" + i + " x:" + s.asteroids[i].x + " y:" + s.asteroids[i].y, font8, blackBrush, x - 15, y - 25);
//g.DrawString("" + s.asteroids[i].TrackingState, font8, blackBrush, x - 20, y - 28);
g.DrawString("" + s.asteroids[i].FramesUntilHit, font8, blackBrush, x - 5, y + 5);
g.DrawString("" + s.asteroids[i].ShotsTaken, font8, blackBrush, x - 5, y - 5);
}
for (int i = 0; i < s.shotsCount; i++)
{
float x = (s.shots[i].Pos.X / scalerX);
float y = (s.shots[i].Pos.Y - top) / scalerY;
float size = 4;
if (s.shots[i].PanicFactor == 100)
g.FillRectangle(blackBrush, x - size / 2, y - size / 2, size, size);
else
g.FillRectangle(pinkBrush, x - size / 2, y - size / 2, size, size);
float velocityX = x + s.shots[i].Vel.X * 10;
float velocityY = y + (-s.shots[i].Vel.Y) * 10;
g.DrawLine(red, x, y, velocityX, velocityY);
g.DrawString(s.shots[i].Text, font8, blackBrush, x + 20, y);
g.DrawString("" + s.shots[i].FramesUntilHit, font8, blackBrush, x - 5, y + 5);
float hitX = (s.shots[i].EstimatedHitPos.X / scalerX);
float hitY = (s.shots[i].EstimatedHitPos.Y - top) / scalerY;
if (hitX != 0 && hitY != 0)
{
g.DrawRectangle(cyan, hitX - size / 2, hitY - size / 2, size, size);
//s.asteroids[i].Text = "hitX:" + s.asteroids[i].EstimatedHitPos.X + "\r\nhitY:" + s.asteroids[i].EstimatedHitPos.Y;
}
}
// DebugVectorBlue
{
float x = (s.ship.Pos.X / scalerX);
float y = ((s.ship.Pos.Y - top) / scalerY);
float velocityX = x + gDebugVectorBlue.X * 10;
float velocityY = y + (-gDebugVectorBlue.Y * 10);
g.DrawLine(blue, x, y, velocityX, velocityY);
}
// DebugVectorYellow1
{
if (gDebugVectorYellow1.X != 0 && gDebugVectorYellow1.Y != 0)
{
float x1 = (s.ship.Pos.X / scalerX);
float y1 = ((s.ship.Pos.Y - top) / scalerY);
float x2 = (gDebugVectorYellow1.X / scalerX);
float y2 = ((gDebugVectorYellow1.Y - top) / scalerY);
g.DrawLine(yellow, x1, y1, x2, y2);
}
}
// DebugVectorYellow2
{
if (gDebugVectorYellow2.X != 0 && gDebugVectorYellow2.Y != 0)
{