Exemplo n.º 1
0
            public WinningBean(Bot ship, double score, double age)
            {
                this.Ship = ship;
                this.DNA = null;

                this.Score = score;
                this.Age = age;
            }
        private async void ShipBot1_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                ClearBot_Click(this, e);

                #region DNA

                List<ShipPartDNA> parts = new List<ShipPartDNA>();

                parts.Add(new ShipPartDNA() { PartType = EnergyTank.PARTTYPE, Orientation = Quaternion.Identity, Position = new Point3D(0.44329742616787, -2.22044604925031E-16, -0.750606111984116), Scale = new Vector3D(1, 1, 1) });

                parts.Add(new ShipPartDNA() { PartType = AmmoBox.PARTTYPE, Orientation = Quaternion.Identity, Position = new Point3D(-0.44329742616787, -2.22044604925031E-16, -0.839035218662306), Scale = new Vector3D(1, 1, 1) });

                parts.Add(new ShipPartDNA() { PartType = ProjectileGun.PARTTYPE, Orientation = Quaternion.Identity, Position = new Point3D(-0.139350859269598, -2.22044604925031E-16, 0.0736057604093046), Scale = new Vector3D(1, 1, 1) });

                parts.Add(new ShipPartDNA() { PartType = CameraColorRGB.PARTTYPE, Orientation = Quaternion.Identity, Position = new Point3D(0.443930484830668, -2.22044604925031E-16, -0.0296267373083678), Scale = new Vector3D(1, 1, 1) });

                parts.Add(new ShipPartDNA() { PartType = Brain.PARTTYPE, Orientation = Quaternion.Identity, Position = new Point3D(1.17108888679796, 2.22044604925031E-16, -0.787190712968899), Scale = new Vector3D(1, 1, 1) });

                parts.Add(new BrainRGBRecognizerDNA() { PartType = BrainRGBRecognizer.PARTTYPE, Orientation = new Quaternion(3.91881768803066E-16, -0.264772715428398, 1.07599743798686E-16, 0.964310846752577), Position = new Point3D(0.977003177386146, 0, -0.204027736848604), Scale = new Vector3D(1, 1, 1) });

                ShipDNA dna = ShipDNA.Create(parts);
                dna.ShipLineage = Guid.NewGuid().ToString();

                #endregion

                ShipCoreArgs core = new ShipCoreArgs()
                {
                    World = _world,
                    Material_Ship = _material_Bot,
                    Map = _map,
                };

                ShipExtraArgs extra = new ShipExtraArgs()
                {
                    Options = _editorOptions,
                    ItemOptions = _itemOptions,
                    Material_Projectile = _material_Projectile,
                    CameraPool = _cameraPool,
                    IsPhysicsStatic = true,
                };

                //DefenseBot bot = DefenseBot.GetNewDefenseBotAsync(dna, _world, _material_Bot, _map, extra).Result;        // can't directly call result.  it deadlocks this main thread
                //DefenseBot bot = await DefenseBot.GetNewDefenseBotAsync(dna, _world, _material_Bot, _map, extra);
                //bot.PhysicsBody.Position = new Point3D(0, 0, -12);

                //_map.AddItem(bot);
                //_bot = bot;




                BotConstruction_Result result = BotConstructor.ConstructBot(parts, core, extra);
                Bot botNew = new Bot(result);

                botNew.Dispose();

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), this.Title, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
Exemplo n.º 3
0
        //TODO: Make a static method off of Ship, and don't rely on world: public static Visual3D CreateVisual(ShipDNA dna, bool isDesign)
        private async Task RenderShipAsync(NewtonDynamics.World world)
        {
            ShipExtraArgs args = new ShipExtraArgs()
            {
                RunNeural = false,
                RepairPartPositions = false,
            };

            //using (Ship ship = await Ship.GetNewShipAsync(this.ShipDNA, world, 0, null, args))
            using (Bot bot = new Bot(BotConstructor.ConstructBot(this.ShipDNA, new ShipCoreArgs() { World = world }, args)))
            {
                if (bot.PhysicsBody.Visuals != null)		// this will never be null
                {
                    // The model coords may not be centered, so move the ship so that it's centered on the origin
                    Point3D minPoint, maxPoint;
                    bot.PhysicsBody.GetAABB(out minPoint, out maxPoint);
                    Vector3D offset = (minPoint + ((maxPoint - minPoint) / 2d)).ToVector();

                    bot.PhysicsBody.Position = (-offset).ToPoint();

                    // Add the visuals
                    foreach (Visual3D visual in bot.PhysicsBody.Visuals)
                    {
                        _viewport.Children.Add(visual);
                    }

                    // Pull the camera back to a good distance
                    _camera.Position = (_camera.Position.ToVector().ToUnit() * (bot.Radius * 2.1d)).ToPoint();
                }
            }
        }
Exemplo n.º 4
0
        private static Cargo[] TransferCargo(Bot from, Bot to, List<ShipPartDNA> unaccountedParts)
        {
            if (from.CargoBays == null)
            {
                return new Cargo[0];
            }

            if (to.CargoBays == null)
            {
                return from.CargoBays.GetCargoSnapshot();
            }

            List<Cargo> retVal = new List<Cargo>();

            foreach (Cargo cargo in from.CargoBays.GetCargoSnapshot())
            {
                if (cargo is Cargo_ShipPart && IsInList(unaccountedParts, ((Cargo_ShipPart)cargo).PartDNA, true))
                {
                    // This cargo is now part of the ship.  Don't transfer to the new cargo
                    continue;
                }

                if (!to.CargoBays.Add(cargo))
                {
                    retVal.Add(cargo);
                }
            }

            return retVal.ToArray();
        }
Exemplo n.º 5
0
        private static void TransferContainers(Bot from, Bot to)
        {
            // Put as much quantity back as will fit.  Overflow just gets thrown away
            if (to.Energy != null && from.Energy != null)
            {
                to.Energy.QuantityCurrent = from.Energy.QuantityCurrent;
            }

            if (to.Plasma != null && from.Plasma != null)
            {
                to.Plasma.QuantityCurrent = from.Plasma.QuantityCurrent;
            }

            if (to.Fuel != null && from.Fuel != null)
            {
                to.Fuel.QuantityCurrent = from.Fuel.QuantityCurrent;
            }

            if (to.Ammo != null && from.Ammo != null)
            {
                to.Ammo.QuantityCurrent = from.Ammo.QuantityCurrent;
            }
        }
Exemplo n.º 6
0
        private const double MAXERROR = 100000000000;       // need something excessively large, but less than double.max

        #endregion

        /// <summary>
        /// This is a wrapper of UtilityAI.DiscoverSolution
        /// </summary>
        public static void DiscoverSolutionAsync(Bot bot, Vector3D? idealLinear, Vector3D? idealRotation, CancellationToken? cancel = null, ThrustContributionModel model = null, Action<ThrusterMap> newBestFound = null, Action<ThrusterMap> finalFound = null, DiscoverSolutionOptions<Tuple<int, int, double>> options = null)
        {
            long token = bot.Token;

            // Cache Thrusters
            Thruster[] allThrusters = bot.Thrusters;
            if (allThrusters == null || allThrusters.Length == 0)
            {
                throw new ArgumentException("This bot has no thrusters");
            }

            // Ensure model is created (if the caller wants to find solutions for several directions at the same time, it would be
            // more efficient to calculate the model once, and pass to all the solution finders)
            model = model ?? new ThrustContributionModel(bot.Thrusters, bot.PhysicsBody.CenterOfMass);

            // Figure out how much force can be generated in the ideal directions
            double maxForceLinear = idealLinear == null ? 0d : ThrustControlUtil.GetMaximumPossible_Linear(model, idealLinear.Value);
            double maxForceRotate = idealRotation == null ? 0d : ThrustControlUtil.GetMaximumPossible_Rotation(model, idealRotation.Value);

            // Mutate 2% of the elements, with a 10% value drift
            MutateUtility.MuateArgs mutateArgs = new MutateUtility.MuateArgs(false, .02, new MutateUtility.MuateFactorArgs(MutateUtility.FactorType.Distance, .1));

            #region delegates

            //NOTE: While breeding/mutating, they don't get normalized.  But error calculation and returned maps need them normalized

            var delegates = new DiscoverSolutionDelegates<Tuple<int, int, double>>()
            {
                GetNewSample = new Func<Tuple<int, int, double>[]>(() =>
                {
                    ThrusterMap map = ThrustControlUtil.GenerateRandomMap(allThrusters, token);
                    return map.Flattened;
                }),

                GetError = new Func<Tuple<int, int, double>[], SolutionError>(o =>
                {
                    ThrusterMap map = new ThrusterMap(ThrustControlUtil.Normalize(o), allThrusters, token);
                    return ThrustControlUtil.GetThrustMapScore(map, model, idealLinear, idealRotation, maxForceLinear, maxForceRotate);
                }),

                Mutate = new Func<Tuple<int, int, double>[], Tuple<int, int, double>[]>(o =>
                {
                    ThrusterMap map = new ThrusterMap(o, allThrusters, token);
                    return ThrustControlUtil.Mutate(map, mutateArgs, false).Flattened;
                }),
            };

            if (cancel != null)
            {
                delegates.Cancel = cancel.Value;
            }

            if (newBestFound != null)
            {
                delegates.NewBestFound = new Action<SolutionResult<Tuple<int, int, double>>>(o =>
                {
                    ThrusterMap map = new ThrusterMap(ThrustControlUtil.Normalize(o.Item), allThrusters, token);
                    newBestFound(map);
                });
            }

            if (finalFound != null)
            {
                delegates.FinalFound = new Action<SolutionResult<Tuple<int, int, double>>>(o =>
                {
                    ThrusterMap map = new ThrusterMap(ThrustControlUtil.Normalize(o.Item), allThrusters, token);
                    finalFound(map);
                });
            }

            #endregion

            // Do it
            //NOTE: If options.ThreadShare is set, then there's no reason to do this async, but there's no harm either
            Task.Run(() => UtilityAI.DiscoverSolution(delegates, options));
        }
        private void ClearCurrent()
        {
            ClearBalanceVisualizer();

            foreach (Visual3D visual in _currentVisuals)
            {
                _viewport.Children.Remove(visual);
            }
            _currentVisuals.Clear();

            if (_currentBody != null)
            {
                _currentBody.Dispose();
                _currentBody = null;
            }

            if (_bot != null)
            {
                _map.RemoveItem(_bot, true);
                _bot.Dispose();
                _bot = null;
            }

            _shipDNA = null;

            if (_thrustController != null)
            {
                _thrustController.Dispose();
                _thrustController = null;
            }
        }
        private async void btnLoadShip_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                string errMsg;
                ShipDNA shipDNA = ShipEditorWindow.LoadShip(out errMsg);
                if (shipDNA == null)
                {
                    if (!string.IsNullOrEmpty(errMsg))
                    {
                        MessageBox.Show(errMsg, this.Title, MessageBoxButton.OK, MessageBoxImage.Warning);
                    }
                    return;
                }

                EnsureWorldStarted();

                ShipCoreArgs core = new ShipCoreArgs()
                {
                    Map = _map,
                    Material_Ship = _material_Bot,
                    World = _world,
                };

                ShipExtraArgs extra = new ShipExtraArgs()
                {
                    Options = _editorOptions,
                    ItemOptions = _itemOptions,
                    Material_Projectile = _material_Bot,
                    Radiation = _radiation,
                    Gravity = _gravity,
                    RunNeural = false,
                    RepairPartPositions = chkLoadRepairPositions.IsChecked.Value,
                };

                //Ship ship = await Ship.GetNewShipAsync(shipDNA, _world, _material_Bot, _map, extra);
                Bot bot = new Bot(BotConstructor.ConstructBot(shipDNA, core, extra));

                ClearCurrent();

                _shipDNA = shipDNA;
                _bot = bot;

                _bot.PhysicsBody.ApplyForceAndTorque += new EventHandler<BodyApplyForceAndTorqueArgs>(Ship_ApplyForceAndTorque);

                _thrustController = new ThrustController(_bot, _viewport, _itemOptions);

                if (_bot.Fuel != null)
                {
                    _bot.Fuel.QuantityCurrent = _bot.Fuel.QuantityMax;
                }
                _bot.RecalculateMass();
                _thrustController.MassChanged(chkShipSimple.IsChecked.Value);

                if (chkShipDebugVisuals.IsChecked.Value)
                {
                    _thrustController.DrawDebugVisuals_Pre();
                }

                _map.AddItem(_bot);

                grdViewPort.Focus();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), this.Title, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
        private async void btnShipChallenge2_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                EnsureWorldStarted();
                ClearCurrent();

                List<ShipPartDNA> parts = new List<ShipPartDNA>();
                parts.Add(new ShipPartDNA() { PartType = FuelTank.PARTTYPE, Position = new Point3D(0.611527511599856, 0, 0.0153375982352619), Orientation = new Quaternion(0, -0.706493084706277, 0, 0.707719945502605), Scale = new Vector3D(4.70545346938791, 4.70545346938791, 1.04748080326409) });
                parts.Add(new ThrusterDNA() { PartType = Thruster.PARTTYPE, Position = new Point3D(-1.48216852903668, 0, 0), Orientation = Quaternion.Identity, Scale = new Vector3D(1.65021551755816, 1.65021551755816, 1.65021551755816), ThrusterDirections = ThrusterDesign.GetThrusterDirections(ThrusterType.Two), ThrusterType = ThrusterType.Two });
                parts.Add(new ThrusterDNA() { PartType = Thruster.PARTTYPE, Position = new Point3D(-2.60730396412872, 1.18811621237628, -0.0147591913688635), Orientation = new Quaternion(0, 0, -0.846976393198269, 0.531630500784946), Scale = new Vector3D(0.71390056433019, 0.71390056433019, 0.71390056433019), ThrusterDirections = ThrusterDesign.GetThrusterDirections(ThrusterType.Two_Two_One), ThrusterType = ThrusterType.Two_Two_One });
                parts.Add(new ThrusterDNA() { PartType = Thruster.PARTTYPE, Position = new Point3D(-2.60721450068017, -1.18828382189838, -0.0147591913688617), Orientation = new Quaternion(0, 0, -0.496864090131338, 0.867828367788216), Scale = new Vector3D(0.71390056433019, 0.71390056433019, 0.71390056433019), ThrusterDirections = ThrusterDesign.GetThrusterDirections(ThrusterType.Two_Two_One), ThrusterType = ThrusterType.Two_Two_One });

                ShipDNA shipDNA = ShipDNA.Create(parts);

                ShipCoreArgs core = new ShipCoreArgs()
                {
                    Map = _map,
                    Material_Ship = _material_Bot,
                    World = _world,
                };

                ShipExtraArgs extra = new ShipExtraArgs()
                {
                    Options = _editorOptions,
                    ItemOptions = _itemOptions,
                    Material_Projectile = _material_Bot,
                    Radiation = _radiation,
                    Gravity = _gravity,
                    RunNeural = false,
                    RepairPartPositions = false,
                };

                //Ship ship = await Ship.GetNewShipAsync(shipDNA, _world, _material_Bot, _map, extra);
                Bot bot = new Bot(BotConstructor.ConstructBot(shipDNA, core, extra));

                ClearCurrent();

                _shipDNA = shipDNA;
                _bot = bot;

                _bot.PhysicsBody.ApplyForceAndTorque += new EventHandler<BodyApplyForceAndTorqueArgs>(Ship_ApplyForceAndTorque);

                _thrustController = new ThrustController(_bot, _viewport, _itemOptions);

                //double mass = _ship.PhysicsBody.Mass;

                _bot.Fuel.QuantityCurrent = _bot.Fuel.QuantityMax;
                _bot.RecalculateMass();
                _thrustController.MassChanged(chkShipSimple.IsChecked.Value);

                if (chkShipDebugVisuals.IsChecked.Value)
                {
                    _thrustController.DrawDebugVisuals_Pre();
                }

                //mass = _ship.PhysicsBody.Mass;

                _map.AddItem(_bot);

                grdViewPort.Focus();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), this.Title, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
        private async void btnShipWackyFlyer_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                EnsureWorldStarted();
                ClearCurrent();

                List<ShipPartDNA> parts = new List<ShipPartDNA>();
                parts.Add(new ShipPartDNA() { PartType = FuelTank.PARTTYPE, Position = new Point3D(0, 0, 0), Orientation = Quaternion.Identity, Scale = new Vector3D(3, 3, 1) });

                ThrusterType thrustType = UtilityCore.GetRandomEnum<ThrusterType>(ThrusterType.Custom);
                parts.Add(new ThrusterDNA() { PartType = Thruster.PARTTYPE, Position = new Point3D(1, 0, 0), Orientation = Math3D.GetRandomRotation(), Scale = new Vector3D(.5d, .5d, .5d), ThrusterDirections = ThrusterDesign.GetThrusterDirections(thrustType), ThrusterType = thrustType });

                thrustType = UtilityCore.GetRandomEnum<ThrusterType>(ThrusterType.Custom);
                parts.Add(new ThrusterDNA() { PartType = Thruster.PARTTYPE, Position = new Point3D(-1, 0, 0), Orientation = Math3D.GetRandomRotation(), Scale = new Vector3D(.5d, .5d, .5d), ThrusterDirections = ThrusterDesign.GetThrusterDirections(thrustType), ThrusterType = thrustType });

                thrustType = UtilityCore.GetRandomEnum<ThrusterType>(ThrusterType.Custom);
                parts.Add(new ThrusterDNA() { PartType = Thruster.PARTTYPE, Position = new Point3D(0, 1, 0), Orientation = Math3D.GetRandomRotation(), Scale = new Vector3D(.5d, .5d, .5d), ThrusterDirections = ThrusterDesign.GetThrusterDirections(thrustType), ThrusterType = thrustType });

                thrustType = UtilityCore.GetRandomEnum<ThrusterType>(ThrusterType.Custom);
                parts.Add(new ThrusterDNA() { PartType = Thruster.PARTTYPE, Position = new Point3D(0, -1, 0), Orientation = Math3D.GetRandomRotation(), Scale = new Vector3D(.5d, .5d, .5d), ThrusterDirections = ThrusterDesign.GetThrusterDirections(thrustType), ThrusterType = thrustType });

                ShipDNA shipDNA = ShipDNA.Create(parts);

                ShipCoreArgs core = new ShipCoreArgs()
                {
                    Map = _map,
                    Material_Ship = _material_Bot,
                    World = _world,
                };

                ShipExtraArgs extra = new ShipExtraArgs()
                {
                    Options = _editorOptions,
                    ItemOptions = _itemOptions,
                    Material_Projectile = _material_Bot,
                    Radiation = _radiation,
                    Gravity = _gravity,
                    RunNeural = false,
                    RepairPartPositions = false,
                };

                //Ship ship = await Ship.GetNewShipAsync(shipDNA, _world, _material_Bot, _map, extra);
                Bot bot = new Bot(BotConstructor.ConstructBot(shipDNA, core, extra));

                ClearCurrent();

                _shipDNA = shipDNA;
                _bot = bot;

                _bot.PhysicsBody.ApplyForceAndTorque += new EventHandler<BodyApplyForceAndTorqueArgs>(Ship_ApplyForceAndTorque);

                _thrustController = new ThrustController(_bot, _viewport, _itemOptions);

                //double mass = _ship.PhysicsBody.Mass;

                _bot.Fuel.QuantityCurrent = _bot.Fuel.QuantityMax;
                _bot.RecalculateMass();
                _thrustController.MassChanged(chkShipSimple.IsChecked.Value);

                if (chkShipDebugVisuals.IsChecked.Value)
                {
                    _thrustController.DrawDebugVisuals_Pre();
                }

                //mass = _ship.PhysicsBody.Mass;

                _map.AddItem(_bot);

                grdViewPort.Focus();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), this.Title, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
        private async void btnShipBasic_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                EnsureWorldStarted();
                ClearCurrent();

                List<ShipPartDNA> parts = new List<ShipPartDNA>();
                parts.Add(new ShipPartDNA() { PartType = FuelTank.PARTTYPE, Position = new Point3D(-.75, 0, 0), Orientation = Quaternion.Identity, Scale = new Vector3D(1, 1, 1) });
                parts.Add(new ShipPartDNA() { PartType = EnergyTank.PARTTYPE, Position = new Point3D(.75, 0, 0), Orientation = Quaternion.Identity, Scale = new Vector3D(1, 1, 1) });

                ShipDNA shipDNA = ShipDNA.Create(parts);

                ShipCoreArgs core = new ShipCoreArgs()
                {
                    Map = _map,
                    Material_Ship = _material_Bot,
                    World = _world,
                };

                ShipExtraArgs extra = new ShipExtraArgs()
                {
                    Options = _editorOptions,
                    ItemOptions = _itemOptions,
                    Material_Projectile = _material_Bot,
                    Radiation = _radiation,
                    Gravity = _gravity,
                    RunNeural = false,
                    RepairPartPositions = false,
                };

                //Ship ship = await Ship.GetNewShipAsync(shipDNA, _world, _material_Ship, _map, extra);
                Bot bot = new Bot(BotConstructor.ConstructBot(shipDNA, core, extra));

                ClearCurrent();

                _shipDNA = shipDNA;
                _bot = bot;

                _map.AddItem(_bot);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), this.Title, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
            public ThrustController(Bot bot, Viewport3D viewport, ItemOptions itemOptions)
            {
                _bot = bot;
                _thrusters = bot.Thrusters;
                _viewport = viewport;
                _itemOptions = itemOptions;

                _lines = new ScreenSpaceLines3D();
                _lines.Color = Colors.Orange;
                _lines.Thickness = 2d;
                _viewport.Children.Add(_lines);
            }
Exemplo n.º 13
0
        private static Visual3D GetShipCompassBlip(Bot ship)
        {
            ScreenSpaceLines3D retVal = new ScreenSpaceLines3D(true);
            retVal.Thickness = 2d;
            retVal.Color = Colors.DodgerBlue;
            retVal.AddLine(new Point3D(0, 170, 20), new Point3D(0, 1000, 20));

            // Exit Function
            return retVal;
        }
Exemplo n.º 14
0
        private static Visual3D GetShipBlip(Bot ship)
        {
            //TODO:  This makes an arrow that always points north, which is just annoying.  Instead, make the ship a dot, but put the arrow along the edge
            // of the minimap (just translate the arrow by a certain Y)

            // Material
            MaterialGroup materials = new MaterialGroup();
            materials.Children.Add(new DiffuseMaterial(new SolidColorBrush(Colors.DodgerBlue)));
            materials.Children.Add(new SpecularMaterial(Brushes.RoyalBlue, 20d));

            // Geometry Model
            GeometryModel3D geometry = new GeometryModel3D();
            geometry.Material = materials;
            geometry.BackMaterial = materials;

            List<TubeRingDefinition_ORIG> rings = new List<TubeRingDefinition_ORIG>();
            rings.Add(new TubeRingDefinition_ORIG(30, 18, 0, true, false));
            rings.Add(new TubeRingDefinition_ORIG(5, false));

            geometry.Geometry = UtilityWPF.GetMultiRingedTube_ORIG(3, rings, false, true);

            Transform3DGroup transform = new Transform3DGroup();
            transform.Children.Add(new TranslateTransform3D(0, 0, 20));
            transform.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, 1), 90)));
            geometry.Transform = transform;

            // Model Visual
            ModelVisual3D retVal = new ModelVisual3D();
            retVal.Content = geometry;

            // Exit Function
            return retVal;
        }
Exemplo n.º 15
0
 public void ShipDied(Bot ship)
 {
     _removedTokens.Add(ship.PhysicsBody.Token);
 }
Exemplo n.º 16
0
        public void ShipCreated(long candidateToken, Bot ship)
        {
            // Find the candidate list that this referes to
            TrackingCandidate finalist = _finalists.Where(o => o.Token == candidateToken).FirstOrDefault();
            if (finalist == null)
            {
                throw new ArgumentException("Didn't find the candidate referenced by the token");
            }

            // Store the ship's token
            finalist.StartedShip(ship.PhysicsBody.Token);
        }