% File "world.tb" % % Written by Phil Edmonds, March 97. % % This implements the world. The world consists of the terrain, and a set % of denizens (its inhabitants and other active objects). It also % contains a priority queue for purposes of the simulation. The simulation % is event-driven, which means that we keep track of events that we know % are going to happen in the future, and the clock jumps ahead to the time % of the next event. An event here is an object waiting to take a turn. % The queue stores all the waiting objects, with priorities assigned % according to the time when the event is supposed to occur. % %+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ unit class WorldBody implement World in "world.ti" import Set in "set.tu", PriorityQueue in "pq.tu", Statistics in "stats.tu", SimVar in "simvar.tu", ActiveObject in "active.tu", Plain in "plain.tu", Water in "water.tu", Mountain in "mountain.tu" %========== CONSTANTS ======================================= %============================================================ % the size of the world const MaxX := SimVar.W_MAX_X const MaxY := SimVar.W_MAX_Y % the number of terrain pieces in each direction const terrainX : int := (MaxX + 1) div Terrain.Size - 1 const terrainY : int := (MaxY + 1) div Terrain.Size - 1 %========== INSTANCE VARIABLES ============================== %============================================================ % the current time in the simulation var curTime : int % the queue stores objects that are waiting to take turns var waitingQ : ^PriorityQueue % terrain is a matrix of pieces of terrain (the base of the world) var terrain : array 0 .. terrainX, 0 .. terrainY of ^Terrain % the set of animals and other active objects in the world var denizens : ^Set % the statistics for the world var stats : ^Statistics %========== PRIVATE FUNCTIONS =============================== %============================================================ % convert back and forth from screen coordinates to terrain coords. %------------------------------------------------------------ function TerrainToCoord (z : int) : real result z * Terrain.Size end TerrainToCoord function CoordToTerrain (z : real) : int result round (z) div Terrain.Size end CoordToTerrain % Randomly selects a piece of terrain for each place in the world % IF A NEW KIND OF TERRAIN IS TO BE ADDED, YOU MUST ADD A CASE HERE %------------------------------------------------------------ % these formulas W, P, M, give relative distributions for the % 3 kinds of terrain across the world. They are normal distributions, % that can be changed. (E.g., for completely random % just result '1.0' from each.) % normal distribution (u is the mean, s is standard deviation) function Normal (x, u, s : real) : real result exp ( - ( (x - u) / s) ** 2) end Normal function W (x, y : real) : real result Normal (x, 0, 0.2) + Normal (y, 0, 0.2) end W function P (x, y : real) : real result Normal (x, 0.4, 0.1) + Normal (y, 0.5, 0.2) end P function M (x, y : real) : real result Normal (x, 0.8, 0.2) + Normal (y, 1.0, 0.2) end M proc BuildTerrain for x : 0 .. terrainX for y : 0 .. terrainY var xcoord := TerrainToCoord (x) var ycoord := TerrainToCoord (y) var xN := x / terrainX var yN := y / terrainY % choose a random number, and see which interval it falls % in. var R := Rand.Real * (W (xN, yN) + P (xN, yN) + M (xN, yN)) if (R <= W (xN, yN)) then % WATER var t : ^Water new t t -> InitWater (xcoord, ycoord) terrain (x, y) := t elsif (R <= W (xN, yN) + P (xN, yN)) then % PLAINS var t : ^Plain new t t -> InitPlain (xcoord, ycoord) terrain (x, y) := t elsif (R <= W (xN, yN) + P (xN, yN) + M (xN, yN)) then % MOUNTAIN var t : ^Mountain new t t -> InitMountain (xcoord, ycoord) terrain (x, y) := t end if end for end for end BuildTerrain %========== INITIALIZE / FINALIZE =========================== %============================================================ body proc Init new waitingQ waitingQ -> Init new denizens BuildTerrain new stats stats -> Init curTime := 0 end Init body proc Fini waitingQ -> Finalize free waitingQ free denizens % may waste memory var stream : int open : stream, "stats.out", put stats -> Prnt (stream) put : stream, "" put : stream, "Executed ", curTime - 1, " iterations." close : stream free stats % may waste memory end Fini %========== PUBLIC FUNCTIONS ================================ %============================================================ % This is the main simulation loop. It succesively dequeues % objects from the queue, and allows them to take a turn. % It stops when maxTime is reached. %------------------------------------------------------------ body proc Simulate (maxTime : int) var iter : int := 0 loop var elem : ^anyclass % get the next object waitingQ -> Leave (elem, curTime) % check to see if we've run out of time exit when curTime > maxTime % if the element is an active object (which it should be) % then converts its type, so we can call TakeTurn if (objectclass (elem) >= ActiveObject) then var denizen : ^ActiveObject denizen := elem denizen -> TakeTurn end if % print the time locate (1, 1) put curTime .. iter += 1 end loop end Simulate % schedule a denizen for its next turn at 'at' timesteps from now % only active objects can schedule a turn %------------------------------------------------------------ body proc Schedule (denizen : ^Object, at : int) assert (objectclass (denizen) >= ActiveObject) waitingQ -> Arrive (denizen, curTime + at) end Schedule % add a denizen to the world, schedule on priority queue as well %------------------------------------------------------------ body proc AddDenizen (denizen : ^Object) assert (objectclass (denizen) >= ActiveObject) denizens -> addElement (denizen) Schedule (denizen, 1) end AddDenizen % add a statistic to the world's statistics %--------------------------------------------- body proc AddStatistic (stat : string) stats -> Add (stat) end AddStatistic % Draw the whole world %------------------------------------- body proc DrawWorld setscreen ("nocursor") cls for x : 0 .. terrainX for y : 0 .. terrainY terrain (x, y) -> DrawImage end for end for end DrawWorld % Just draw the part of the world specified by the square centered % at loc with 'radius' r %------------------------------------------------------------ body proc DrawWorldPart (loc : ^Location, r : int) var xl := CoordToTerrain (loc -> GetX - r) var xr := CoordToTerrain (loc -> GetX + r) var yl := CoordToTerrain (loc -> GetY - r) var yr := CoordToTerrain (loc -> GetY + r) for xi : xl .. xr for yi : yl .. yr terrain (xi, yi) -> DrawImage end for end for end DrawWorldPart % draws all the denizens: goes through set and calls draw for % each denizen %------------------------------------------------------------ body proc DrawAllDenizens var denizen : ^Object := nil loop denizen := denizens -> GetNextElement (denizen) exit when denizen = nil denizen -> DrawImage end loop end DrawAllDenizens % Choose a denizen randomly %------------------------------------------------------------ body function GetRandomDenizen : ^Object result denizens -> GetRandomElement end GetRandomDenizen % Return a random location in the world within frame units % the edges %------------------------------------------------------------ body function RandomLocation (frame : int) : ^Location var loc : ^Location new loc loc -> SetX (Rand.Int (0 + frame, MaxX - frame)) loc -> SetY (Rand.Int (0 + frame, MaxY - frame)) result loc end RandomLocation % Return the type of terrain found at loc %------------------------------------------------------------ body function WhatTerrain (loc : ^Location) : ^Terrain var x := CoordToTerrain (loc -> GetX) var y := CoordToTerrain (loc -> GetY) result terrain (x, y) end WhatTerrain end WorldBody