;;; ;;; Schlieffen Plan Rail Logistics Model ;;; ;;; Spring 2013 ;;; ;;; Robert Allen - averyallen71@gmail.com ;;; Brian Coffey - btpcoffey@gmail.com ;;; Dante Montgomery - dante.e.montgomery@gmail.com ;;; Ashley Rawson - ashleyrawson@gmail.com ;;; Brian Stebar - stebar2@gmail.com ;; ;; calculate-x ;; ;; Translates a longitude value into an X pixel value ;; to-report calculate-x [long] ;; First, calculate the longitudinal width of a single patch let long-patch-size ((max-long - min-long) / max-pxcor) ;; Next, translate a longitude value into a patch coordinate report (floor ((long - min-long) / long-patch-size)) end ;; ;; calculate-y ;; ;; Translates a latitude value into an Y pixel value ;; to-report calculate-y [lat] ;; First, calculate the latitudinal height of a single patch let lat-patch-size ((max-lat - min-lat) / max-pycor) ;; Next, translate a latitude value into a patch coordinate report (floor ((lat - min-lat) / lat-patch-size)) end ;; ;; import-csv-data ;; ;; Loads and parses the data from an input CSV file; Also creates and places the troop formations within ;; the model and preloads the coordinates they will follow into each unit ;; ;; Each row in the CSV has the following format: ;; ;; start_timestamp,end_timestamp,nation,army,corps,latitude,longitude,num_infantry,num_cavalry,num_artillery ;; to import-csv-data file-close-all ;; Get the target filename based on the path-of-advance dropdown let filename "" ifelse (path-of-advance = "original plan") [ set filename "waypoints/original_plan.csv" ] [ set filename "waypoints/executed_plan.csv" ] ;; Load the file ifelse ( file-exists? filename ) [ file-open filename ;; Skip first line since it's nothing but headers let headers parse-csv-line file-read-line ;; Read in all the data in the file while [ not file-at-end? ] [ let current-line parse-csv-line file-read-line ;; Make sure that this unit has waypoints defined before we try to place it in the world if ((not empty? item 5 current-line) and (not empty? item 6 current-line)) [ ;; Check to see if the unit corresponding to this line has already been created let target-soldier one-of soldiers with [owner = (item 2 current-line) and army = (item 3 current-line) and corps = (item 4 current-line)] if (target-soldier = nobody) [ ;; Create the unit associated with this CSV line create-soldiers 1[ ;; Set up the name and initial attributes of this unit set owner item 2 current-line set army item 3 current-line set corps item 4 current-line set num-infantry read-from-string item 7 current-line set num-cavalry read-from-string item 8 current-line set num-artillery read-from-string item 9 current-line set waypoints [] set ticks-delayed 0 ;; Position and draw this unit in the world setxy (calculate-x read-from-string item 6 current-line) (calculate-y read-from-string item 5 current-line) set color green set size 10 ] ] ;; Now add this line's info to the target unit's waypoint list ;; Each waypoint will be a list in the following format: ;; (ETA ETD latitude longitude num_infantry num_cavalry num_artillery) ;; - ETA signifies the time at which a unit will arrive at the waypoint ;; - ETD signifies when the corps unit leave the waypoint set target-soldier one-of soldiers with [owner = (item 2 current-line) and army = (item 3 current-line) and corps = (item 4 current-line)] let waypoint (list (item 0 current-line) (item 1 current-line) (read-from-string item 5 current-line) (read-from-string item 6 current-line) (item 7 current-line) (item 8 current-line) (item 9 current-line)) ask target-soldier [ set waypoints lput waypoint waypoints ] ] ] file-close ] [ user-message (word "Could not find '" filename "'.") ] end ;; ;; parse-csv-line ;; ;; Splits a string containing a line from a CSV into an array ;; to-report parse-csv-line [line] let parsed-line (list) ;; Iterate through the string and extract each value between commas while [(position "," line) != false] [ let element "" ;; Check to see if this is a quote-encapsulated value (that may possibly contain literal commas) ifelse (item 0 line) = "\"" [ ;; We've got a quote-encapsulated value, so let's strip off the opening quote set line substring line 1 (length line) ;; Now let's extract the current element, the drop the closing quote and comma set element substring line 0 (position "\"" line) set line substring line ((position "\"" line) + 2) (length line) ] [ ;; We've got a non-quote-encapsulated value set element substring line 0 (position "," line) set line substring line ((position "," line) + 1) (length line) ] set parsed-line lput element parsed-line ] ;; Add the last item to the parsed line set parsed-line lput line parsed-line report parsed-line end ;; ;; tick-from-timestamp ;; ;; Calculates the tick number in which the given timestamp will occur; Expects input timestamps ;; to be of the format: 'mm/dd/yy hh:mm' ;; to-report tick-from-timestamp [string] ;; Parse timestamp elements from string let month read-from-string substring string 0 (position "/" string) set string substring string ((position "/" string) + 1) (length string) let day read-from-string substring string 0 (position "/" string) set string substring string ((position "/" string) + 1) (length string) let year (read-from-string substring string 0 (position " " string)) + 1900 set string substring string ((position " " string) + 1) (length string) let hour read-from-string substring string 0 (position ":" string) set string substring string ((position ":" string) + 1) (length string) let minute read-from-string substring string 0 (length string) ;; Calculate tick number of the parsed date let tick-count 0 if (minute != 0) [ set tick-count (tick-count + (round (minute / minutes-per-tick))) ] if (hour != 0) [ set tick-count (tick-count + (round ((60 * hour) / minutes-per-tick))) ] if ((day - start-day) != 0) [ set tick-count (tick-count + (round (24 * 60 * (day - start-day) / minutes-per-tick))) ] if ((month - start-month) != 0) [ set tick-count (tick-count + (round (31 * 24 * 60 * (month - start-month) / minutes-per-tick))) ] if ((year - start-year) != 0) [ set tick-count (tick-count + (round (365 * 24 * 60 * (year - start-year) / minutes-per-tick))) ] ;; Adjust arrival time based on troop speed multiplier set tick-count (round (tick-count / troop-speed-multiplier)) report tick-count end ;; ;; timestamp-from-tick ;; ;; Translates the current tick count into a date and time stamp ;; to-report timestamp-from-tick ;; Set up some helpful lists let month-names (list "January" "February" "March" "April" "May" "June" "July" "August" "September" "October" "November" "December") let days-in-months (list 31 28 31 30 31 30 31 31 30 31 30 31) ;; Calculate how many years, months, and days have passed ;; NOTE: The months and days here are zero-indexed to make math easier let start-years-passed-in-minutes (start-year * 60 * 24 * 365) let start-months-passed-in-minutes ((sum (sublist days-in-months 0 (start-month - 1))) * 60 * 24) let start-days-passed-in-minutes ((start-day - 1) * 60 * 24) let total-minutes (start-years-passed-in-minutes + start-months-passed-in-minutes + start-days-passed-in-minutes + (ticks * minutes-per-tick)) ;; Determine what year we're in let year floor (total-minutes / 60 / 24 / 365) let years-passed-in-minutes (year * 365 * 24 * 60) ;; Determine what month we're currently in let days-this-year (floor ((total-minutes - years-passed-in-minutes) / 60 / 24)) let index 1 let month -1 foreach days-in-months [ if (sum (sublist days-in-months 0 index) > days-this-year ) and (month = -1) [ set month (index - 1) ;; NOTE: The month value will be zero-indexed ] set index (index + 1) ] let months-passed-in-minutes (sum (sublist days-in-months 0 month) * 24 * 60) ;; Determine current day (NOTE: The day value is zero-indexed) let day floor ((total-minutes - years-passed-in-minutes - months-passed-in-minutes) / 60 / 24) let days-passed-in-minutes (day * 24 * 60) ;; Determine current hour let hour floor ((total-minutes - years-passed-in-minutes - months-passed-in-minutes - days-passed-in-minutes) / 60) let hours-passed-in-minutes (hour * 60) ;; Determine current minute let minute (total-minutes - years-passed-in-minutes - months-passed-in-minutes - days-passed-in-minutes - hours-passed-in-minutes) if minute < 10 [ set minute (word "0" minute) ] report (word (item month month-names) " " (day + 1) ", " year " " hour ":" minute) end ;; ;; train-departures-this-tick ;; ;; Returns the number of trains that can use each link this tick ;; to-report train-departures-this-tick ;; First, translate trains per day into trains per tick let trains-per-minute (trains-per-day / (24 * 60)) let trains-per-tick (trains-per-minute * minutes-per-tick) ;; Since it's likely that trains-per-tick is a fractional value, let's take the difference from ;; between the rounded values for this tick and the next tick let trains-this-tick (round ((ticks + 1) * trains-per-tick)) - (round (ticks * trains-per-tick)) report trains-this-tick end ;; ;; train-speed-in-patches ;; ;; Translates the train speed from miles per hour to patches per tick ;; to-report train-speed-in-patches ;; First, determine the size (in miles) of a patch let x-size (((max-long - min-long) * miles-per-long) / max-pxcor) let y-size (((max-lat - min-lat) * miles-per-lat) / max-pycor) let avg-patch-size ((x-size + y-size) / 2) ;; Next, determine how many patches can be traversed in an hour let patches-per-minute (train-speed / avg-patch-size / 60) report (patches-per-minute * minutes-per-tick) end