/// <summary> /// Attach a new state to a world vector. /// </summary> /// <param name="state">The world state used ot init the vector.</param> public WorldVector(WorldState state) { if (!state.IsImmutable) { throw new ApplicationException("WorldState must be immutable to be added to vector."); } State = state; }
/// <summary> /// Processes turns in a phase manner. After 10 calls to ProcessTurn /// all 10 phases will be complete and the method will have completed /// one game tick. /// </summary> /// <returns>True if a tick has been processed, false otherwise.</returns> public Boolean ProcessTurn() { // shutdownError and invalidPeerError are two errors that get set from // deep in the system. We process them here because it is a clean place // to restart the game since we aren't in the middle of any phases. // This is just a mechanism for cleanly throwing an error from a known good // location. if (_shutdownError) { _shutdownError = false; throw new ShutdownFailureException(); } if (_invalidPeerError) { _invalidPeerError = false; throw new InvalidPeerException(); } // We break the main processing of the game into 10 phases that have been tuned to each // take roughly the same amount of time. We paint the screen in between each one. This way // We can have a constant frame rate and incrementally do the engine processing we need to // do. // After we do all 10 phases we've done one game "tick". We can do two ticks a second which means // we have a frame rate of 20. switch (_turnPhase) { case 0: // If we are in ecosystem mode, we want to save the game state periodically in case the user // shuts down the screensaver by pressing ctl-alt-del which just kills the process. This way // we won't have lost too much processing time. // We always want to save on the tick after we've reported so we don't get our state messed up // on the server. For example: never start the game in a state that was before the information // was sent to the server because the server will think we're corrupted. if (_logState || (_ecosystemMode && _populationData.IsReportingTick(CurrentVector.State.TickNumber - 1))) { Debug.WriteLine("Saving state."); serializeState(_currentStateFileName); } // Give 1/5 of the animals a chance to do their processing Scheduler.Tick(); break; case 1: // Give 1/5 of the animals a chance to do their processing Scheduler.Tick(); break; case 2: // Give 1/5 of the animals a chance to do their processing Scheduler.Tick(); break; case 3: // Give 1/5 of the animals a chance to do their processing Scheduler.Tick(); break; case 4: // Give 1/5 of the animals a chance to do their processing Scheduler.Tick(); break; case 5: // Get all the actions that the animals want to perform in this tick. TickActions act = _scheduler.GatherTickActions(); CurrentVector.Actions = act; // Create a mutable version of the world state that we'll change to create the next // world state. _newWorldState = (WorldState) CurrentVector.State.DuplicateMutable(); _newWorldState.TickNumber = _newWorldState.TickNumber + 1; _populationData.BeginTick(_newWorldState.TickNumber, CurrentVector.State.StateGuid); // Remove any organisms queued to be removed removeOrganismsFromQueue(); // We take a snapshot of the organism IDs since we do several foreach loops // and change the state, which causes exceptions to occur _organismIDList = new string[_newWorldState.OrganismIDs.Count]; _newWorldState.OrganismIDs.CopyTo(_organismIDList, 0); killDiseasedOrganisms2(); break; case 6: Debug.Assert(_newWorldState != null, "Worldstate did not get created for this tick"); // Do this first, since it always must happen so that things (like growing) // won't happen if there isn't enough energy burnBaseEnergy(); // Do attacks before movement so that when a carnivore asks if it can attack // they actually get to hit the target before it moves doAttacks(); doDefends(); changeMovementVectors(); break; case 7: moveAnimals(); break; case 8: doBites(); growAllOrganisms(); incubate(); startReproduction(); heal(); break; case 9: // Do this last so that the plant always starts charged up, // and has to operate with what it has for the next turn giveEnergyToPlants(); // Teleport after all actions have been processed so that there are // no single turn pending actions left for the organism. teleportOrganisms(); // Insert any new organisms insertOrganismsFromQueue(); // Set Antenna States doAntennas(); // We're done changing the state, now make it immutable _newWorldState.MakeImmutable(); Debug.Assert(_newWorldState.Organisms.Count == Scheduler.Organisms.Count); WorldVector vector = new WorldVector(_newWorldState); CurrentVector = vector; _scheduler.CurrentState = _newWorldState; _populationData.EndTick(_newWorldState.TickNumber); _newWorldState = null; break; } _turnPhase++; if (_turnPhase == 10) { _turnPhase = 0; return true; } return false; }
/// <summary> /// Constructs a new game engine. /// </summary> /// <param name="dataPath">The path to save game directory.</param> /// <param name="useNetwork">Controls the usage of the network engine.</param> /// <param name="deserializeState">Controls if the state is deserialized or not.</param> /// <param name="fileName">The path to the state file.</param> /// <param name="reportData">Determines if data should be reported.</param> /// <param name="leds">Provides a listing of game leds that can be used.</param> /// <param name="trackLastRun">Controls whether the PAC keeps track of the last run creature for blacklisting.</param> private GameEngine(string dataPath, bool useNetwork, bool deserializeState, string fileName, bool reportData, TerrariumLed[] leds, bool trackLastRun) { _ledIndicators = leds; _currentStateFileName = fileName; // test to make sure we're not violating any constraints by current // physics settings in the engine. EngineSettings.EngineSettingsAsserts(); // Calculate quanta and worldsize if we haven't done so yet if (_reloadSettings) CalculateWorldSize(); _pac = new PrivateAssemblyCache(dataPath, fileName, true, trackLastRun); // Reset the appdomain policy since we changed the location of the organism dlls // This must be done before any animals are loaded in any way. Make sure this call stays // as soon as possible AppDomain.CurrentDomain.SetAppDomainPolicy(SecurityUtils.MakePolicyLevel(_pac.AssemblyDirectory)); _usingNetwork = useNetwork; _populationData = new PopulationData(reportData, leds[(int) LedIndicators.ReportWebService]); // Should only happen if an exception prevented a previous attempt to start a game if (AppMgr.CurrentScheduler != null) { AppMgr.DestroyScheduler(); } // Create a scheduler that manages giving the creatures timeslices _scheduler = AppMgr.CreateSameDomainScheduler(this); _scheduler.Quantum = _organismQuanta; if (useNetwork) { // Required to start up the network listeners _networkEngine = new NetworkEngine(); } WorldState currentState; Boolean successfulDeserialization = false; if (deserializeState && File.Exists(fileName)) { try { if (_pac.LastRun.Length != 0) { // The process was killed while an organism was being run, blacklist it // Since this potentially means the animal hung the game. _pac.BlacklistAssemblies(new string[] {_pac.LastRun}); } this.deserializeState(fileName); currentState = CurrentVector.State; _scheduler.CurrentState = currentState; _scheduler.CompleteOrganismDeserialization(); successfulDeserialization = true; } catch (Exception e) { ErrorLog.LogHandledException(e); } } if (successfulDeserialization) return; // Set up initial world state currentState = new WorldState(GridWidth, GridHeight); currentState.TickNumber = 0; currentState.StateGuid = Guid.NewGuid(); currentState.Teleporter = new Teleporter(AnimalCount/EngineSettings.NumberOfAnimalsPerTeleporter); currentState.MakeImmutable(); WorldVector vector = new WorldVector(currentState); CurrentVector = vector; _scheduler.CurrentState = currentState; }
/// <summary> /// Copies the object, but not the isImmutable bit. Makes a newly /// immutable copy. /// </summary> /// <returns>A new WorldState object that is newly mutable.</returns> public object DuplicateMutable() { var newState = new WorldState(_gridWidth, _gridHeight); foreach (OrganismState state in Organisms) { var newOrganismState = state.CloneMutable(); Debug.Assert(newOrganismState != null, "Organism State is null in WorldState.DuplicateMutable()"); Debug.Assert(newOrganismState.ID != null, "Organism State ID is null in WorldState.DuplicateMutable()"); newState._organisms.Add(newOrganismState.ID, newOrganismState); } newState._tickNumber = _tickNumber; newState._stateGuid = _stateGuid; if (_teleporter != null) { newState._teleporter = _teleporter.Clone(); } return newState; }