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)
                {