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)
                {
                    float x1 = (s.ship.Pos.X / scalerX);
                    float y1 = ((s.ship.Pos.Y - top) / scalerY);
 
                    float x2 = (gDebugVectorYellow2.X / scalerX);
                    float y2 = ((gDebugVectorYellow2.Y - top) / scalerY);
                    g.DrawLine(yellow, x1, y1, x2, y2);
                }
            }
            // DebugRectYellow1
            {
                if (gDebugRectYellow1.X != 0 && gDebugRectYellow1.Y != 0)
                {
                    float x1 = (gDebugRectYellow1.X / scalerX);
                    float y1 = ((gDebugRectYellow1.Y - top) / scalerY);
 
                    g.DrawRectangle(yellow, x1 - 5, y1 - 5, 10, 10);
                }
            }
            // DebugRectRed1
            {
                if (gDebugRectRed1.X != 0 && gDebugRectRed1.Y != 0)
                {
                    float x1 = (gDebugRectRed1.X / scalerX);
                    float y1 = ((gDebugRectRed1.Y - top) / scalerY);
 
                    g.DrawRectangle(red, x1 - 5, y1 - 5, 10, 10);
                }
            }
            // aimed object
            {
                float x = (aimedObject.Pos.X / scalerX);
                float y = (aimedObject.Pos.Y - top) / scalerY;
                float size = 8.0f;
                if (aimedObject.Size == E_GOBJECT_SIZE.Big) size = 35.0f;
                if (aimedObject.Size == E_GOBJECT_SIZE.Mid) size = 18.0f;
 
                g.DrawEllipse(green, x - size / 2, y - size / 2, size, size);
            }
        }
 
 
        // ////////////////////////////////////////////////////////
 
        #region internal shot rotation array [256]
        // ////////////////////////////////////////////////////////
 
        public struct RotationTableEntry
        {
            public int index;
            public int shipDX;
            public int shipDY;
            public float shotRotation;
            public int shotPosX;
            public int shotPosY;
            public float shotVectorX;
            public float shotVectorY;
 
            public RotationTableEntry(int i, int sX, int sY, float rot, int shotX, int shotY, float shotVx, float shotVy)
            {
                index = i;
                shipDX = sX;
                shipDY = sY;
                shotRotation = rot;
                shotPosX = shotX;
                shotPosY = shotY;
                shotVectorX = shotVx;
                shotVectorY = shotVy;
            }
        }
 
        RotationTableEntry[] rotationTable = 
        {   //new RotationTableEntry(0, 0, 0, 0F, 0, 0) };
            new RotationTableEntry( 0,    1536,    0,    0F,    19,    0,    8.15F,    0F),
            new RotationTableEntry( 1,    1536,    0,    3.510679F,    19,    1,    8.15F,    0.5F),
            new RotationTableEntry( 2,    1528,    152,    8.031656F,    19,    2,    8.15F,    1.15F),
            new RotationTableEntry( 3,    1504,    296,    12.64215F,    19,    4,    8.025F,    1.8F),
            new RotationTableEntry( 4,    1472,    440,    16.39936F,    19,    5,    7.9F,    2.325F),
            new RotationTableEntry( 5,    1472,    440,    21.25051F,    18,    7,    7.65F,    2.975F),
            new RotationTableEntry( 6,    1416,    584,    25.38791F,    17,    8,    7.375F,    3.5F),
            new RotationTableEntry( 7,    1360,    720,    29.31001F,    17,    9,    7.125F,    4F),
            new RotationTableEntry( 8,    1280,    856,    33.44811F,    16,    10,    6.85F,    4.525F),
            new RotationTableEntry( 9,    1280,    856,    37.95148F,    15,    12,    6.475F,    5.05F),
            new RotationTableEntry( 10,    1192,    976,    41.76498F,    14,    13,    6.075F,    5.425F),
            new RotationTableEntry( 11,    1088,    1088,    46.22934F,    13,    14,    5.7F,    5.95F),
            new RotationTableEntry( 12,    976,    1192,    50.82132F,    12,    15,    5.175F,    6.35F),
            new RotationTableEntry( 13,    976,    1192,    55.33809F,    11,    16,    4.65F,    6.725F),
            new RotationTableEntry( 14,    856,    1280,    59.33812F,    10,    16,    4.15F,    7F),
            new RotationTableEntry( 15,    720,    1360,    63.43495F,    8,    17,    3.625F,    7.25F),
            new RotationTableEntry( 16,    584,    1416,    67.54306F,    7,    18,    3.1F,    7.5F),
            new RotationTableEntry( 17,    584,    1416,    71.67566F,    6,    18,    2.575F,    7.775F),
            new RotationTableEntry( 18,    440,    1472,    76.30556F,    4,    19,    1.925F,    7.9F),
            new RotationTableEntry( 19,    296,    1504,    79.93094F,    3,    19,    1.425F,    8.025F),
            new RotationTableEntry( 20,    152,    1528,    84.56796F,    1,    19,    0.775F,    8.15F),
            new RotationTableEntry( 21,    152,    1528,    89.1213F,    0,    19,    0.125F,    8.15F),
            new RotationTableEntry( 22,    -152,    1528,    92.80981F,    -1,    19,    -0.4F,    8.15F),
            new RotationTableEntry( 23,    -296,    1504,    97.34122F,    -3,    19,    -1.05F,    8.15F),
            new RotationTableEntry( 24,    -296,    1504,    101.9606F,    -5,    19,    -1.7F,    8.025F),
            new RotationTableEntry( 25,    -440,    1472,    105.7296F,    -6,    19,    -2.225F,    7.9F),
            new RotationTableEntry( 26,    -584,    1416,    110.2931F,    -7,    18,    -2.875F,    7.775F),
            new RotationTableEntry( 27,    -720,    1360,    114.2277F,    -9,    18,    -3.375F,    7.5F),
            new RotationTableEntry( 28,    -720,    1360,    118.2772F,    -10,    17,    -3.9F,    7.25F),
            new RotationTableEntry( 29,    -856,    1280,    122.8619F,    -11,    16,    -4.425F,    6.85F),
            new RotationTableEntry( 30,    -976,    1192,    126.8699F,    -12,    15,    -4.95F,    6.6F),
            new RotationTableEntry( 31,    -1088,    1088,    131.2022F,    -14,    15,    -5.45F,    6.225F),
            new RotationTableEntry( 32,    -1088,    1088,    135.1227F,    -15,    14,    -5.85F,    5.825F),
            new RotationTableEntry( 33,    -1192,    976,    139.5887F,    -15,    12,    -6.225F,    5.3F),
            new RotationTableEntry( 34,    -1280,    856,    143.373F,    -16,    11,    -6.625F,    4.925F),
            new RotationTableEntry( 35,    -1360,    720,    147.9397F,    -17,    10,    -7.025F,    4.4F),
            new RotationTableEntry( 36,    -1360,    720,    151.9582F,    -18,    9,    -7.275F,    3.875F),
            new RotationTableEntry( 37,    -1416,    584,    156.8014F,    -19,    7,    -7.525F,    3.225F),
            new RotationTableEntry( 38,    -1472,    440,    160.9065F,    -19,    6,    -7.8F,    2.7F),
            new RotationTableEntry( 39,    -1504,    296,    164.4852F,    -20,    5,    -7.925F,    2.2F),
            new RotationTableEntry( 40,    -1504,    296,    169.264F,    -20,    3,    -8.175F,    1.55F),
            new RotationTableEntry( 41,    -1528,    152,    172.8534F,    -20,    2,    -8.175F,    1.025F),
            new RotationTableEntry( 42,    -1536,    0,    177.4131F,    -20,    0,    -8.3F,    0.375F),
            new RotationTableEntry( 43,    -1536,    0,    -178.1023F,    -20,    -1,    -8.3F,    -0.275F),
            new RotationTableEntry( 44,    -1528,    -152,    -174.4109F,    -20,    -2,    -8.175F,    -0.8F),
            new RotationTableEntry( 45,    -1528,    -152,    -169.942F,    -20,    -4,    -8.175F,    -1.45F),
            new RotationTableEntry( 46,    -1504,    -296,    -165.5459F,    -20,    -5,    -8.05F,    -2.075F),
            new RotationTableEntry( 47,    -1472,    -440,    -161.8366F,    -20,    -7,    -7.925F,    -2.6F),
            new RotationTableEntry( 48,    -1416,    -584,    -157.0496F,    -19,    -8,    -7.675F,    -3.25F),
            new RotationTableEntry( 49,    -1416,    -584,    -152.9723F,    -18,    -10,    -7.4F,    -3.775F),
            new RotationTableEntry( 50,    -1360,    -720,    -148.9774F,    -18,    -11,    -7.15F,    -4.3F),
            new RotationTableEntry( 51,    -1280,    -856,    -144.5829F,    -17,    -12,    -6.75F,    -4.8F),
            new RotationTableEntry( 52,    -1192,    -976,    -140.1282F,    -16,    -13,    -6.375F,    -5.325F),
            new RotationTableEntry( 53,    -1192,    -976,    -136.2241F,    -15,    -14,    -5.975F,    -5.725F),
            new RotationTableEntry( 54,    -1088,    -1088,    -132.5529F,    -14,    -15,    -5.6F,    -6.1F),
            new RotationTableEntry( 55,    -976,    -1192,    -127.9816F,    -13,    -16,    -5.075F,    -6.5F),
            new RotationTableEntry( 56,    -856,    -1280,    -124.2157F,    -12,    -17,    -4.675F,    -6.875F),
            new RotationTableEntry( 57,    -856,    -1280,    -119.7025F,    -10,    -18,    -4.15F,    -7.275F),
            new RotationTableEntry( 58,    -720,    -1360,    -115.1002F,    -9,    -19,    -3.525F,    -7.525F),
            new RotationTableEntry( 59,    -584,    -1416,    -111.3495F,    -8,    -19,    -3F,    -7.675F),
            new RotationTableEntry( 60,    -440,    -1472,    -107.3437F,    -6,    -20,    -2.475F,    -7.925F),
            new RotationTableEntry( 61,    -440,    -1472,    -102.7735F,    -5,    -20,    -1.825F,    -8.05F),
            new RotationTableEntry( 62,    -296,    -1504,    -99.0356F,    -4,    -20,    -1.3F,    -8.175F),
            new RotationTableEntry( 63,    -152,    -1528,    -94.47788F,    -2,    -20,    -0.65F,    -8.3F),
            new RotationTableEntry( 64,    0,    -1536,    -90F,    0,    -20,    0F,    -8.3F),
            new RotationTableEntry( 65,    152,    -1528,    -86.55261F,    1,    -20,    0.5F,    -8.3F),
            new RotationTableEntry( 66,    296,    -1504,    -81.99258F,    2,    -20,    1.15F,    -8.175F),
            new RotationTableEntry( 67,    440,    -1472,    -77.39585F,    4,    -20,    1.8F,    -8.05F),
            new RotationTableEntry( 68,    440,    -1472,    -73.64961F,    5,    -20,    2.325F,    -7.925F),
            new RotationTableEntry( 69,    584,    -1416,    -68.81257F,    7,    -19,    2.975F,    -7.675F),
            new RotationTableEntry( 70,    720,    -1360,    -65.05609F,    8,    -19,    3.5F,    -7.525F),
            new RotationTableEntry( 71,    856,    -1280,    -61.19677F,    9,    -18,    4F,    -7.275F),
            new RotationTableEntry( 72,    856,    -1280,    -56.64782F,    10,    -17,    4.525F,    -6.875F),
            new RotationTableEntry( 73,    976,    -1192,    -52.15554F,    12,    -16,    5.05F,    -6.5F),
            new RotationTableEntry( 74,    1088,    -1088,    -48.35189F,    13,    -15,    5.425F,    -6.1F),
            new RotationTableEntry( 75,    1192,    -976,    -43.89594F,    14,    -14,    5.95F,    -5.725F),
            new RotationTableEntry( 76,    1192,    -976,    -39.98261F,    15,    -13,    6.35F,    -5.325F),
            new RotationTableEntry( 77,    1280,    -856,    -35.51752F,    16,    -12,    6.725F,    -4.8F),
            new RotationTableEntry( 78,    1360,    -720,    -31.56181F,    16,    -11,    7F,    -4.3F),
            new RotationTableEntry( 79,    1416,    -584,    -27.50553F,    17,    -10,    7.25F,    -3.775F),
            new RotationTableEntry( 80,    1416,    -584,    -23.42869F,    18,    -8,    7.5F,    -3.25F),
            new RotationTableEntry( 81,    1472,    -440,    -18.4902F,    18,    -7,    7.775F,    -2.6F),
            new RotationTableEntry( 82,    1504,    -296,    -14.71678F,    19,    -5,    7.9F,    -2.075F),
            new RotationTableEntry( 83,    1528,    -152,    -10.24201F,    19,    -4,    8.025F,    -1.45F),
            new RotationTableEntry( 84,    1528,    -152,    -5.606166F,    19,    -2,    8.15F,    -0.8F),
            new RotationTableEntry( 85,    1536,    0,    -1.93256F,    19,    -1,    8.15F,    -0.275F),
            new RotationTableEntry( 86,    1536,    0,    2.634451F,    19,    0,    8.15F,    0.375F),
            new RotationTableEntry( 87,    1528,    152,    7.168275F,    19,    2,    8.15F,    1.025F),
            new RotationTableEntry( 88,    1504,    296,    10.93186F,    19,    3,    8.025F,    1.55F),
            new RotationTableEntry( 89,    1504,    296,    15.56151F,    19,    5,    7.9F,    2.2F),
            new RotationTableEntry( 90,    1472,    440,    19.15042F,    18,    6,    7.775F,    2.7F),
            new RotationTableEntry( 91,    1416,    584,    23.2677F,    18,    7,    7.5F,    3.225F),
            new RotationTableEntry( 92,    1360,    720,    28.12374F,    17,    9,    7.25F,    3.875F),
            new RotationTableEntry( 93,    1360,    720,    32.71408F,    16,    10,    6.85F,    4.4F),
            new RotationTableEntry( 94,    1280,    856,    36.73075F,    15,    11,    6.6F,    4.925F),
            new RotationTableEntry( 95,    1192,    976,    40.41126F,    15,    12,    6.225F,    5.3F),
            new RotationTableEntry( 96,    1088,    1088,    45F,    14,    14,    5.825F,    5.825F),
            new RotationTableEntry( 97,    1088,    1088,    49.58874F,    12,    15,    5.3F,    6.225F),
            new RotationTableEntry( 98,    976,    1192,    53.26925F,    11,    15,    4.925F,    6.6F),
            new RotationTableEntry( 99,    856,    1280,    57.28592F,    10,    16,    4.4F,    6.85F),
            new RotationTableEntry( 100,    720,    1360,    61.87627F,    9,    17,    3.875F,    7.25F),
            new RotationTableEntry( 101,    720,    1360,    66.73229F,    7,    18,    3.225F,    7.5F),
            new RotationTableEntry( 102,    584,    1416,    70.84958F,    6,    18,    2.7F,    7.775F),
            new RotationTableEntry( 103,    440,    1472,    74.43849F,    5,    19,    2.2F,    7.9F),
            new RotationTableEntry( 104,    296,    1504,    79.06814F,    3,    19,    1.55F,    8.025F),
            new RotationTableEntry( 105,    296,    1504,    82.83173F,    2,    19,    1.025F,    8.15F),
            new RotationTableEntry( 106,    152,    1528,    87.36555F,    0,    19,    0.375F,    8.15F),
            new RotationTableEntry( 107,    -152,    1528,    91.93256F,    -1,    19,    -0.275F,    8.15F),
            new RotationTableEntry( 108,    -152,    1528,    95.60616F,    -2,    19,    -0.8F,    8.15F),
            new RotationTableEntry( 109,    -296,    1504,    100.242F,    -4,    19,    -1.45F,    8.025F),
            new RotationTableEntry( 110,    -440,    1472,    104.7168F,    -5,    19,    -2.075F,    7.9F),
            new RotationTableEntry( 111,    -584,    1416,    108.4902F,    -7,    18,    -2.6F,    7.775F),
            new RotationTableEntry( 112,    -584,    1416,    113.4287F,    -8,    18,    -3.25F,    7.5F),
            new RotationTableEntry( 113,    -720,    1360,    117.5055F,    -10,    17,    -3.775F,    7.25F),
            new RotationTableEntry( 114,    -856,    1280,    121.5618F,    -11,    16,    -4.3F,    7F),
            new RotationTableEntry( 115,    -976,    1192,    125.5175F,    -12,    16,    -4.8F,    6.725F),
            new RotationTableEntry( 116,    -976,    1192,    129.9826F,    -13,    15,    -5.325F,    6.35F),
            new RotationTableEntry( 117,    -1088,    1088,    133.8959F,    -14,    14,    -5.725F,    5.95F),
            new RotationTableEntry( 118,    -1192,    976,    138.3519F,    -15,    13,    -6.1F,    5.425F),
            new RotationTableEntry( 119,    -1280,    856,    142.1555F,    -16,    12,    -6.5F,    5.05F),
            new RotationTableEntry( 120,    -1280,    856,    146.6478F,    -17,    10,    -6.875F,    4.525F),
            new RotationTableEntry( 121,    -1360,    720,    151.1968F,    -18,    9,    -7.275F,    4F),
            new RotationTableEntry( 122,    -1416,    584,    155.0561F,    -19,    8,    -7.525F,    3.5F),
            new RotationTableEntry( 123,    -1472,    440,    158.8126F,    -19,    7,    -7.675F,    2.975F),
            new RotationTableEntry( 124,    -1472,    440,    163.6496F,    -20,    5,    -7.925F,    2.325F),
            new RotationTableEntry( 125,    -1504,    296,    167.3959F,    -20,    4,    -8.05F,    1.8F),
            new RotationTableEntry( 126,    -1528,    152,    171.9926F,    -20,    2,    -8.175F,    1.15F),
            new RotationTableEntry( 127,    -1536,    0,    176.5526F,    -20,    1,    -8.3F,    0.5F),
            new RotationTableEntry( 128,    -1536,    0,    180F,    -20,    0,    -8.3F,    0F),
            new RotationTableEntry( 129,    -1536,    0,    -175.5221F,    -20,    -2,    -8.3F,    -0.65F),
            new RotationTableEntry( 130,    -1528,    -152,    -170.9644F,    -20,    -4,    -8.175F,    -1.3F),
            new RotationTableEntry( 131,    -1504,    -296,    -167.2265F,    -20,    -5,    -8.05F,    -1.825F),
            new RotationTableEntry( 132,    -1472,    -440,    -162.6563F,    -20,    -6,    -7.925F,    -2.475F),
            new RotationTableEntry( 133,    -1472,    -440,    -158.6505F,    -19,    -8,    -7.675F,    -3F),
            new RotationTableEntry( 134,    -1416,    -584,    -154.8998F,    -19,    -9,    -7.525F,    -3.525F),
            new RotationTableEntry( 135,    -1360,    -720,    -150.2975F,    -18,    -10,    -7.275F,    -4.15F),
            new RotationTableEntry( 136,    -1280,    -856,    -145.7843F,    -17,    -12,    -6.875F,    -4.675F),
            new RotationTableEntry( 137,    -1280,    -856,    -142.0184F,    -16,    -13,    -6.5F,    -5.075F),
            new RotationTableEntry( 138,    -1192,    -976,    -137.4471F,    -15,    -14,    -6.1F,    -5.6F),
            new RotationTableEntry( 139,    -1088,    -1088,    -133.7759F,    -14,    -15,    -5.725F,    -5.975F),
            new RotationTableEntry( 140,    -976,    -1192,    -129.8718F,    -13,    -16,    -5.325F,    -6.375F),
            new RotationTableEntry( 141,    -976,    -1192,    -125.4171F,    -12,    -17,    -4.8F,    -6.75F),
            new RotationTableEntry( 142,    -856,    -1280,    -121.0226F,    -11,    -18,    -4.3F,    -7.15F),
            new RotationTableEntry( 143,    -720,    -1360,    -117.0277F,    -10,    -18,    -3.775F,    -7.4F),
            new RotationTableEntry( 144,    -584,    -1416,    -112.9504F,    -8,    -19,    -3.25F,    -7.675F),
            new RotationTableEntry( 145,    -584,    -1416,    -108.1634F,    -7,    -20,    -2.6F,    -7.925F),
            new RotationTableEntry( 146,    -440,    -1472,    -104.4541F,    -5,    -20,    -2.075F,    -8.05F),
            new RotationTableEntry( 147,    -296,    -1504,    -100.058F,    -4,    -20,    -1.45F,    -8.175F),
            new RotationTableEntry( 148,    -152,    -1528,    -95.58913F,    -2,    -20,    -0.8F,    -8.175F),
            new RotationTableEntry( 149,    -152,    -1528,    -91.89766F,    -1,    -20,    -0.275F,    -8.3F),
            new RotationTableEntry( 150,    152,    -1528,    -87.41309F,    0,    -20,    0.375F,    -8.3F),
            new RotationTableEntry( 151,    296,    -1504,    -82.85342F,    2,    -20,    1.025F,    -8.175F),
            new RotationTableEntry( 152,    296,    -1504,    -79.26402F,    3,    -20,    1.55F,    -8.175F),
            new RotationTableEntry( 153,    440,    -1472,    -74.48521F,    5,    -20,    2.2F,    -7.925F),
            new RotationTableEntry( 154,    584,    -1416,    -70.90651F,    6,    -19,    2.7F,    -7.8F),
            new RotationTableEntry( 155,    720,    -1360,    -66.80141F,    7,    -19,    3.225F,    -7.525F),
            new RotationTableEntry( 156,    720,    -1360,    -61.95818F,    9,    -18,    3.875F,    -7.275F),
            new RotationTableEntry( 157,    856,    -1280,    -57.93967F,    10,    -17,    4.4F,    -7.025F),
            new RotationTableEntry( 158,    976,    -1192,    -53.37303F,    11,    -16,    4.925F,    -6.625F),
            new RotationTableEntry( 159,    1088,    -1088,    -49.58874F,    12,    -15,    5.3F,    -6.225F),
            new RotationTableEntry( 160,    1088,    -1088,    -45.12269F,    14,    -15,    5.825F,    -5.85F),
            new RotationTableEntry( 161,    1192,    -976,    -41.20221F,    15,    -14,    6.225F,    -5.45F),
            new RotationTableEntry( 162,    1280,    -856,    -36.8699F,    15,    -12,    6.6F,    -4.95F),
            new RotationTableEntry( 163,    1360,    -720,    -32.86187F,    16,    -11,    6.85F,    -4.425F),
            new RotationTableEntry( 164,    1360,    -720,    -28.27719F,    17,    -10,    7.25F,    -3.9F),
            new RotationTableEntry( 165,    1416,    -584,    -24.22775F,    18,    -9,    7.5F,    -3.375F),
            new RotationTableEntry( 166,    1472,    -440,    -20.29313F,    18,    -7,    7.775F,    -2.875F),
            new RotationTableEntry( 167,    1504,    -296,    -15.72964F,    19,    -6,    7.9F,    -2.225F),
            new RotationTableEntry( 168,    1504,    -296,    -11.9606F,    19,    -5,    8.025F,    -1.7F),
            new RotationTableEntry( 169,    1528,    -152,    -7.341226F,    19,    -3,    8.15F,    -1.05F),
            new RotationTableEntry( 170,    1536,    0,    -2.809808F,    19,    -1,    8.15F,    -0.4F),
            new RotationTableEntry( 171,    1536,    0,    0.8787007F,    19,    0,    8.15F,    0.125F),
            new RotationTableEntry( 172,    1528,    152,    5.432038F,    19,    1,    8.15F,    0.775F),
            new RotationTableEntry( 173,    1528,    152,    10.06906F,    19,    3,    8.025F,    1.425F),
            new RotationTableEntry( 174,    1504,    296,    13.69444F,    19,    4,    7.9F,    1.925F),
            new RotationTableEntry( 175,    1472,    440,    18.32434F,    18,    6,    7.775F,    2.575F),
            new RotationTableEntry( 176,    1416,    584,    22.45694F,    18,    7,    7.5F,    3.1F),
            new RotationTableEntry( 177,    1416,    584,    26.56505F,    17,    8,    7.25F,    3.625F),
            new RotationTableEntry( 178,    1360,    720,    30.66188F,    16,    10,    7F,    4.15F),
            new RotationTableEntry( 179,    1280,    856,    34.66191F,    16,    11,    6.725F,    4.65F),
            new RotationTableEntry( 180,    1192,    976,    39.17868F,    15,    12,    6.35F,    5.175F),
            new RotationTableEntry( 181,    1192,    976,    43.77066F,    14,    13,    5.95F,    5.7F),
            new RotationTableEntry( 182,    1088,    1088,    48.23502F,    13,    14,    5.425F,    6.075F),
            new RotationTableEntry( 183,    976,    1192,    52.04852F,    12,    15,    5.05F,    6.475F),
            new RotationTableEntry( 184,    856,    1280,    56.55189F,    10,    16,    4.525F,    6.85F),
            new RotationTableEntry( 185,    856,    1280,    60.68999F,    9,    17,    4F,    7.125F),
            new RotationTableEntry( 186,    720,    1360,    64.61209F,    8,    17,    3.5F,    7.375F),
            new RotationTableEntry( 187,    584,    1416,    68.7495F,    7,    18,    2.975F,    7.65F),
            new RotationTableEntry( 188,    440,    1472,    73.60065F,    5,    19,    2.325F,    7.9F),
            new RotationTableEntry( 189,    440,    1472,    77.35785F,    4,    19,    1.8F,    8.025F),
            new RotationTableEntry( 190,    296,    1504,    81.96835F,    2,    19,    1.15F,    8.15F),
            new RotationTableEntry( 191,    152,    1528,    86.48932F,    1,    19,    0.5F,    8.15F),
            new RotationTableEntry( 192,    0,    1536,    90F,    0,    19,    0F,    8.15F),
            new RotationTableEntry( 193,    -152,    1528,    94.55995F,    -2,    19,    -0.65F,    8.15F),
            new RotationTableEntry( 194,    -296,    1504,    99.06286F,    -4,    19,    -1.3F,    8.15F),
            new RotationTableEntry( 195,    -440,    1472,    102.812F,    -5,    19,    -1.825F,    8.025F),
            new RotationTableEntry( 196,    -440,    1472,    107.3953F,    -6,    19,    -2.475F,    7.9F),
            new RotationTableEntry( 197,    -584,    1416,    111.413F,    -8,    18,    -3F,    7.65F),
            new RotationTableEntry( 198,    -720,    1360,    115.5462F,    -9,    17,    -3.525F,    7.375F),
            new RotationTableEntry( 199,    -856,    1280,    120.2189F,    -10,    17,    -4.15F,    7.125F),
            new RotationTableEntry( 200,    -856,    1280,    124.3128F,    -12,    16,    -4.675F,    6.85F),
            new RotationTableEntry( 201,    -976,    1192,    128.0888F,    -13,    15,    -5.075F,    6.475F),
            new RotationTableEntry( 202,    -1088,    1088,    132.6702F,    -14,    14,    -5.6F,    6.075F),
            new RotationTableEntry( 203,    -1192,    976,    136.3493F,    -15,    13,    -5.975F,    5.7F),
            new RotationTableEntry( 204,    -1192,    976,    140.9315F,    -16,    12,    -6.375F,    5.175F),
            new RotationTableEntry( 205,    -1280,    856,    145.4375F,    -17,    11,    -6.75F,    4.65F),
            new RotationTableEntry( 206,    -1360,    720,    149.8683F,    -18,    10,    -7.15F,    4.15F),
            new RotationTableEntry( 207,    -1416,    584,    153.9014F,    -18,    8,    -7.4F,    3.625F),
            new RotationTableEntry( 208,    -1416,    584,    158.0058F,    -19,    7,    -7.675F,    3.1F),
            new RotationTableEntry( 209,    -1472,    440,    161.9999F,    -20,    6,    -7.925F,    2.575F),
            new RotationTableEntry( 210,    -1504,    296,    166.5514F,    -20,    4,    -8.05F,    1.925F),
            new RotationTableEntry( 211,    -1528,    152,    170.112F,    -20,    3,    -8.175F,    1.425F),
            new RotationTableEntry( 212,    -1528,    152,    174.5845F,    -20,    1,    -8.175F,    0.775F),
            new RotationTableEntry( 213,    -1536,    0,    179.1372F,    -20,    0,    -8.3F,    0.125F),
            new RotationTableEntry( 214,    -1536,    0,    -177.2409F,    -20,    -1,    -8.3F,    -0.4F),
            new RotationTableEntry( 215,    -1528,    -152,    -172.681F,    -20,    -3,    -8.175F,    -1.05F),
            new RotationTableEntry( 216,    -1504,    -296,    -168.2527F,    -20,    -5,    -8.175F,    -1.7F),
            new RotationTableEntry( 217,    -1504,    -296,    -164.3175F,    -20,    -6,    -7.925F,    -2.225F),
            new RotationTableEntry( 218,    -1472,    -440,    -159.7666F,    -19,    -7,    -7.8F,    -2.875F),
            new RotationTableEntry( 219,    -1416,    -584,    -155.8435F,    -19,    -9,    -7.525F,    -3.375F),
            new RotationTableEntry( 220,    -1360,    -720,    -151.805F,    -18,    -10,    -7.275F,    -3.9F),
            new RotationTableEntry( 221,    -1360,    -720,    -147.7935F,    -17,    -11,    -7.025F,    -4.425F),
            new RotationTableEntry( 222,    -1280,    -856,    -143.234F,    -16,    -12,    -6.625F,    -4.95F),
            new RotationTableEntry( 223,    -1192,    -976,    -138.7978F,    -15,    -14,    -6.225F,    -5.45F),
            new RotationTableEntry( 224,    -1088,    -1088,    -135F,    -15,    -15,    -5.85F,    -5.85F),
            new RotationTableEntry( 225,    -1088,    -1088,    -131.2022F,    -14,    -15,    -5.45F,    -6.225F),
            new RotationTableEntry( 226,    -976,    -1192,    -126.766F,    -12,    -16,    -4.95F,    -6.625F),
            new RotationTableEntry( 227,    -856,    -1280,    -122.2066F,    -11,    -17,    -4.425F,    -7.025F),
            new RotationTableEntry( 228,    -720,    -1360,    -118.195F,    -10,    -18,    -3.9F,    -7.275F),
            new RotationTableEntry( 229,    -720,    -1360,    -114.1565F,    -9,    -19,    -3.375F,    -7.525F),
            new RotationTableEntry( 230,    -584,    -1416,    -110.2334F,    -7,    -19,    -2.875F,    -7.8F),
            new RotationTableEntry( 231,    -440,    -1472,    -105.6825F,    -6,    -20,    -2.225F,    -7.925F),
            new RotationTableEntry( 232,    -296,    -1504,    -101.7473F,    -5,    -20,    -1.7F,    -8.175F),
            new RotationTableEntry( 233,    -296,    -1504,    -97.31902F,    -3,    -20,    -1.05F,    -8.175F),
            new RotationTableEntry( 234,    -152,    -1528,    -92.75911F,    -1,    -20,    -0.4F,    -8.3F),
            new RotationTableEntry( 235,    152,    -1528,    -89.13718F,    0,    -20,    0.125F,    -8.3F),
            new RotationTableEntry( 236,    152,    -1528,    -84.58447F,    1,    -20,    0.775F,    -8.175F),
            new RotationTableEntry( 237,    296,    -1504,    -80.11201F,    3,    -20,    1.425F,    -8.175F),
            new RotationTableEntry( 238,    440,    -1472,    -76.55138F,    4,    -20,    1.925F,    -8.05F),
            new RotationTableEntry( 239,    584,    -1416,    -71.99992F,    6,    -20,    2.575F,    -7.925F),
            new RotationTableEntry( 240,    584,    -1416,    -68.00578F,    7,    -19,    3.1F,    -7.675F),
            new RotationTableEntry( 241,    720,    -1360,    -63.90139F,    8,    -18,    3.625F,    -7.4F),
            new RotationTableEntry( 242,    856,    -1280,    -59.86828F,    10,    -18,    4.15F,    -7.15F),
            new RotationTableEntry( 243,    976,    -1192,    -55.43748F,    11,    -17,    4.65F,    -6.75F),
            new RotationTableEntry( 244,    976,    -1192,    -50.93153F,    12,    -16,    5.175F,    -6.375F),
            new RotationTableEntry( 245,    1088,    -1088,    -46.34933F,    13,    -15,    5.7F,    -5.975F),
            new RotationTableEntry( 246,    1192,    -976,    -42.67019F,    14,    -14,    6.075F,    -5.6F),
            new RotationTableEntry( 247,    1280,    -856,    -38.08877F,    15,    -13,    6.475F,    -5.075F),
            new RotationTableEntry( 248,    1280,    -856,    -34.31282F,    16,    -12,    6.85F,    -4.675F),
            new RotationTableEntry( 249,    1360,    -720,    -30.21892F,    17,    -10,    7.125F,    -4.15F),
            new RotationTableEntry( 250,    1416,    -584,    -25.54622F,    17,    -9,    7.375F,    -3.525F),
            new RotationTableEntry( 251,    1472,    -440,    -21.41297F,    18,    -8,    7.65F,    -3F),
            new RotationTableEntry( 252,    1472,    -440,    -17.39531F,    19,    -6,    7.9F,    -2.475F),
            new RotationTableEntry( 253,    1504,    -296,    -12.81198F,    19,    -5,    8.025F,    -1.825F),
            new RotationTableEntry( 254,    1528,    -152,    -9.062856F,    19,    -4,    8.15F,    -1.3F),
            new RotationTableEntry( 255,    1536,    0,    -4.55995F,    19,    -2,    8.15F,    -0.65F)
        };
 
 
        public void writeRotationTableLogFile()
        {
            char prevframe = (char)0;
            float dx = 0;
            float dy = 0;
            float dist = 0;
            int i = 0;
            int winkelByteIndex = 0;
            int wait = 0;
            System.IO.StreamWriter file = new System.IO.StreamWriter("D:/Public/Projects/C#/csAsteroidsBot/mame-windows-bin/out.txt");
            int waitForShotOnScreen = 0;
            int initialShotPosX = 0;
            int initialShotPosY = 0;
            GState s = getCurrentGState();
 
            while (!doExit)
            {
                ++keys.ping;
                if (keys.ping > 255) keys.ping = (char)0;
 
                SendPacket(keys);
                storeKeysToPreviousKeys();
                //Thread.Sleep(0);
                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;
                }
 
                GState current = getCurrentGState();
                GState last = getLastGState();
                storeFrameToGState(current);
                //rotationTableIndex = reSyncShotRotation(current);
                updateTracking(current, last);
 
                keys.clear();
 
                s = current;
                gStateToDraw = current;
 
                if (!s.shipPresent) continue;
 
                playing = true;
 
 
                if (s.shotsCount == 1)
                {
                    dx = s.shots[0].Pos.X - s.ship.Pos.X;
                    dy = s.shots[0].Pos.Y - s.ship.Pos.Y;
                    dx = wrapScreenX(dx);
                    dy = wrapScreenY(dy);
                    dist = dx * dx + dy * dy;  // Quadrat des Abstands zu diesem Shot
 
                    i++;
                }
 
                if (s.shotsCount == 0)
                {
                    if (wait == 5)
                    {
                        if (i > 40)
                        {
                            keys.left(true);
                            //log("left");
                            i = 0;
                        }
                    }
                    wait++;
                }
 
                if (wait > 10)
                {
                    wait = 0;
                    i = 0;
                    keys.fire(true);
                    //log("shot");
                    waitForShotOnScreen = 1;
                }
 
                if (waitForShotOnScreen == 1) waitForShotOnScreen++;
                else if (waitForShotOnScreen == 2) waitForShotOnScreen++;
                else if (waitForShotOnScreen == 3)
                {
                    waitForShotOnScreen = 0;
                    initialShotPosX = (int) Math.Round(s.shots[0].Pos.X - s.ship.Pos.X);
                    initialShotPosY = (int) Math.Round(s.shots[0].Pos.Y - s.ship.Pos.Y);
                }
 
                if (i == 40)
                {
                    i++;
                    float shotVectorX = dx / 40;
                    String shotVxString = shotVectorX.ToString();
                    shotVxString = shotVxString.Replace(',', '.');
                    shotVxString += "F";
                    float shotVectorY = dy / 40;
                    String shotVyString = shotVectorY.ToString();
                    shotVyString = shotVyString.Replace(',', '.');
                    shotVyString += "F";
                    float rot = (float)(Math.Atan2(dy, dx) * 180.0 / Math.PI);
                    String rotString = rot.ToString();
                    rotString = rotString.Replace(',', '.');
                    rotString += "F";
                    String str = "new RotationTableEntry( ";
                    str += winkelByteIndex + ",\t";
                    str += s.shipDX + ",\t" + s.shipDY + ",\t";
                    str += rotString + ",\t";
                    str += initialShotPosX + ",\t" + initialShotPosY + ",\t";
                    str += shotVxString + ",\t";
                    str += shotVyString + "),";
                    winkelByteIndex++;
                    file.WriteLine(str);
                    log(str);
                }
 
                incrementGStateIndex();
            }
 
            file.Flush();
            file.Close();
            file.Dispose();
        }
 
        #endregion
 
    }
}