/* * The Ants simulator * * The environment description */ namespace Nemerle.Ants { using System; using System.IO; using Nemerle.IO; using Nemerle.Assertions; public type Marker = int; /* ranging from 0 to 5 */ public type Direction = int; /* ranging from 0 to 5 */ public type Position = int * int; /* ranging from (0, 0) to (99, 99) */ public variant LeftOrRight { | Left | Right } public variant SenseDirection { | Here /* sense the ant's current cell */ | Ahead /* sense the cell straight ahead in the direction ant is facing */ | LeftAhead /* sense the cell that would be ahead if ant turned left */ | RightAhead /* sense the cell that would be ahead if ant turned right */ } public class Color_base { public this () {} public OtherColor : Color { get { match (this) { | _ is Color.Red => Color.Black () | _ => Color.Red () } } } } public variant Color : Color_base { | Red | Black } public variant CellType { | Rocky | Clear | RedAntHill | BlackAntHill } /* ----------------------------------------------------------------------- */ /* -- ANT ---------------------------------------------------------------- */ /* ----------------------------------------------------------------------- */ /** * Description of an ant */ public class Ant { public this (id : int, color : Color, pos : Position) { m_id = id; m_color = color; m_state = 0; m_resting = 0; m_direction = 0; m_position = pos; m_has_food = false; m_is_alive = true } /* -- PUBLIC METHODS --------------------------------------------------- */ /** * Returns true if an ant of a color is this ant's friend */ public IsFriend (col : Color) : bool { match ((m_color, col)) { | (Color.Red, Color.Red) => true | (Color.Black, Color.Black) => true | _ => false } } /** * Makes the ant rest some turns */ public SetRestPeriod (rest_period : int) : void { m_resting = rest_period } /** * Ticks the resting period */ [Requires (m_resting > 0)] public Rest () : void { --m_resting } /** * Dumps the state of the ant in the ICFP contest format */ public DumpIcfp () : string { def color = match (m_color) { | Color.Red => "red" | _ => "black" }; sprintf ("%s ant of id %i, dir %i, food %s, state %i, resting %i", color, m_id, m_direction, if (m_has_food) "1" else "0", m_state, m_resting) } /* -- PUBLIC PROPERTIES ------------------------------------------------ */ /** * The ant's brain state */ public State : State { get { m_state } set { m_state = value } } /** * Returns true if the ant is carrying food */ public HasFood : bool { get { m_has_food } set { m_has_food = value } } /** * Returns the ant's resting counter */ public Resting : int { get { m_resting } set { m_resting = value } } /** * Dead or alive? */ public IsAlive : bool { get { m_is_alive } set { m_is_alive = value } } /** * The direction the ant is facing */ public Direction : Direction { get { m_direction } set { m_direction = value } } /** * The ant's color */ public Color : Color { get { m_color } } /** * Returns the position of the cell this ant's occupying */ public GetPosition () : Position { m_position } /** * Sets the position of the cell this ant's occupying */ public SetPosition (position : Position) : void { m_position = position } /* -- PRIVATE FIELDS --------------------------------------------------- */ private m_id : int; private m_color : Color; private mutable m_is_alive : bool; private mutable m_state : int; private mutable m_resting : int; private mutable m_position : Position; private mutable m_direction : Direction; private mutable m_has_food : bool; } /* ----------------------------------------------------------------------- */ /* -- CELL --------------------------------------------------------------- */ /* ----------------------------------------------------------------------- */ /** * Description of a cell */ public class Cell { public static this () { m_current_round = 0 } public this (cell_type : CellType) { m_type = cell_type; m_food = 0; m_red_markers = array (6); m_black_markers = array (6); m_ant = None (); m_modified = 0 } [Requires (food >= 0)] public this (cell_type : CellType, food : int) { m_type = cell_type; m_food = food; m_red_markers = array (6); m_black_markers = array (6); m_ant = None (); m_modified = 0 } /* -- PUBLIC PROPERTIES ------------------------------------------------ */ /** * Returns true if this cell is rocky */ public IsRocky : bool { get { match (m_type) { | CellType.Rocky => true | _ => false } } } /** * Returns the amount of food stacked at this cell */ public Food : int { get { m_food } set { m_food = value; m_modified = m_current_round } } /** * Returns the ant occupying this cell (if any) */ public Ant : option [Ant] { get { m_ant } set { m_ant = value; m_modified = m_current_round } } /** * Is the cell occupied? */ public IsOccupied : bool { get { Option.IsSome (m_ant) } } /** * Checks if the cell has been modified recently */ public IsModified : bool { get { m_modified == m_current_round } set { if (value) m_modified = m_current_round else m_modified = -1 } } /** * The static round counter */ public static CurrentRound : int { get { m_current_round } set { m_current_round = value } } /* -- PUBLIC METHODS --------------------------------------------------- */ /** * Given a color, checks if the cell is an anthill */ public IsHome (col : Color) : bool { match ((m_type, col)) { | (CellType.RedAntHill, Color.Red) => true | (CellType.BlackAntHill, Color.Black) => true | _ => false } } /** * Checks if a marker has been set */ [Requires (marker >= 0 && marker < 6)] public CheckMarker (color : Color, marker : Marker) : bool { match (color) { | Color.Red => m_red_markers [marker] | _ => m_black_markers [marker] } } /** * Checks if a marker has been set */ public CheckAnyMarker (color : Color) : bool { def loop (marker) { CheckMarker (color, marker) || (marker < 5 && loop (marker + 1)) }; loop (0) } /** * Adds a marker */ [Requires (marker >= 0 && marker < 6)] public AddMarker (color : Color, marker : Marker) : void { match (color) { | Color.Red => m_red_markers [marker] = true | _ => m_black_markers [marker] = true }; m_modified = m_current_round } /** * Removes a marker */ [Requires (marker >= 0 && marker < 6)] public RemoveMarker (color : Color, marker : Marker) : void { match (color) { | Color.Red => m_red_markers [marker] = false | _ => m_black_markers [marker] = false }; m_modified = m_current_round } /** * Dumps the state of the cell in the ICFP contest format */ public DumpIcfp () : string { def dump_markers (title : string, markers : array [bool], index : int, acc : string) : string { if (index < 6) dump_markers (title, markers, index + 1, if (markers [index]) acc + String.Format ("{0}", index) else acc) else if (acc != "") title + " marks: " + acc + "; " else "" }; if (IsRocky) "rock" else { def food = if (m_food > 0) sprintf ("%i food; ", m_food) else ""; def anthills = if (IsHome (Color.Red ())) "red hill; " else if (IsHome (Color.Black ())) "black hill; " else ""; def red_markers = dump_markers ("red", m_red_markers, 0, ""); def black_markers = dump_markers ("black", m_black_markers, 0, ""); def ant = match (m_ant) { | Some (ant) => ant.DumpIcfp () | None => "" }; food + anthills + red_markers + black_markers + ant } } /* -- PRIVATE FIELDS --------------------------------------------------- */ private m_type : CellType; private m_red_markers : array [bool]; private m_black_markers : array [bool]; private mutable m_food : int; private mutable m_ant : option [Ant]; private mutable m_modified : int; private static mutable m_current_round : int; } /* ----------------------------------------------------------------------- */ /* -- WORLD MAP ---------------------------------------------------------- */ /* ----------------------------------------------------------------------- */ /** * The (stateful) world map. Contains an array of the cells. */ public class WorldMap { /** * Reads the world map description from a file */ public this (map_file_name : string) { m_anthills_count = 0; try { m_map_stream = StreamReader (map_file_name); create_map (); read_map () } catch { | e => assert (false, "WorldMap: failed to load the world map file: " + e.Message) } } /* -- PUBLIC PROPERTIES ------------------------------------------------ */ /** * The map's width */ public Width : int { get { m_width } } /** * The map's height */ public Height : int { get { m_height } } /** * The number of ant hills */ public AntHillsCount : int { get { m_anthills_count } } /** * The scores calculation */ public GetScores () : int * int { mutable red = 0; mutable black = 0; for (mutable y = 0; y < m_height; ++y) for (mutable x = 0; x < m_width; ++x) { def cell = At ((x, y)); when (cell.Food > 0) { if (cell.IsHome (Color.Red ())) red = red + cell.Food else when (cell.IsHome (Color.Black ())) black = black + cell.Food } }; (red, black) } /* -- PUBLIC METHODS --------------------------------------------------- */ /** * Returns a direction after performing a turn */ public Turn (lr : LeftOrRight, dir : Direction) : Direction { match (lr) { | LeftOrRight.Left => (dir + 5) % 6 | _ => (dir + 1) % 6 } } /** * Returns the cell at given map coordinates */ [Requires (position_in_range (pos))] public At (pos : Position) : Cell { def (x, y) = pos; (m_map [y * m_width + x] :> Cell) } /** * Returns the position of a cell's neighbour in a given direction */ [Requires (position_in_range (pos))] public AdjacentCell (pos : Position, dir : Direction) : Position { def (x, y) = pos; def y_is_even = (y % 2 == 0); match (dir) { | 0 => (x + 1, y) | 1 => if (y_is_even) (x, y + 1) else (x + 1, y + 1) | 2 => if (y_is_even) (x - 1, y + 1) else (x, y + 1) | 3 => (x - 1, y) | 4 => if (y_is_even) (x - 1, y - 1) else (x, y - 1) | _ => if (y_is_even) (x, y - 1) else (x + 1, y - 1) } } /** * Returns the position of a cell's neighbour in a sensed direction */ [Requires (position_in_range (pos))] public SensedCell (pos : Position, dir : Direction, sd : SenseDirection) : Position { match (sd) { | SenseDirection.Here => pos | SenseDirection.Ahead => AdjacentCell (pos, dir) | SenseDirection.LeftAhead => AdjacentCell (pos, Turn (LeftOrRight.Left (), dir)) | _ => AdjacentCell (pos, Turn (LeftOrRight.Right (), dir)) } } /** * Counts the number of ants of a given color in cells * adjacent to a selected cell */ [Requires (position_in_range (pos))] public AdjacentAnts (pos : Position, col : Color) : int { mutable n = 0; for (mutable dir = 0; dir < 6; ++dir) { match (At (AdjacentCell (pos, dir)).Ant) { | Some (ant) => when (ant.IsFriend (col)) ++n | _ => () } }; n } /** * Dumps the current state of the map */ public Dump (output : System.IO.TextWriter) : void { for (mutable y = 0; y < m_height; ++y) { for (mutable x = 0; x < m_width; ++x) { def cell = At ((x, y)); def cell_dump = if (cell.IsRocky) "@@" else { def food = if (cell.Food > 0 && cell.Food <= 9) String.Format ("{0}", cell.Food); else if (cell.Food > 9) "$" else " "; def food = "\e[01;33m" + food + "\e[0m"; def ant = match (cell.Ant) { | Some (ant) => match (ant.Color) { | Color.Red => "\e[01;31m%\e[0m" | _ => "\e[01;34m%\e[0m" } | None => if (cell.IsHome (Color.Red ())) "\e[01;31m.\e[0m" else if (cell.IsHome (Color.Black ())) "\e[01;34m.\e[0m" else " " }; ant + food }; output.Write (cell_dump) }; output.WriteLine () } } /** * Performs a dump of the world state in a format compatible with * the dumps found on ifcpcontest.org site */ public DumpIcfp (output : TextWriter) : void { for (mutable y = 0; y < m_height; ++y) { for (mutable x = 0; x < m_width; ++x) { def cell = At ((x, y)); when (cell.IsModified) output.WriteLine ("cell ({0}, {1}): {2}", x, y, cell.DumpIcfp ()) } } } /* -- PRIVATE METHODS -------------------------------------------------- */ /// reads the map dimensions and initializes the cells array private create_map () : void { match ((get_next_line (), get_next_line ())) { | (Some (width_line), Some (height_line)) => m_width = Int32.Parse (width_line); m_height = Int32.Parse (height_line) | _ => assert (false, "WorldMap: failed to parse the world map size") }; m_map = array (m_width * m_height) } /// reads the entire map private read_map () : void { for (mutable i = 0; i < m_height; ++i) read_map_row (i) } /// reads a map's row private read_map_row (row : int) : void { def line = match (get_next_line ()) { | Some (line) => line | _ => assert (false, "WorldMap: failed to read a world map's row") }; for (mutable column = 0; column < m_width; ++column) { def cell = match (line [row % 2 + column * 2]) { | '#' => Cell (CellType.Rocky ()) | '.' => Cell (CellType.Clear ()) | '+' => ++m_anthills_count; Cell (CellType.RedAntHill ()) | '-' => ++m_anthills_count; Cell (CellType.BlackAntHill ()) | c when c >= '1' && c <= '9' => Cell (CellType.Clear (), (c :> int) - ('1' :> int) + 1) | _ => assert (false, "WorldMap: invalid character on a world map's row") }; m_map [row * m_width + column] = cell } } /// reads a line from the input file private get_next_line () : option [string] { def line = m_map_stream.ReadLine (); if (line != null) Some (line) else None () } /// checks if a position is in the map's valid range private position_in_range (pos : Position) : bool { def (x, y) = pos; x >= 0 && x < m_width && y >= 0 && y < m_height } /* -- PRIVATE FIELDS --------------------------------------------------- */ private mutable m_width : int; private mutable m_height : int; private mutable m_map : array [object]; private mutable m_map_stream : StreamReader; private mutable m_anthills_count : int; } } /*** END OF FILE ***/