/// <summary> /// Initializes a new instance of the <see cref="MovingObstacle"/> class. /// </summary> /// <param name="position">The initial position.</param> /// <param name="type">The type of the obstacle.</param> public MovingObstacle(GridPosition position, MovingObstacleType type) : base(position) { this.type = type; this.PossibleMovements = MovementAction.VonNeumannNeighbourhoodMovements(); EpisodeManager.AddEpisodeChangesReceiver(this); }
/// <summary> /// Converts a logical <see cref="T:Garden.GridPosition"/> into an absolute position in the Unity coordinate space. /// </summary> /// <description> /// Note: Do not call this method prior to Start() being called on this <see cref="T:GuiGarden"/> object. /// </description> /// <returns>The absolute position in Unity coordinate space.</returns> /// <param name="gridPosition">The <see cref="T:Garden.GridPosition"/> to convert.</param> public Vector3 GridPositionToUnityPosition(Garden.GridPosition gridPosition) { Vector3 position = transform.position + new Vector3(-(float)(garden.GardenWidth) / 2f * TilePrefabWidth, (garden.GardenHeigth) / 2f * TilePrefabHeight, transform.position.z); position = position + new Vector3(gridPosition.X * TilePrefabWidth, -gridPosition.Y * TilePrefabHeight); return(position); }
/// <summary> /// Performs the given movement action. /// </summary> /// <param name="movementAction">The Movement action to perform.</param> private void PerformMovement(MovementAction movementAction) { GridPosition oldPosition = this.Position; this.Position = oldPosition + movementAction.GridMovement; foreach (var observer in observers) { observer.MovingObstacleMoved(this, oldPosition, this.Position); } }
/// <summary> /// Resets the state of the object to reflect the beginning of a new episode. /// </summary> protected override void ResetToInitialState() { GridPosition oldPosition = Position; base.ResetToInitialState(); foreach (var observer in observers) { observer.MovingObstacleMoved(this, oldPosition, Position); } }
/// <summary> /// Sets the garden model at the given position. /// </summary> /// <returns><c>true</c>, if the garden model at position could be set, <c>false</c> otherwise.</returns> /// <param name="position">The position.</param> /// <param name="tileStatus">The tile status to set at the given position in the garden model.</param> private bool SetGardenModelAtPosition(Garden.GridPosition position, Tile.MowStatus tileStatus) { if (IsValidPositionInModel(position)) { int index = position.Y * (int)gardenWidth + position.X; gardenModel[index] = tileStatus; return(true); } return(false); }
/// <summary> /// Returns the current belief of the <see cref="T:Tile.MowStatus"/> at the given position in the garden model. /// </summary> /// <returns>The position in the garden model.</returns> /// <param name="position">Position.</param> public Tile.MowStatus GardenModelAtPosition(Garden.GridPosition position) { int index = position.Y * (int)gardenWidth + position.X; if (IsValidPositionInModel(position) && index < gardenModel.Count) { return(gardenModel[index]); } return(Tile.MowStatus.Obstacle); }
/// <summary> /// Resets the state of the object to reflect the beginning of a new episode. /// </summary> protected override void ResetToInitialState() { GridPosition oldPosition = Position; base.ResetToInitialState(); foreach (var observer in observers) { observer.MowerMoved(this, oldPosition, Position); } RemainingTilesToMow = Garden.NumGrassTiles; }
/// <summary> /// Incorporates the given state that was observed by the mower into the garden model. /// </summary> /// <param name="observedState">The state observed by the mower.</param> public void IncorporateObservedState(State observedState) { Garden.GridPosition mowerPosition = observedState.Position; SetGardenModelAtPosition(mowerPosition + MovementAction.North().GridMovement, observedState.NorthTileStatus); SetGardenModelAtPosition(mowerPosition + MovementAction.East().GridMovement, observedState.EastTileStatus); SetGardenModelAtPosition(mowerPosition + MovementAction.South().GridMovement, observedState.SouthTileStatus); SetGardenModelAtPosition(mowerPosition + MovementAction.West().GridMovement, observedState.WestTileStatus); SetGardenModelAtPosition(mowerPosition + MovementAction.NorthEast().GridMovement, observedState.NorthEastTileStatus); SetGardenModelAtPosition(mowerPosition + MovementAction.SouthEast().GridMovement, observedState.SouthEastTileStatus); SetGardenModelAtPosition(mowerPosition + MovementAction.SouthWest().GridMovement, observedState.SouthWestTileStatus); SetGardenModelAtPosition(mowerPosition + MovementAction.NorthWest().GridMovement, observedState.NorthWestTileStatus); }
/// <summary> /// Performs the given movement action. /// </summary> /// <param name="movementAction">The Movement action to perform.</param> private void PerformMovement(MovementAction movementAction) { GridPosition oldPosition = this.Position; this.Position = this.Position + movementAction.GridMovement; float reward = Mow(); learner.Learn(reward); foreach (var observer in observers) { observer.MowerMoved(this, oldPosition, this.Position); } }
/// <summary> /// Gets the <see cref="T:GuiTile"/> game object at the given logical <see cref="T:Garden.GridPosition"/>. /// </summary> /// <returns>The <see cref="T:GuiTile"/> game object at the given position.</returns> /// <param name="gridPosition">The <see cref="T:Garden.GridPosition"/>.</param> public GuiTile GuiTileAtPosition(Garden.GridPosition gridPosition) { int index = gridPosition.Y * (int)Garden.GardenWidth + gridPosition.X; if (Garden.IsValidPosition(gridPosition) && index < Tiles.Count) { return(Tiles[index].GetComponent <GuiTile>()); } else { return(null); } }
/// <summary> /// Determines whether the given position is a valid position in the garden model. /// </summary> /// <returns><c>true</c> if this position is valid; otherwise, <c>false</c>.</returns> /// <param name="position">The position to check.</param> private bool IsValidPositionInModel(Garden.GridPosition position) { if (position.X >= gardenWidth || position.X < 0 || position.Y >= gardenHeight || position.Y < 0) { return(false); } else { return(true); } }
/// <summary> /// Generates a virtual step of a mower at a random position in the garden model. /// </summary> /// <description> /// The step is created by randomly choosing a position inside the garden as well as a random movement. /// The outcome of this action is determined by the current belief the garden model holds. /// It might happen, that the garden model does not have enough information to generate such a virtual step. /// Therefore, the return value of this method always has to be checked! /// </description> /// <returns><c>true</c>, if a random model step could be generated, <c>false</c> otherwise.</returns> /// <param name="fromState">The state the step originated.</param> /// <param name="performedAction">The action performed at the fromState.</param> /// <param name="toState">The state that is observed after the movement.</param> /// <param name="receivedReward">The received reward for this step.</param> /// <param name="numRetries">The number of retries to generate a virtual step, before this method aborts.</param> public bool GenerateRandomModelStep(out State fromState, out MovementAction performedAction, out State toState, out float receivedReward, uint numRetries = 1) { fromState = null; toState = null; performedAction = null; receivedReward = 0f; uint retries = 0; bool stepGenerated = false; while (retries <= numRetries) { // Select random position inside garden model int randX = UnityEngine.Random.Range(0, (int)gardenWidth); int randY = UnityEngine.Random.Range(0, (int)gardenHeight); Garden.GridPosition virtualPosition = new Garden.GridPosition(randX, randY); fromState = ModelTable.ModelStateExtractor.ExtractStateFromGardenModelAtPosition(this, virtualPosition); // Check which positions can be reached from here List <MovementAction> possibleMovements = new List <MovementAction>(mowerMovements.Count); foreach (var movement in mowerMovements) { Garden.GridPosition resultingPosition = virtualPosition + movement.GridMovement; if (GardenModelAtPosition(resultingPosition) != Tile.MowStatus.Obstacle) { possibleMovements.Add(movement); } } if (possibleMovements.Count == 0) { retries++; continue; } else { // Select random movement from the possible ones performedAction = possibleMovements[UnityEngine.Random.Range(0, possibleMovements.Count)]; Garden.GridPosition resultingPosition = virtualPosition + performedAction.GridMovement; receivedReward = (GardenModelAtPosition(resultingPosition) == Tile.MowStatus.LongGrass) ? mowReward : notMownReward; // Predict new state after the movement toState = ModelTable.ModelStateExtractor.ExtractStateFromGardenModelAtPosition(this, resultingPosition); stepGenerated = true; break; } } return(stepGenerated); }
/// <summary> /// Initializes a new instance of the <see cref="State"/> class. /// </summary> /// <param name="northTileStatus">North tile status.</param> /// <param name="northEastTileStatus">North east tile status.</param> /// <param name="eastTileStatus">East tile status.</param> /// <param name="southEastStatus">South east status.</param> /// <param name="southTileStatus">South tile status.</param> /// <param name="southWestTileStatus">South west tile status.</param> /// <param name="westTileStatus">West tile status.</param> /// <param name="northWestTileStatus">North west tile status.</param> /// <param name="position">Current position.</param> public State(Tile.MowStatus northTileStatus, Tile.MowStatus northEastTileStatus, Tile.MowStatus eastTileStatus, Tile.MowStatus southEastStatus, Tile.MowStatus southTileStatus, Tile.MowStatus southWestTileStatus, Tile.MowStatus westTileStatus, Tile.MowStatus northWestTileStatus, Garden.GridPosition position ) { this.NorthTileStatus = northTileStatus; this.NorthEastTileStatus = northEastTileStatus; this.EastTileStatus = eastTileStatus; this.SouthEastTileStatus = southEastStatus; this.SouthTileStatus = southTileStatus; this.SouthWestTileStatus = southWestTileStatus; this.WestTileStatus = westTileStatus; this.NorthWestTileStatus = northWestTileStatus; this.Position = position; }
/// <summary> /// Creates the initial garden UI. /// </summary> void Start() { Application.runInBackground = true; // Extract size from tile prefab TilePrefabWidth = TilePrefab.GetComponent <SpriteRenderer>().bounds.size.x; TilePrefabHeight = TilePrefab.GetComponent <SpriteRenderer>().bounds.size.y; // Initi GuiTiles for (int row = 0; row < garden.GardenHeigth; row++) { for (int col = 0; col < garden.GardenWidth; col++) { Garden.GridPosition gridPosition = new Garden.GridPosition(col, row); GameObject go = (GameObject)Instantiate(TilePrefab, transform.position, Quaternion.identity); go.transform.SetParent(this.transform); go.layer = this.gameObject.layer; go.transform.position = GridPositionToUnityPosition(gridPosition); go.GetComponent <GuiTile>().SetColorForState(garden.GetTileStatus(gridPosition)); tiles.Add(go); } } Garden.AddObserver(this); // Init GuiMower GameObject mowerGo = Instantiate(MowerPrefab); mowerGo.GetComponent <GuiMower>().Init(this); // Init GuiMovingObstacles foreach (var movingObstacle in Garden.MovingObstacles) { GameObject obstacleGO = Instantiate(AnimalPrefab); obstacleGO.GetComponent <GuiMovingObstacle>().Init(this, movingObstacle); } }
/// <summary> /// Initializes a new instance of the <see cref="MovingGardenObject"/> class. /// </summary> /// <param name="position">The initial position.</param> public MovingGardenObject(GridPosition position) : base(position) { }
/// <summary> /// Initializes a new instance of the <see cref="Mower"/> class. /// </summary> /// <param name="position">The start position.</param> /// <param name="garden">The <see cref="T:Garden"/> this mower operates in.</param> /// <param name="paramSet">The <see cref="T:ParamSet"/> of the project which contains the neccessary parameters for the mower.</param> public Mower(GridPosition position, Garden garden, ParamSet paramSet) : this((uint)position.X, (uint)position.Y, garden, paramSet) { }
/// <summary> /// Creates a Garden object using a xml-based representation. /// </summary> /// <returns>The garden from the xml file.</returns> /// <param name="filename">The filename of the xml-file defining the garden.</param> /// <param name="paramSet">The set of parameters for this project.</param> public static Garden CreateGardenFromFile(string filename, ParamSet paramSet) { // Init the xml document XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(filename); // Init containers for the extracted information uint gardenWidth, gardenHeight; Garden.GridPosition mowerStartPosition; List <Tile> tiles; List <MovingObstacle> movingObstacles; List <StaticObstacle> staticObstacles = new List <StaticObstacle>(); bool parseResult = true; XmlNode gardenNode = xmlDoc.SelectSingleNode("/" + gardenNodeName); // Parse the garden if (gardenNode == null) { throw new InvalidGardenXMLException(MissingNodeErrorMessage(gardenNodeName)); } XmlNode widthAttribute = gardenNode.SelectSingleNode("@" + widthAttributeName); if (widthAttribute == null) { throw new InvalidGardenXMLException(MissingAttributeInNodeErrorMessage(gardenNodeName, widthAttributeName)); } parseResult = uint.TryParse(widthAttribute.InnerText, out gardenWidth); XmlNode heightAttribute = gardenNode.SelectSingleNode("@" + heightAttributeName); if (heightAttribute == null) { throw new InvalidGardenXMLException(MissingAttributeInNodeErrorMessage(gardenNodeName, heightAttributeName)); } parseResult = uint.TryParse(heightAttribute.InnerText, out gardenHeight); // Parse the mower starting position XmlNode startPosNode = gardenNode.SelectSingleNode(startPosNodeName); if (startPosNode == null) { throw new InvalidGardenXMLException(MissingNodeErrorMessage(startPosNodeName)); } XmlNode xAttribute = startPosNode.SelectSingleNode("@" + xPosAttributeName); if (xAttribute == null) { throw new InvalidGardenXMLException(MissingAttributeInNodeErrorMessage(startPosNodeName, xPosAttributeName)); } uint x, y; parseResult = uint.TryParse(xAttribute.InnerText, out x); XmlNode yAttribute = startPosNode.SelectSingleNode("@" + yPosAttributeName); if (yAttribute == null) { throw new InvalidGardenXMLException(MissingAttributeInNodeErrorMessage(startPosNodeName, yPosAttributeName)); } parseResult = uint.TryParse(yAttribute.InnerText, out y); mowerStartPosition = new Garden.GridPosition((int)x, (int)y); // Parse the tiles XmlNodeList tileNodes = gardenNode.SelectNodes(tilesNodeName + "/" + tileNodeName); if (tileNodes.Count != (gardenWidth * gardenHeight)) { throw new InvalidGardenXMLException(string.Format("Only found {0} <" + tileNodeName + "> nodes. Expecting {1}.", tileNodes.Count, gardenWidth * gardenHeight)); } tiles = new List <Tile>(tileNodes.Count); foreach (XmlNode tileNode in tileNodes) { xAttribute = tileNode.SelectSingleNode("@" + xPosAttributeName); if (xAttribute == null) { throw new InvalidGardenXMLException(MissingAttributeInNodeErrorMessage(tileNodeName, xPosAttributeName)); } parseResult = uint.TryParse(xAttribute.InnerText, out x); yAttribute = tileNode.SelectSingleNode("@" + yPosAttributeName); if (yAttribute == null) { throw new InvalidGardenXMLException(MissingAttributeInNodeErrorMessage(tileNodeName, yPosAttributeName)); } parseResult = uint.TryParse(yAttribute.InnerText, out y); XmlNode typeAttribute = tileNode.SelectSingleNode("@" + typeAttributeName); if (typeAttribute == null) { throw new InvalidGardenXMLException(MissingAttributeInNodeErrorMessage(tileNodeName, typeAttributeName)); } Garden.GridPosition position = new Garden.GridPosition((int)x, (int)y); Tile.MowStatus tileStatus = Tile.MowStatus.LongGrass; switch (typeAttribute.InnerText) { case tileTypeLongGrass: tileStatus = Tile.MowStatus.LongGrass; break; case tileTypeShortGrass: tileStatus = Tile.MowStatus.ShortGrass; break; case tileTypeRock: tileStatus = Tile.MowStatus.Obstacle; staticObstacles.Add(new StaticObstacle(position, StaticObstacle.StaticObstacleType.Rock)); break; case tileTypeWater: tileStatus = Tile.MowStatus.Obstacle; staticObstacles.Add(new StaticObstacle(position, StaticObstacle.StaticObstacleType.Water)); break; case tileTypeChargingStation: tileStatus = Tile.MowStatus.ChargingStation; break; default: throw new InvalidGardenXMLException( InvalidAttributeValueInNodeErrorMessage(tileNodeName, new KeyValuePair <string, string>(typeAttributeName, typeAttribute.InnerText)) ); } bool occupiedByMower = position.Equals(mowerStartPosition); tiles.Add(new Tile(position, tileStatus, occupiedByMower)); } // Parse the moving obstacles XmlNodeList obstacleNodes = gardenNode.SelectNodes(movingObstaclesNodeName + "/" + movingObstacleNodeName); movingObstacles = new List <MovingObstacle>(obstacleNodes.Count); foreach (XmlNode obstacleNode in obstacleNodes) { xAttribute = obstacleNode.SelectSingleNode("@" + xPosAttributeName); if (xAttribute == null) { throw new InvalidGardenXMLException(MissingAttributeInNodeErrorMessage(movingObstacleNodeName, xPosAttributeName)); } parseResult = uint.TryParse(xAttribute.InnerText, out x); yAttribute = obstacleNode.SelectSingleNode("@" + yPosAttributeName); if (yAttribute == null) { throw new InvalidGardenXMLException(MissingAttributeInNodeErrorMessage(movingObstacleNodeName, yPosAttributeName)); } parseResult = uint.TryParse(yAttribute.InnerText, out y); XmlNode typeAttribute = obstacleNode.SelectSingleNode("@" + typeAttributeName); if (typeAttribute == null) { throw new InvalidGardenXMLException(MissingAttributeInNodeErrorMessage(movingObstacleNodeName, typeAttributeName)); } Garden.GridPosition position = new Garden.GridPosition((int)x, (int)y); MovingObstacle.MovingObstacleType obstacleType = MovingObstacle.MovingObstacleType.Animal; switch (typeAttribute.InnerText) { case movingObstacleTypeAnimal: obstacleType = MovingObstacle.MovingObstacleType.Animal; break; case movingObstacleTypePerson: obstacleType = MovingObstacle.MovingObstacleType.Person; break; default: throw new InvalidGardenXMLException( InvalidAttributeValueInNodeErrorMessage(movingObstacleNodeName, new KeyValuePair <string, string>(typeAttributeName, typeAttribute.InnerText)) ); } MovingObstacle obstacle = new MovingObstacle(position, obstacleType); // Register the tile as occupied foreach (var tile in tiles) { if (tile.Position.Equals(position)) { if (tile.Occupied == true) { throw new InvalidGardenXMLException(string.Format("Multiple objects positioned at {0}.", position)); } else { tile.Occupied = true; } break; } } movingObstacles.Add(obstacle); // Save the initial state of all tiles foreach (var tile in tiles) { tile.SetCurrentStateAsInitialState(); } } // Create the garden (finally...) return(new Garden(gardenWidth, gardenHeight, tiles, mowerStartPosition, movingObstacles, staticObstacles, paramSet)); }
/// <summary> /// Initializes a new instance of the <see cref="MovementAction"/> class. /// </summary> /// <param name="direction">The direction of the movement.</param> /// <param name="gridMovement">The mathematical movement on the 2D grid.</param> public MovementAction(MovementAction.Direction direction, GridPosition gridMovement) { this.MovementDirection = direction; this.GridMovement = gridMovement; }
/// <summary> /// Moves this object to the given position on the 2D grid. /// </summary> /// <param name="gridPosition">Grid position.</param> private void MoveToPosition(Garden.GridPosition gridPosition) { this.transform.position = GuiGarden.GridPositionToUnityPosition(gridPosition); }
/// <summary> /// Notification that the obstacle moved from one position to another. /// </summary> /// <param name="obstacle">The moving obstacle.</param> /// <param name="oldPosition">Its old position.</param> /// <param name="newPosition">Its new position.</param> public void MovingObstacleMoved(MovingObstacle obstacle, Garden.GridPosition oldPosition, Garden.GridPosition newPosition) { MoveToPosition(newPosition); }
/// <summary> /// Extracts the state from the garden model at the given position. /// </summary> /// <param name="gardenModel">The garden model.</param> /// <param name="position">The position.</param> /// <returns>The extracted state.</returns> public static State ExtractStateFromGardenModelAtPosition(ModelTable gardenModel, Garden.GridPosition position) { return(new State( gardenModel.GardenModelAtPosition(position + MovementAction.North().GridMovement), gardenModel.GardenModelAtPosition(position + MovementAction.NorthEast().GridMovement), gardenModel.GardenModelAtPosition(position + MovementAction.East().GridMovement), gardenModel.GardenModelAtPosition(position + MovementAction.SouthEast().GridMovement), gardenModel.GardenModelAtPosition(position + MovementAction.South().GridMovement), gardenModel.GardenModelAtPosition(position + MovementAction.SouthWest().GridMovement), gardenModel.GardenModelAtPosition(position + MovementAction.West().GridMovement), gardenModel.GardenModelAtPosition(position + MovementAction.NorthWest().GridMovement), position )); }
/// <summary> /// Notification that the mower moved from one position to another. /// </summary> /// <param name="mower">The mower.</param> /// <param name="oldPosition">Its old position.</param> /// <param name="newPosition">Its new position.</param> public void MowerMoved(Mower mower, Garden.GridPosition oldPosition, Garden.GridPosition newPosition) { this.MoveToPosition(newPosition); }
/// <summary> /// Initializes a new instance of the <see cref="GardenObject"/> class. /// </summary> /// <param name="position">The initial position.</param> public GardenObject(GridPosition position) { this.Position = new GridPosition(position); this.InitialPosition = Position; }
/// <summary> /// Initializes a new instance of the <see cref="Tile"/> class. /// </summary> /// <param name="position">The position of the tile.</param> /// <param name="mowStatus">The mow status of the tile.</param> /// <param name="occupied">Whether the tile is occupied or not.</param> public Tile(GridPosition position, MowStatus mowStatus, bool occupied) : this((uint)position.X, (uint)position.Y, mowStatus, occupied) { }
/// <summary> /// Initializes a new instance of the <see cref="StaticObstacle"/> class. /// </summary> /// <param name="position">The initial position.</param> /// <param name="type">The type of the obstacle.</param> public StaticObstacle(GridPosition position, StaticObstacleType type) : base(position) { this.type = type; }