Table of contents

Project: DrugMarket

Global
Event scheduling algorithm Deterministically
Import import java.util.*;
import java.io.*;
import java.text.*;

Package: animation

File: OptionBox.java

// Filename: OptionBox.java // Revision: 1 // Author: Joey Morris // Purpose: Create a simple "option box" widget for use in AnyLogic models. // Option boxes may also be known as "drop-down menus" or // "combo boxes". // // Software: AnyLogic 5.5 // // Revision History // Date Author Comment // ----------- ------ ------- // 12-Jun-2007 JM Initial revision. // Modify the following line so that the package name here matches the package // name in the AnyLogic model. package animation; import com.xj.anylogic.animation.*; import java.util.List; import java.util.ArrayList; import java.awt.Color; //------------------------------------------------------------------------------ // Usage :: Installing // // 1) Copy OptionBox.java and OptionItem.java into a folder accessible by your // AnyLogic model. // 2) Add the files into your model by right-clicking a package and selecting // "Add External File..." from the menu. (You should be able to move your // model and the .java files around to different folders as long as their // relative paths stay the same. // 3) Modify the package declaration in both OptionBox.java and OptionItem.java // to match the package name where you added the files in step (2). // // Usage :: Adding an OptionBox to your animation // // 1) In the animation's Additional Class Code, declare the OptionBox: // // OptionBox shapeOption; // // 2) In the animation's Setup Code, create the OptionBox: // // shapeOption = new OptionBox(this); // // 3) In the animation's Setup Code, add options to the OptionBox by passing // OptionItem objects to the addOptionItem() method. To be useful, you will // need to create subclasses of OptionItem and override its onSelect() // method to run whatever code you want to run when the option is selected. // You can do this the quick way using "anonymous inner classes": // // shapeOption.addOptionItem( // new OptionItem("Circle") { // public void onSelect() { // // your code here, for example: // shape = "circle"; // } // } // ); // shapeOption.addOptionItem( // new OptionItem("Square") { // public void onSelect() { // // your code here, for example: // shape = "square"; // } // } // ); // // You can also explicitly create the subclasses if you want: // // class CircleOption extends OptionItem { // CircleOption(String name) { // super(name); // } // public void onSelect() { // // your code here, for example: // shape = "circle"; // } // } // class SquareOption extends OptionItem { // SquareOption(String name) { // super(name); // } // public void onSelect() { // // your code here, for example: // shape = "square"; // } // } // // OptionItem circle = new CircleOption("Circle"); // shapeOption.addOptionItem(circle); // shapeOption.addOptionItem(new SquareOption("Square")); // // You may also override OptionItem's onUnselect() method to execute code // whenever selection is changed from that OptionItem to another. // // Code that you place in the onSelect() methods must be accessible from the // animation. Typically, any variables and methods that you define in the // animation's Additional Class Code or in the Active Object that contains // the animation will be accessible. // // 4) In the animation's Setup Code, add code to configure the OptionBox's // size, colors, etc.: // // shapeOption.setPos(0, 100); // shapeOption.setItemWidth(80); // shapeOption.setTextColor(Color.blue); // // 5) In the animation's Setup Code, choose the initially selected option by // calling one of the setSelectedItem() methods: // // shapeOption.setSelectedItem(0); // // or // shapeOption.setSelectedItem("Circle"); // // or // shapeOption.setSelectedItem(circle); // // 6) In the animation's Additional Class Code, override the animation's // onMouseClicked() method to pass mouse clicks on to the option box: // // protected void onMouseClicked(java.awt.Point p) { // if (shapeOption.handleClick(p.getX(), p.getY())) { // // the click was inside the shapeOption, so there is no need to // // process the click further // return; // } // // ... // } // // The code above that should go in the animation's Setup Code may instead // need to go in the animation's Update Code, enclosed in the condition // if (Engine.getTime() == 0). This may be necessary if the code references // any encapsulated objects, because Setup Code might not be executed after // the encapsulated objects are created, but the Update Code at time 0 will. // //------------------------------------------------------------------------------ public class OptionBox { /** * Class constructor. * * @param g animation group to which this option box is to be added */ public OptionBox(Group g) { animation = g; optionItems = new ArrayList(); selectedItem = null; areOptionsVisible = false; itemWidth = 100; itemHeight = 20; fillColor = Color.white; textColor = Color.black; textOffsetX = 10; textOffsetY = 3; fontSize = 10; unselectedText = "Select One"; headerBox = new ShapeRect(); headerLabel = new ShapeText(); arrowBox = new ShapeRect(); arrow = new ShapePoly(); arrow.setPoints(3); arrow.setClosed(true); optionsBox = new ShapeRect(); optionsBox.setVisible(false); container = new Group(); headerContainer = new Group(); arrowContainer = new Group(); animation.add(container); container.add(headerContainer); container.add(optionsBox); headerContainer.add(arrowContainer); headerContainer.add(headerBox); headerContainer.add(headerLabel); arrowContainer.add(arrowBox); arrowContainer.add(arrow); this.redraw(); this.resetHeaderText(); } //------------------------------------------------------- // Behavioral methods. // /** * Inserts the specified option item at the specified position in this option * box's list of options. Shifts the element currently at that position and * all subsequent elements down in the list. * * @param index position at which the item is to be inserted * @param item item to be inserted */ public void addOptionItem(int index, OptionItem item) { optionItems.add(index, item); item.setVisible(false); // Since OptionItem is not derived from ShapeBase, we cannot add it // directly to our animation. Instead, we must ask the OptionItem to do // it so that it can add its own shapes to the animation. item.addToAnimation(animation); redraw(); } /** * Appends the specified option item to this option box's list of options. * * @param item item to be appended */ public void addOptionItem(OptionItem item) { this.addOptionItem(optionItems.size(), item); } /** * Processes a mouse click at the specified coordinates. If this option box * is not visible, then the mouse click is ignored. * * @param x x-coordinate of the mouse click * @param y y-coordinate of the mouse click * @return <code>true</code> if the mouse click was handled by this option * box; <code>false</code> otherwise */ public boolean handleClick(double x, double y) { boolean isClicked = false; if (headerContainer.isVisible()) { if (areOptionsVisible) { if (headerContainer.contains(x - container.getX(), y - container.getY())) { isClicked = true; this.hideOptions(); } else { for (int i = 0; i < optionItems.size(); i++) { OptionItem item = (OptionItem)optionItems.get(i); if (item.contains(x, y)) { isClicked = true; this.setSelectedItem(i); this.hideOptions(); break; } } } } else { if (headerContainer.contains(x - container.getX(), y - container.getY())) { isClicked = true; this.showOptions(); } } } return isClicked; } /** * Makes the specified option item the currently selected item in this option * box. Execute the previously selected (if any) item's * <code>onUnselect</code> method and then the newly selected item's * <code>onSelect</code> method. If the item is not in this option box's list * of options, do nothing. * * @param item the item to select */ public void setSelectedItem(OptionItem item) { if (selectedItem == null || !selectedItem.equals(item)) { if (optionItems.contains(item)) { if (selectedItem != null) { selectedItem.onUnselect(); } selectedItem = item; selectedItem.onSelect(); this.resetHeaderText(); } } } /** * Makes the option item with the specified index the currently selected item * in this option box. Items are assigned an index starting at 0 based on the * order they were added to this option box using the * <code>addOptionItem</code> method. Execute the previously selected (if * any) item's <code>onUnselect</code> method and then the newly selected * item's <code>onSelect</code> method. If no item in this option box's * list of options has the specified index, do nothing. * * @param index the index of the item to select */ public void setSelectedItem(int index) { if (index >= 0 && index < optionItems.size()) { this.setSelectedItem((OptionItem)optionItems.get(index)); } } /** * Makes the option item with the specified name the currently selected item * in this option box. Execute the previously selected (if any) item's * <code>onUnselect</code> method and then the newly selected item's * <code>onSelect</code> method. If no item in this option box's list of * options has the specified name, do nothing. * * @param name name of the item to select */ public void setSelectedItem(String name) { for (int i = 0; i < optionItems.size(); i++) { OptionItem item = (OptionItem)optionItems.get(i); if (item.getText().equals(name)) { this.setSelectedItem(item); } } } /** * Sets this option box to have no selected item. Execute the currently * selected (if any) item's <code>onUnselect</code> method. */ public void setUnselected() { if (selectedItem != null) { selectedItem.onUnselect(); selectedItem = null; this.resetHeaderText(); } } /** * Sets the visibility of this option box. * * @param visible <code>true</code> if the option box should be visible; * <code>false</code> if not */ public void setVisible(boolean visible) { headerContainer.setVisible(visible); optionsBox.setVisible(visible && areOptionsVisible); for (int i = 0; i < optionItems.size(); i++) { OptionItem item = (OptionItem)optionItems.get(i); item.setVisible(visible && areOptionsVisible); } } //------------------------------------------------------- // Info methods. // /** * Returns the currently selected option item. * * @return the currently selected item, or <code>null</code> if no item is * currently selected */ public OptionItem getSelectedItem() { return selectedItem; } //------------------------------------------------------- // Configuration methods. // /** * Sets the position of the top left corner of this option box. * * @param x the x-coordinate where the option box is to be placed. The * default is 0. * @param y the y-coordinate where the option box is to be placed. The * default is 0. */ public void setPos(double x, double y) { if (container.getX() != x || container.getY() != y) { container.setPos(x, y); redraw(); } } /** * Sets the width of the items in this option box. The entire option box will * be slightly wider since it also includes an arrow beside the items. * * @param width width, in pixels, of the option items. The default is 100. */ public void setItemWidth(double width) { if (itemWidth != width) { itemWidth = width; redraw(); } } /** * Sets the height of the items in this option box. When the list of options * is hidden, this will be the height of the option box. When the list of * options is shown, the height of the option box will be (n+1)*height, where * n is the number of items in the option box. * * @param height height, in pixels, of the option items. The default is 20. */ public void setItemHeight(double height) { if (itemHeight != height) { itemHeight = height; this.redraw(); } } /** * Sets the background color for all the elements of this option box. * * @param color background color for the option box. The default is white. */ public void setFillColor(Color color) { if (!fillColor.equals(color)) { fillColor = color; this.redraw(); } } /** * Sets the text color for all the elements of this option box. The arrow * will also be displayed in this color. * * @param color text color for the option box. The default is black. */ public void setTextColor(Color color) { if (!textColor.equals(color)) { textColor = color; this.redraw(); } } /** * Sets the distance from the top left corner of each option item to the top * left corner of the text label in the item. * * @param x horizontal distance, in pixels, from the left edge of the item to * the left edge of the text label. The default is 10. * @param y vertical distance, in pixels, from the top edge of the item to * the top edge of the text label. The default is 3. */ public void setTextOffset(double x, double y) { if (textOffsetX != x || textOffsetY != y) { textOffsetX = x; textOffsetY = y; this.redraw(); } } /** * Sets the font size for all text labels in this option box. * * @param size the font size. The default is 10. */ public void setFontSize(int size) { if (fontSize != size) { fontSize = size; this.redraw(); } } /** * Sets the text that appears in this option box's selected area when no item * is currently selected. * * @param text text that appears when no item is selected. The default is * "Select One". */ public void setUnselectedText(String text) { if (!unselectedText.equals(text)) { unselectedText = text; this.resetHeaderText(); } } /** * Sets the text label for the item with the specified index. Items are * assigned an index starting at 0 based on the order they were added to * this option box using the <code>addOptionItem</code> method. If no item * in this option box's list of options has the specified index, do nothing. * * @param index the index of the item whose text is to be changed * @param text the new text label for the item */ public void setItemText(int index, String text) { if (index >= 0 && index < optionItems.size()) { OptionItem item = (OptionItem)optionItems.get(index); item.setText(text); if (item.equals(selectedItem)) { this.resetHeaderText(); } } } //------------------------------------------------------- // Protected methods. // /** * Redraws all elements of this option box. This method should be called by * methods that change the size, position, or color of any element. */ protected void redraw() { headerContainer.setPos(0, 0); headerBox.setPos(0, 0); headerBox.setSize(itemWidth, itemHeight); headerBox.setFillColor(fillColor); headerLabel.setPos(textOffsetX, textOffsetY); headerLabel.setFontColor(textColor); headerLabel.setFontSize(fontSize); arrowContainer.setPos(itemWidth, 0); arrowBox.setPos(0, 0); arrowBox.setSize(itemHeight, itemHeight); arrowBox.setFillColor(fillColor); arrow.setPoint(0, 0.25 * itemHeight, 0.50 * itemHeight); arrow.setPoint(1, 0.75 * itemHeight, 0.50 * itemHeight); arrow.setPoint(2, 0.50 * itemHeight, 0.75 * itemHeight); arrow.setLineColor(textColor); arrow.setFillColor(textColor); optionsBox.setPos(0, itemHeight); optionsBox.setSize(itemWidth, optionItems.size() * itemHeight); optionsBox.setFillColor(fillColor); for (int i = 0; i < optionItems.size(); i++) { OptionItem item = (OptionItem)optionItems.get(i); item.setPos(container.getX(), container.getY() + itemHeight * (i + 1)); item.setWidth(itemWidth); item.setHeight(itemHeight); item.setTextColor(textColor); item.setTextOffset(textOffsetX, textOffsetY); item.setFontSize(fontSize); } } /** * Displays the list of options, allowing the user to choose an option by * clicking on it. */ protected void showOptions() { areOptionsVisible = true; optionsBox.setVisible(true); for (int i = 0; i < optionItems.size(); i++) { OptionItem item = (OptionItem)optionItems.get(i); item.setVisible(true); } } /** * Hides the list of options that were shown by <code>showOptions</code>. */ protected void hideOptions() { areOptionsVisible = false; optionsBox.setVisible(false); for (int i = 0; i < optionItems.size(); i++) { OptionItem item = (OptionItem)optionItems.get(i); item.setVisible(false); } } /** * Sets the text in this option box's header area to the text associated with * the currently selected option item, or to the <code>unselectedText</code> * if no item is selected. */ protected void resetHeaderText() { if (selectedItem == null) { headerLabel.setText(unselectedText); } else { headerLabel.setText(selectedItem.getText()); } } //------------------------------------------------------- // Fields. // protected Group animation; protected Group container; protected Group headerContainer; protected Group arrowContainer; protected List optionItems; protected OptionItem selectedItem; protected boolean areOptionsVisible; protected double itemWidth; protected double itemHeight; protected Color fillColor; protected Color textColor; protected double textOffsetX; protected double textOffsetY; protected int fontSize; protected String unselectedText; protected ShapeRect headerBox; protected ShapeText headerLabel; protected ShapeRect arrowBox; protected ShapePoly arrow; protected ShapeRect optionsBox; }

File: OptionItem.java

// Filename: OptionItem.java // Revision: 1 // Author: Joey Morris // Purpose: Create an "option item" for use in the option boxes created in // OptionBox.java. // // Software: AnyLogic 5.5 // // Revision History // Date Author Comment // ----------- ------ ------- // 11-Jun-2007 JM Initial revision. // Modify the following line so that the package name here matches the package // name in the AnyLogic model. package animation; import com.xj.anylogic.animation.*; import java.awt.Color; //------------------------------------------------------------------------------ // See the discussion in OptionBox.java for usage instructions. //------------------------------------------------------------------------------ public class OptionItem { public OptionItem(String name) { box = new ShapeRect(); box.setLineColor(null); box.setFillColor(null); label = new ShapeText(); label.setText(name); container = new Group(); container.add(box); container.add(label); } public void addToAnimation(Group g) { g.add(container); } public void setVisible(boolean visible) { container.setVisible(visible); } public void setPos(double x, double y) { container.setPos(x, y); } public void setWidth(double width) { box.setWidth(width); } public void setHeight(double height) { box.setHeight(height); } public void setText(String name) { label.setText(name); } public String getText() { return label.getText(); } public void setTextColor(Color color) { label.setFontColor(color); } public void setTextOffset(double x, double y) { label.setPos(x, y); } public void setFontSize(int size) { label.setFontSize(size); } public boolean contains(double x, double y) { return container.contains(x, y); } public void onSelect() { // the default action upon selecting the item is to do nothing } public void onUnselect() { // the default action upon unselecting the item is to do nothing } protected Group container; protected ShapeText label; protected ShapeRect box; }

File: SeriesPlot.java

// Filename: SeriesPlot.java // Revision: 1 // Author: Joey Morris // Purpose: Plot one or more data series on a single set of axes in an AnyLogic // model. The SeriesPlot is intended as a replacement for the // ChartTime object in AnyLogic's Business Graphics Library. // SeriesPlot offers much of the same functionality while providing // more control and, in some cases, is faster. // // Software: AnyLogic 5.5 // // Revision History // Date Author Comment // ----------- ------ ------- // 11-Jun-2007 JM Initial revision. // 19-Jul-2007 JM Removed the code that automatically set the point (0,0) // to be the first point in each series. // Modify the following line so that the package name here matches the package // name in the AnyLogic model. package animation; import com.xj.anylogic.animation.*; import java.text.NumberFormat; import java.awt.Color; import java.awt.Font; //------------------------------------------------------------------------------ // Usage :: Installing // // 1) Copy SeriesPlot.java into a folder accessible by your AnyLogic model. // 2) Add the file into your model by right-clicking a package and selecting // "Add External File..." from the menu. (You should be able to move your // model and the .java file around to different folders as long as their // relative paths stay the same. // 3) Modify the package declaration in SeriesPlot.java to match the package // name where you added the files in step (2). // // Usage :: Adding a SeriesPlot to your animation // // 1) In the animation's Additional Class Code, declare the SeriesPlot: // // SeriesPlot hivPlot; // // 2) In the animation's Setup Code, create the SeriesPlot: // // hivPlot = new SeriesPlot(this, 3); // // 3) public class SeriesPlot { /** * Creates a <code>SeriesPlot</code> in the specified animation having the * specified number of data series. * * @param g animation group to which this <code>SeriesPlot</code> is to be * added * @param numSeries number of data series in this <code>SeriesPlot</code> */ public SeriesPlot(Group g, int numSeries) { container = new Group(); seriesNames = new String[numSeries]; seriesPlots = new ShapePoly[numSeries]; seriesLastValueX = new double[numSeries]; seriesLastValueY = new double[numSeries]; for (int i = 0; i < numSeries; i++) { seriesNames[i] = new String("series " + i); seriesPlots[i] = new ShapePoly(); seriesPlots[i].setPos(0, 0); seriesPlots[i].setClosed(false); seriesPlots[i].setPoints(0); container.add(seriesPlots[i]); } xaxis = new ShapeLine(); xaxis.setPoint1(0, 0); xaxis.setPoint2Y(0); container.add(xaxis); xaxisTickLabelMin = new ShapeText(); xaxisTickLabelMin.setPosX(0); container.add(xaxisTickLabelMin); xaxisTickLabelMax = new ShapeText(); container.add(xaxisTickLabelMax); xaxisTitle = new ShapeText(); container.add(xaxisTitle); yaxis = new ShapeLine(); yaxis.setPoint1(0, 0); yaxis.setPoint2X(0); container.add(yaxis); yaxisTickLabelMin = new ShapeText(); yaxisTickLabelMin.setPosY(0); container.add(yaxisTickLabelMin); yaxisTickLabelMax = new ShapeText(); container.add(yaxisTickLabelMax); yaxisTitle = new ShapeText(); container.add(yaxisTitle); legendContainer = new Group(); legendLines = new ShapeLine[numSeries]; legendLabels = new ShapeText[numSeries]; for (int i = 0; i < numSeries; i++) { legendLines[i] = new ShapeLine(); legendContainer.add(legendLines[i]); legendLabels[i] = new ShapeText(); legendContainer.add(legendLabels[i]); } container.add(legendContainer); plotTitle = new ShapeText(); container.add(plotTitle); xaxisScale = 1; yaxisScale = 1; xaxisValueMin = 0; yaxisValueMin = 0; this.setPos(0, 0); this.setWidth(200); this.setHeight(100); this.setXaxisRescaleFactor(2); this.setXaxisTickLabelOffset(5); this.setXaxisTickLabelRotation(0); this.setXaxisTickLabelFontSize(8); this.setXaxisTickLabelFormat(null); this.setXaxisTitle("x"); this.setXaxisTitleOffset(20, 15); this.setXaxisTitleFontSize(8); this.setXaxisTitleFontStyle(Font.BOLD); this.setXaxisTitleVisible(false); this.setYaxisRescaleFactor(2); this.setYaxisTickLabelOffset(13); this.setYaxisTickLabelRotation(90); this.setYaxisTickLabelFontSize(8); this.setYaxisTickLabelFormat(null); this.setYaxisTitle("y"); this.setYaxisTitleOffset(25, 20); this.setYaxisTitleRotation(90); this.setYaxisTitleFontSize(8); this.setYaxisTitleFontStyle(Font.BOLD); this.setYaxisTitleVisible(false); this.setLegendOffset(0, 40); this.setLegendItemHeight(10); this.setLegendLineLength(10); this.setLegendLabelOffset(5); this.setLegendLabelFontSize(8); this.setLegendContainsValues(false); this.setLegendValuesFormat(null); this.setLegendVisible(false); this.setPlotTitleOffset(10, 20); this.setPlotTitleFontSize(10); this.setPlotTitleFontStyle(Font.BOLD); this.setPlotTitleVisible(false); g.add(container); } public void addPoint(int seriesNum, double valueX, double valueY) { if (seriesNum >= 0 && seriesNum < seriesPlots.length) { seriesLastValueX[seriesNum] = valueX; seriesLastValueY[seriesNum] = valueY; double pointX = valueX / xaxisScale; double pointY = -valueY / yaxisScale; seriesPlots[seriesNum].addPoint(pointX, pointY); if (pointX > xaxisLength) { rescaleX(xaxisRescaleFactor); } if (-pointY > yaxisLength) { rescaleY(yaxisRescaleFactor); } if (legendContainsValues) { redrawLegendLabel(seriesNum); } } } //------------------------------------------------------ // configuring the data series // public void setSeriesColor(int seriesNum, Color color) { if (seriesNum >= 0 && seriesNum < seriesPlots.length) { seriesPlots[seriesNum].setColor(color); legendLines[seriesNum].setColor(color); } } public void setSeriesName(int seriesNum, String name) { if (seriesNum >= 0 && seriesNum < seriesPlots.length) { seriesNames[seriesNum] = name; redrawLegendLabel(seriesNum); } } //------------------------------------------------------ // configuring the plot as a whole // public void setVisible(boolean visible) { container.setVisible(visible); } public void setPos(double x, double y) { container.setPos(x, y); } public void setWidth(double width) { xaxisLength = width; xaxis.setPoint2X(xaxisLength); xaxisTickLabelMax.setPosX(xaxisLength); double newXaxisScale = (xaxisValueMax - xaxisValueMin) / xaxisLength; rescaleX(newXaxisScale / xaxisScale); } public void setHeight(double height) { double deltaHeight = height - yaxisLength; yaxisLength = height; yaxis.setPoint2Y(-yaxisLength); yaxisTickLabelMax.setPosY(-yaxisLength); double newYaxisScale = (yaxisValueMax - yaxisValueMin) / yaxisLength; rescaleY(newYaxisScale / yaxisScale); plotTitle.setPosY(plotTitle.getY() - deltaHeight); } //------------------------------------------------------ // configuring the x-axis // public void setXaxisValueMax(double max) { if (max > xaxisValueMin) { double newXaxisScale = (max - xaxisValueMin) / xaxisLength; rescaleX(newXaxisScale / xaxisScale); } } public void setXaxisRescaleFactor(double factor) { xaxisRescaleFactor = factor; } public void setXaxisTickLabelOffset(double y) { xaxisTickLabelMin.setPosY(y); xaxisTickLabelMax.setPosY(y); } public void setXaxisTickLabelRotation(double degrees) { xaxisTickLabelMin.setRotation(-(degrees/180) * java.lang.Math.PI); xaxisTickLabelMax.setRotation(-(degrees/180) * java.lang.Math.PI); } public void setXaxisTickLabelFontSize(int size) { xaxisTickLabelMin.setFontSize(size); xaxisTickLabelMax.setFontSize(size); } public void setXaxisTickLabelFormat(NumberFormat fmt) { xaxisTickLabelFormat = fmt; redrawXaxisTickLabels(); } //------------------------------------------------------ // configuring the x-axis title // public void setXaxisTitle(String text) { xaxisTitle.setText(text); } public void setXaxisTitleOffset(double x, double y) { xaxisTitle.setPos(x, y); } public void setXaxisTitleFontSize(int size) { xaxisTitle.setFontSize(size); } public void setXaxisTitleFontStyle(int style) { xaxisTitle.setFontStyle(style); } public void setXaxisTitleVisible(boolean visible) { xaxisTitle.setVisible(visible); } //------------------------------------------------------ // configuring the y-axis // public void setYaxisValueMax(double max) { if (max > yaxisValueMin) { double newYaxisScale = (max - yaxisValueMin) / yaxisLength; rescaleY(newYaxisScale / yaxisScale); } } public void setYaxisRescaleFactor(double factor) { yaxisRescaleFactor = factor; } public void setYaxisTickLabelOffset(double x) { yaxisTickLabelMin.setPosX(-x); yaxisTickLabelMax.setPosX(-x); } public void setYaxisTickLabelRotation(double degrees) { yaxisTickLabelMin.setRotation(-(degrees/180) * java.lang.Math.PI); yaxisTickLabelMax.setRotation(-(degrees/180) * java.lang.Math.PI); } public void setYaxisTickLabelFontSize(int size) { yaxisTickLabelMin.setFontSize(size); yaxisTickLabelMax.setFontSize(size); } public void setYaxisTickLabelFormat(NumberFormat fmt) { yaxisTickLabelFormat = fmt; redrawYaxisTickLabels(); } //------------------------------------------------------ // configuring the y-axis title // public void setYaxisTitle(String text) { yaxisTitle.setText(text); } public void setYaxisTitleOffset(double x, double y) { yaxisTitle.setPos(-x, -y); } public void setYaxisTitleRotation(double degrees) { yaxisTitle.setRotation(-(degrees/180) * java.lang.Math.PI); } public void setYaxisTitleFontSize(int size) { yaxisTitle.setFontSize(size); } public void setYaxisTitleFontStyle(int style) { yaxisTitle.setFontStyle(style); } public void setYaxisTitleVisible(boolean visible) { yaxisTitle.setVisible(visible); } //------------------------------------------------------ // configuring the plot title // public void setPlotTitleVisible(boolean visible) { plotTitle.setVisible(visible); } public void setPlotTitle(String title) { plotTitle.setText(title); } public void setPlotTitleOffset(double x, double y) { plotTitle.setPos(x, -(yaxisLength + y)); } public void setPlotTitleFontSize(int size) { plotTitle.setFontSize(size); } public void setPlotTitleFontStyle(int style) { plotTitle.setFontStyle(style); } //------------------------------------------------------ // configuring the legend // public void setLegendVisible(boolean visible) { legendContainer.setVisible(visible); } public void setLegendOffset(double x, double y) { legendContainer.setPos(x, y); } public void setLegendItemHeight(double height) { for (int i = 0; i < seriesPlots.length; i++) { legendLines[i].setPoint1Y(height*i + height/2); legendLines[i].setPoint2Y(height*i + height/2); legendLabels[i].setPosY(height*i); } } public void setLegendLineLength(double length) { for (int i = 0; i < seriesPlots.length; i++) { double deltaLength = length - legendLines[i].length(); legendLines[i].setPoint2X(length); legendLabels[i].setPosX(legendLabels[i].getX() + deltaLength); } } public void setLegendLabelOffset(double x) { for (int i = 0; i < seriesPlots.length; i++) { legendLabels[i].setPosX(legendLines[i].length() + x); } } public void setLegendLabelFontSize(int size) { for (int i = 0; i < seriesPlots.length; i++) { legendLabels[i].setFontSize(size); } } public void setLegendContainsValues(boolean hasValues) { legendContainsValues = hasValues; for (int i = 0; i < seriesPlots.length; i++) { redrawLegendLabel(i); } } public void setLegendValuesFormat(NumberFormat fmt) { legendValuesFormat = fmt; for (int i = 0; i < seriesPlots.length; i++) { redrawLegendLabel(i); } } //------------------------------------------------------ // protected methods // protected void rescaleX(double factor) { if (factor > 0) { for (int seriesNum = 0; seriesNum < seriesPlots.length; seriesNum++) { for (int i = 0; i < seriesPlots[seriesNum].getPoints(); i++) { seriesPlots[seriesNum].setPointX(i, seriesPlots[seriesNum].getPointX(i) / factor); } } xaxisScale *= factor; xaxisValueMax = xaxisValueMin + xaxisLength * xaxisScale; redrawXaxisTickLabels(); } } protected void rescaleY(double factor) { if (factor > 0) { for (int seriesNum = 0; seriesNum < seriesPlots.length; seriesNum++) { for (int i = 0; i < seriesPlots[seriesNum].getPoints(); i++) { seriesPlots[seriesNum].setPointY(i, seriesPlots[seriesNum].getPointY(i) / factor); } } yaxisScale *= factor; yaxisValueMax = yaxisValueMin + yaxisLength * yaxisScale; redrawYaxisTickLabels(); } } protected void redrawXaxisTickLabels() { if (xaxisTickLabelFormat != null) { xaxisTickLabelMin.setText(xaxisTickLabelFormat.format(xaxisValueMin)); xaxisTickLabelMax.setText(xaxisTickLabelFormat.format(xaxisValueMax)); } else { xaxisTickLabelMin.setText(String.valueOf(xaxisValueMin)); xaxisTickLabelMax.setText(String.valueOf(xaxisValueMax)); } } protected void redrawYaxisTickLabels() { if (yaxisTickLabelFormat != null) { yaxisTickLabelMin.setText(yaxisTickLabelFormat.format(yaxisValueMin)); yaxisTickLabelMax.setText(yaxisTickLabelFormat.format(yaxisValueMax)); } else { yaxisTickLabelMin.setText(String.valueOf(yaxisValueMin)); yaxisTickLabelMax.setText(String.valueOf(yaxisValueMax)); } } protected void redrawLegendLabel(int seriesNum) { if (seriesNum >= 0 && seriesNum < seriesPlots.length) { String seriesName = seriesNames[seriesNum]; if (legendContainsValues) { if (legendValuesFormat != null) { legendLabels[seriesNum].setText(seriesName + " :: " + legendValuesFormat.format(seriesLastValueY[seriesNum])); } else { legendLabels[seriesNum].setText(seriesName + " :: " + seriesLastValueY[seriesNum]); } } else { legendLabels[seriesNum].setText(seriesName); } } } //------------------------------------------------------ // fields // protected Group container; protected String[] seriesNames; protected ShapePoly[] seriesPlots; protected double[] seriesLastValueX; protected double[] seriesLastValueY; protected ShapeText plotTitle; protected ShapeLine xaxis; protected double xaxisScale; protected double xaxisRescaleFactor; protected double xaxisLength; protected double xaxisValueMin; protected double xaxisValueMax; protected ShapeText xaxisTickLabelMin; protected ShapeText xaxisTickLabelMax; protected NumberFormat xaxisTickLabelFormat; protected ShapeText xaxisTitle; protected ShapeLine yaxis; protected double yaxisScale; protected double yaxisRescaleFactor; protected double yaxisLength; protected double yaxisValueMin; protected double yaxisValueMax; protected ShapeText yaxisTickLabelMin; protected ShapeText yaxisTickLabelMax; protected NumberFormat yaxisTickLabelFormat; protected ShapeText yaxisTitle; protected Group legendContainer; protected ShapeText[] legendLabels; protected ShapeLine[] legendLines; protected boolean legendContainsValues; protected NumberFormat legendValuesFormat; }

Package: drugmarket

Active Object: Customer

General
Import import message.*;
import misc.*;

Parameters
Name homeX
Type real
Name homeY
Type real
Name initialMoney
Type real
Name initialInventory
Type real
Name initialAddiction
Type real
Name initialConcentration
Type real
Name metabolismRate
Type real
Name addictionIncreaseDuration
Type real
Name addictionIncreaseRate
Type real
Name addictionDecreaseOnset
Type real
Name addictionDecreaseRate
Type real
Name incomeAmount
Type real
Name incomeInterval
Type real
Name windfallAmountMin
Type real
Name windfallAmountMax
Type real
Name windfallIntervalMin
Type real
Name windfallIntervalMax
Type real
Name randomUseIntervalMin
Type real
Name randomUseIntervalMax
Type real
Name useDelayMin
Type real
Name useDelayMax
Type real
Name retryDelayMin
Type real
Name retryDelayMax
Type real
Name buyMethod
Type String
Name visionRadius
Type real
Name desperateNeedThreshold
Type real
Name desperateAddictionThreshold
Type real
Name probUseAtSeller
Type real
Name probSeekMoney
Type real
Name probSeekMoneySuccess
Type real
Name seekMoneyDelayMin
Type real
Name seekMoneyDelayMax
Type real
Name probTryPrivateDealer
Type real
Name acceptableSdQueueLength
Type integer
Name acceptablePdQueueLength
Type integer
Name treatmentDelayMin
Type real
Name treatmentDelayMax
Type real
Name treatmentAddictionReduction
Type real
Name initialTimeSinceLastUse
Type real
Name probUseTogether
Type real
Name initialHIV
Type boolean
Name marketQuitTime
Type real

Icon
Picture

Structure
Picture
ChartTimer dealQuitTimer
Expire No (manual mode)
Expiry Action DealDeniedMsg msg = new DealDeniedMsg();
msg.dealID = dealToSeller.dealID;
self.receiveDealDeniedMsg(msg);
ChartTimer windfallTimer
Timeout uniform(windfallIntervalMin, windfallIntervalMax)
Expire At Startup No
Expiry Action updateMoney(uniform(windfallAmountMin, windfallAmountMax));
ChartTimer incomeTimer
Timeout incomeInterval
Expire At Startup No
Expiry Action updateMoney(incomeAmount);
Variable isUsingAlone
Variable type boolean
Initial value false
Variable targetPrice
Variable type real
Variable targetUnits
Variable type real
Variable pendingInvitations
Variable type List
Initial value new ArrayList()
Variable isUsingWithSeller
Variable type boolean
Initial value false
Variable lastHarassTime
Variable type real
Initial value 0
Variable isWaitingOnSeller
Variable type boolean
Initial value false
Variable isBeingHarassed
Variable type boolean
Initial value false
Variable possessionUnits
Variable type real
Initial value 0
Variable numArrests
Variable type real
Initial value 0
Variable arrestDuration
Variable type real
Variable dealFromSeller
Variable type DealCompleteMsg
Initial value null
Variable visibleStreetSellersIterator
Variable type Iterator
Initial value null
Variable visibleStreetSellers
Variable type List
Initial value new ArrayList()
Variable currentPlace
Variable type integer
Initial value Main.PLACE_HOME
Variable marketStartTime
Variable type real
Initial value 0
Variable main
Variable type Main
Initial value (Main)getOwner()
Variable targetSellerHistory
Variable type AgentHistory
Initial value null
Variable targetSellerY
Variable type real
Initial value 0
Variable targetSellerX
Variable type real
Initial value 0
Variable willUseWithSeller
Variable type boolean
Initial value false
Variable willUseAtSeller
Variable type boolean
Initial value false
Variable targetUsePartner
Variable type User
Initial value null
Variable isActive
Variable type boolean
Initial value false
Variable dealToSeller
Variable type DealRequestMsg
Initial value null
Variable self
Variable type Customer
Initial value this
Variable targetMarket
Variable type Market
Initial value null
Variable targetSeller
Variable type Seller
Initial value null
Variable money
Variable type real
Initial value initialMoney
Port randomUseListener
Message type RandomUseMsg
Port wantFixListener
Message type WantFixMsg
Port travelCompleteListener
Message type TravelCompleteMsg
Statechart simulationState
Object location
Type misc.Location
Parameters
Name Value
startX homeX
startY homeY
Object userBehavior
Type misc.UserBehavior
Parameters
Name Value
initialInventory initialInventory
initialConcentration initialConcentration
initialAddiction initialAddiction
randomUseIntervalMin randomUseIntervalMin
randomUseIntervalMax randomUseIntervalMax
metabolismRate metabolismRate
addictionIncreaseRate addictionIncreaseRate
addictionDecreaseRate addictionDecreaseRate
addictionIncreaseDuration addictionIncreaseDuration
addictionDecreaseOnset addictionDecreaseOnset
desperateNeedThreshold desperateNeedThreshold
desperateAddictionThreshold desperateAddictionThreshold
initialTimeSinceLastUse initialTimeSinceLastUse
initialHIV initialHIV
Object marketHistories
Type misc.AgentHistoryList
Object streetSellerHistories
Type misc.AgentHistoryList
Object privateDealerHistories
Type misc.AgentHistoryList
Object marketTrips
Type misc.TransactionCounter
Object allTransactions
Type misc.TransactionCounter
Object sbTransactions
Type misc.TransactionCounter
Object sdTransactions
Type misc.TransactionCounter
Object pdTransactions
Type misc.TransactionCounter

Statechart
Name simulationState
Picture
ChoicePoint seekPrivateDealer
Action seekPrivateDealer();
ChoicePoint seekStreetSeller
Action seekStreetSeller();
ChoicePoint chooseMarket
Action chooseMarket();
History preHarassed
Type Shallow
Action // NOTE:
//
// Due to a bug in AnyLogic 5, a deep history state
// will not transition to a simple state that is
// inside a composite state when the composite state
// is on the same level as the deep history state.
// Instead, it will cause a runtime error. Therefore
// we avoid deep history states and live with the
// limitations of shallow history states.
Transition transition56
Source/target satisfied=>treatment
Fire Immediately
Guard willGetTreatment()
Transition transition5
Source/target harassed=>preHarassed
Fire Signal event occurs
Signal event HarassCompleteMsg
Action applyHarassCompleteMsg((HarassCompleteMsg)getEvent());
Transition transition55
Source/target state=>harassed
Fire Signal event occurs
Signal event HarassMsg
Action applyHarassMsg((HarassMsg)getEvent());
Transition transition4
Source/target waitForPrivateDealer=>seekPrivateDealer
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition7
Source/target canBuy1=>travelToHome1
Fire If all other guards are closed
Action madeMarketTrip(false);
Transition transition6
Source/target canBuy1=>seekStreetSeller
Fire If guard is open
Guard money > targetPrice
Transition transition57
Source/target arrested=>active
Fire Timeout
Timeout arrestDuration
Transition transition54
Source/target active=>arrested
Fire Signal event occurs
Signal event ArrestMsg
Action applyArrestMsg((ArrestMsg)getEvent());
Transition transition53
Source/target seekStreetSeller=>seekStreetSeller
Fire Signal event occurs
Guard targetSeller != null && dealToSeller == null
Signal event DealDeniedMsg
Action applyDealDeniedMsg(new DealDeniedMsg());
Transition transition52
Source/target startPrivateDeal=>seekPrivateDealer
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition51
Source/target seekPrivateDealer=>startPrivateDeal
Fire Signal event occurs
Guard targetSeller != null && dealToSeller != null
Action isWaitingOnSeller = true;
sendDealRequestMsg();
Transition transition50
Source/target seekStreetSeller=>startStreetDeal
Fire Signal event occurs
Guard targetSeller != null && dealToSeller != null
Signal event TargetFoundMsg
Action isWaitingOnSeller = true;
sendDealRequestMsg();
Transition transition49
Source/target randomWalk=>seekPrivateDealer
Fire Immediately
Guard Engine.getTime() - marketStartTime > marketQuitTime
Action madeMarketTrip(false);
setupKnownPrivateDealers();
Transition transition48
Source/target willSeekPrivateDealer=>chooseMarket
Fire If all other guards are closed
Transition transition47
Source/target willSeekPrivateDealer=>seekPrivateDealer
Fire If guard is open
Guard willSeekPrivateDealer()
Action setupKnownPrivateDealers();
Transition transition46
Source/target startStreetDeal=>seekStreetSeller
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition45
Source/target onPrivateDeal=>seekPrivateDealer
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition44
Source/target startStreetDeal=>onStreetDeal
Fire Signal event occurs
Signal event DealStartMsg
Action applyDealStartMsg((DealStartMsg)getEvent());
isWaitingOnSeller = true;

Transition transition27
Source/target onStreetDeal=>canBuy1
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
setTargetDeal();
Transition transition15
Source/target seekPrivateDealer=>seekPrivateDealer
Fire Signal event occurs
Guard targetSeller != null && dealToSeller == null
Action applyDealDeniedMsg(new DealDeniedMsg());
Transition transition9
Source/target onStreetDeal=>isTravelRequired
Fire Signal event occurs
Signal event DealCompleteMsg
Action applyDealCompleteMsg((DealCompleteMsg)getEvent());
isWaitingOnSeller = false;
madeMarketTrip(true);
Transition transition43
Source/target willSeekMoney=>tryLater
Fire If all other guards are closed
Transition transition42
Source/target willSeekMoney=>seekMoney
Fire If guard is open
Guard willSeekMoney()
Transition transition41
Source/target willGetTreatment=>treatment
Fire If guard is open
Guard willGetTreatment()
Transition transition40
Source/target seekMoney=>wantFix
Fire Timeout
Timeout uniform(seekMoneyDelayMin, seekMoneyDelayMax)
Action seekMoney();
Transition transition39
Source/target willGetTreatment=>willSeekMoney
Fire If all other guards are closed
Transition transition38
Source/target isDesperate=>willGetTreatment
Fire If guard is open
Guard userBehavior.isDesperate()
Transition transition36
Source/target isDesperate=>tryLater
Fire If all other guards are closed
Transition transition35
Source/target useAtSeller=>travelToHome2
Fire Timeout
Timeout uniform(useDelayMin, useDelayMax)
Action // Waiting until the useDelay timeout fires before calling useAtSeller()
// means that we will not call useAtSeller() if we are arrested while
// waiting for the timeout to fire. In other words, if we are arrested
// in the process of using drugs, the model will assume we did not use
// any drugs at all.
//
// The other option is to call useAtSeller() in the transition entering
// the useAtSeller state. In this case, if we are arrested in the process
// of using drugs, the model will assume that we used all of our drugs.
// Neither situation is realistic.
//
// The useAtSeller() call was placed here for a practical reason.
// useAtSeller() needs to check that the drug partner is also in a
// useTogether state, and by waiting until this point to call
// useAtSeller(), we can be sure that the drug partner has had an
// opportunity to set his status variables properly.
useAtSeller();

isUsingAlone = false;

if (isUsingWithSeller) {
  sendUseTogetherCompleteMsg();
  isUsingWithSeller = false;
}
Transition transition37
Source/target travelToHome2=>satisfied
Fire Signal event occurs
Signal event TravelCompleteMsg
Action currentPlace = Main.PLACE_HOME;
possessionUnits = 0;
Transition transition34
Source/target willUseAtSeller=>travelToHome
Fire If all other guards are closed
Transition transition33
Source/target willUseAtSeller=>useAtSeller
Fire If guard is open
Guard willUseAtSeller
Action isUsingWithSeller = willUseWithSeller;
isUsingAlone = !isUsingWithSeller;
Transition transition32
Source/target canUse=>canBuy
Fire If all other guards are closed
Action setTargetDeal();
Transition transition31
Source/target randomWalk=>seekStreetSeller
Fire Signal event occurs
Signal event TravelCompleteMsg
Action setupVisibleStreetSellers();
Transition transition28
Source/target seekStreetSeller=>randomWalk
Fire Signal event occurs
Guard targetSeller == null
Signal event TargetFoundMsg
Transition transition30
Source/target chooseMarket=>travelToHome1
Transition transition29
Source/target chooseMarket=>travelToMarket
Fire Signal event occurs
Guard targetMarket != null
Signal event TargetFoundMsg
Transition transition26
Source/target travelToHome1=>isDesperate
Fire Signal event occurs
Signal event TravelCompleteMsg
Action currentPlace = Main.PLACE_HOME;
Transition transition25
Source/target randomWalk=>travelToHome1
Fire If all other guards are closed
Action madeMarketTrip(false);
Transition transition24
Source/target travelToPrivateDealer=>onPrivateDeal
Fire Signal event occurs
Signal event TravelCompleteMsg
Action currentPlace = Main.PLACE_PRIVATE_DEALER;
isWaitingOnSeller = true;
sendWaitCompleteMsg();
Transition transition23
Source/target waitForPrivateDealer=>onPrivateDeal
Fire Signal event occurs
Signal event WaitCompleteMsg
Action applyWaitCompleteMsg((WaitCompleteMsg)getEvent());
isWaitingOnSeller = true;
Transition transition12
Source/target travelToMarket=>seekStreetSeller
Fire Signal event occurs
Signal event TravelCompleteMsg
Action currentPlace = Main.PLACE_MARKET;
marketStartTime = Engine.getTime();
setupVisibleStreetSellers();
Transition transition22
Source/target seekPrivateDealer=>chooseMarket
Fire Signal event occurs
Guard targetSeller == null
Transition transition21
Source/target travelToHome=>useAtHome
Fire Signal event occurs
Signal event TravelCompleteMsg
Action currentPlace = Main.PLACE_HOME;
possessionUnits = 0;
Transition transition20
Source/target isTravelRequired=>useAtHome
Fire If guard is open
Guard currentPlace == Main.PLACE_HOME
Transition transition19
Source/target isTravelRequired=>willUseAtSeller
Fire If all other guards are closed
Transition transition16
Source/target onPrivateDeal=>isTravelRequired
Fire Signal event occurs
Signal event DealCompleteMsg
Action applyDealCompleteMsg((DealCompleteMsg)getEvent());
isWaitingOnSeller = false;
Transition transition18
Source/target startPrivateDeal=>waitForPrivateDealer
Fire Signal event occurs
Guard ((DealerDeliversMsg)getEvent()).delivers
Signal event DealerDeliversMsg
Action applyDealerDeliversMsg((DealerDeliversMsg)getEvent());
isWaitingOnSeller = true;
Transition transition17
Source/target startPrivateDeal=>travelToPrivateDealer
Action applyDealerDeliversMsg((DealerDeliversMsg)getEvent());
isWaitingOnSeller = false;
Transition transition14
Source/target tryLater=>wantFix
Fire Timeout
Timeout uniform(retryDelayMin, retryDelayMax)
Transition transition13
Source/target canBuy=>isDesperate
Fire If all other guards are closed
Transition transition3
Source/target useAtHome=>satisfied
Fire Timeout
Timeout uniform(useDelayMin, useDelayMax)
Transition transition11
Source/target wantFix=>canUse
Fire Immediately
Transition transition10
Source/target canBuy=>willSeekPrivateDealer
Fire Change event occurs
Guard money >= targetPrice
Change event Util.isDaytime()
Transition transition2
Source/target canUse=>useAtHome
Fire If guard is open
Guard userBehavior.inventory > 0
Transition transition1
Source/target satisfied=>wantFix
Fire Signal event occurs
Signal event WantFixMsg
Transition transition8
Source/target treatment=>active
Fire Timeout
Timeout uniform(treatmentDelayMin, treatmentDelayMax)
Transition transition
Source/target satisfied=>wantFix
Fire Signal event occurs
Signal event RandomUseMsg
State useAtHome
Entry action isUsingAlone = true;
useAtHome();
Exit action isUsingAlone = false;
State travelToHome
Entry action travelToHome();
State travelToPrivateDealer
Deferred events new DealDeniedEvent()
Entry action travelToPrivateDealer();
State waitForPrivateDealer
Entry action dealQuitTimer.restart(Main.csDealQuitTime);
Exit action dealQuitTimer.reset();
State travelToMarket
Entry action travelToMarket();
Exit action
State travelToHome1
Entry action travelToHome();
State randomWalk
Entry action wanderInMarket();
State travelToHome2
Entry action travelToHome();
State onPrivateDeal
Entry action dealQuitTimer.restart(Main.csDealQuitTime);
Exit action dealQuitTimer.reset();
State startStreetDeal
Entry action dealQuitTimer.restart(Main.csDealQuitTime);
Exit action dealQuitTimer.reset();
State onStreetDeal
Entry action dealQuitTimer.restart(Main.csDealQuitTime);
Exit action dealQuitTimer.reset();
State startPrivateDeal
Entry action dealQuitTimer.restart(Main.csDealQuitTime);
Exit action dealQuitTimer.reset();
State harassed
Deferred events new GenericEvent()
Entry action isBeingHarassed = true;
Exit action isBeingHarassed = false;
State arrested
Entry action currentPlace = Main.PLACE_JAIL;
State treatment
Entry action currentPlace = Main.PLACE_TREATMENT;
userBehavior.isAddictionFrozen = true;
Exit action userBehavior.addiction *= (1 - treatmentAddictionReduction);
userBehavior.isAddictionFrozen = false;
State active
Entry action isActive = true;

currentPlace = Main.PLACE_HOME;

location.currentX = homeX;
location.currentY = homeY;
Exit action isActive = false;
isWaitingOnSeller = false;
isUsingAlone = false;

if (isUsingWithSeller) {
  sendUseTogetherCompleteMsg();
  isUsingWithSeller = false;
}

Algorithmic Functions
Name applyArrestMsg
Type void
Arguments
Type Name
ArrestMsg msg
Body location.stopTravel();
numArrests++;
arrestDuration = msg.duration;
updateInventory(-possessionUnits);
possessionUnits = 0;
Name applyDealCompleteMsg
Type void
Arguments
Type Name
DealCompleteMsg msg
Body dealFromSeller = msg;


// Add the acquired drugs to the customer's inventory.

updateInventory(msg.units);


// Note that the customer now has drugs in his possession.
// This is important because he can now be arrested for
// possession. We consider it to be possession only if he
// has drugs outside of his home.

if (currentPlace != Main.PLACE_HOME) {
  possessionUnits = msg.units;
}


// Subtract the cost from the customer's money unless the
// customer used a broker (in which case the customer already
// gave the broker his money when he started the deal).
//
// ASSUME: The actual price equals the target price.

if (!(targetSeller instanceof StreetBroker)) {
  updateMoney(-msg.price);
}


// Update the customer's history with this seller.

targetSellerHistory.madeDeal(true, targetSellerX, targetSellerY);


// Update transaction counters.

allTransactions.addGood(msg.units, msg.price);
main.csAllTransactions.addGood(msg.units, msg.price);

if (targetSeller instanceof StreetBroker) {
  sbTransactions.addGood(msg.units, msg.price);
  main.csSbTransactions.addGood(msg.units, msg.price);
}
else if (targetSeller instanceof StreetDealer) {
  sdTransactions.addGood(msg.units, msg.price);
  main.csSdTransactions.addGood(msg.units, msg.price);
}
else if (targetSeller instanceof PrivateDealer) {
  pdTransactions.addGood(msg.units, msg.price);
  main.csPdTransactions.addGood(msg.units, msg.price);
}
else {
  traceln("***ERROR in Customer.receiveDealCompleteMsg(): Unexpected seller type for " + self + " @ " + Engine.getTime());
}


// Set up variables indicating the customer's course of action
// from this point until he has finished using.

if (currentPlace != Main.PLACE_HOME) {
  // Determine whether the customer will use at the seller's
  // location before returning home, or if he will return home
  // first and then use.

  willUseAtSeller = userBehavior.isDesperate() || randomTrue(probUseAtSeller);

  // Determine whether the customer will use with the seller.

  if (willUseAtSeller
      && targetSeller instanceof User
      && targetSeller.isWaitingOnBuyer(dealFromSeller.dealID)
     ) {
    User usingSeller = (User)targetSeller;
    willUseWithSeller = willUseTogether() && usingSeller.willUseTogether();
  }
  else {
    willUseWithSeller = false;
  }
}


// If the seller is also a user, let him know whether the customer will
// use with him.

if (targetSeller instanceof User
    && targetSeller.isWaitingOnBuyer(dealFromSeller.dealID)
   ) {
  User usingSeller = (User)targetSeller;

  UseTogetherMsg useMsg = new UseTogetherMsg();
  useMsg.dealID = dealToSeller.dealID;
  useMsg.uses = willUseWithSeller;
  usingSeller.receiveUseTogetherMsg(useMsg);

  targetUsePartner = willUseWithSeller ? usingSeller : null;
}
else {
  targetUsePartner = null;
}
Name applyDealDeniedMsg
Type void
Arguments
Type Name
DealDeniedMsg msg
Body // The denial of the deal may come with a refund of the customer's
// money. For example, in a brokered deal the customer would have
// given the broker his money at the beginning of the deal, so if
// the deal ends unsuccessfully the customer should get his money
// back.

if (msg.refund != 0) {
  updateMoney(msg.refund);
}


// Update the customer's history with this seller.

targetSellerHistory.madeDeal(false, targetSellerX, targetSellerY);


// Update transaction counters.

allTransactions.addFailed();
main.csAllTransactions.addFailed();

if (targetSeller instanceof StreetBroker) {
  sbTransactions.addFailed();
  main.csSbTransactions.addFailed();
}
else if (targetSeller instanceof StreetDealer) {
  sdTransactions.addFailed();
  main.csSdTransactions.addFailed();
}
else if (targetSeller instanceof PrivateDealer) {
  pdTransactions.addFailed();
  main.csPdTransactions.addFailed();
}
else {
  traceln("***ERROR in Customer.receiveDealDeniedMsg(): Unexpected seller type for " + self + " @ " + Engine.getTime());
}
Name applyDealStartMsg
Type void
Arguments
Type Name
DealStartMsg msg
Body if (targetSeller instanceof StreetBroker) {
  updateMoney(-targetPrice);
}
Name applyDealerDeliversMsg
Type void
Arguments
Type Name
DealerDeliversMsg msg
Body // Nothing needs to be done.
Name applyHarassCompleteMsg
Type void
Arguments
Type Name
HarassCompleteMsg msg
Body // Nothing needs to be done.
Name applyHarassMsg
Type void
Arguments
Type Name
HarassMsg msg
Body location.stopTravel();
lastHarassTime = Engine.getTime();
Name applyWaitCompleteMsg
Type void
Arguments
Type Name
WaitCompleteMsg msg
Body // Nothing needs to be done.
Name chooseMarket
Type void
Body // TODO: Select an algorithm for choosing a market.
// For now, choose the market with the highest proportion of
// good trips.
// May also consider:
// distance
// random variation

targetMarket = null;

// Decide whether to try going to a market based on the current
// time of day.
//
// The customer has knowledge of when dealers typically enter
// and leave the market. He knows they are never there before
// earlyStartHour, and he knows they are always there by
// lateStartHour. For the times in between, he decides randomly
// whether to go, where the probability of going is higher
// the closer it gets to the lateStartHour. At the end of the
// day, the customer knows that the dealers sometimes leave by
// earlyEndHour. Considering that he has to travel to the market
// and find a seller, he will not go if the time is past
// earlyEndHour.

double earlyStartHour = util.Params.getDouble(Main.sdShiftStartRange, 0);
double lateStartHour = util.Params.getDouble(Main.sdShiftStartRange, 1);
double earlyEndHour = util.Params.getDouble(Main.sdShiftEndRange, 0);

if (randomTrue((util.Clock.getCurrentHour() - earlyStartHour) / (lateStartHour - earlyStartHour))
    && util.Clock.getCurrentHour() < earlyEndHour
   ) {
  // Sort the markets by proportion of good deals, and
  // choose the first market in the list.
  marketHistories.shuffle(); // avoid always breaking ties the same way
  marketHistories.sort(AgentHistoryList.proportionGoodDealsComparator);
  targetMarket = (Market)marketHistories.get(0).agent;
}
Name createDealRequestMsg
Type void
Body dealToSeller = new DealRequestMsg();
dealToSeller.dealID = main.getNewDealID();
dealToSeller.units = targetUnits;
dealToSeller.price = targetPrice;
dealToSeller.buyer = self;
dealToSeller.buyerPlace = currentPlace;
dealToSeller.customer = self;
Name getDrugsOnPerson
Type real
Body return possessionUnits;
Name getLastHarassTime
Type real
Body return lastHarassTime;
Name getLocation
Type Location
Body return location;
Name hasHIV
Type boolean
Body return userBehavior.hasHIV;
Name isBeingHarassed
Type boolean
Body return isBeingHarassed;
Name isInPublic
Type boolean
Body // isInPublic: Can the seller currently be seen in public?

// Customers can be seen in public only when they are traveling
// or when they are in the market.

return currentPlace == Main.PLACE_TRANSIT || currentPlace == Main.PLACE_MARKET;
Name isQueueLengthAcceptable
Type boolean
Arguments
Type Name
Seller seller
Body int acceptableQueueLength = 0;

if (seller instanceof StreetBroker) {
  acceptableQueueLength = Main.csAcceptableSbQueueLength;
}
else if (seller instanceof StreetDealer) {
  acceptableQueueLength = acceptableSdQueueLength;
}
else if (seller instanceof PrivateDealer) {
  acceptableQueueLength = acceptablePdQueueLength;
}
else {
  traceln("***ERROR in Customer.isQueueLengthAcceptable(): Unexpected seller type for " + self + " @ " + Engine.getTime());
  return false;
}

return targetSeller.getQueueLength() <= acceptableQueueLength;
Name isUsing
Type boolean
Body // isUsing: Is this user currently using drugs?

return isUsingAlone || isUsingWithSeller;
Name isUsingTogether
Type boolean
Arguments
Type Name
integer dealID
Body // isUsingTogether: Is this user using drugs with another user as
// a result of the specified deal?

return isUsingWithSeller && dealToSeller.dealID == dealID;
Name isWaitingOnSeller
Type boolean
Arguments
Type Name
integer dealID
Body return isWaitingOnSeller && dealToSeller.dealID == dealID;
Name madeMarketTrip
Type void
Arguments
Type Name
boolean success
Body // Record the success or failure of the trip.
//
// Record the market location of the deal if the trip was a success.
// Note that a failed trip may not have a specific location associated
// with it since the failure could be the result of wandering in the
// market for too long and eventually giving up. For this reason, we
// will record a dummy location for a failed trip.

AgentHistory history = marketHistories.find(targetMarket);

if (success) {
  if (targetMarket.isLocationInMarket(targetSellerX, targetSellerY)) {
    history.madeDeal(true, targetSellerX, targetSellerY);
  }
  else {
    history.madeDeal(true, -1, -1);
    traceln("***ERROR in Customer.madeMarketTrip: Seller location is not in market: customer=" + self + " seller=" + targetSeller + " time=" + Engine.getTime());
  }
}
else {
  // Record the market's reference coordinates as the location of a
  // failed trip.
  history.madeDeal(false, targetMarket.x, targetMarket.y);
}


// Update the market trip counters.

if (success) {
  marketTrips.addGood(dealFromSeller.units, dealFromSeller.price);
  main.csMarketTrips.addGood(dealFromSeller.units, dealFromSeller.price);
}
else {
  marketTrips.addFailed();
  main.csMarketTrips.addFailed();
}
Name processPendingInvitations
Type void
Body // Transfer any private dealers from our pendingInvitations list
// to our privateDealerHistories list. See receiveInviteBuyerMsg()
// for a discussion of the pendingInvitations list.

for (int i = 0; i < pendingInvitations.size(); i++) {
  PrivateDealer pd = (PrivateDealer)pendingInvitations.get(i);
  privateDealerHistories.add(pd);
}

pendingInvitations.clear();
Name receiveArrestMsg
Type void
Arguments
Type Name
ArrestMsg msg
Body if (currentPlace != Main.PLACE_JAIL) {
  simulationState.fireEvent(msg);
}
Name receiveDealCompleteMsg
Type void
Arguments
Type Name
DealCompleteMsg msg
Body if (isWaitingOnSeller(msg.dealID)) {
  simulationState.fireEvent(msg);
}
else {
  traceln("***WARNING in Customer.receiveDealCompleteMsg(): " + self + " ignoring message about unexpected deal " + msg.dealID + " @ " + Engine.getTime());
}
Name receiveDealDeniedMsg
Type void
Arguments
Type Name
DealDeniedMsg msg
Body if (isWaitingOnSeller(msg.dealID)) {
  simulationState.fireEvent(msg);
}
else {
  traceln("***WARNING in Customer.receiveDealDeniedMsg(): " + self + " ignoring message about unexpected deal " + msg.dealID + " @ " + Engine.getTime());
}
Name receiveDealStartMsg
Type void
Arguments
Type Name
DealStartMsg msg
Body if (isWaitingOnSeller(msg.dealID)) {
  simulationState.fireEvent(msg);
}
else {
  traceln("***WARNING in Customer.receiveDealStartMsg(): " + self + " ignoring message about unexpected deal " + msg.dealID + " @ " + Engine.getTime());
}
Name receiveDealerDeliversMsg
Type void
Arguments
Type Name
DealerDeliversMsg msg
Body if (isWaitingOnSeller(msg.dealID)) {
  simulationState.fireEvent(msg);
}
else {
  traceln("***WARNING in Customer.receiveDealerDeliversMsg(): " + self + " ignoring message about unexpected deal " + msg.dealID + " @ " + Engine.getTime());
}
Name receiveHarassCompleteMsg
Type void
Arguments
Type Name
HarassCompleteMsg msg
Body if (isBeingHarassed()) {
  simulationState.fireEvent(msg);
}
else {
  traceln("***WARNING in Customer.receiveHarassCompleteMsg(): " + self + " ignoring unexpected message @ " + Engine.getTime());
}

Name receiveHarassMsg
Type void
Arguments
Type Name
HarassMsg msg
Body if (!isBeingHarassed()) {
  simulationState.fireEvent(msg);
}

Name receiveInviteBuyerMsg
Type void
Arguments
Type Name
InviteBuyerMsg msg
Body // We have received an invitation from a private dealer to make
// direct deals with him instead of going through a broker.
//
// We can't immediately add him to our list of private dealers,
// however, because it's possible that we are currently iterating
// over that list looking for another private dealer. If we modify
// the list during the iteration process, we will cause a
// ConcurrentModificationException and the model will crash.
//
// Instead, we add the dealer to a separate list of pending
// invitations. At a point when we know we are not iterating
// over the list, we will add the private dealers from this
// pending list to our full list of private dealers. This will
// happen in the setupKnownPrivateDealers function.

pendingInvitations.add(msg.privateDealer);
Name receiveUseTogetherCompleteMsg
Type void
Arguments
Type Name
UseTogetherCompleteMsg msg
Body // Customers should not receive a UseTogetherCompleteMsg.
// This function exists to implement the User interface.
traceln("***WARNING in Customer.receiveUseTogetherCompleteMsg(): " + self + " ignoring message about unexpected deal " + msg.dealID + " @ " + Engine.getTime());
Name receiveUseTogetherMsg
Type void
Arguments
Type Name
UseTogetherMsg msg
Body // Customers should not receive a UseTogetherMsg.
// This function exists to implement the User interface.
traceln("***WARNING in Customer.receiveUseTogetherMsg(): " + self + " ignoring message about unexpected deal " + msg.dealID + " @ " + Engine.getTime());
Name receiveWaitCompleteMsg
Type void
Arguments
Type Name
WaitCompleteMsg msg
Body if (isWaitingOnSeller(msg.dealID)) {
  simulationState.fireEvent(msg);
}
else {
  traceln("***WARNING in Customer.receiveWaitCompleteMsg(): " + self + " ignoring message about unexpected deal " + msg.dealID + " @ " + Engine.getTime());
}
Name seekMoney
Type void
Body if (randomTrue(probSeekMoneySuccess)) {
  updateMoney(uniform(windfallAmountMin, windfallAmountMax));
}
Name seekPrivateDealer
Type void
Body // We expect this method to be called repeatedly, with each new call
// returning the next dealer in the list until an available dealer is
// found or the list is exhausted.

targetSeller = null;
dealToSeller = null;

if (privateDealerHistories.hasNext()) {
  targetSellerHistory = privateDealerHistories.next();
  targetSeller = (Seller)targetSellerHistory.agent;
  targetSellerX = targetSeller.getLocation().currentX;
  targetSellerY = targetSeller.getLocation().currentY;

  if (targetSeller.isAcceptingRequests()
      && targetSeller.willAcceptDeal(self, targetUnits, targetPrice)
      && isQueueLengthAcceptable(targetSeller)
     ) {
    createDealRequestMsg();
  }
}
Name seekStreetSeller
Type void
Body // We expect this method to be called repeatedly, with each new call
// returning the next seller in the list until an available seller is
// found or the list is exhausted.

targetSeller = null;
dealToSeller = null;

if (visibleStreetSellersIterator.hasNext()) {
  targetSeller = (Seller)visibleStreetSellersIterator.next();
  targetSellerX = targetSeller.getLocation().currentX;
  targetSellerY = targetSeller.getLocation().currentY;
  targetSellerHistory = streetSellerHistories.find(targetSeller);

  // Bail out without creating a deal request under certain conditions.
  if (targetSeller instanceof User && ((User)targetSeller).isUsing()) return;
  if (((PoliceTarget)targetSeller).isBeingHarassed()) return;
  if (!isQueueLengthAcceptable(targetSeller)) return;

  createDealRequestMsg();
}
Name sendDealRequestMsg
Type void
Body targetSeller.receiveDealRequestMsg(dealToSeller);
Name sendUseTogetherCompleteMsg
Type void
Body if (targetUsePartner.isUsingTogether(dealToSeller.dealID)) {
  UseTogetherCompleteMsg msg = new UseTogetherCompleteMsg();
  msg.dealID = dealToSeller.dealID;
  targetUsePartner.receiveUseTogetherCompleteMsg(msg);
}

Name sendWaitCompleteMsg
Type void
Body if (targetSeller.isWaitingOnBuyer(dealToSeller.dealID)) {
  WaitCompleteMsg msg = new WaitCompleteMsg();
  msg.dealID = dealToSeller.dealID;
  targetSeller.receiveWaitCompleteMsg(msg);
}

Name setTargetDeal
Type void
Body boolean foundDeal = false;


// For convenience below, create true arrays from the stringified
// Main array parameters. If Main.dealUnits and Main.dealPrices are
// not the same length, we use the length of Main.dealUnits and
// replicate the last value of Main.dealPrices as necessary to equal
// the length of Main.dealUnits. This replication process is handled
// in the util.Params.getDouble function.

double[] dealUnits = new double[util.Params.count(Main.dealUnitsList)];
double[] dealPrices = new double[util.Params.count(Main.dealUnitsList)];

for (int i = 0; i < dealUnits.length; i++) {
  dealUnits[i] = util.Params.getDouble(Main.dealUnitsList, i);
  dealPrices[i] = util.Params.getDouble(Main.dealPricesList, i);
}


// If the customer buys based on need, the target deal
// is smallest deal amount that is larger than what he
// needs. If he can't afford this amount, then he buys
// based on what he can afford.
//
// NOTE: This algorithm requires that the code for
// "need" come before the code for "afford".

if (buyMethod.equals("need")) {
  for (int i = 0; i < dealUnits.length; i++) {
    if (dealUnits[i] >= userBehavior.addiction) {
      if (dealPrices[i] <= money) {
        targetUnits = dealUnits[i];
        targetPrice = dealPrices[i];
        foundDeal = true;
      }
      break;
    }
  }
}


// If the customer buys based on what he can afford,
// then the target deal is the one with the largest
// price that is less than how much money he has.

if (!foundDeal || buyMethod.equals("afford")) {
  for (int i = dealPrices.length - 1; i >= 0; i--) {
    if (dealPrices[i] <= money) {
      targetUnits = dealUnits[i];
      targetPrice = dealPrices[i];
      foundDeal = true;
      break;
    }
  }
}


// If the customer couldn't find a target deal, this
// means he couldn't afford any of the defined deals.
// In this case, just set the target deal to the first
// defined deal. Here we assume that the customer will
// always check whether he can afford the target deal
// after this function has been called.
//
// NOTE: We will have to do something smarter if we add
// the ability to make deals on credit.

if (!foundDeal) {
  targetUnits = dealUnits[0];
  targetPrice = dealPrices[0];
}
Name setupKnownPrivateDealers
Type void
Body // If any private dealers sent us invitations since the last
// time we searched for one, add them to the list of all private
// dealers that we know about.
processPendingInvitations();

// Sort the dealers so that those with the highest proportion
// of good deals appear at the front of the list.
privateDealerHistories.shuffle(); // avoid always breaking ties the same way
privateDealerHistories.sort(AgentHistoryList.proportionGoodDealsComparator);

// Reset the iterator to point to the beginning of the list.
// We'll use this to cycle through the list once we're inside
// the seekPrivateDealer() function.
privateDealerHistories.resetIterator();
Name setupVisibleStreetSellers
Type void
Body // Find all the street sellers near this customer. Create a list of these
// sellers, and add the sellers to the list of known street sellers.

visibleStreetSellers.clear();

for (int i = 0; i < targetMarket.streetBrokers.size(); i++) {
  StreetBroker sb = (StreetBroker)targetMarket.streetBrokers.get(i);
  if (sb.isInPublic()
      && location.distance(sb.getLocation().currentX, sb.getLocation().currentY) <= max(visionRadius, sb.visionRadius)
     ) {
    visibleStreetSellers.add(sb);
    streetSellerHistories.add(sb);
  }
}

for (int i = 0; i < targetMarket.streetDealers.size(); i++) {
  StreetDealer sd = (StreetDealer)targetMarket.streetDealers.get(i);
  if (sd.isInPublic()
      && location.distance(sd.getLocation().currentX, sd.getLocation().currentY) <= max(visionRadius, sd.visionRadius)
     ) {
    visibleStreetSellers.add(sd);
    streetSellerHistories.add(sd);
  }
}

// Sort the sellers so that those with the highest proportion
// of good deals appear at the front of the list. We'll first
// sort the list of all known street sellers, and then we'll
// sort the list of visible street sellers to match the sort
// order of the full list of known street sellers.

streetSellerHistories.shuffle(); // avoid always breaking ties the same way
streetSellerHistories.sort(AgentHistoryList.proportionGoodDealsComparator);

Collections.sort(visibleStreetSellers,
  // Use a comparison function that compares the visibleStreetSellers
  // elements based on their position in streetSellerHistories. This
  // comparator needs to be defined somewhere within the Customer
  // definition because it needs access to streetSellerHistories.
  new Comparator() {
    public int compare(Object o1, Object o2) {
      int i1 = streetSellerHistories.indexOf(o1);
      int i2 = streetSellerHistories.indexOf(o2);

      if (i1 == -1 || i2 == -1) {
        // One of the objects isn't in the full list. This shouldn't
        // happen since we added each visible seller to the full list,
        // so report an error.
        traceln("***ERROR in Customer.setupVisibleStreetSellers(): Visible seller not in list of all sellers for " + self + " @ " + Engine.getTime());
        return 0;
      }
      else {
        if (i1 < i2) {
          return -1;
        }
        else if (i2 > i2) {
          return 1;
        }
        else {
          return 0;
        }
      }
    }
  }
);



// Get an iterator that we will use to cycle through the list
// once we're inside the seekStreetSeller() function.

visibleStreetSellersIterator = visibleStreetSellers.iterator();
Name travelToHome
Type void
Body if (currentPlace != Main.PLACE_HOME) {
  // Begin traveling to home. Assume the travel takes place
  // at driving speed (by car, bus, etc.).
  location.travel(homeX, homeY, Main.TRAVEL_DRIVE);
  
  // Record that the customer is now traveling.
  currentPlace = Main.PLACE_TRANSIT;
}
else {
  simulationState.fireEvent(new TravelCompleteMsg());
}
Name travelToMarket
Type void
Body // Choose a target location in the market. If the customer has made
// a good deal in the market, travel to the location of the most
// recent one. If not, travel to a random location in the market.

AgentHistory history = marketHistories.find(targetMarket);

double destinationX;
double destinationY;

if (history.numGoodDeals > 0) {
  destinationX = history.lastGoodDealX;
  destinationY = history.lastGoodDealY;
}
else {
  destinationX = targetMarket.getRandomX();
  destinationY = targetMarket.getRandomY();
}

// Begin movement to the market. Assume the travel takes place
// at driving speed (by car, bus, etc.).
location.travel(destinationX, destinationY, Main.TRAVEL_DRIVE);

// Record that the customer is now traveling.
currentPlace = Main.PLACE_TRANSIT;
Name travelToPrivateDealer
Type void
Body // Begin movement to the private dealer. Assume the travel
// takes place at driving speed (by car, bus, etc.).
location.travel(targetSellerX, targetSellerY, Main.TRAVEL_DRIVE);

// Record that the customer is now traveling.
currentPlace = Main.PLACE_TRANSIT;
Name updateInventory
Type void
Arguments
Type Name
real inventoryChange
Body userBehavior.inventory += inventoryChange;

if (userBehavior.inventory < 0) {
  traceln("***ERROR in Customer.updateInventory(): Negative inventory for " + self + " @ " + Engine.getTime());
}
Name updateMoney
Type void
Arguments
Type Name
real moneyChange
Body money += moneyChange;

if (money < 0) {
  traceln("***ERROR in Customer.updateMoney(): Negative money for " + self + " @ " + Engine.getTime());
}
Name useAtHome
Type void
Body userBehavior.useNeed();
Name useAtSeller
Type void
Body double unitsUsed = userBehavior.useNeed();
possessionUnits -= unitsUsed;

if (willUseWithSeller) {
  if (targetUsePartner.isUsingTogether(dealToSeller.dealID)
      && !hasHIV()
      && targetUsePartner.hasHIV()
      && randomTrue(Main.probTransmitHIV)
     ) {
    userBehavior.hasHIV = true;
  }
}
Name wanderInMarket
Type void
Body location.wanderInMarket(targetMarket);

Name willGetTreatment
Type boolean
Body if (userBehavior.getNeed() > 0) {
  return randomTrue(Main.csProbTreatmentNotSatisfied);
}
else {
  return randomTrue(Main.csProbTreatmentSatisfied);
}
Name willSeekMoney
Type boolean
Body return money < util.Params.getDouble(Main.dealPricesList, 0) && randomTrue(probSeekMoney);
Name willSeekPrivateDealer
Type boolean
Body return randomTrue(probTryPrivateDealer);
Name willUseTogether
Type boolean
Body return randomTrue(probUseTogether);

Animation
Name animation
X 0
Y 0
Additional class code public boolean isSelected = false;
Picture
Rectangle agentShape
X main.animation.getAgentLocationX(main.AGENT_CUSTOMER, isSelected, location.currentX)
Y main.animation.getAgentLocationY(main.AGENT_CUSTOMER, isSelected, location.currentY)
Width main.animation.getAgentSize(main.AGENT_CUSTOMER, isSelected)
Height main.animation.getAgentSize(main.AGENT_CUSTOMER, isSelected)
Fill color main.animation.getAgentFillColor(main.AGENT_CUSTOMER, isSelected)
Line color main.animation.getAgentLineColor(main.AGENT_CUSTOMER, isSelected)
Line width main.animation.getAgentLineWidth(main.AGENT_CUSTOMER, isSelected)
Visible isActive && main.animation.getAgentVisibility(main.AGENT_CUSTOMER, isSelected, location.currentX, location.currentY)

Active Object: Homeless

General
Import import message.*;
import misc.*;

Parameters
Name initialMarket
Type Market

Icon
Picture

Structure
Picture
ChartTimer dailyTimer
Timeout util.Units.days(1)
Expire At Startup Yes
Expiry Action // Each day, the homeless start their "shifts" sometime during
// the time period that they know the street dealers tend to
// enter the market. They end their shifts sometime during the
// time period that they know the street dealers tend to leave
// the market.

if (isActive) {
  double earlyStartHour = util.Params.getDouble(Main.sdShiftStartRange, 0);
  double lateStartHour = util.Params.getDouble(Main.sdShiftStartRange, 1);
  double shiftStartHour = util.Units.hours(uniform(earlyStartHour, lateStartHour));
  shiftStartTimer.restart(shiftStartHour);

  double earlyEndHour = util.Params.getDouble(Main.sdShiftEndRange, 0);
  double lateEndHour = util.Params.getDouble(Main.sdShiftEndRange, 1);
  shiftEndTime = Engine.getTime() + util.Units.hours(uniform(earlyEndHour, lateEndHour));
}
ChartTimer shiftStartTimer
Expire No (manual mode)
Expiry Action simulationState.fireEvent(new ShiftStartMsg());
Variable shiftEndTime
Variable type real
Variable currentPlace
Variable type integer
Initial value Main.PLACE_MARKET
Variable isActive
Variable type boolean
Initial value false
Variable market
Variable type Market
Initial value initialMarket
Variable main
Variable type Main
Initial value (Main)getOwner()
Variable self
Variable type Homeless
Initial value this
Variable lastHarassTime
Variable type real
Initial value 0
Variable isBeingHarassed
Variable type boolean
Initial value false
Port travelCompleteListener
Message type TravelCompleteMsg
Statechart simulationState
Object location
Type misc.Location
Parameters
Name Value
startX market.getRandomX()
startY market.getRandomY()

Statechart
Name simulationState
Picture
History preHarassed
Type Shallow
Action // NOTE:
//
// Due to a bug in AnyLogic 5, a deep history state
// will not transition to a simple state that is
// inside a composite state when the composite state
// is on the same level as the deep history state.
// Instead, it will cause a runtime error. Therefore
// we avoid deep history states and live with the
// limitations of shallow history states.
Transition transition5
Source/target harassed=>preHarassed
Fire Signal event occurs
Signal event HarassCompleteMsg
Action applyHarassCompleteMsg((HarassCompleteMsg)getEvent());
Transition transition55
Source/target state=>harassed
Fire Signal event occurs
Signal event HarassMsg
Action applyHarassMsg((HarassMsg)getEvent());


Transition transition8
Source/target idle=>randomWalk
Fire Signal event occurs
Signal event ShiftStartMsg
Transition transition7
Source/target randomWalk=>idle
Fire Immediately
Guard !market.isOpen && Engine.getTime() > shiftEndTime
Action location.travel(location.startX, location.startY, Main.TRAVEL_WALK);
Transition transition9
Source/target randomWalk=>randomWalk
Fire Signal event occurs
Signal event TravelCompleteMsg
State randomWalk
Entry action location.wanderInMarket(market);
State harassed
Deferred events new GenericEvent()
Entry action isBeingHarassed = true;
Exit action isBeingHarassed = false;
State active
Entry action isActive = true;
currentPlace = Main.PLACE_MARKET;
location.currentX = location.startX;
location.currentY = location.startY;
market.addHomeless(self);
Exit action isActive = false;
market.removeHomeless(self);

Algorithmic Functions
Name applyHarassCompleteMsg
Type void
Arguments
Type Name
HarassCompleteMsg msg
Body // Nothing needs to be done.
Name applyHarassMsg
Type void
Arguments
Type Name
HarassMsg msg
Body location.stopTravel();
lastHarassTime = Engine.getTime();
Name getDrugsOnPerson
Type real
Body // The homeless do not have drugs.
return 0;
Name getLastHarassTime
Type real
Body return lastHarassTime;
Name getLocation
Type Location
Body return location;
Name isBeingHarassed
Type boolean
Body return isBeingHarassed;
Name isInPublic
Type boolean
Body // isInPublic: Can the seller currently be seen in public?

// The homeless are always out in public.

return true;
Name receiveArrestMsg
Type void
Arguments
Type Name
ArrestMsg msg
Body // Do nothing. The homeless are not arrested.
Name receiveHarassCompleteMsg
Type void
Arguments
Type Name
HarassCompleteMsg msg
Body if (isBeingHarassed()) {
  simulationState.fireEvent(msg);
}
else {
  traceln("***WARNING in Homeless.receiveHarassCompleteMsg(): " + self + " ignoring unexpected message @ " + Engine.getTime());
}
Name receiveHarassMsg
Type void
Arguments
Type Name
HarassMsg msg
Body if (!isBeingHarassed()) {
  simulationState.fireEvent(msg);
}


Animation
Name animation
X 0
Y 0
Additional class code public boolean isSelected = false;
Picture
Rectangle agentShape
X main.animation.getAgentLocationX(main.AGENT_HOMELESS, isSelected, location.currentX)
Y main.animation.getAgentLocationY(main.AGENT_HOMELESS, isSelected, location.currentY)
Width main.animation.getAgentSize(main.AGENT_HOMELESS, isSelected)
Height main.animation.getAgentSize(main.AGENT_HOMELESS, isSelected)
Fill color main.animation.getAgentFillColor(main.AGENT_HOMELESS, isSelected)
Line color main.animation.getAgentLineColor(main.AGENT_HOMELESS, isSelected)
Line width main.animation.getAgentLineWidth(main.AGENT_HOMELESS, isSelected)
Visible isActive && main.animation.getAgentVisibility(main.AGENT_HOMELESS, isSelected, location.currentX, location.currentY)

Active Object: Main

General
Import import misc.*;
import message.*;
import animation.*;
Startup code initializeParameters();
initializeAgents();
initializeNetworks();
startTimers();
Additional class code public static final int AGENT_CUSTOMER = 0;
public static final int AGENT_STREET_BROKER = 1;
public static final int AGENT_STREET_DEALER = 2;
public static final int AGENT_PRIVATE_DEALER = 3;
public static final int AGENT_HOMELESS = 4;
public static final int AGENT_POLICE = 5;
public static final int AGENT_MARKET = 6;
public static final int NUM_AGENTS = 7;

public static final int PLACE_HOME = 0;
public static final int PLACE_TRANSIT = 1;
public static final int PLACE_MARKET = 2;
public static final int PLACE_PRIVATE_DEALER = 3;
public static final int PLACE_TREATMENT = 4;
public static final int PLACE_JAIL = 5;

public static final int TRAVEL_WALK = 0;
public static final int TRAVEL_DRIVE = 1;

Parameters
Name numReplications
Type integer
Default value 1
Name replicationRunTime
Type real
Default value 180
Name animationScaleFactor
Type real
Default value 1
Name minutesPerTimeStep
Type real
Default value 1
Modificator Global
Name cityWidth
Type real
Default value 5
Name cityHeight
Type real
Default value 5
Name marketSizeRange
Type String
Default value "0.5,0.5"
Name walkingSpeed
Type real
Default value 2
Modificator Global
Name drivingSpeed
Type real
Default value 30
Modificator Global
Name visionRadiusRange
Type String
Default value "60,60"
Name dealUnitsList
Type String
Default value "10,30,40,60,120,360"
Modificator Global
Name dealPricesList
Type String
Default value "20,40,50,70,130,330"
Modificator Global
Name useIncrement
Type real
Default value 5
Modificator Global
Name param7
Modificator Separator
Name outfileSummaryCreate
Type boolean
Default value false
Name outfileSummaryName
Type String
Default value "summary.tsv"
Name outfileSummaryAppend
Type boolean
Default value false
Name outfileSummaryInterval
Type real
Default value 1
Name outfileCustomerCreate
Type boolean
Default value false
Name outfileCustomerName
Type String
Default value "customer.tsv"
Name outfileCustomerAppend
Type boolean
Default value false
Name outfileCustomerInterval
Type real
Default value 24
Name outfileStreetBrokerCreate
Type boolean
Default value false
Name outfileStreetBrokerName
Type String
Default value "streetBroker.tsv"
Name outfileStreetBrokerAppend
Type boolean
Default value false
Name outfileStreetBrokerInterval
Type real
Default value 24
Name param
Modificator Separator
Name numInitialMarkets
Type integer
Default value 1
Name numInitialCustomers
Type integer
Default value 200
Name numInitialStreetBrokers
Type integer
Default value 50
Name numInitialStreetDealers
Type integer
Default value 20
Name numInitialPrivateDealers
Type integer
Default value 25
Name numInitialPolice
Type integer
Default value 1
Name numInitialHomeless
Type integer
Default value 100
Name param2
Modificator Separator
Name metabolismRateRange
Type String
Default value "0.004,0.004"
Name addictionIncreaseDurationRange
Type String
Default value "1,1"
Name addictionIncreaseRateRange
Type String
Default value "0.0007,0.0007"
Name addictionDecreaseOnsetRange
Type String
Default value "12,12"
Name addictionDecreaseRateRange
Type String
Default value "0.00014,0.00014"
Name addictionUpperBound
Type real
Default value 360
Modificator Global
Name desperateNeedThresholdRange
Type String
Default value "0.75,0.75"
Name desperateAddictionThresholdRange
Type String
Default value "5,5"
Name useDelayRange
Type String
Default value "30,30"
Name probTransmitHIV
Type real
Default value 0.005
Modificator Global
Name treatmentDelayRange
Type String
Default value "1,30"
Name treatmentAddictionReductionRange
Type String
Default value "0.5,0.9"
Name param3
Modificator Separator
Name csInitialConcentrationDistrParams
Type String
Default value "15,45"
Name csInitialAddictionDistrParams
Type String
Default value "120,55"
Name csInitialInventoryDistrParams
Type String
Default value "0,33"
Name csInitialTimeSinceLastUseDistrParams
Type String
Default value "1,12"
Name csInitialMoneyDistrParams
Type String
Default value "18"
Name csProbInitialHIV
Type real
Default value 0.05
Name csIncomeAmountDailyRange
Type String
Default value "2,20"
Name csProbIncomeIntervalWeekly
Type real
Default value 0.25
Name csProbIncomeIntervalMonthly
Type real
Default value 0.25
Name csWindfallAmountRange
Type String
Default value "20,500"
Name csWindfallIntervalRange
Type String
Default value "1,365"
Name csProbBuyMethodAfford
Type real
Default value 0.25
Description Probability that a customer's buying method is to buy
as much as he can afford. The alternative method is
to buy only what he needs.
Name csRandomUseIntervalRange
Type String
Default value "1,7"
Name csProbKnowPrivateDealer
Type real
Default value 0.10
Name csProbTryPrivateDealerRange
Type String
Default value "0.50,0.50"
Name csAcceptableSbQueueLength
Type integer
Default value 0
Modificator Global
Name csAcceptableSdQueueLengthRange
Type String
Default value "2,2"
Name csAcceptablePdQueueLengthRange
Type String
Default value "3,3"
Name csDealQuitTime
Type real
Default value 4
Modificator Global
Name csMarketQuitTimeRange
Type String
Default value "2,2"
Name csProbUseAtSellerRange
Type String
Default value "0.1,0.1"
Name csProbUseTogetherRange
Type String
Default value "0.5,0.5"
Name csProbTreatmentSatisfied
Type real
Default value 0.001
Modificator Global
Name csProbTreatmentNotSatisfied
Type real
Default value 0
Modificator Global
Name csProbSeekMoneyRange
Type String
Default value "0.1,0.1"
Name csProbSeekMoneySuccessRange
Type String
Default value "0.5,0.5"
Name csSeekMoneyDelayRange
Type String
Default value "60,360"
Name csRetryDelayRange
Type String
Default value "30,30"
Name param4
Modificator Separator
Name sbInitialConcentrationDistrParams
Type String
Default value "0,10"
Name sbInitialAddictionDistrParams
Type String
Default value "1,10"
Name sbInitialTimeSinceLastUseDistrParams
Type String
Default value "12,24"
Name sbProbInitialHIV
Type real
Default value 0.10
Name sbProbKnowPrivateDealer
Type real
Default value 0.9
Name sbProbTryPrivateDealerRange
Type String
Default value "0.10,0.10"
Name sbCheckAllKnownSd
Type boolean
Default value false
Modificator Global
Name sbNumKnownSdToCheck
Type integer
Default value 1
Modificator Global
Name sbAcceptableSdQueueLengthRange
Type String
Default value "2,2"
Name sbAcceptablePdQueueLengthRange
Type String
Default value "3,3"
Name sbDealQuitTime
Type real
Default value 2
Modificator Global
Name sbMarketQuitTimeRange
Type String
Default value "2,2"
Name sbTipPercent
Type real
Default value 10
Modificator Global
Name sbTipLowerBound
Type real
Default value 3
Modificator Global
Name sbTipUpperBound
Type real
Default value 30
Modificator Global
Name sbProbUseTogetherRange
Type String
Default value "0.5,0.5"
Name param5
Modificator Separator
Name sdInitialMoneyDistrParams
Type String
Default value "500,2000"
Name sdInitialInventoryDistrParams
Type String
Default value "120,3600"
Name sdShiftStartRange
Type String
Default value "6,7"
Modificator Global
Name sdShiftEndRange
Type String
Default value "22,22"
Modificator Global
Name sdProbChangeLocationRange
Type String
Default value "0.1,0.1"
Name sdDealDelayRange
Type String
Default value "15,15"
Name param6
Modificator Separator
Name pdInitialMoneyDistrParams
Type String
Default value "2000,10000"
Name pdInitialInventoryDistrParams
Type String
Default value "1200,3600"
Name pdShiftStartHourRange
Type String
Default value "6,10"
Name pdShiftLengthRange
Type String
Default value "8,16"
Name pdAcceptableDeliverUnitsRange
Type String
Default value "120,120"
Name pdProbDeliverHomeRange
Type String
Default value "0.5,0.5"
Name pdProbDeliverMarketRange
Type String
Default value "0.1,0.1"
Name pdDealQuitTime
Type real
Default value 4
Modificator Global
Name pdDealDelayRange
Type String
Default value "30,30"
Name pdInvitationDealsRange
Type String
Default value "6,6"
Name param8
Modificator Separator
Name poVisionRadius
Type real
Default value 100
Modificator Global
Name poProbHarassCustomer
Type real
Default value 0.25
Modificator Global
Name poProbHarassStreetBroker
Type real
Default value 0.5
Modificator Global
Name poProbHarassStreetDealer
Type real
Default value 0.75
Modificator Global
Name poProbHarassPrivateDealer
Type real
Default value 0.25
Modificator Global
Name poProbHarassHomeless
Type real
Default value 0.5
Modificator Global
Name poHarassDuration
Type real
Default value 60
Modificator Global
Name poHarassRepeatDelay
Type real
Default value 24
Modificator Global
Name poProbArrestCustomer
Type real
Default value 0.8
Modificator Global
Name poProbArrestStreetBroker
Type real
Default value 0.8
Modificator Global
Name poProbArrestStreetDealer
Type real
Default value 0.9
Modificator Global
Name poProbArrestPrivateDealer
Type real
Default value 0.1
Modificator Global
Name poProbArrestHomeless
Type real
Default value 0.8
Modificator Global
Name poArrestDuration
Type real
Default value 60
Modificator Global
Name poJailPerDrugUnit
Type real
Default value 0.05
Modificator Global
Name poUseBusts
Type boolean
Default value false
Modificator Global
Name poBustStartTimes
Type String
Default value "30,60,90,120,150"
Modificator Global
Name poBustDuration
Type real
Default value 24
Modificator Global
Name poBustOfficers
Type integer
Default value 30
Modificator Global
Name poBustVisionRadius
Type real
Default value 300
Modificator Global
Name poBustProbHarassCustomer
Type real
Default value 0.50
Modificator Global
Name poBustProbHarassStreetBroker
Type real
Default value 0.75
Modificator Global
Name poBustProbHarassStreetDealer
Type real
Default value 1
Modificator Global
Name poBustProbHarassPrivateDealer
Type real
Default value 0.50
Modificator Global
Name poBustProbHarassHomeless
Type real
Default value 0.75
Modificator Global
Name poBustProbArrestCustomer
Type real
Default value 1
Modificator Global
Name poBustProbArrestStreetBroker
Type real
Default value 1
Modificator Global
Name poBustProbArrestStreetDealer
Type real
Default value 1
Modificator Global
Name poBustProbArrestPrivateDealer
Type real
Default value 0.1
Modificator Global
Name poBustProbArrestHomeless
Type real
Default value 1
Modificator Global

Icon
Picture

Structure
Picture
ChartTimer outfileStreetBrokerTimer
Expire No (manual mode)
Expiry Action // We set this timer's expiry to manual mode so that we can control
// precisely when it expires. This is useful mainly at the model
// startup. If we set the expiry to cyclic mode expiring at startup,
// then AnyLogic will expire the timer for us just before running our
// Main startup code. This is a problem since our Main startup code:
// 1) modifies variables that are used in defining the expiration time
// 2) creates agents that are accessed in the action code below


// Write to the outfile only if requested and only if we are not in
// applet mode (since applets can't write to files).

if (outfileStreetBrokerCreate && !util.Model.getRunMode().equals("applet")) {
  // Note that we end lines in the outfile with an explicit "\r\n"
  // instead of using the writeln() method to do it for us. This is
  // because writeln() chooses the line terminator appropriate to the
  // platform, meaning it will use "\r\n" on Windows and "\n" on Linux.
  // However, we want the outfile to have the same line terminators
  // whether we run the model on our Windows PCs or on the Linux cluster.


  //-----------------------------------------------------------------
  // Open the outfile at the beginning of each replication.

  if (Engine.getTime() == 0) {

    // Append to the outfile if either of the following conditions
    // is true:
    //
    // 1) The append parameter is true.
    // 2) This is not the first replication in this simulation.
    //
    // If either is false, we will overwrite the outfile. Whenever
    // we overwrite the outfile, we will add column headers.

    if (outfileStreetBrokerAppend || Engine.getReplication() > 1) {
      // appending

      try {
        outfileStreetBrokerWriter = new BufferedWriter(new FileWriter(outfileStreetBrokerName, true));
      }
      catch (IOException e) {
        traceln("***ERROR in outfileStreetBrokerTimer.action: Cannot open outfile in append mode: " + e.getMessage());
      }
    }
    else {
      // overwriting

      try {
        outfileStreetBrokerWriter = new BufferedWriter(new FileWriter(outfileStreetBrokerName));
      }
      catch (IOException e) {
        traceln("***ERROR in outfileStreetBrokerTimer.action: Cannot open outfile in overwrite mode: " + e.getMessage());
      }

      try {
        outfileStreetBrokerWriter.write(
                   "replication"
          + "\t" + "modelMinutes"
          + "\t" + "id"
          + "\t" + "money"
          + "\t" + "inventory"
          + "\t" + "addiction"
          + "\t" + "concentration"
          + "\t" + "numUses"
          + "\t" + "allTransactionsGood"
          + "\t" + "sdTransactionsGood"
          + "\t" + "pdTransactionsGood"
          + "\r\n"
          );
      }
      catch (IOException e) {
        traceln("***ERROR in outfileStreetBrokerTimer.action: Cannot write headers to outfile: " + e.getMessage());
      }
    }
  }

  //-----------------------------------------------------------------
  // Write a data record to the outfile at every expiration.

  try {
    for (int id = 0; id < streetBroker.size(); id++) {
      StreetBroker sb = streetBroker.item(id);

      outfileStreetBrokerWriter.write(
                 Engine.getReplication()
        + "\t" + util.Format.decimalFormat(Engine.getTime() / util.Units.minutes(1), 0)
        + "\t" + id
        + "\t" + util.Format.decimalFormat(sb.money, 0)
        + "\t" + util.Format.decimalFormat(sb.userBehavior.inventory, 0)
        + "\t" + util.Format.decimalFormat(sb.userBehavior.addiction, 2)
        + "\t" + util.Format.decimalFormat(sb.userBehavior.concentration, 2)
        + "\t" + sb.userBehavior.numUses
        + "\t" + sb.allTransactions.numGood
        + "\t" + sb.sdTransactions.numGood
        + "\t" + sb.pdTransactions.numGood
        + "\r\n"
        );
    }
  }
  catch (IOException e) {
    traceln("***ERROR in outfileStreetBrokerTimer.action: Cannot write data to outfile: " + e.getMessage());
  }


  //-----------------------------------------------------------------
  // Set the timer to expire at the end of the next interval.

  restart(outfileStreetBrokerInterval);


  //-----------------------------------------------------------------
  // Close the outfile at the end of the replication.
  // This is performed in the replicationRunTimer code.
}
ChartTimer outfileCustomerTimer
Expire No (manual mode)
Expiry Action // We set this timer's expiry to manual mode so that we can control
// precisely when it expires. This is useful mainly at the model
// startup. If we set the expiry to cyclic mode expiring at startup,
// then AnyLogic will expire the timer for us just before running our
// Main startup code. This is a problem since our Main startup code:
// 1) modifies variables that are used in defining the expiration time
// 2) creates agents that are accessed in the action code below


// Write to the outfile only if requested and only if we are not in
// applet mode (since applets can't write to files).

if (outfileCustomerCreate && !util.Model.getRunMode().equals("applet")) {
  // Note that we end lines in the outfile with an explicit "\r\n"
  // instead of using the writeln() method to do it for us. This is
  // because writeln() chooses the line terminator appropriate to the
  // platform, meaning it will use "\r\n" on Windows and "\n" on Linux.
  // However, we want the outfile to have the same line terminators
  // whether we run the model on our Windows PCs or on the Linux cluster.


  //-----------------------------------------------------------------
  // Open the outfile at the beginning of each replication.

  if (Engine.getTime() == 0) {

    // Append to the outfile if either of the following conditions
    // is true:
    //
    // 1) The append parameter is true.
    // 2) This is not the first replication in this simulation.
    //
    // If either is false, we will overwrite the outfile. Whenever
    // we overwrite the outfile, we will add column headers.

    if (outfileCustomerAppend || Engine.getReplication() > 1) {
      // appending

      try {
        outfileCustomerWriter = new BufferedWriter(new FileWriter(outfileCustomerName, true));
      }
      catch (IOException e) {
        traceln("***ERROR in outfileCustomerTimer.action: Cannot open outfile in append mode: " + e.getMessage());
      }
    }
    else {
      // overwriting

      try {
        outfileCustomerWriter = new BufferedWriter(new FileWriter(outfileCustomerName));
      }
      catch (IOException e) {
        traceln("***ERROR in outfileCustomerTimer.action: Cannot open outfile in overwrite mode: " + e.getMessage());
      }

      try {
        outfileCustomerWriter.write(
                   "replication"
          + "\t" + "modelMinutes"
          + "\t" + "id"
          + "\t" + "money"
          + "\t" + "inventory"
          + "\t" + "addiction"
          + "\t" + "concentration"
          + "\t" + "numUses"
          + "\t" + "allTransactionsGood"
          + "\t" + "sbTransactionsGood"
          + "\t" + "sdTransactionsGood"
          + "\t" + "pdTransactionsGood"
          + "\t" + "marketTripsGood"
          + "\r\n"
          );
      }
      catch (IOException e) {
        traceln("***ERROR in outfileCustomerTimer.action: Cannot write headers to outfile: " + e.getMessage());
      }
    }
  }

  //-----------------------------------------------------------------
  // Write a data record to the outfile at every expiration.

  try {
    for (int id = 0; id < customer.size(); id++) {
      Customer cs = customer.item(id);

      outfileCustomerWriter.write(
                 Engine.getReplication()
        + "\t" + util.Format.decimalFormat(Engine.getTime() / util.Units.minutes(1), 0)
        + "\t" + id
        + "\t" + util.Format.decimalFormat(cs.money, 0)
        + "\t" + util.Format.decimalFormat(cs.userBehavior.inventory, 0)
        + "\t" + util.Format.decimalFormat(cs.userBehavior.addiction, 2)
        + "\t" + util.Format.decimalFormat(cs.userBehavior.concentration, 2)
        + "\t" + cs.userBehavior.numUses
        + "\t" + cs.allTransactions.numGood
        + "\t" + cs.sbTransactions.numGood
        + "\t" + cs.sdTransactions.numGood
        + "\t" + cs.pdTransactions.numGood
        + "\t" + cs.marketTrips.numGood
        + "\r\n"
        );
    }
  }
  catch (IOException e) {
    traceln("***ERROR in outfileCustomerTimer.action: Cannot write data to outfile: " + e.getMessage());
  }


  //-----------------------------------------------------------------
  // Set the timer to expire at the end of the next interval.

  restart(outfileCustomerInterval);


  //-----------------------------------------------------------------
  // Close the outfile at the end of the replication.
  // This is performed in the replicationRunTimer code.
}
ChartTimer replicationRunTimer
Expire No (manual mode)
Expiry Action // We set this timer's expiry to manual mode so that we can control
// precisely when it expires. This is useful mainly at the model
// startup. If we set the expiry to 'expire once' mode, then AnyLogic
// will expire the timer for us just before running our Main startup code.
// This is a problem since our Main startup code modifies variables that
// are used in defining the expiration time.


// Close any open outfiles.

if (outfileSummaryWriter != null) {
  try {
    outfileSummaryWriter.close();
  }
  catch (IOException e) {
    traceln("***ERROR in replicationRunTimer.action: Cannot close outfileSummaryWriter: " + e.getMessage());
  }
}

if (outfileCustomerWriter != null) {
  try {
    outfileCustomerWriter.close();
  }
  catch (IOException e) {
    traceln("***ERROR in replicationRunTimer.action: Cannot close outfileCustomerWriter: " + e.getMessage());
  }
}

if (outfileStreetBrokerWriter != null) {
  try {
    outfileStreetBrokerWriter.close();
  }
  catch (IOException e) {
    traceln("***ERROR in replicationRunTimer.action: Cannot close outfileStreetBrokerWriter: " + e.getMessage());
  }
}


// Call the next replication.

Engine.nextReplication();
ChartTimer outfileSummaryTimer
Expire No (manual mode)
Expiry Action // We set this timer's expiry to manual mode so that we can control
// precisely when it expires. This is useful mainly at the model
// startup. If we set the expiry to cyclic mode expiring at startup,
// then AnyLogic will expire the timer for us just before running our
// Main startup code. This is a problem since our Main startup code:
// 1) modifies variables that are used in defining the expiration time
// 2) creates agents that are accessed in the action code below


// Write to the outfile only if requested and only if we are not in
// applet mode (since applets can't write to files).

if (outfileSummaryCreate && !util.Model.getRunMode().equals("applet")) {
  // Note that we end lines in the outfile with an explicit "\r\n"
  // instead of using the writeln() method to do it for us. This is
  // because writeln() chooses the line terminator appropriate to the
  // platform, meaning it will use "\r\n" on Windows and "\n" on Linux.
  // However, we want the outfile to have the same line terminators
  // whether we run the model on our Windows PCs or on the Linux cluster.


  //-----------------------------------------------------------------
  // Open the outfile at the beginning of each replication.

  if (Engine.getTime() == 0) {

    // Append to the outfile if either of the following conditions
    // is true:
    //
    // 1) The append parameter is true.
    // 2) This is not the first replication in this simulation.
    //
    // If either is false, we will overwrite the outfile. Whenever
    // we overwrite the outfile, we will add column headers.

    if (outfileSummaryAppend || Engine.getReplication() > 1) {
      // appending

      try {
        outfileSummaryWriter = new BufferedWriter(new FileWriter(outfileSummaryName, true));
      }
      catch (IOException e) {
        traceln("***ERROR in outfileSummaryTimer.action: Cannot open outfile in append mode: " + e.getMessage());
      }
    }
    else {
      // overwriting

      try {
        outfileSummaryWriter = new BufferedWriter(new FileWriter(outfileSummaryName));
      }
      catch (IOException e) {
        traceln("***ERROR in outfileSummaryTimer.action: Cannot open outfile in overwrite mode: " + e.getMessage());
      }

      try {
        outfileSummaryWriter.write(
                   "replication"
          + "\t" + "modelMinutes"
          + "\t" + "csAvgMoney"
          + "\t" + "csAvgInventory"
          + "\t" + "csAvgAddiction"
          + "\t" + "csAvgConcentration"
          + "\t" + "csAllTransactionsGood"
          + "\t" + "csAllTransactionsUnits"
          + "\t" + "csAllTransactionsPrice"
          + "\t" + "csAllTransactionsFailed"
          + "\t" + "csSbTransactionsGood"
          + "\t" + "csSbTransactionsUnits"
          + "\t" + "csSbTransactionsPrice"
          + "\t" + "csSbTransactionsFailed"
          + "\t" + "csSdTransactionsGood"
          + "\t" + "csSdTransactionsUnits"
          + "\t" + "csSdTransactionsPrice"
          + "\t" + "csSdTransactionsFailed"
          + "\t" + "csPdTransactionsGood"
          + "\t" + "csPdTransactionsUnits"
          + "\t" + "csPdTransactionsPrice"
          + "\t" + "csPdTransactionsFailed"
          + "\t" + "csMarketTripsGood"
          + "\t" + "csMarketTripsUnits"
          + "\t" + "csMarketTripsPrice"
          + "\t" + "csMarketTripsFailed"
          + "\t" + "csNumTreatment"
          + "\t" + "csNumJail"
          + "\t" + "csNumHiv"
          + "\t" + "sbAvgMoney"
          + "\t" + "sbAvgInventory"
          + "\t" + "sbAvgAddiction"
          + "\t" + "sbAvgConcentration"
          + "\t" + "sbAllTransactionsGood"
          + "\t" + "sbAllTransactionsUnits"
          + "\t" + "sbAllTransactionsPrice"
          + "\t" + "sbAllTransactionsFailed"
          + "\t" + "sbSdTransactionsGood"
          + "\t" + "sbSdTransactionsUnits"
          + "\t" + "sbSdTransactionsPrice"
          + "\t" + "sbSdTransactionsFailed"
          + "\t" + "sbPdTransactionsGood"
          + "\t" + "sbPdTransactionsUnits"
          + "\t" + "sbPdTransactionsPrice"
          + "\t" + "sbPdTransactionsFailed"
          + "\t" + "sbNumJail"
          + "\t" + "sbNumHiv"
          + "\t" + "sdNumJail"
          + "\t" + "pdNumJail"
          + "\t" + "poNumHarassments"
          + "\t" + "poNumArrests"
          + "\r\n"
          );
      }
      catch (IOException e) {
        traceln("***ERROR in outfileSummaryTimer.action: Cannot write headers to outfile: " + e.getMessage());
      }
    }
  }

  //-----------------------------------------------------------------
  // Write a data record to the outfile at every expiration.

  // Aggregate values across all customers.

  int csNumTreatment = 0;
  int csNumJail = 0;
  int csNumHiv = 0;
  double csTotInventory = 0;
  double csTotAddiction = 0;
  double csTotConcentration = 0;

  for (int i = 0; i < customer.size(); i++) {
    if (customer.item(i).currentPlace == Main.PLACE_TREATMENT) {
      csNumTreatment++;
    }
    else if (customer.item(i).currentPlace == Main.PLACE_JAIL) {
      csNumJail++;
    }

    if (customer.item(i).hasHIV()) {
      csNumHiv++;
    }

    csTotInventory += customer.item(i).userBehavior.inventory;
    csTotAddiction += customer.item(i).userBehavior.addiction;
    csTotConcentration += customer.item(i).userBehavior.concentration;
  }

  double csAvgInventory = (customer.size() == 0 ? 0 : csTotInventory / customer.size());
  double csAvgAddiction = (customer.size() == 0 ? 0 : csTotAddiction / customer.size());
  double csAvgConcentration = (customer.size() == 0 ? 0 : csTotConcentration / customer.size());

  // Aggregate values across all street brokers.

  int sbNumJail = 0;
  int sbNumHiv = 0;
  double sbTotInventory = 0;
  double sbTotAddiction = 0;
  double sbTotConcentration = 0;

  for (int i = 0; i < streetBroker.size(); i++) {
    if (streetBroker.item(i).currentPlace == Main.PLACE_JAIL) {
      sbNumJail++;
    }

    if (streetBroker.item(i).hasHIV()) {
      sbNumHiv++;
    }

    sbTotInventory += streetBroker.item(i).userBehavior.inventory;
    sbTotAddiction += streetBroker.item(i).userBehavior.addiction;
    sbTotConcentration += streetBroker.item(i).userBehavior.concentration;
  }

  double sbAvgInventory = (streetBroker.size() == 0 ? 0 : sbTotInventory / streetBroker.size());
  double sbAvgAddiction = (streetBroker.size() == 0 ? 0 : sbTotAddiction / streetBroker.size());
  double sbAvgConcentration = (streetBroker.size() == 0 ? 0 : sbTotConcentration / streetBroker.size());

  // Aggregate values across all street dealers.

  int sdNumJail = 0;

  for (int i = 0; i < streetDealer.size(); i++) {
    if (streetDealer.item(i).currentPlace == Main.PLACE_JAIL) {
      sdNumJail++;
    }
  }

  // Aggregate values across all private dealers.

  int pdNumJail = 0;

  for (int i = 0; i < privateDealer.size(); i++) {
    if (privateDealer.item(i).currentPlace == Main.PLACE_JAIL) {
      pdNumJail++;
    }
  }

  // Write the data record.

  try {
    outfileSummaryWriter.write(
               Engine.getReplication()
      + "\t" + util.Format.decimalFormat(Engine.getTime() / util.Units.minutes(1), 0)
      + "\t" + util.Format.decimalFormat(customer.average("money"), 2)
      + "\t" + util.Format.decimalFormat(csAvgInventory, 2)
      + "\t" + util.Format.decimalFormat(csAvgAddiction, 2)
      + "\t" + util.Format.decimalFormat(csAvgConcentration, 2)
      + "\t" + csAllTransactions.numGood
      + "\t" + util.Format.decimalFormat(csAllTransactions.totalUnits, 0)
      + "\t" + util.Format.decimalFormat(csAllTransactions.totalPrice, 0)
      + "\t" + csAllTransactions.numFailed
      + "\t" + csSbTransactions.numGood
      + "\t" + util.Format.decimalFormat(csSbTransactions.totalUnits, 0)
      + "\t" + util.Format.decimalFormat(csSbTransactions.totalPrice, 0)
      + "\t" + csSbTransactions.numFailed
      + "\t" + csSdTransactions.numGood
      + "\t" + util.Format.decimalFormat(csSdTransactions.totalUnits, 0)
      + "\t" + util.Format.decimalFormat(csSdTransactions.totalPrice, 0)
      + "\t" + csSdTransactions.numFailed
      + "\t" + csPdTransactions.numGood
      + "\t" + util.Format.decimalFormat(csPdTransactions.totalUnits, 0)
      + "\t" + util.Format.decimalFormat(csPdTransactions.totalPrice, 0)
      + "\t" + csPdTransactions.numFailed
      + "\t" + csMarketTrips.numGood
      + "\t" + util.Format.decimalFormat(csMarketTrips.totalUnits, 0)
      + "\t" + util.Format.decimalFormat(csMarketTrips.totalPrice, 0)
      + "\t" + csMarketTrips.numFailed
      + "\t" + csNumTreatment
      + "\t" + csNumJail
      + "\t" + csNumHiv
      + "\t" + util.Format.decimalFormat(streetBroker.average("money"), 2)
      + "\t" + util.Format.decimalFormat(sbAvgInventory, 2)
      + "\t" + util.Format.decimalFormat(sbAvgAddiction, 2)
      + "\t" + util.Format.decimalFormat(sbAvgConcentration, 2)
      + "\t" + sbAllTransactions.numGood
      + "\t" + util.Format.decimalFormat(sbAllTransactions.totalUnits, 0)
      + "\t" + util.Format.decimalFormat(sbAllTransactions.totalPrice, 0)
      + "\t" + sbAllTransactions.numFailed
      + "\t" + sbSdTransactions.numGood
      + "\t" + util.Format.decimalFormat(sbSdTransactions.totalUnits, 0)
      + "\t" + util.Format.decimalFormat(sbSdTransactions.totalPrice, 0)
      + "\t" + sbSdTransactions.numFailed
      + "\t" + sbPdTransactions.numGood
      + "\t" + util.Format.decimalFormat(sbPdTransactions.totalUnits, 0)
      + "\t" + util.Format.decimalFormat(sbPdTransactions.totalPrice, 0)
      + "\t" + sbPdTransactions.numFailed
      + "\t" + sbNumJail
      + "\t" + sbNumHiv
      + "\t" + sdNumJail
      + "\t" + pdNumJail
      + "\t" + util.Format.decimalFormat(police.sum("numHarassments"), 0)
      + "\t" + util.Format.decimalFormat(police.sum("numArrests"), 0)
      + "\r\n"
      );
  }
  catch (IOException e) {
    traceln("***ERROR in outfileSummaryTimer.action: Cannot write data to outfile: " + e.getMessage());
  }


  //-----------------------------------------------------------------
  // Set the timer to expire at the end of the next interval.

  restart(outfileSummaryInterval);


  //-----------------------------------------------------------------
  // Close the outfile at the end of the replication.
  // This is performed in the replicationRunTimer code.
}
Variable outfileStreetBrokerWriter
Variable type BufferedWriter
Initial value null
Variable dealCounter
Variable type integer
Initial value 0
Variable outfileCustomerWriter
Variable type BufferedWriter
Initial value null
Variable outfileSummaryWriter
Variable type BufferedWriter
Initial value null
Object sbSdTransactions
Type misc.TransactionCounter
Object sbPdTransactions
Type misc.TransactionCounter
Object sbAllTransactions
Type misc.TransactionCounter
Object csSbTransactions
Type misc.TransactionCounter
Object csSdTransactions
Type misc.TransactionCounter
Object csPdTransactions
Type misc.TransactionCounter
Object csAllTransactions
Type misc.TransactionCounter
Object customer
Type drugmarket.Customer
Number of objects 0
Object market
Type drugmarket.Market
Number of objects 0
Object streetBroker
Type drugmarket.StreetBroker
Number of objects 0
Object privateDealer
Type drugmarket.PrivateDealer
Number of objects 0
Object csMarketTrips
Type misc.TransactionCounter
Object police
Type drugmarket.Police
Number of objects 0
Object homeless
Type drugmarket.Homeless
Number of objects 0
Object marketBustTimers
Type misc.MarketBustTimer
Number of objects 0
Object streetDealerGroup
Type drugmarket.StreetDealerGroup
Number of objects 0
Object streetDealer
Type drugmarket.StreetDealer
Number of objects 0

Algorithmic Functions
Name executionControl
Type void
Body // Override the ActiveObject.executionControl() function to allow
// us to run multiple replications in a single simulation.

// Engine.getReplication() returns the current replication
// number (starting with 1) *during* the replication. However,
// we are calling it here *before* the replication. In this
// case it returns the previous replication number. If we have
// not yet run any replications, it returns 0.

while (Engine.getReplication() < numReplications) {
  // Run one replication of the model.
  Engine.execute();
}
Name getNewDealID
Type integer
Body if (dealCounter < Integer.MAX_VALUE) {
  dealCounter++;
}
else {
  traceln("***WARNING in Main.getNewDealID(): Reached maximum dealID, resetting counter to 1 @ " + Engine.getTime());
  dealCounter = 1;
}

return dealCounter;
Name initializeAgents
Type void
Body // We manually initialize all the encapsulated objects because we need more
// control over the order of this process and more flexibility than we would
// have if we let AnyLogic do it for us.


//-------------------------------------------------------------------------
// Initialize the markets. This must be done prior to initializing
// any of the agents that need a reference to a market.

for (int i = 0; i < numInitialMarkets; i++) {
  Market mk = new Market();

  mk.width = uniform(util.Params.getDouble(marketSizeRange, 0), util.Params.getDouble(marketSizeRange, 1));
  mk.height = uniform(util.Params.getDouble(marketSizeRange, 0), util.Params.getDouble(marketSizeRange, 1));

  // Find a position in the city for the market. Make sure it is
  // fully contained within the city, and make sure it does not
  // overlap any other markets.

  boolean foundLocation = false;
  int numLocationTries = 0;

  while (!foundLocation && numLocationTries < 20) {
    mk.x = uniform(cityWidth);
    mk.y = uniform(cityHeight);
    numLocationTries++;

    boolean validMarketLocation = true;

    if (!mk.isContainedInRectangle(0, 0, cityWidth, cityHeight)) {
      validMarketLocation = false;
    }

    for (int j = 0; j < i; j++) {
      if (mk.isOverlappingMarket(market.item(j))) {
        validMarketLocation = false;
      }
    }

    foundLocation = validMarketLocation;
  }

  setup_market(mk, market.size());
}


//-------------------------------------------------------------------------
// Initialize customers.

for (int i = 0; i < numInitialCustomers; i++) {
  Customer cs = new Customer();

  cs.homeX = uniform(cityWidth);
  cs.homeY = uniform(cityHeight);

  cs.initialMoney = exponential(1/util.Params.getDouble(csInitialMoneyDistrParams, 0));
  cs.initialInventory = util.Math.round(uniform(util.Params.getDouble(csInitialInventoryDistrParams, 0), util.Params.getDouble(csInitialInventoryDistrParams, 1)), useIncrement);
  cs.initialAddiction = max(useIncrement, normal(util.Params.getDouble(csInitialAddictionDistrParams, 1), util.Params.getDouble(csInitialAddictionDistrParams, 0)));
  cs.initialConcentration = max(0, normal(util.Params.getDouble(csInitialConcentrationDistrParams, 1), util.Params.getDouble(csInitialConcentrationDistrParams, 0)));
  cs.initialTimeSinceLastUse = uniform(util.Params.getDouble(csInitialTimeSinceLastUseDistrParams, 0), util.Params.getDouble(csInitialTimeSinceLastUseDistrParams, 1));

  cs.metabolismRate = uniform(util.Params.getDouble(metabolismRateRange, 0), util.Params.getDouble(metabolismRateRange, 1));
  cs.addictionIncreaseDuration = uniform(util.Params.getDouble(addictionIncreaseDurationRange, 0), util.Params.getDouble(addictionIncreaseDurationRange, 1));
  cs.addictionIncreaseRate = uniform(util.Params.getDouble(addictionIncreaseRateRange, 0), util.Params.getDouble(addictionIncreaseRateRange, 1));
  cs.addictionDecreaseOnset = uniform(util.Params.getDouble(addictionDecreaseOnsetRange, 0), util.Params.getDouble(addictionDecreaseOnsetRange, 1));
  cs.addictionDecreaseRate = uniform(util.Params.getDouble(addictionDecreaseRateRange, 0), util.Params.getDouble(addictionDecreaseRateRange, 1));
  cs.desperateNeedThreshold = uniform(util.Params.getDouble(desperateNeedThresholdRange, 0), util.Params.getDouble(desperateNeedThresholdRange, 1));
  cs.desperateAddictionThreshold = uniform(util.Params.getDouble(desperateAddictionThresholdRange, 0), util.Params.getDouble(desperateAddictionThresholdRange, 1));
  cs.useDelayMin = util.Params.getDouble(useDelayRange, 0);
  cs.useDelayMax = util.Params.getDouble(useDelayRange, 1);

  if (randomTrue(csProbIncomeIntervalWeekly)) {
    cs.incomeInterval = util.Units.days(7);
    cs.incomeAmount = 7 * uniform(util.Params.getDouble(csIncomeAmountDailyRange, 0), util.Params.getDouble(csIncomeAmountDailyRange, 1));
  }
  else if (randomTrue(csProbIncomeIntervalMonthly)) {
    cs.incomeInterval = util.Units.days(30);
    cs.incomeAmount = 30 * uniform(util.Params.getDouble(csIncomeAmountDailyRange, 0), util.Params.getDouble(csIncomeAmountDailyRange, 1));
  }
  else {
    cs.incomeInterval = util.Units.days(15);
    cs.incomeAmount = 15 * uniform(util.Params.getDouble(csIncomeAmountDailyRange, 0), util.Params.getDouble(csIncomeAmountDailyRange, 1));
  }

  cs.windfallAmountMin = util.Params.getDouble(csWindfallAmountRange, 0);
  cs.windfallAmountMax = util.Params.getDouble(csWindfallAmountRange, 1);
  cs.windfallIntervalMin = util.Params.getDouble(csWindfallIntervalRange, 0);
  cs.windfallIntervalMax = util.Params.getDouble(csWindfallIntervalRange, 1);

  cs.buyMethod = randomTrue(csProbBuyMethodAfford) ? "afford" : "need";

  cs.randomUseIntervalMin = util.Params.getDouble(csRandomUseIntervalRange, 0);
  cs.randomUseIntervalMax = util.Params.getDouble(csRandomUseIntervalRange, 1);

  cs.retryDelayMin = util.Params.getDouble(csRetryDelayRange, 0);
  cs.retryDelayMax = util.Params.getDouble(csRetryDelayRange, 1);

  cs.probUseAtSeller = uniform(util.Params.getDouble(csProbUseAtSellerRange, 0), util.Params.getDouble(csProbUseAtSellerRange, 1));
  cs.probUseTogether = uniform(util.Params.getDouble(csProbUseTogetherRange, 0), util.Params.getDouble(csProbUseTogetherRange, 1));
  cs.initialHIV = randomTrue(csProbInitialHIV);

  cs.probSeekMoney = uniform(util.Params.getDouble(csProbSeekMoneyRange, 0), util.Params.getDouble(csProbSeekMoneyRange, 1));
  cs.probSeekMoneySuccess = uniform(util.Params.getDouble(csProbSeekMoneySuccessRange, 0), util.Params.getDouble(csProbSeekMoneySuccessRange, 1));
  cs.seekMoneyDelayMin = util.Params.getDouble(csSeekMoneyDelayRange, 0);
  cs.seekMoneyDelayMax = util.Params.getDouble(csSeekMoneyDelayRange, 1);

  cs.visionRadius = uniform(util.Params.getDouble(visionRadiusRange, 0), util.Params.getDouble(visionRadiusRange, 1));
  cs.marketQuitTime = uniform(util.Params.getDouble(csMarketQuitTimeRange, 0), util.Params.getDouble(csMarketQuitTimeRange, 1));

  cs.probTryPrivateDealer = uniform(util.Params.getDouble(csProbTryPrivateDealerRange, 0), util.Params.getDouble(csProbTryPrivateDealerRange, 1));

  cs.acceptableSdQueueLength = uniform_discr(util.Params.getInt(csAcceptableSdQueueLengthRange, 0), util.Params.getInt(csAcceptableSdQueueLengthRange, 1));
  cs.acceptablePdQueueLength = uniform_discr(util.Params.getInt(csAcceptablePdQueueLengthRange, 0), util.Params.getInt(csAcceptablePdQueueLengthRange, 1));

  cs.treatmentDelayMin = util.Params.getDouble(treatmentDelayRange, 0);
  cs.treatmentDelayMax = util.Params.getDouble(treatmentDelayRange, 1);
  cs.treatmentAddictionReduction = uniform(util.Params.getDouble(treatmentAddictionReductionRange, 0), util.Params.getDouble(treatmentAddictionReductionRange, 1));

  setup_customer(cs, customer.size());
}


//-------------------------------------------------------------------------
// Initialize the street dealer group.

StreetDealerGroup sdg = new StreetDealerGroup();
sdg.initialMarket = market.random();
setup_streetDealerGroup(sdg, streetDealerGroup.size());


//-------------------------------------------------------------------------
// Initialize street dealers.

for (int i = 0; i < numInitialStreetDealers; i++) {
  StreetDealer sd = new StreetDealer();

  sd.initialGroup = streetDealerGroup.random();
  sd.initialMoney = util.Math.round(uniform(util.Params.getDouble(sdInitialMoneyDistrParams, 0), util.Params.getDouble(sdInitialMoneyDistrParams, 1)), useIncrement);
  sd.initialInventory = util.Math.round(uniform(util.Params.getDouble(sdInitialInventoryDistrParams, 0), util.Params.getDouble(sdInitialInventoryDistrParams, 1)), useIncrement);
  sd.probChangeLocation = uniform(util.Params.getDouble(sdProbChangeLocationRange, 0), util.Params.getDouble(sdProbChangeLocationRange, 1));
  sd.dealDelayMin = util.Params.getDouble(sdDealDelayRange, 0);
  sd.dealDelayMax = util.Params.getDouble(sdDealDelayRange, 1);
  sd.visionRadius = uniform(util.Params.getDouble(visionRadiusRange, 0), util.Params.getDouble(visionRadiusRange, 1));

  setup_streetDealer(sd, streetDealer.size());
}


//-------------------------------------------------------------------------
// Initialize street brokers.

for (int i = 0; i < numInitialStreetBrokers; i++) {
  StreetBroker sb = new StreetBroker();

  sb.initialMarket = market.random();

  sb.initialAddiction = uniform(util.Params.getDouble(sbInitialAddictionDistrParams, 0), util.Params.getDouble(sbInitialAddictionDistrParams, 1));
  sb.initialConcentration = uniform(util.Params.getDouble(sbInitialConcentrationDistrParams, 0), util.Params.getDouble(sbInitialConcentrationDistrParams, 1));
  sb.initialTimeSinceLastUse = uniform(util.Params.getDouble(sbInitialTimeSinceLastUseDistrParams, 0), util.Params.getDouble(sbInitialTimeSinceLastUseDistrParams, 1));

  sb.metabolismRate = uniform(util.Params.getDouble(metabolismRateRange, 0), util.Params.getDouble(metabolismRateRange, 1));
  sb.addictionIncreaseDuration = uniform(util.Params.getDouble(addictionIncreaseDurationRange, 0), util.Params.getDouble(addictionIncreaseDurationRange, 1));
  sb.addictionIncreaseRate = uniform(util.Params.getDouble(addictionIncreaseRateRange, 0), util.Params.getDouble(addictionIncreaseRateRange, 1));
  sb.addictionDecreaseOnset = uniform(util.Params.getDouble(addictionDecreaseOnsetRange, 0), util.Params.getDouble(addictionDecreaseOnsetRange, 1));
  sb.addictionDecreaseRate = uniform(util.Params.getDouble(addictionDecreaseRateRange, 0), util.Params.getDouble(addictionDecreaseRateRange, 1));
  sb.useDelayMin = util.Params.getDouble(useDelayRange, 0);
  sb.useDelayMax = util.Params.getDouble(useDelayRange, 1);

  sb.visionRadius = uniform(util.Params.getDouble(visionRadiusRange, 0), util.Params.getDouble(visionRadiusRange, 1));
  sb.marketQuitTime = uniform(util.Params.getDouble(sbMarketQuitTimeRange, 0), util.Params.getDouble(sbMarketQuitTimeRange, 1));

  sb.probTryPrivateDealer = uniform(util.Params.getDouble(sbProbTryPrivateDealerRange, 0), util.Params.getDouble(sbProbTryPrivateDealerRange, 1));

  sb.acceptableSdQueueLength = uniform_discr(util.Params.getInt(sbAcceptableSdQueueLengthRange, 0), util.Params.getInt(csAcceptableSdQueueLengthRange, 1));
  sb.acceptablePdQueueLength = uniform_discr(util.Params.getInt(sbAcceptablePdQueueLengthRange, 0), util.Params.getInt(csAcceptablePdQueueLengthRange, 1));

  sb.probUseTogether = uniform(util.Params.getDouble(sbProbUseTogetherRange, 0), util.Params.getDouble(sbProbUseTogetherRange, 1));
  sb.initialHIV = randomTrue(sbProbInitialHIV);

  setup_streetBroker(sb, streetBroker.size());
}


//-------------------------------------------------------------------------
// Initialize private dealers.

for (int i = 0; i < numInitialPrivateDealers; i++) {
  PrivateDealer pd = new PrivateDealer();

  pd.homeX = uniform(cityWidth);
  pd.homeY = uniform(cityHeight);

  pd.initialMoney = uniform(util.Params.getDouble(pdInitialMoneyDistrParams, 0), util.Params.getDouble(pdInitialMoneyDistrParams, 1));
  pd.initialInventory = util.Math.round(uniform(util.Params.getDouble(pdInitialInventoryDistrParams, 0), util.Params.getDouble(pdInitialInventoryDistrParams, 1)), useIncrement);
  pd.shiftStartHourMin = util.Params.getDouble(pdShiftStartHourRange, 0);
  pd.shiftStartHourMax = util.Params.getDouble(pdShiftStartHourRange, 1);
  pd.shiftLengthMin = util.Params.getDouble(pdShiftLengthRange, 0);
  pd.shiftLengthMax = util.Params.getDouble(pdShiftLengthRange, 1);
  pd.dealDelayMin = util.Params.getDouble(pdDealDelayRange, 0);
  pd.dealDelayMax = util.Params.getDouble(pdDealDelayRange, 1);

  pd.acceptableDeliverUnits = uniform(util.Params.getDouble(pdAcceptableDeliverUnitsRange, 0), util.Params.getDouble(pdAcceptableDeliverUnitsRange, 1));
  pd.probDeliverHome = uniform(util.Params.getDouble(pdProbDeliverHomeRange, 0), util.Params.getDouble(pdProbDeliverHomeRange, 1));
  pd.probDeliverMarket = uniform(util.Params.getDouble(pdProbDeliverMarketRange, 0), util.Params.getDouble(pdProbDeliverMarketRange, 1));

  pd.invitationDeals = uniform_discr(util.Params.getInt(pdInvitationDealsRange, 0), util.Params.getInt(pdInvitationDealsRange, 1));

  setup_privateDealer(pd, privateDealer.size());
}


//-------------------------------------------------------------------------
// Initialize police.

for (int i = 0; i < numInitialPolice; i++) {
  Police po = new Police();

  po.initialMarket = market.random();
  po.type = Police.TYPE_REGULAR;

  setup_police(po, police.size());
}

if (poUseBusts) {
  for (int i = 0; i < market.size(); i++) {
    // Create the reserve police officers for this market.

    for (int j = 0; j < poBustOfficers; j++) {
      Police po = new Police();

      po.initialMarket = market.item(i);
      po.type = Police.TYPE_RESERVE;

      setup_police(po, police.size());
    }

    // Create the bust timer for this market.

    MarketBustTimer timer = new MarketBustTimer();
    timer.market = market.item(i);
    setup_marketBustTimers(timer, marketBustTimers.size());
  }
}


//-------------------------------------------------------------------------
// Initialize homeless.

for (int i = 0; i < numInitialHomeless; i++) {
  Homeless hm = new Homeless();

  hm.initialMarket = market.random();

  setup_homeless(hm, homeless.size());
}
Name initializeNetworks
Type void
Body //-------------------------------------------------------------------------
// Set up each customer's list of known markets.

// TODO: Select an algorithm for assigning known markets.
// For now, assign all markets.

for (int i = 0; i < customer.size(); i++) {
  Customer cs = customer.item(i);

  for (int j = 0; j < market.size(); j++) {
    cs.marketHistories.add(market.item(j));
  }
}


//-------------------------------------------------------------------------
// Set up each customer's list of known private dealers and vice-versa.

// TODO: Select an algorithm for assigning known private dealers.
// For now, assign one private dealer to any customer who
// knows a private dealer.

for (int i = 0; i < customer.size(); i++) {
  Customer cs = customer.item(i);

  if (randomTrue(csProbKnowPrivateDealer)) {
    PrivateDealer pd = privateDealer.random();
    cs.privateDealerHistories.add(pd);
    pd.customerHistories.add(cs);
  }
}


//-------------------------------------------------------------------------
// Set up each street broker's list of known street dealers.

// TODO: Select an algorithm for assigning known street dealers.
// For now, assign all street dealers in the market.

for (int i = 0; i < streetBroker.size(); i++) {
  StreetBroker sb = streetBroker.item(i);

  for (int j = 0; j < streetDealer.size(); j++) {
    StreetDealer sd = streetDealer.item(j);

    if (sd.group.market == sb.market) {
      sb.streetDealerHistories.add(sd);
    }
  }
}


//-------------------------------------------------------------------------
// Set up each street broker's list of known private dealers.

// TODO: Select an algorithm for assigning known private dealers.
// For now, assign one private dealer to any street broker
// who knows a private dealer.

for (int i = 0; i < streetBroker.size(); i++) {
  StreetBroker sb = streetBroker.item(i);

  if (randomTrue(sbProbKnowPrivateDealer)) {
    PrivateDealer pd = privateDealer.random();
    sb.privateDealerHistories.add(pd);
  }
}
Name initializeParameters
Type void
Body // Make any necessary adjustments to the user-defined parameters.


//---------------------------------------------------------------------
// Allow only one replication if the animation is enabled.

if (Engine.getAnimation() != null) {
  numReplications = 1;
}


//---------------------------------------------------------------------
// Make sure numInitialMarkets is set to 1.
//
// To support more than one market, we must either support multiple
// streetDealerGroups or we must allow the dealers in a single
// streetDealerGroup to split up across multiple markets.

if (numInitialMarkets != 1) {
  traceln("***NOTE: Setting numInitialMarkets=1 because other values are currently unsupported.");
  numInitialMarkets = 1;
}


//---------------------------------------------------------------------
// Make sure csAcceptableSbQueueLength is set to 0.
//
// To support street broker queues, we must either make the street broker
// travel to the customer's location before seeking a street dealer, or
// we must have the street broker accept the customer's money as soon as
// he receives the deal request.

if (csAcceptableSbQueueLength != 0) {
  traceln("***NOTE: Setting csAcceptableSbQueueLength=0 because other values are currently unsupported.");
  csAcceptableSbQueueLength = 0;
}


//---------------------------------------------------------------------
// Some parameters are specified in particular time or distance
// units. Modify them so that they are all expressed in consistent
// units.
//
// We must be careful with this since we are overwriting the existing
// parameter values. If we try to use a parameter before this function
// is executed, then the value that gets used will be the original value
// instead of the modified value. Therefore, we must make sure that all
// references to these parameters come after the call to this function.
// This includes some of the things that AnyLogic does automatically,
// like the initial values of variable objects, the expiration times
// of timers, and parameter values that get passed to encapsulated
// objects.

replicationRunTime = util.Units.days(replicationRunTime);

cityHeight = util.Units.miles(cityHeight);

cityWidth = util.Units.miles(cityWidth);

marketSizeRange = util.Units.miles(util.Params.getDouble(marketSizeRange, 0))
          + "," + util.Units.miles(util.Params.getDouble(marketSizeRange, 1));

walkingSpeed = util.Units.milesPerHour(walkingSpeed);

drivingSpeed = util.Units.milesPerHour(drivingSpeed);

visionRadiusRange = util.Units.feet(util.Params.getDouble(visionRadiusRange, 0))
            + "," + util.Units.feet(util.Params.getDouble(visionRadiusRange, 1));

outfileSummaryInterval = util.Units.hours(outfileSummaryInterval);

outfileCustomerInterval = util.Units.hours(outfileCustomerInterval);

outfileStreetBrokerInterval = util.Units.hours(outfileStreetBrokerInterval);

metabolismRateRange = util.Params.getDouble(metabolismRateRange, 0) / util.Units.minutes(1)
              + "," + util.Params.getDouble(metabolismRateRange, 1) / util.Units.minutes(1);

addictionIncreaseDurationRange = util.Units.hours(util.Params.getDouble(addictionIncreaseDurationRange, 0))
                         + "," + util.Units.hours(util.Params.getDouble(addictionIncreaseDurationRange, 1));

addictionIncreaseRateRange = util.Params.getDouble(addictionIncreaseRateRange, 0) / util.Units.minutes(1)
                     + "," + util.Params.getDouble(addictionIncreaseRateRange, 1) / util.Units.minutes(1);

addictionDecreaseOnsetRange = util.Units.hours(util.Params.getDouble(addictionDecreaseOnsetRange, 0))
                      + "," + util.Units.hours(util.Params.getDouble(addictionDecreaseOnsetRange, 1));

addictionDecreaseRateRange = util.Params.getDouble(addictionDecreaseRateRange, 0) / util.Units.minutes(1)
                     + "," + util.Params.getDouble(addictionDecreaseRateRange, 1) / util.Units.minutes(1);

useDelayRange = util.Units.minutes(util.Params.getDouble(useDelayRange, 0))
        + "," + util.Units.minutes(util.Params.getDouble(useDelayRange, 1));

treatmentDelayRange = util.Units.days(util.Params.getDouble(treatmentDelayRange, 0))
              + "," + util.Units.days(util.Params.getDouble(treatmentDelayRange, 1));

csInitialTimeSinceLastUseDistrParams = util.Units.hours(util.Params.getDouble(csInitialTimeSinceLastUseDistrParams, 0))
                               + "," + util.Units.hours(util.Params.getDouble(csInitialTimeSinceLastUseDistrParams, 1));

csWindfallIntervalRange = util.Units.days(util.Params.getDouble(csWindfallIntervalRange, 0))
                  + "," + util.Units.days(util.Params.getDouble(csWindfallIntervalRange, 1));

csRandomUseIntervalRange = util.Units.days(util.Params.getDouble(csRandomUseIntervalRange, 0))
                   + "," + util.Units.days(util.Params.getDouble(csRandomUseIntervalRange, 1));

csDealQuitTime = util.Units.hours(csDealQuitTime);

csMarketQuitTimeRange = util.Units.hours(util.Params.getDouble(csMarketQuitTimeRange, 0))
                + "," + util.Units.hours(util.Params.getDouble(csMarketQuitTimeRange, 1));

csSeekMoneyDelayRange = util.Units.minutes(util.Params.getDouble(csSeekMoneyDelayRange, 0))
                + "," + util.Units.minutes(util.Params.getDouble(csSeekMoneyDelayRange, 1));

csRetryDelayRange = util.Units.minutes(util.Params.getDouble(csRetryDelayRange, 0))
            + "," + util.Units.minutes(util.Params.getDouble(csRetryDelayRange, 1));

sbInitialTimeSinceLastUseDistrParams = util.Units.hours(util.Params.getDouble(sbInitialTimeSinceLastUseDistrParams, 0))
                               + "," + util.Units.hours(util.Params.getDouble(sbInitialTimeSinceLastUseDistrParams, 1));

sbDealQuitTime = util.Units.hours(sbDealQuitTime);

sbMarketQuitTimeRange = util.Units.hours(util.Params.getDouble(sbMarketQuitTimeRange, 0))
                + "," + util.Units.hours(util.Params.getDouble(sbMarketQuitTimeRange, 1));

sdDealDelayRange = util.Units.minutes(util.Params.getDouble(sdDealDelayRange, 0))
           + "," + util.Units.minutes(util.Params.getDouble(sdDealDelayRange, 1));

pdShiftLengthRange = util.Units.hours(util.Params.getDouble(pdShiftLengthRange, 0))
             + "," + util.Units.hours(util.Params.getDouble(pdShiftLengthRange, 1));

pdDealQuitTime = util.Units.hours(pdDealQuitTime);

pdDealDelayRange = util.Units.minutes(util.Params.getDouble(pdDealDelayRange, 0))
           + "," + util.Units.minutes(util.Params.getDouble(pdDealDelayRange, 1));

poVisionRadius = util.Units.feet(poVisionRadius);

poHarassDuration = util.Units.minutes(poHarassDuration);

poHarassRepeatDelay = util.Units.hours(poHarassRepeatDelay);

poArrestDuration = util.Units.minutes(poArrestDuration);

poJailPerDrugUnit = util.Units.days(poJailPerDrugUnit);

StringBuffer new_poBustStartTimes = new StringBuffer();
for (int i = 0; i < util.Params.count(poBustStartTimes); i++) {
  if (i > 0) {
    new_poBustStartTimes.append(",");
  }
  new_poBustStartTimes.append(util.Units.days(util.Params.getDouble(poBustStartTimes, i)));
}
poBustStartTimes = new_poBustStartTimes.toString();

poBustDuration = util.Units.hours(poBustDuration);

poBustVisionRadius = util.Units.feet(poBustVisionRadius);


//---------------------------------------------------------------------
// If outfiles are being created and the replication end time is
// a multiple of the write interval, then the replication will end
// before the last expected write to the outfile. Therefore we will
// lengthen the replication run time slightly to allow the final
// write to the outfile.

if ( (outfileSummaryCreate && replicationRunTime % outfileSummaryInterval == 0)
    || (outfileCustomerCreate && replicationRunTime % outfileCustomerInterval == 0)
    || (outfileStreetBrokerCreate && replicationRunTime % outfileStreetBrokerInterval == 0)
   ) {
  replicationRunTime += 1;
}
Name startTimers
Type void
Body // The replication end timer will expire once at the end of the
// replication.
replicationRunTimer.restart(replicationRunTime);


// The timers for writing the outfiles should be set to expire at
// model startup. Each will handle restarting itself at the
// appropriate times.
outfileSummaryTimer.restart(0);
outfileCustomerTimer.restart(0);
outfileStreetBrokerTimer.restart(0);


// The timers for starting market busts should be started just
// once. After that, they will handle restarting themselves at
// the appropriate times.
if (poUseBusts) {
  for (int i = 0; i < marketBustTimers.size(); i++) {
    marketBustTimers.item(i).scheduleNextBust();
  }
}

Animation
Name animation
Setup code setScale(animationScaleFactor);

// Enable anti-aliasing by default. For some reason this causes a
// NullPointerException in interactive mode but not in applet mode,
// so execute it only for applet mode.
if (util.Model.getRunMode().equals("applet")) {
  Engine.getAnimation().setAntiAliasingEnabled(true);
}
Update code // On every animation update, AnyLogic updates all the animation elements defined
// in the animation GUI and then runs the code in this section. Animation updates
// may happen many times during a single model time step, and we can also force
// an update using the update() method.
//
// We want to run certain pieces of code on every update, such as code that sets
// the visibility of objects. We want to run other pieces of code less frequently,
// though. An example of this is code that adds a point to a plot, which we may
// want to run on every time step or every N time steps. To deal with these timing
// issues, we place code that should be run at a specified time interval inside a
// condition that allows the code to be run only when the time interval boundaries
// are crossed.
//
// Further complicating this section, we must perform some initialization
// activities here. We do this here instead of in the "Setup code" section because
// our initialization requires access to members of the replicated objects in Main.
// Those objects are not yet created when the "Setup code" is executed, but they
// have been created by the time the "Update code" is first executed. We place other
// initialization activities here, even though it's not necessary, to keep all the
// initialization code together.
//
// An important consideration is that the initialization code itself might trigger
// another animation update. If we don't account for this, we will cause an infinite
// loop at time 0 where the initialization code triggers an update, which runs the
// the initialization code again, which triggers another update, etc. To handle this,
// we use a flag variable to indicate whether we've already run the initialization
// code, and we check this flag variable before running the initialization code again.
//
// Another important consideration is that the initialization code may perform some
// setup activities that are dependencies for "normal" update code. Therefore, we
// create another flag variable to indicate when the initialization code is complete,
// and then we check this flag variable before runing the normal update code.

if (Engine.getTime() == 0 && !hasInitializationStarted) {
  hasInitializationStarted = true;

  disableAppletButtons();

  initializeAgentColors();
  initializeAgentNames();
  initializeAgentSelection();
  initializeOptionBoxes();
  initializePlots();

  for (int i = 0; i < NUM_AGENTS; i++) {
    selectAgent(i, selectedAgents[i]);
  }

  setPov(AGENT_CUSTOMER);
  setZoomLevel(ZOOM_CITY);

  isInitializationComplete = true;
}

if (isInitializationComplete) {
  if ((int)(Engine.getTime() / plotUpdateInterval) > (int)(prevUpdateTime / plotUpdateInterval)) {
    addPointToPlots();
  }
  
  setOptionBoxVisibility();
  setPlotVisibility();

  prevUpdateTime = Engine.getTime();
}
Additional class code private boolean hasInitializationStarted = false;
private boolean isInitializationComplete = false;

private double prevUpdateTime = -1;


//---------------------------------------------------------------------
// Set positions and sizes.
//

public double distanceToPixels(double distance) {
  double pixelsPerDistanceUnit;

  if (zoomLevel == ZOOM_CITY) {
    pixelsPerDistanceUnit = min(cityRectangle.getWidth(), cityRectangle.getHeight())
                            / max(cityWidth, cityHeight);
  }
  else {
    Market mk = market.item(selectedAgents[AGENT_MARKET]);
    pixelsPerDistanceUnit = min(cityRectangle.getWidth() - 2*zoomBuffer, cityRectangle.getHeight() - 2*zoomBuffer)
                            / max(mk.width, mk.height);
  }

  return pixelsPerDistanceUnit * distance;
}


// We make the following assumption throughout this model:
// private double cityX = 0;
// private double cityY = 0;
// However, we do not want to use these variables in all our calculations,
// so we will not let them be changed.

private double optionBoxY = -30;
private double optionBoxHeight = 20;

private double filterBoxX = 440;
private double filterBoxWidth = 135;
private double statBoxX = filterBoxX + filterBoxWidth + optionBoxHeight + 10;
private double statBoxWidth = 135;

private double statsX = 450;
private double statsY = 10;

private double plotsX = 490;
private double plotsY = 230;

private double plotsWidth = 300;
private double plotsHeight = 150;



//---------------------------------------------------------------------
// Set colors.
//

private Color titleColor = new Color(140, 161, 175);
private Color outlineColor = new Color(85, 104, 117);
private Color subtitleColor = new Color(209, 217, 223);

private Color inPovButtonColor = new Color(255, 204, 153);
private Color outPovButtonColor = Color.lightGray;

private Color[] agentFillColors = new Color[NUM_AGENTS];
private Color[] agentLineColors = new Color[NUM_AGENTS];
private Color[] selectedAgentFillColors = new Color[NUM_AGENTS];
private Color[] selectedAgentLineColors = new Color[NUM_AGENTS];

private void initializeAgentColors() {
  agentFillColors[AGENT_CUSTOMER] = Color.darkGray;
  agentFillColors[AGENT_STREET_BROKER] = new Color(153, 204, 0);
  agentFillColors[AGENT_STREET_DEALER] = new Color(255, 153, 0);
  agentFillColors[AGENT_PRIVATE_DEALER] = new Color(204, 153, 255);
  agentFillColors[AGENT_HOMELESS] = Color.lightGray;
  agentFillColors[AGENT_POLICE] = new Color(0, 85, 255);
  agentFillColors[AGENT_MARKET] = null;
  
  agentLineColors[AGENT_CUSTOMER] = Color.darkGray;
  agentLineColors[AGENT_STREET_BROKER] = new Color(153, 204, 0);
  agentLineColors[AGENT_STREET_DEALER] = new Color(255, 153, 0);
  agentLineColors[AGENT_PRIVATE_DEALER] = new Color(204, 153, 255);
  agentLineColors[AGENT_HOMELESS] = Color.lightGray;
  agentLineColors[AGENT_POLICE] = new Color(0, 85, 255);
  agentLineColors[AGENT_MARKET] = Color.black;
  
  selectedAgentFillColors[AGENT_CUSTOMER] = Color.red;
  selectedAgentFillColors[AGENT_STREET_BROKER] = Color.red;
  selectedAgentFillColors[AGENT_STREET_DEALER] = Color.red;
  selectedAgentFillColors[AGENT_PRIVATE_DEALER] = Color.red;
  selectedAgentFillColors[AGENT_HOMELESS] = Color.red;
  selectedAgentFillColors[AGENT_POLICE] = Color.red;
  selectedAgentFillColors[AGENT_MARKET] = null;
  
  selectedAgentLineColors[AGENT_CUSTOMER] = Color.darkGray;
  selectedAgentLineColors[AGENT_STREET_BROKER] = new Color(153, 204, 0);
  selectedAgentLineColors[AGENT_STREET_DEALER] = new Color(255, 153, 0);
  selectedAgentLineColors[AGENT_PRIVATE_DEALER] = new Color(204, 153, 255);
  selectedAgentLineColors[AGENT_HOMELESS] = Color.lightGray;
  selectedAgentLineColors[AGENT_POLICE] = new Color(0, 85, 255);
  selectedAgentLineColors[AGENT_MARKET] = Color.red;
}


//---------------------------------------------------------------------
// Functions to determine the current agent attributes.
//

public double getAgentLocationX(int agent, boolean isSelected, double locCity) {
  double distance;
  double originOffset;

  if (zoomLevel == ZOOM_CITY) {
    distance = locCity;
    originOffset = 0;
  }
  else {
    Market mk = market.item(selectedAgents[AGENT_MARKET]);
    distance = mk.getLocationInMarketX(locCity);
    originOffset = zoomBuffer;
  }

  return originOffset + distanceToPixels(distance) - getAgentSize(agent, isSelected) / 2;
}

public double getAgentLocationY(int agent, boolean isSelected, double locCity) {
  double distance;
  double originOffset;

  if (zoomLevel == ZOOM_CITY) {
    distance = locCity;
    originOffset = 0;
  }
  else {
    Market mk = market.item(selectedAgents[AGENT_MARKET]);
    distance = mk.getLocationInMarketY(locCity);
    originOffset = zoomBuffer;
  }

  return originOffset + distanceToPixels(distance) - getAgentSize(agent, isSelected) / 2;
}

public double getAgentSize(int agent, boolean isSelected) {
  if (agent == AGENT_MARKET) {
    // this does not apply to markets
    return 0;
  }
  else {
    // all agents are the same size for now
    return isSelected && agentPov == agent ? 8 : 4;
  }
}

public Color getAgentFillColor(int agent, boolean isSelected) {
  if (agent >= 0 && agent < NUM_AGENTS) {
    if (isSelected && agentPov == agent) {
      return selectedAgentFillColors[agent];
    }
    else {
      return agentFillColors[agent];
    }
  }
  else {
    traceln("***ERROR in Main.animation.getAgentFillColor: Unexpected agent type: " + agent + " @ " + Engine.getTime());
    return null;
  }
}

public Color getAgentLineColor(int agent, boolean isSelected) {
  if (agent >= 0 && agent < NUM_AGENTS) {
    if (isSelected && agentPov == agent) {
      return selectedAgentLineColors[agent];
    }
    else {
      return agentLineColors[agent];
    }
  }
  else {
    traceln("***ERROR in Main.animation.getAgentLineColor: Unexpected agent type: " + agent + " @ " + Engine.getTime());
    return null;
  }
}

public double getAgentLineWidth(int agent, boolean isSelected) {
  // all agents have the same line width for now
  return isSelected && agentPov == agent ? 2 : 1;
}

public boolean getAgentVisibility(int agent, boolean isSelected, double x, double y) {
  if (zoomLevel == ZOOM_CITY) {
    return true;
  }
  else {
    return cityRectangle.contains(
      getAgentLocationX(agent, isSelected, x),
      getAgentLocationY(agent, isSelected, y)
    );
  }
}


//---------------------------------------------------------------------
// Handle the selection of agent point-of-view.
//

private int agentPov;

private String[] agentNames = new String[NUM_AGENTS];
private String[] agentNamesPlural = new String[NUM_AGENTS];

private void initializeAgentNames() {
  agentNames[AGENT_CUSTOMER] = "Customer";
  agentNames[AGENT_STREET_BROKER] = "Street Broker";
  agentNames[AGENT_STREET_DEALER] = "Street Dealer";
  agentNames[AGENT_PRIVATE_DEALER] = "Private Dealer";
  agentNames[AGENT_HOMELESS] = "Homeless";
  agentNames[AGENT_POLICE] = "Police";
  agentNames[AGENT_MARKET] = "Market";
  
  agentNamesPlural[AGENT_CUSTOMER] = "Customers";
  agentNamesPlural[AGENT_STREET_BROKER] = "Street Brokers";
  agentNamesPlural[AGENT_STREET_DEALER] = "Street Dealers";
  agentNamesPlural[AGENT_PRIVATE_DEALER] = "Private Dealers";
  agentNamesPlural[AGENT_HOMELESS] = "Homeless";
  agentNamesPlural[AGENT_POLICE] = "Police";
  agentNamesPlural[AGENT_MARKET] = "Markets";
}

private void setPov(int agent) {
  if (agent >= 0 && agent < NUM_AGENTS) {
    agentPov = agent;

    selectAgent(agentPov, selectedAgents[agentPov]);

    filterBox.setItemText(0, "All " + agentNamesPlural[agentPov]);
    filterBox.setItemText(1, "Selected " + agentNames[agentPov]);
  }
}


//---------------------------------------------------------------------
// Handle the selection of specific agents.
//

private int[] selectedAgents = new int[NUM_AGENTS];
private ReplicatedObject[] agentLists = new ReplicatedObject[NUM_AGENTS];

private void initializeAgentSelection() {
  selectedAgents[AGENT_CUSTOMER] = 0;
  selectedAgents[AGENT_STREET_BROKER] = 0;
  selectedAgents[AGENT_STREET_DEALER] = 0;
  selectedAgents[AGENT_PRIVATE_DEALER] = 0;
  selectedAgents[AGENT_HOMELESS] = 0;
  selectedAgents[AGENT_POLICE] = 0;
  selectedAgents[AGENT_MARKET] = 0;
  
  agentLists[AGENT_CUSTOMER] = customer;
  agentLists[AGENT_STREET_BROKER] = streetBroker;
  agentLists[AGENT_STREET_DEALER] = streetDealer;
  agentLists[AGENT_PRIVATE_DEALER] = privateDealer;
  agentLists[AGENT_HOMELESS] = homeless;
  agentLists[AGENT_POLICE] = police;
  agentLists[AGENT_MARKET] = market;
}

private void selectAgent(int agent, int id) {

  // If the agent type is not valid, then do nothing.
  if (agent < 0 || agent >= NUM_AGENTS) {
    return;
  }

  // If the requested id is out of range, then do nothing.
  if (id < 0 || id >= agentLists[agent].size()) {
    return;
  }

  // Update the isSelected indicator for the old and new selected agents.
  // Ideally we would use our agentLists array and loop over the array
  // instead of using the series of if/else statements. However, agentLists
  // is too generic for our needs. We need access to the animation.isSelected
  // field, and we cannot get it from the ReplicatedObjects in agentLists.
  if (agent == AGENT_CUSTOMER) {
    customer.item(selectedAgents[agent]).animation.isSelected = false;
    customer.item(id).animation.isSelected = true;
  }
  else if (agent == AGENT_STREET_BROKER) {
    streetBroker.item(selectedAgents[agent]).animation.isSelected = false;
    streetBroker.item(id).animation.isSelected = true;
  }
  else if (agent == AGENT_STREET_DEALER) {
    streetDealer.item(selectedAgents[agent]).animation.isSelected = false;
    streetDealer.item(id).animation.isSelected = true;
  }
  else if (agent == AGENT_PRIVATE_DEALER) {
    privateDealer.item(selectedAgents[agent]).animation.isSelected = false;
    privateDealer.item(id).animation.isSelected = true;
  }
  else if (agent == AGENT_HOMELESS) {
    homeless.item(selectedAgents[agent]).animation.isSelected = false;
    homeless.item(id).animation.isSelected = true;
  }
  else if (agent == AGENT_POLICE) {
    police.item(selectedAgents[agent]).animation.isSelected = false;
    police.item(id).animation.isSelected = true;
  }
  else if (agent == AGENT_MARKET) {
    market.item(selectedAgents[agent]).animation.isSelected = false;
    market.item(id).animation.isSelected = true;
  }
  else {
    traceln("***ERROR in Main.animation.selectAgent: Unexpected agent type: " + agent + " @ " + Engine.getTime());
  }

  // If the requested agent type has the current POV, update the text box.
  if (agentPov == agent) {
    selectedAgentEdit.setValue(id);
  }

  // Update our array that tracks the currently selected agents.
  selectedAgents[agent] = id;

  // Force an animation update.
  update();
}


//---------------------------------------------------------------------
// Handle zooming in and out of the selected market.
//

private double zoomBuffer = 10;

private int zoomLevel;
private final int ZOOM_CITY = 0;
private final int ZOOM_MARKET = 1;

private void setZoomLevel(int level) {
  zoomLevel = level;
  zoomLevelCheckbox.setValue(zoomLevel == ZOOM_MARKET);
}


//---------------------------------------------------------------------
// Create option boxes.
//

private OptionBox filterBox;
private OptionItem filterBoxAllItem;
private OptionItem filterBoxSelectedItem;
private int optionFilter;
private final int FILTER_ALL = 0;
private final int FILTER_SELECTED = 1;

// customers

private OptionBox csAllStatBox;
private int optionCsAllStat;
private final int STAT_CS_ALL_TRANSACTIONS_GOOD = 0;
private final int STAT_CS_ALL_TRANSACTIONS_COST = 1;
private final int STAT_CS_ALL_MODEL_STATUS = 2;

private OptionBox csSelectedStatBox;
private int optionCsSelectedStat;
private final int STAT_CS_SELECTED_ALL = 0;

// street brokers

private OptionBox sbAllStatBox;
private int optionSbAllStat;
private final int STAT_SB_ALL_TRANSACTIONS_GOOD = 0;
private final int STAT_SB_ALL_MODEL_STATUS = 1;

private OptionBox sbSelectedStatBox;
private int optionSbSelectedStat;
private final int STAT_SB_SELECTED_ALL = 0;

// street dealers

private OptionBox sdAllStatBox;
private int optionSdAllStat;
private final int STAT_SD_ALL_MODEL_STATUS = 0;

private OptionBox sdSelectedStatBox;
private int optionSdSelectedStat;
private final int STAT_SD_SELECTED_ALL = 0;

// private dealers

private OptionBox pdAllStatBox;
private int optionPdAllStat;
private final int STAT_PD_ALL_MODEL_STATUS = 0;

private OptionBox pdSelectedStatBox;
private int optionPdSelectedStat;
private final int STAT_PD_SELECTED_ALL = 0;

// police

private OptionBox poAllStatBox;
private int optionPoAllStat;
private final int STAT_PO_ALL_NUMBER_ACTIVE = 0;
private final int STAT_PO_ALL_HARASSMENTS = 1;

private OptionBox poSelectedStatBox;
private int optionPoSelectedStat;
private final int STAT_PO_SELECTED_ALL = 0;

// markets

private OptionBox mkAllStatBox;
private int optionMkAllStat;
private final int STAT_MK_ALL_NONE = 0;

private OptionBox mkSelectedStatBox;
private int optionMkSelectedStat;
private final int STAT_MK_SELECTED_NONE = 0;


private void initializeOptionBoxes() {
  filterBox = new OptionBox(this);
  filterBox.addOptionItem(
    new OptionItem("All") {
      public void onSelect() {
        optionFilter = FILTER_ALL;
        update();
      }
    }
  );
  filterBox.addOptionItem(
    new OptionItem("Selected") {
      public void onSelect() {
        optionFilter = FILTER_SELECTED;
        update();
      }
    }
  );
  filterBox.setPos(filterBoxX, optionBoxY);
  filterBox.setItemWidth(filterBoxWidth);
  filterBox.setItemHeight(optionBoxHeight);
  filterBox.setSelectedItem("All");
  filterBox.setVisible(true);
  
  csAllStatBox = new OptionBox(this);
  csAllStatBox.addOptionItem(
    new OptionItem("Completed transactions") {
      public void onSelect() {
        optionCsAllStat = STAT_CS_ALL_TRANSACTIONS_GOOD;
        update();
      }
    }
  );
  csAllStatBox.addOptionItem(
    new OptionItem("Transaction cost") {
      public void onSelect() {
        optionCsAllStat = STAT_CS_ALL_TRANSACTIONS_COST;
        update();
      }
    }
  );
  csAllStatBox.addOptionItem(
    new OptionItem("Model Status") {
      public void onSelect() {
        optionCsAllStat = STAT_CS_ALL_MODEL_STATUS;
        update();
      }
    }
  );
  csAllStatBox.setPos(statBoxX, optionBoxY);
  csAllStatBox.setItemWidth(statBoxWidth);
  csAllStatBox.setItemHeight(optionBoxHeight);
  csAllStatBox.setSelectedItem("Completed transactions");
  csAllStatBox.setVisible(false);
  
  csSelectedStatBox = new OptionBox(this);
  csSelectedStatBox.addOptionItem(
    new OptionItem("All stats") {
      public void onSelect() {
        optionCsSelectedStat = STAT_CS_SELECTED_ALL;
        update();
      }
    }
  );
  csSelectedStatBox.setPos(statBoxX, optionBoxY);
  csSelectedStatBox.setItemWidth(statBoxWidth);
  csSelectedStatBox.setItemHeight(optionBoxHeight);
  csSelectedStatBox.setSelectedItem("All stats");
  csSelectedStatBox.setVisible(false);
  
  sbAllStatBox = new OptionBox(this);
  sbAllStatBox.addOptionItem(
    new OptionItem("Completed transactions") {
      public void onSelect() {
        optionSbAllStat = STAT_SB_ALL_TRANSACTIONS_GOOD;
        update();
      }
    }
  );
  sbAllStatBox.addOptionItem(
    new OptionItem("Model Status") {
      public void onSelect() {
        optionSbAllStat = STAT_SB_ALL_MODEL_STATUS;
        update();
      }
    }
  );
  sbAllStatBox.setPos(statBoxX, optionBoxY);
  sbAllStatBox.setItemWidth(statBoxWidth);
  sbAllStatBox.setItemHeight(optionBoxHeight);
  sbAllStatBox.setSelectedItem("Completed transactions");
  sbAllStatBox.setVisible(false);
  
  sbSelectedStatBox = new OptionBox(this);
  sbSelectedStatBox.addOptionItem(
    new OptionItem("All stats") {
      public void onSelect() {
        optionSbSelectedStat = STAT_SB_SELECTED_ALL;
        update();
      }
    }
  );
  sbSelectedStatBox.setPos(statBoxX, optionBoxY);
  sbSelectedStatBox.setItemWidth(statBoxWidth);
  sbSelectedStatBox.setItemHeight(optionBoxHeight);
  sbSelectedStatBox.setSelectedItem("All stats");
  sbSelectedStatBox.setVisible(false);

  sdAllStatBox = new OptionBox(this);
  sdAllStatBox.addOptionItem(
    new OptionItem("Model Status") {
      public void onSelect() {
        optionSdAllStat = STAT_SD_ALL_MODEL_STATUS;
        update();
      }
    }
  );
  sdAllStatBox.setPos(statBoxX, optionBoxY);
  sdAllStatBox.setItemWidth(statBoxWidth);
  sdAllStatBox.setItemHeight(optionBoxHeight);
  sdAllStatBox.setSelectedItem("Model Status");
  sdAllStatBox.setVisible(false);

  sdSelectedStatBox = new OptionBox(this);
  sdSelectedStatBox.addOptionItem(
    new OptionItem("All stats") {
      public void onSelect() {
        optionSdSelectedStat = STAT_SD_SELECTED_ALL;
        update();
      }
    }
  );
  sdSelectedStatBox.setPos(statBoxX, optionBoxY);
  sdSelectedStatBox.setItemWidth(statBoxWidth);
  sdSelectedStatBox.setItemHeight(optionBoxHeight);
  sdSelectedStatBox.setSelectedItem("All stats");
  sdSelectedStatBox.setVisible(false);

  pdAllStatBox = new OptionBox(this);
  pdAllStatBox.addOptionItem(
    new OptionItem("Model Status") {
      public void onSelect() {
        optionPdAllStat = STAT_PD_ALL_MODEL_STATUS;
        update();
      }
    }
  );
  pdAllStatBox.setPos(statBoxX, optionBoxY);
  pdAllStatBox.setItemWidth(statBoxWidth);
  pdAllStatBox.setItemHeight(optionBoxHeight);
  pdAllStatBox.setSelectedItem("Model Status");
  pdAllStatBox.setVisible(false);

  pdSelectedStatBox = new OptionBox(this);
  pdSelectedStatBox.addOptionItem(
    new OptionItem("All stats") {
      public void onSelect() {
        optionPdSelectedStat = STAT_PD_SELECTED_ALL;
        update();
      }
    }
  );
  pdSelectedStatBox.setPos(statBoxX, optionBoxY);
  pdSelectedStatBox.setItemWidth(statBoxWidth);
  pdSelectedStatBox.setItemHeight(optionBoxHeight);
  pdSelectedStatBox.setSelectedItem("All stats");
  pdSelectedStatBox.setVisible(false);

  poAllStatBox = new OptionBox(this);
  poAllStatBox.addOptionItem(
    new OptionItem("Harassments and Arrests") {
      public void onSelect() {
        optionPoAllStat = STAT_PO_ALL_HARASSMENTS;
        update();
      }
    }
  );
  poAllStatBox.addOptionItem(
    new OptionItem("Number Active") {
      public void onSelect() {
        optionPoAllStat = STAT_PO_ALL_NUMBER_ACTIVE;
        update();
      }
    }
  );
  poAllStatBox.setPos(statBoxX, optionBoxY);
  poAllStatBox.setItemWidth(statBoxWidth);
  poAllStatBox.setItemHeight(optionBoxHeight);
  poAllStatBox.setSelectedItem("Harassments and Arrests");
  poAllStatBox.setVisible(false);

  poSelectedStatBox = new OptionBox(this);
  poSelectedStatBox.addOptionItem(new OptionItem("None available"));
  poSelectedStatBox.setPos(statBoxX, optionBoxY);
  poSelectedStatBox.setItemWidth(statBoxWidth);
  poSelectedStatBox.setItemHeight(optionBoxHeight);
  poSelectedStatBox.setSelectedItem("None available");
  poSelectedStatBox.setVisible(false);

  mkAllStatBox = new OptionBox(this);
  mkAllStatBox.addOptionItem(new OptionItem("None available"));
  mkAllStatBox.setPos(statBoxX, optionBoxY);
  mkAllStatBox.setItemWidth(statBoxWidth);
  mkAllStatBox.setItemHeight(optionBoxHeight);
  mkAllStatBox.setSelectedItem("None available");
  mkAllStatBox.setVisible(false);

  mkSelectedStatBox = new OptionBox(this);
  mkSelectedStatBox.addOptionItem(new OptionItem("None available"));
  mkSelectedStatBox.setPos(statBoxX, optionBoxY);
  mkSelectedStatBox.setItemWidth(statBoxWidth);
  mkSelectedStatBox.setItemHeight(optionBoxHeight);
  mkSelectedStatBox.setSelectedItem("None available");
  mkSelectedStatBox.setVisible(false);
}

private void setOptionBoxVisibility() {
  csAllStatBox.setVisible(agentPov == AGENT_CUSTOMER && optionFilter == FILTER_ALL);
  csSelectedStatBox.setVisible(agentPov == AGENT_CUSTOMER && optionFilter == FILTER_SELECTED);
  sbAllStatBox.setVisible(agentPov == AGENT_STREET_BROKER && optionFilter == FILTER_ALL);
  sbSelectedStatBox.setVisible(agentPov == AGENT_STREET_BROKER && optionFilter == FILTER_SELECTED);
  sdAllStatBox.setVisible(agentPov == AGENT_STREET_DEALER && optionFilter == FILTER_ALL);
  sdSelectedStatBox.setVisible(agentPov == AGENT_STREET_DEALER && optionFilter == FILTER_SELECTED);
  pdAllStatBox.setVisible(agentPov == AGENT_PRIVATE_DEALER && optionFilter == FILTER_ALL);
  pdSelectedStatBox.setVisible(agentPov == AGENT_PRIVATE_DEALER && optionFilter == FILTER_SELECTED);
  poAllStatBox.setVisible(agentPov == AGENT_POLICE && optionFilter == FILTER_ALL);
  poSelectedStatBox.setVisible(agentPov == AGENT_POLICE && optionFilter == FILTER_SELECTED);
  mkAllStatBox.setVisible(agentPov == AGENT_MARKET && optionFilter == FILTER_ALL);
  mkSelectedStatBox.setVisible(agentPov == AGENT_MARKET && optionFilter == FILTER_SELECTED);
}


//---------------------------------------------------------------------
// Create plots.
//

private double plotUpdateInterval = util.Units.hours(1);

private SeriesPlot csTransactionsNumGoodPlot;
private SeriesPlot csTransactionsCostPlot;
private SeriesPlot csModelStatusPlot;
private SeriesPlot sbTransactionsNumGoodPlot;
private SeriesPlot sbModelStatusPlot;
private SeriesPlot sdModelStatusPlot;
private SeriesPlot pdModelStatusPlot;
private SeriesPlot poNumberActivePlot;
private SeriesPlot poHarassmentsPlot;

private void initializePlots() {
  csTransactionsNumGoodPlot = new SeriesPlot(this, 4);
  csTransactionsCostPlot = new SeriesPlot(this, 4);
  csModelStatusPlot = new SeriesPlot(this, 3);
  sbTransactionsNumGoodPlot = new SeriesPlot(this, 3);
  sbModelStatusPlot = new SeriesPlot(this, 2);
  sdModelStatusPlot = new SeriesPlot(this, 2);
  pdModelStatusPlot = new SeriesPlot(this, 2);
  poNumberActivePlot = new SeriesPlot(this, 1);
  poHarassmentsPlot = new SeriesPlot(this, 2);

  SeriesPlot[] allPlots = {
      csTransactionsNumGoodPlot
    , csTransactionsCostPlot
    , csModelStatusPlot
    , sbTransactionsNumGoodPlot
    , sbModelStatusPlot
    , sdModelStatusPlot
    , pdModelStatusPlot
    , poNumberActivePlot
    , poHarassmentsPlot
    };
  
  for (int i = 0; i < allPlots.length; i++) {
    allPlots[i].setPos(plotsX, plotsY);
    allPlots[i].setWidth(plotsWidth);
    allPlots[i].setXaxisValueMax(min(10, util.Clock.getDay(replicationRunTime)));
    allPlots[i].setXaxisTickLabelFormat(new DecimalFormat("0"));
    allPlots[i].setXaxisTitle("model time (days)");
    allPlots[i].setXaxisTitleVisible(true);
    allPlots[i].setHeight(plotsHeight);
    allPlots[i].setYaxisTickLabelRotation(90);
    allPlots[i].setYaxisTitleRotation(90);
    allPlots[i].setYaxisTitleVisible(true);
    allPlots[i].setLegendVisible(true);
    allPlots[i].setLegendContainsValues(true);
    allPlots[i].setPlotTitleVisible(true);
  }

  csTransactionsNumGoodPlot.setYaxisValueMax(200);
  csTransactionsNumGoodPlot.setYaxisTickLabelFormat(new DecimalFormat("0"));
  csTransactionsNumGoodPlot.setYaxisTitle("# transactions");
  csTransactionsNumGoodPlot.setSeriesName(0, "Total");
  csTransactionsNumGoodPlot.setSeriesName(1, "From street brokers");
  csTransactionsNumGoodPlot.setSeriesName(2, "From street dealers");
  csTransactionsNumGoodPlot.setSeriesName(3, "From private dealers");
  csTransactionsNumGoodPlot.setSeriesColor(0, getAgentFillColor(AGENT_CUSTOMER, false));
  csTransactionsNumGoodPlot.setSeriesColor(1, getAgentFillColor(AGENT_STREET_BROKER, false));
  csTransactionsNumGoodPlot.setSeriesColor(2, getAgentFillColor(AGENT_STREET_DEALER, false));
  csTransactionsNumGoodPlot.setSeriesColor(3, getAgentFillColor(AGENT_PRIVATE_DEALER, false));
  csTransactionsNumGoodPlot.setLegendValuesFormat(new DecimalFormat("0"));
  csTransactionsNumGoodPlot.setPlotTitle("Completed Customer Transactions, by Seller");

  csTransactionsCostPlot.setYaxisValueMax(5000);
  csTransactionsCostPlot.setYaxisTickLabelFormat(new DecimalFormat("0"));
  csTransactionsCostPlot.setYaxisTitle("cost ($)");
  csTransactionsCostPlot.setSeriesName(0, "Total");
  csTransactionsCostPlot.setSeriesName(1, "From street brokers");
  csTransactionsCostPlot.setSeriesName(2, "From street dealers");
  csTransactionsCostPlot.setSeriesName(3, "From private dealers");
  csTransactionsCostPlot.setSeriesColor(0, getAgentFillColor(AGENT_CUSTOMER, false));
  csTransactionsCostPlot.setSeriesColor(1, getAgentFillColor(AGENT_STREET_BROKER, false));
  csTransactionsCostPlot.setSeriesColor(2, getAgentFillColor(AGENT_STREET_DEALER, false));
  csTransactionsCostPlot.setSeriesColor(3, getAgentFillColor(AGENT_PRIVATE_DEALER, false));
  csTransactionsCostPlot.setLegendValuesFormat(new DecimalFormat("$#,##0"));
  csTransactionsCostPlot.setPlotTitle("Customer Cost for Transactions, by Seller");

  csModelStatusPlot.setYaxisValueMax(100);
  csModelStatusPlot.setYaxisTickLabelFormat(new DecimalFormat("0"));
  csModelStatusPlot.setYaxisTitle("percent");
  csModelStatusPlot.setSeriesName(0, "Active");
  csModelStatusPlot.setSeriesName(1, "Treatment");
  csModelStatusPlot.setSeriesName(2, "Arrested");
  csModelStatusPlot.setSeriesColor(0, getAgentFillColor(AGENT_CUSTOMER, false));
  csModelStatusPlot.setSeriesColor(1, Color.orange);
  csModelStatusPlot.setSeriesColor(2, getAgentFillColor(AGENT_POLICE, false));
  csModelStatusPlot.setLegendValuesFormat(new DecimalFormat("0.0'%'"));
  csModelStatusPlot.setPlotTitle("Customer Distribution by Model Status");

  sbTransactionsNumGoodPlot.setYaxisValueMax(200);
  sbTransactionsNumGoodPlot.setYaxisTickLabelFormat(new DecimalFormat("0"));
  sbTransactionsNumGoodPlot.setYaxisTitle("# transactions");
  sbTransactionsNumGoodPlot.setSeriesName(0, "Total");
  sbTransactionsNumGoodPlot.setSeriesName(1, "From street dealers");
  sbTransactionsNumGoodPlot.setSeriesName(2, "From private dealers");
  sbTransactionsNumGoodPlot.setSeriesColor(0, getAgentFillColor(AGENT_STREET_BROKER, false));
  sbTransactionsNumGoodPlot.setSeriesColor(1, getAgentFillColor(AGENT_STREET_DEALER, false));
  sbTransactionsNumGoodPlot.setSeriesColor(2, getAgentFillColor(AGENT_PRIVATE_DEALER, false));
  sbTransactionsNumGoodPlot.setLegendValuesFormat(new DecimalFormat("0"));
  sbTransactionsNumGoodPlot.setPlotTitle("Completed Street Broker Transactions, by Seller");

  sbModelStatusPlot.setYaxisValueMax(100);
  sbModelStatusPlot.setYaxisTickLabelFormat(new DecimalFormat("0"));
  sbModelStatusPlot.setYaxisTitle("percent");
  sbModelStatusPlot.setSeriesName(0, "Active");
  sbModelStatusPlot.setSeriesName(1, "Arrested");
  sbModelStatusPlot.setSeriesColor(0, getAgentFillColor(AGENT_STREET_BROKER, false));
  sbModelStatusPlot.setSeriesColor(1, getAgentFillColor(AGENT_POLICE, false));
  sbModelStatusPlot.setLegendValuesFormat(new DecimalFormat("0.0'%'"));
  sbModelStatusPlot.setPlotTitle("Street Broker Distribution by Model Status");

  sdModelStatusPlot.setYaxisValueMax(100);
  sdModelStatusPlot.setYaxisTickLabelFormat(new DecimalFormat("0"));
  sdModelStatusPlot.setYaxisTitle("percent");
  sdModelStatusPlot.setSeriesName(0, "Active");
  sdModelStatusPlot.setSeriesName(1, "Arrested");
  sdModelStatusPlot.setSeriesColor(0, getAgentFillColor(AGENT_STREET_DEALER, false));
  sdModelStatusPlot.setSeriesColor(1, getAgentFillColor(AGENT_POLICE, false));
  sdModelStatusPlot.setLegendValuesFormat(new DecimalFormat("0.0'%'"));
  sdModelStatusPlot.setPlotTitle("Street Dealer Distribution by Model Status");

  pdModelStatusPlot.setYaxisValueMax(100);
  pdModelStatusPlot.setYaxisTickLabelFormat(new DecimalFormat("0"));
  pdModelStatusPlot.setYaxisTitle("percent");
  pdModelStatusPlot.setSeriesName(0, "Active");
  pdModelStatusPlot.setSeriesName(1, "Arrested");
  pdModelStatusPlot.setSeriesColor(0, getAgentFillColor(AGENT_PRIVATE_DEALER, false));
  pdModelStatusPlot.setSeriesColor(1, getAgentFillColor(AGENT_POLICE, false));
  pdModelStatusPlot.setLegendValuesFormat(new DecimalFormat("0.0'%'"));
  pdModelStatusPlot.setPlotTitle("Private Dealer Distribution by Model Status");

  poNumberActivePlot.setYaxisValueMax(police.size());
  poNumberActivePlot.setYaxisTickLabelFormat(new DecimalFormat("0"));
  poNumberActivePlot.setYaxisTitle("# active");
  poNumberActivePlot.setSeriesName(0, "Active");
  poNumberActivePlot.setSeriesColor(0, getAgentFillColor(AGENT_POLICE, false));
  poNumberActivePlot.setLegendValuesFormat(new DecimalFormat("0"));
  poNumberActivePlot.setPlotTitle("Number of Active Police Officers");

  poHarassmentsPlot.setYaxisValueMax(200);
  poHarassmentsPlot.setYaxisTickLabelFormat(new DecimalFormat("0"));
  poHarassmentsPlot.setYaxisTitle("number");
  poHarassmentsPlot.setSeriesName(0, "Harassments");
  poHarassmentsPlot.setSeriesName(1, "Arrests");
  poHarassmentsPlot.setSeriesColor(0, Color.blue);
  poHarassmentsPlot.setSeriesColor(1, Color.red);
  poHarassmentsPlot.setLegendValuesFormat(new DecimalFormat("0"));
  poHarassmentsPlot.setPlotTitle("Number of Harassments and Arrests");
}

private void addPointToPlots() {
  csTransactionsNumGoodPlot.addPoint(0, util.Clock.getCurrentDay(), csAllTransactions.numGood);
  csTransactionsNumGoodPlot.addPoint(1, util.Clock.getCurrentDay(), csSbTransactions.numGood);
  csTransactionsNumGoodPlot.addPoint(2, util.Clock.getCurrentDay(), csSdTransactions.numGood);
  csTransactionsNumGoodPlot.addPoint(3, util.Clock.getCurrentDay(), csPdTransactions.numGood);

  csTransactionsCostPlot.addPoint(0, util.Clock.getCurrentDay(), csAllTransactions.totalPrice);
  csTransactionsCostPlot.addPoint(1, util.Clock.getCurrentDay(), csSbTransactions.totalPrice);
  csTransactionsCostPlot.addPoint(2, util.Clock.getCurrentDay(), csSdTransactions.totalPrice);
  csTransactionsCostPlot.addPoint(3, util.Clock.getCurrentDay(), csPdTransactions.totalPrice);

  int csInTreatment = 0;
  int csInJail = 0;
  int csActive = 0;
  for (int i = 0; i < customer.size(); i++) {
    if (customer.item(i).currentPlace == PLACE_TREATMENT) {
      csInTreatment++;
    }
    else if (customer.item(i).currentPlace == PLACE_JAIL) {
      csInJail++;
    }
    else if (customer.item(i).isActive) {
      csActive++;
    }
    else {
      traceln("***ERROR in Main.animation.addPointToPlots(): Unexpected customer status.");
    }
  }
  csModelStatusPlot.addPoint(0, util.Clock.getCurrentDay(), 100.0 * csActive / customer.size());
  csModelStatusPlot.addPoint(1, util.Clock.getCurrentDay(), 100.0 * csInTreatment / customer.size());
  csModelStatusPlot.addPoint(2, util.Clock.getCurrentDay(), 100.0 * csInJail / customer.size());

  sbTransactionsNumGoodPlot.addPoint(0, util.Clock.getCurrentDay(), sbAllTransactions.numGood);
  sbTransactionsNumGoodPlot.addPoint(1, util.Clock.getCurrentDay(), sbSdTransactions.numGood);
  sbTransactionsNumGoodPlot.addPoint(2, util.Clock.getCurrentDay(), sbPdTransactions.numGood);

  int sbInJail = 0;
  int sbActive = 0;
  for (int i = 0; i < streetBroker.size(); i++) {
    if (streetBroker.item(i).currentPlace == PLACE_JAIL) {
      sbInJail ++;
    }
    else if (streetBroker.item(i).isActive) {
      sbActive++;
    }
    else {
      traceln("***ERROR in Main.animation.addPointToPlots(): Unexpected street broker status.");
    }
  }
  sbModelStatusPlot.addPoint(0, util.Clock.getCurrentDay(), 100.0 * sbActive / streetBroker.size());
  sbModelStatusPlot.addPoint(1, util.Clock.getCurrentDay(), 100.0 * sbInJail / streetBroker.size());

  int sdInJail = 0;
  int sdActive = 0;
  for (int i = 0; i < streetDealer.size(); i++) {
    if (streetDealer.item(i).currentPlace == PLACE_JAIL) {
      sdInJail ++;
    }
    else if (streetDealer.item(i).isActive) {
      sdActive++;
    }
    else {
      traceln("***ERROR in Main.animation.addPointToPlots(): Unexpected street dealer status.");
    }
  }
  sdModelStatusPlot.addPoint(0, util.Clock.getCurrentDay(), 100.0 * sdActive / streetDealer.size());
  sdModelStatusPlot.addPoint(1, util.Clock.getCurrentDay(), 100.0 * sdInJail / streetDealer.size());

  int pdInJail = 0;
  int pdActive = 0;
  for (int i = 0; i < privateDealer.size(); i++) {
    if (privateDealer.item(i).currentPlace == PLACE_JAIL) {
      pdInJail ++;
    }
    else if (privateDealer.item(i).isActive) {
      pdActive++;
    }
    else {
      traceln("***ERROR in Main.animation.addPointToPlots(): Unexpected street dealer status.");
    }
  }
  pdModelStatusPlot.addPoint(0, util.Clock.getCurrentDay(), 100.0 * pdActive / privateDealer.size());
  pdModelStatusPlot.addPoint(1, util.Clock.getCurrentDay(), 100.0 * pdInJail / privateDealer.size());

  poNumberActivePlot.addPoint(0, util.Clock.getCurrentDay(), police.sum("isActive"));

  poHarassmentsPlot.addPoint(0, util.Clock.getCurrentDay(), police.sum("numHarassments"));
  poHarassmentsPlot.addPoint(1, util.Clock.getCurrentDay(), police.sum("numArrests"));
}

private void setPlotVisibility() {
  csTransactionsNumGoodPlot.setVisible(agentPov == AGENT_CUSTOMER && optionFilter == FILTER_ALL && optionCsAllStat == STAT_CS_ALL_TRANSACTIONS_GOOD);
  csTransactionsCostPlot.setVisible(agentPov == AGENT_CUSTOMER && optionFilter == FILTER_ALL && optionCsAllStat == STAT_CS_ALL_TRANSACTIONS_COST);
  csModelStatusPlot.setVisible(agentPov == AGENT_CUSTOMER && optionFilter == FILTER_ALL && optionCsAllStat == STAT_CS_ALL_MODEL_STATUS);
  sbTransactionsNumGoodPlot.setVisible(agentPov == AGENT_STREET_BROKER && optionFilter == FILTER_ALL && optionSbAllStat == STAT_SB_ALL_TRANSACTIONS_GOOD);
  sbModelStatusPlot.setVisible(agentPov == AGENT_STREET_BROKER && optionFilter == FILTER_ALL && optionSbAllStat == STAT_SB_ALL_MODEL_STATUS);
  sdModelStatusPlot.setVisible(agentPov == AGENT_STREET_DEALER && optionFilter == FILTER_ALL && optionSdAllStat == STAT_SD_ALL_MODEL_STATUS);
  pdModelStatusPlot.setVisible(agentPov == AGENT_PRIVATE_DEALER && optionFilter == FILTER_ALL && optionPdAllStat == STAT_PD_ALL_MODEL_STATUS);
  poNumberActivePlot.setVisible(agentPov == AGENT_POLICE && optionFilter == FILTER_ALL && optionPoAllStat == STAT_PO_ALL_NUMBER_ACTIVE);
  poHarassmentsPlot.setVisible(agentPov == AGENT_POLICE && optionFilter == FILTER_ALL && optionPoAllStat == STAT_PO_ALL_HARASSMENTS);
}


//---------------------------------------------------------------------
// Process mouse clicks.
//

protected void onMouseClicked(java.awt.Point p) {
  if (filterBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (csAllStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (csSelectedStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (sbAllStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (sbSelectedStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (sdAllStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (sdSelectedStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (pdAllStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (pdSelectedStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (poAllStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (poSelectedStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (mkAllStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }
  if (mkSelectedStatBox.handleClick(p.getX(), p.getY())) {
    return;
  }

  if (cityRectangle.contains(p.getX(), p.getY())) {
    if (agentPov == AGENT_CUSTOMER) {
      for (int i = 0; i < customer.size(); i++) {
        if (customer.item(i).animation.agentShape.contains(p.getX(), p.getY())) {
          selectAgent(agentPov, i);
          return;
        }
      }
    }
    else if (agentPov == AGENT_STREET_BROKER) {
      for (int i = 0; i < streetBroker.size(); i++) {
        if (streetBroker.item(i).animation.agentShape.contains(p.getX(), p.getY())) {
          selectAgent(agentPov, i);
          return;
        }
      }
    }
    else if (agentPov == AGENT_STREET_DEALER) {
      for (int i = 0; i < streetDealer.size(); i++) {
        if (streetDealer.item(i).animation.agentShape.contains(p.getX(), p.getY())) {
          selectAgent(agentPov, i);
          return;
        }
      }
    }
    else if (agentPov == AGENT_PRIVATE_DEALER) {
      for (int i = 0; i < privateDealer.size(); i++) {
        if (privateDealer.item(i).animation.agentShape.contains(p.getX(), p.getY())) {
          selectAgent(agentPov, i);
          return;
        }
      }
    }
    else if (agentPov == AGENT_HOMELESS) {
      for (int i = 0; i < homeless.size(); i++) {
        if (homeless.item(i).animation.agentShape.contains(p.getX(), p.getY())) {
          selectAgent(agentPov, i);
          return;
        }
      }
    }
    else if (agentPov == AGENT_POLICE) {
      for (int i = 0; i < police.size(); i++) {
        if (police.item(i).animation.agentShape.contains(p.getX(), p.getY())) {
          selectAgent(agentPov, i);
          return;
        }
      }
    }
    else if (agentPov == AGENT_MARKET) {
      for (int i = 0; i < market.size(); i++) {
        if (market.item(i).animation.agentShape.contains(p.getX(), p.getY())) {
          selectAgent(agentPov, i);
          return;
        }
      }
    }
  }
}


private void disableAppletButtons() {
  // This function will disable or remove buttons from the toolbar that is added
  // to the top of applets. This is particularly important for the Reset button,
  // since its behavior does not work as expected.
  //
  // To disable a button, use setEnabled(false). This will leave the button in
  // place but gray it out.
  //
  // To remove a button, use setVisible(false). Note however, that the button
  // to the right of the removed button will slide over into its place. The
  // next time this function is called, that button will be removed, the one to
  // its right will slide over, and so on.

  if (util.Model.getRunMode().equals("applet")) {
    java.awt.Component toolbar = Engine.getAnimation().getToolbar();
  
    // disable applet Reset button
    toolbar.getComponentAt(75, 15).setEnabled(false);
  
    // Run button -- x = 0, y = 1, width = 30, height = 30
    //toolbar.getComponentAt(15, 15);
  
    // Pause button -- x = 30, y = 1, width = 30, height = 30
    //toolbar.getComponentAt(45, 15);
  
    // Reset button -- x = 60, y = 1, width = 30, height = 30
    //toolbar.getComponentAt(75, 15);
  
    // Model speed slider -- x = 90, y = 0, width = 120, height = 32
    //toolbar.getComponentAt(105, 15);
  
    // Animation Options button -- x = 210, y = 1, width = 30, height = 30
    //toolbar.getComponentAt(225, 15);
  
    // blank rectangle - javax.swing.JPanel object
    //toolbar.getComponentAt(255, 15);
  
    // XJ logo -- x = (applet width - 92), y = 0, width = 92, height = 32
    //toolbar.getComponentAt(850, 15);
  }
}
Picture
Button button11
Fill color agentPov ==AGENT_MARKET ? inPovButtonColor : outPovButtonColor
Label Market
Event handling code setPov(AGENT_MARKET);
Button button10
Label >
Event handling code int oldId = selectedAgents[agentPov];
int newId;

if (oldId == agentLists[agentPov].size() - 1) {
  newId = 0;
}
else {
  newId = oldId + 1;
}

selectAgent(agentPov, newId);
Button button9
Label <
Event handling code int oldId = selectedAgents[agentPov];
int newId;

if (oldId > 0) {
  newId = oldId - 1;
}
else {
  newId = agentLists[agentPov].size() - 1;
}

selectAgent(agentPov, newId);
Checkbox zoomLevelCheckbox
Label Zoom selected market
Event handling code if (getValue()) {
  setZoomLevel(ZOOM_MARKET);
}
else {
  setZoomLevel(ZOOM_CITY);
}
Button button5
Fill color agentPov ==AGENT_POLICE ? inPovButtonColor : outPovButtonColor
Label Police
Event handling code setPov(AGENT_POLICE);
Button button4
Fill color agentPov ==AGENT_PRIVATE_DEALER ? inPovButtonColor : outPovButtonColor
Label Private Dealer
Event handling code setPov(AGENT_PRIVATE_DEALER);
Button button3
Fill color agentPov == AGENT_STREET_DEALER ? inPovButtonColor : outPovButtonColor
Label Street Dealer
Event handling code setPov(AGENT_STREET_DEALER);
Button button2
Fill color agentPov == AGENT_STREET_BROKER ? inPovButtonColor : outPovButtonColor
Label Street Broker
Event handling code setPov(AGENT_STREET_BROKER);
Button button
Label Pick Random
Event handling code int size = agentLists[agentPov].size() - 1;
selectAgent(agentPov, uniform_discr(size));
Editbox selectedAgentEdit
Label editBox
Event handling code try {
  // Integer.parseInt will throw a NumberFormatException if
  // its argument does not contain an integer.
  int newID = Integer.parseInt(getValue());

  // We throw our own NumberFormatException if the newID is
  // out of range.
  if (newID < 0 || newID >= agentLists[agentPov].size()) {
    throw new NumberFormatException();
  }

  // If we made it this far without an exception, the newID
  // is valid, so go ahead and select the corresponding agent.
  selectAgent(agentPov, newID);
}
catch (NumberFormatException e) {
  // If an invalid id was entered, reset the text box to its
  // last valid value.
  setValue(selectedAgents[agentPov]);
}
Button button1
Fill color agentPov == AGENT_CUSTOMER ? inPovButtonColor : outPovButtonColor
Label Customer
Event handling code setPov(AGENT_CUSTOMER);
Encapsulated encapsulatedAnimation7
Object homeless
Pivot pivot6
X statsX
Y statsY
Visible agentPov == AGENT_PRIVATE_DEALER && optionFilter == FILTER_SELECTED && optionPdSelectedStat == STAT_PD_SELECTED_ALL
Pivot pivot3
X statsX
Y statsY
Visible agentPov == AGENT_STREET_DEALER && optionFilter == FILTER_SELECTED && optionSdSelectedStat == STAT_SD_SELECTED_ALL
Encapsulated encapsulatedAnimation5
Object police
Text statBoxLabel
X statBoxX
Y optionBoxY - 20
Text
Stats to show:
Text filterBoxLabel
X filterBoxX
Y optionBoxY - 20
Text
Show stats for:
Rectangle rectangle11
Line color outlineColor
Pivot pivot5
X statsX
Y statsY
Visible agentPov == AGENT_STREET_BROKER && optionFilter == FILTER_SELECTED && optionSbSelectedStat == STAT_SB_SELECTED_ALL
Line line2
Line color outlineColor
Pivot pivot4
X statsX
Y statsY
Visible agentPov == AGENT_CUSTOMER && optionFilter == FILTER_SELECTED && optionCsSelectedStat == STAT_CS_SELECTED_ALL
Rectangle rectangle8
Line color outlineColor
Text text39
Visible false
Text
Encapsulated animations
Pivot pivot2
X statsX
Y statsY
Visible false /*agentPov == AGENT_STREET_BROKER && optionFilter == FILTER_ALL && optionSbAllStat == STAT_SB_ALL_ALL*/
Pivot pivot1
X statsX
Y statsY
Visible false /*agentPov == AGENT_CUSTOMER && optionFilter == FILTER_ALL*/
Rectangle rectangle7
Fill color getAgentFillColor(AGENT_MARKET, false)
Line color getAgentLineColor(AGENT_MARKET, false)
Rectangle rectangle6
Fill color getAgentFillColor(AGENT_HOMELESS, false)
Line color getAgentLineColor(AGENT_HOMELESS, false)
Rectangle rectangle5
Fill color getAgentFillColor(AGENT_POLICE, false)
Line color getAgentLineColor(AGENT_POLICE, false)
Rectangle rectangle4
Fill color getAgentFillColor(AGENT_PRIVATE_DEALER, false)
Line color getAgentLineColor(AGENT_PRIVATE_DEALER, false)
Rectangle rectangle3
Fill color getAgentFillColor(AGENT_STREET_DEALER, false)
Line color getAgentLineColor(AGENT_STREET_DEALER, false)
Rectangle rectangle2
Fill color getAgentFillColor(AGENT_STREET_BROKER, false)
Line color getAgentLineColor(AGENT_STREET_BROKER, false)
Rectangle rectangle1
Fill color getAgentFillColor(AGENT_CUSTOMER, false)
Line color getAgentLineColor(AGENT_CUSTOMER, false)
Encapsulated encapsulatedAnimation4
Object privateDealer
Encapsulated encapsulatedAnimation3
Object streetBroker
Encapsulated encapsulatedAnimation2
Object market
Encapsulated encapsulatedAnimation1
Object streetDealer
Encapsulated encapsulatedAnimation
Object customer
Rectangle cityRectangle
Line color outlineColor
Rectangle rectangle9
Fill color titleColor
Line color outlineColor
Rectangle rectangle12
Fill color subtitleColor

Active Object: Market

General
Import import message.*;
import misc.*;

Parameters
Name x
Type real
Name y
Type real
Name width
Type real
Name height
Type real

Icon
Picture

Structure
Picture
Variable homeless
Variable type List
Initial value new ArrayList()
Variable main
Variable type Main
Initial value (Main)getOwner()
Variable streetBrokers
Variable type List
Initial value new ArrayList()
Variable isOpen
Variable type boolean
Variable streetDealers
Variable type List
Initial value new ArrayList()

Algorithmic Functions
Name addHomeless
Type void
Arguments
Type Name
Homeless hm
Body if (!homeless.contains(hm)) {
  homeless.add(hm);
}


Name addStreetBroker
Type void
Arguments
Type Name
StreetBroker sb
Body if (!streetBrokers.contains(sb)) {
  streetBrokers.add(sb);
}
Name addStreetDealer
Type void
Arguments
Type Name
StreetDealer sd
Body if (!streetDealers.contains(sd)) {
  streetDealers.add(sd);
}
Name close
Type void
Body isOpen = false;
Name getLocationInMarketX
Type real
Arguments
Type Name
real loc
Body return loc - (x - width/2);
Name getLocationInMarketY
Type real
Arguments
Type Name
real loc
Body return loc - (y - height/2);
Name getRandomX
Type real
Body return uniform(x - width / 2, x + width / 2);
Name getRandomY
Type real
Body return uniform(y - height / 2, y + height / 2);
Name isContainedInRectangle
Type boolean
Arguments
Type Name
real rectX
real rectY
real rectWidth
real rectHeight
Body return
     rectX <= x - width / 2
  && rectX + rectWidth >= x + width / 2
  && rectY <= y - height / 2
  && rectY + rectHeight >= y + height / 2
  ;
Name isLocationInMarket
Type boolean
Arguments
Type Name
real locX
real locY
Body return
     locX >= x - width / 2
  && locX <= x + width / 2
  && locY >= y - height / 2
  && locY <= y + height / 2
  ;

Name isOverlappingMarket
Type boolean
Arguments
Type Name
Market m
Body return
  // is one of this Market's corners inside Market m?
     m.isLocationInMarket(x - width / 2, y - height / 2)
  || m.isLocationInMarket(x + width / 2, y - height / 2)
  || m.isLocationInMarket(x + width / 2, y + height / 2)
  || m.isLocationInMarket(x - width / 2, y + height / 2)

  // is one of Market m's corners inside this market?
  || isLocationInMarket(m.x - m.width / 2, m.y - m.height / 2)
  || isLocationInMarket(m.x + m.width / 2, m.y - m.height / 2)
  || isLocationInMarket(m.x + m.width / 2, m.y + m.height / 2)
  || isLocationInMarket(m.x - m.width / 2, m.y + m.height / 2)
  ;
Name open
Type void
Body isOpen = true;
Name removeHomeless
Type void
Arguments
Type Name
Homeless hm
Body homeless.remove(hm);

Name removeStreetBroker
Type void
Arguments
Type Name
StreetBroker sb
Body streetBrokers.remove(sb);
Name removeStreetDealer
Type void
Arguments
Type Name
StreetDealer sd
Body streetDealers.remove(sd);

Animation
Name animation
X 0
Y 0
Additional class code public boolean isSelected = false;
Picture
Rectangle agentShape
X main.animation.getAgentLocationX(main.AGENT_MARKET, isSelected, x - width / 2)
Y main.animation.getAgentLocationY(main.AGENT_MARKET, isSelected, y - height / 2)
Width main.animation.distanceToPixels(width)
Height main.animation.distanceToPixels(height)
Line color main.animation.getAgentLineColor(main.AGENT_MARKET, isSelected)
Line width main.animation.getAgentLineWidth(main.AGENT_MARKET, isSelected)
Visible main.animation.getAgentVisibility(main.AGENT_MARKET, isSelected, x, y)

Active Object: Police

General
Import import message.*;
import misc.*;
Additional class code public static final int TYPE_REGULAR = 0;
public static final int TYPE_RESERVE = 1;
public static final int NUM_TYPES = 2;

// Our convention is to avoid defining classes and functions here in the
// additional class code because it is somewhat hidden. However, we break
// that convention here because:
// 1) We want to use it by passing parameters to it when we instantiate it.
// This prevents us from defining it as an ActiveObject.
// 2) This is such a simple class that we do not bother defining it in an
// external file.
private class HarassTarget {
  public HarassTarget(PoliceTarget agent, double harassProb, double arrestProb) {
    this.agent = agent;
    this.harassProb = harassProb;
    this.arrestProb = arrestProb;
  }

  public PoliceTarget agent;
  public double harassProb;
  public double arrestProb;
}

Parameters
Name initialMarket
Type Market
Name type
Type integer

Icon
Picture

Structure
Picture
Variable arrestTarget
Variable type PoliceTarget
Initial value null
Variable numArrests
Variable type integer
Initial value 0
Variable numHarassments
Variable type integer
Initial value 0
Variable isOnBust
Variable type boolean
Initial value false
Variable isMakingArrest
Variable type boolean
Initial value false
Variable harassTarget
Variable type HarassTarget
Initial value null
Variable isActive
Variable type boolean
Initial value false
Variable main
Variable type Main
Initial value (Main)getOwner()
Variable self
Variable type Police
Initial value this
Variable market
Variable type Market
Initial value initialMarket
Port travelCompleteListener
Message type TravelCompleteMsg
Statechart simulationState
Object location
Type misc.Location
Parameters
Name Value
startX market.getRandomX()
startY market.getRandomY()

Statechart
Name simulationState
Picture
ChoicePoint checkArrest
Action checkArrest();
ChoicePoint seekHarassTarget
Action seekHarassTarget();
Transition transition8
Source/target branch=>inactive
Fire If guard is open
Guard type == Police.TYPE_RESERVE
Transition transition7
Source/target branch=>active
Fire If guard is open
Guard type == Police.TYPE_REGULAR
Transition transition6
Source/target inactive=>active
Fire Signal event occurs
Signal event BustStartMsg
Action applyBustStartMsg((BustStartMsg)getEvent());
Transition transition5
Source/target randomWalk=>inactive
Fire Signal event occurs
Guard type == Police.TYPE_RESERVE
Signal event BustEndMsg
Action applyBustEndMsg((BustEndMsg)getEvent());
Transition transition4
Source/target checkArrest=>randomWalk
Fire Signal event occurs
Guard arrestTarget == null
Action PoliceTarget pt = (PoliceTarget)harassTarget.agent;
pt.receiveHarassCompleteMsg(new HarassCompleteMsg());
Transition transition3
Source/target harass=>checkArrest
Fire Timeout
Timeout Main.poHarassDuration
Transition transition2
Source/target checkArrest=>arrest
Fire Signal event occurs
Guard arrestTarget != null
Transition transition1
Source/target arrest=>randomWalk
Fire Timeout
Timeout Main.poArrestDuration
Transition transition
Source/target seekHarassTarget=>harass
Fire Signal event occurs
Guard harassTarget != null
Transition transition31
Source/target randomWalk=>seekHarassTarget
Fire Signal event occurs
Signal event TravelCompleteMsg
Transition transition28
Source/target seekHarassTarget=>randomWalk
Fire Signal event occurs
Guard harassTarget == null
Signal event TargetFoundMsg
State randomWalk
Entry action wanderInMarket();
State arrest
Deferred events new BustEndEvent()
Entry action isMakingArrest = true;
arrest();
Exit action isMakingArrest = false;
State harass
Deferred events new BustEndEvent()
Entry action harass();
State active
Entry action isActive = true;
Exit action isActive = false;

Algorithmic Functions
Name applyBustEndMsg
Type void
Arguments
Type Name
BustEndMsg msg
Body isOnBust = false;
Name applyBustStartMsg
Type void
Arguments
Type Name
BustStartMsg msg
Body isOnBust = true;
Name arrest
Type void
Body ArrestMsg msg = new ArrestMsg();
msg.duration = Main.poJailPerDrugUnit * arrestTarget.getDrugsOnPerson();
arrestTarget.receiveArrestMsg(msg);

numArrests++;
Name checkArrest
Type void
Body arrestTarget = null;

PoliceTarget pt = (PoliceTarget)harassTarget.agent;

if (pt.getDrugsOnPerson() > 0 && randomTrue(harassTarget.arrestProb)) {
  arrestTarget = pt;
}

Name harass
Type void
Body PoliceTarget pt = (PoliceTarget)harassTarget.agent;
pt.receiveHarassMsg(new HarassMsg());
numHarassments++;
Name receiveBustEndMsg
Type void
Arguments
Type Name
BustEndMsg msg
Body if (msg.market == market) {
  // We handle BustEndMsg messages differently depending on the
  // officer type. Regular officers do not respond to these messages
  // in their statecharts. Instead, they immediately change their
  // parameters to non-bust parameters, which is done in applyBustEndMsg().
  // Reserve officers do respond to these messages in their statecharts,
  // so for them we just pass the message along to the statechart.
  if (type == Police.TYPE_REGULAR) {
    applyBustEndMsg(msg);
  }
  else if (type == Police.TYPE_RESERVE) {
    simulationState.fireEvent(msg);
  }
  else {
    traceln("***ERROR in Police.receiveBustEndMsg(): Unexpected officer type [" + type + "] for " + self + " @ " + Engine.getTime());
  }
}
Name receiveBustStartMsg
Type void
Arguments
Type Name
BustStartMsg msg
Body if (msg.market == market) {
  // We handle BustStartMsg messages differently depending on the
  // officer type. Regular officers do not respond to these messages
  // in their statecharts. Instead, they immediately change their
  // parameters to bust parameters, which is done in applyBustStartMsg().
  // Reserve officers do respond to these messages in their statecharts,
  // so for them we just pass the message along to the statechart.
  if (type == Police.TYPE_REGULAR) {
    applyBustStartMsg(msg);
  }
  else if (type == Police.TYPE_RESERVE) {
    simulationState.fireEvent(msg);
  }
  else {
    traceln("***ERROR in Police.receiveBustStartMsg(): Unexpected officer type [" + type + "] for " + self + " @ " + Engine.getTime());
  }
}
Name seekHarassTarget
Type void
Body // Create a list of all the possible people we can harass.

List targets = new ArrayList();

for (int i = 0; i < market.streetBrokers.size(); i++) {
  targets.add(new HarassTarget((PoliceTarget)market.streetBrokers.get(i)
                               , isOnBust ? Main.poBustProbHarassStreetBroker : Main.poProbHarassStreetBroker
                               , isOnBust ? Main.poBustProbArrestStreetBroker : Main.poProbArrestStreetBroker
                              ));
}
for (int i = 0; i < market.streetDealers.size(); i++) {
  targets.add(new HarassTarget((PoliceTarget)market.streetDealers.get(i)
                               , isOnBust ? Main.poBustProbHarassStreetDealer : Main.poProbHarassStreetDealer
                               , isOnBust ? Main.poBustProbArrestStreetDealer : Main.poProbArrestStreetDealer
                              ));
}
for (int i = 0; i < market.homeless.size(); i++) {
  targets.add(new HarassTarget((PoliceTarget)market.homeless.get(i)
                               , isOnBust ? Main.poBustProbHarassHomeless : Main.poProbHarassHomeless
                               , isOnBust ? Main.poBustProbArrestHomeless : Main.poProbArrestHomeless
                              ));
}

for (int i = 0; i < main.customer.size(); i++) {
  targets.add(new HarassTarget((PoliceTarget)main.customer.get(i)
                               , isOnBust ? Main.poBustProbHarassCustomer : Main.poProbHarassCustomer
                               , isOnBust ? Main.poBustProbArrestCustomer : Main.poProbArrestCustomer
                              ));
}
for (int i = 0; i < main.privateDealer.size(); i++) {
  targets.add(new HarassTarget((PoliceTarget)main.privateDealer.get(i)
                               , isOnBust ? Main.poBustProbHarassPrivateDealer : Main.poProbHarassPrivateDealer
                               , isOnBust ? Main.poBustProbArrestPrivateDealer : Main.poProbArrestPrivateDealer
                              ));
}



// Sort the list randomly.

util.List.shuffle(targets);


// Scan the list of suspects and find the first one within our vision
// radius that we have not harassed recently. If we find one, he will
// be our harassment target with probability equal to his harassment
// probability.

harassTarget = null;

double visionRadius = isOnBust ? Main.poBustVisionRadius : Main.poVisionRadius;

for (int i = 0; i < targets.size(); i++) {
  HarassTarget ht = (HarassTarget)targets.get(i);
  PoliceTarget pt = (PoliceTarget)ht.agent;

  if ( pt.isInPublic()
      && location.distance(pt.getLocation().currentX, pt.getLocation().currentY) <= visionRadius
      && !pt.isBeingHarassed()
      && (pt.getLastHarassTime() == 0 || Engine.getTime() - pt.getLastHarassTime() > Main.poHarassRepeatDelay)
      && randomTrue(ht.harassProb)
     ) {
     harassTarget = ht;
     break;
  }
}
Name wanderInMarket
Type void
Body location.wanderInMarket(market);

Animation
Name animation
X 0
Y 0
Additional class code public boolean isSelected = false;
Picture
Rectangle agentShape
X main.animation.getAgentLocationX(main.AGENT_POLICE, isSelected, location.currentX)
Y main.animation.getAgentLocationY(main.AGENT_POLICE, isSelected, location.currentY)
Width main.animation.getAgentSize(main.AGENT_POLICE, isSelected)
Height main.animation.getAgentSize(main.AGENT_POLICE, isSelected)
Fill color main.animation.getAgentFillColor(main.AGENT_POLICE, isSelected)
Line color main.animation.getAgentLineColor(main.AGENT_POLICE, isSelected)
Line width main.animation.getAgentLineWidth(main.AGENT_POLICE, isSelected)
Visible isActive && !isMakingArrest && main.animation.getAgentVisibility(main.AGENT_POLICE, isSelected, location.currentX, location.currentY)

Active Object: PrivateDealer

General
Import import message.*;
import misc.*;

Parameters
Name homeX
Type real
Name homeY
Type real
Name initialMoney
Type real
Name initialInventory
Type real
Name probDeliverHome
Type real
Name probDeliverMarket
Type real
Name dealDelayMin
Type real
Name dealDelayMax
Type real
Name shiftStartHourMin
Type real
Name shiftStartHourMax
Type real
Name shiftLengthMin
Type real
Name shiftLengthMax
Type real
Name invitationDeals
Type integer
Name acceptableDeliverUnits
Type real

Icon
Picture

Structure
Picture
ChartTimer shiftEndTimer
Expire No (manual mode)
Expiry Action // Alert the dealer's statechart that he is ending his shift.
simulationState.fireEvent(new ShiftEndMsg());
ChartTimer dailyTimer
Timeout util.Units.days(1)
Expire At Startup Yes
Expiry Action if (isActive) {
  double shiftStart = uniform(util.Units.hours(shiftStartHourMin), util.Units.hours(shiftStartHourMax));
  shiftStartTimer.restart(shiftStart);
}
ChartTimer shiftStartTimer
Expire No (manual mode)
Expiry Action if (!isOnShift) {
  // The dealer is off shift, so we can safely start his shift.

  // Reset the dealer's inventory.
  //
  // For now, we assume that private dealers replenish their
  // inventories between shifts as needed, and that they never
  // have problems with shortages. We implement this assumption
  // by resetting the inventory to its initial value every day.
  inventory = initialInventory;
  
  // Alert the dealer's statechart that he is starting his shift.
  simulationState.fireEvent(new ShiftStartMsg());

  // Start a timer to expire when the dealer's shift ends.
  shiftEndTimer.restart(uniform(shiftLengthMin, shiftLengthMax));
}
else {
  // The dealer is on shift, so we need to wait until he ends
  // his current shift before starting the next one. We add
  // a minute to make sure we restart after the shift ends.

  restart(shiftEndTimer.getRest() + util.Units.minutes(1));
}
Variable isWaitingOnBuyer
Variable type boolean
Initial value false
Variable lastHarassTime
Variable type real
Initial value 0
Variable isBeingHarassed
Variable type boolean
Initial value false
Variable isOnDeal
Variable type boolean
Initial value false
Variable possessionUnits
Variable type real
Initial value 0
Variable currentPlace
Variable type integer
Initial value Main.PLACE_HOME
Variable numArrests
Variable type real
Initial value 0
Variable arrestDuration
Variable type real
Variable main
Variable type Main
Initial value (Main)getOwner()
Variable money
Variable type real
Initial value initialMoney
Variable inventory
Variable type real
Initial value initialInventory
Variable self
Variable type PrivateDealer
Initial value this
Variable buyerY
Variable type real
Initial value 0
Variable buyerX
Variable type real
Initial value 0
Variable dealFromBuyer
Variable type DealRequestMsg
Initial value null
Variable isOnShift
Variable type boolean
Initial value false
Variable isActive
Variable type boolean
Initial value false
Port travelCompleteListener
Message type TravelCompleteMsg
Statechart simulationState
Object location
Type misc.Location
Parameters
Name Value
startX homeX
startY homeY
Object customerHistories
Type misc.AgentHistoryList

Statechart
Name simulationState
Picture
History preHarassed
Type Shallow
Action // NOTE:
//
// Due to a bug in AnyLogic 5, a deep history state
// will not transition to a simple state that is
// inside a composite state when the composite state
// is on the same level as the deep history state.
// Instead, it will cause a runtime error. Therefore
// we avoid deep history states and live with the
// limitations of shallow history states.
Transition transition25
Source/target harassed=>preHarassed
Fire Signal event occurs
Signal event HarassCompleteMsg
Action applyHarassCompleteMsg((HarassCompleteMsg)getEvent());
Transition transition24
Source/target state=>harassed
Fire Signal event occurs
Signal event HarassMsg
Action applyHarassMsg((HarassMsg)getEvent());
Transition transition23
Source/target waitForBuyer=>available
Fire Timeout
Timeout Main.pdDealQuitTime
Action isWaitingOnBuyer = false;
quitDeal();
Transition transition22
Source/target isBuyerWaiting4=>travelToHome
Fire If all other guards are closed
Action endDeal(false);
Transition transition21
Source/target isBuyerWaiting4=>travelToHome
Fire If guard is open
Guard dealFromBuyer.buyer.isWaitingOnSeller(dealFromBuyer.dealID)
Action possessionUnits = 0;
endDeal(true);
Transition transition20
Source/target isBuyerWaiting3=>travelToHome
Fire If all other guards are closed
Action endDeal(false);
Transition transition19
Source/target isBuyerWaiting3=>onDeal
Fire If guard is open
Guard dealFromBuyer.buyer.isWaitingOnSeller(dealFromBuyer.dealID)
Action sendWaitCompleteMsg();
Transition transition18
Source/target isBuyerWaiting2=>available
Fire If all other guards are closed
Action endDeal(false);
Transition transition17
Source/target isBuyerWaiting2=>available
Fire If guard is open
Guard dealFromBuyer.buyer.isWaitingOnSeller(dealFromBuyer.dealID)
Action endDeal(true);
Transition transition15
Source/target arrested=>active
Fire Timeout
Timeout arrestDuration
Transition transition16
Source/target active=>arrested
Fire Signal event occurs
Signal event ArrestMsg
Action applyArrestMsg((ArrestMsg)getEvent());
Transition transition14
Source/target willAcceptDeal=>available
Fire If all other guards are closed
Action sendDealDeniedMsg(dealFromBuyer);
Transition transition13
Source/target isBuyerWaiting=>available
Fire If all other guards are closed
Transition transition12
Source/target isBuyerWaiting=>willAcceptDeal
Fire If guard is open
Guard dealFromBuyer.buyer.isWaitingOnSeller(dealFromBuyer.dealID)
Transition transition11
Source/target willAcceptDeal=>willDeliver
Fire If guard is open
Guard willAcceptDeal(dealFromBuyer.buyer, dealFromBuyer.units, dealFromBuyer.price)
Action startDeal();
Transition transition10
Source/target available=>offShift
Fire Immediately
Guard inventory < util.Params.getDouble(Main.dealUnitsList, 0)
Transition transition9
Source/target waitForBuyer=>onDeal1
Fire Signal event occurs
Signal event WaitCompleteMsg
Action applyWaitCompleteMsg((WaitCompleteMsg)getEvent());
isWaitingOnBuyer = false;
Transition transition8
Source/target willDeliver=>travelToBuyer
Fire If guard is open
Guard willDeliver()
Action sendDealerDeliversMsg(true);
Transition transition7
Source/target onDeal1=>isBuyerWaiting2
Fire Timeout
Timeout uniform(dealDelayMin, dealDelayMax)
Transition transition6
Source/target willDeliver=>waitForBuyer
Fire If all other guards are closed
Action sendDealerDeliversMsg(false);
isWaitingOnBuyer = true;
Transition transition5
Source/target travelToHome=>available
Fire Signal event occurs
Signal event TravelCompleteMsg
Action currentPlace = Main.PLACE_HOME;
Transition transition4
Source/target onDeal=>isBuyerWaiting4
Fire Timeout
Timeout uniform(dealDelayMin, dealDelayMax)
Transition transition3
Source/target travelToBuyer=>isBuyerWaiting3
Fire Signal event occurs
Signal event TravelCompleteMsg
Action currentPlace = dealFromBuyer.buyerPlace;
Transition transition2
Source/target available=>isBuyerWaiting
Fire Signal event occurs
Signal event DealRequestMsg
Action applyDealRequestMsg((DealRequestMsg)getEvent());
Transition transition1
Source/target available=>offShift
Fire Signal event occurs
Signal event ShiftEndMsg
Transition transition
Source/target offShift=>onShift
Fire Signal event occurs
Signal event ShiftStartMsg
State travelToHome
Deferred events new ShiftEndEvent(), new DealRequestEvent()
Entry action currentPlace = Main.PLACE_TRANSIT;
travelToHome();
State onDeal1
Deferred events new ShiftEndEvent(), new DealRequestEvent()
State waitForBuyer
Deferred events new ShiftEndEvent(), new DealRequestEvent()
State onDeal
Deferred events new ShiftEndEvent(), new DealRequestEvent()
Exit action
State travelToBuyer
Deferred events new ShiftEndEvent(), new DealRequestEvent()
Entry action currentPlace = Main.PLACE_TRANSIT;
possessionUnits = dealFromBuyer.units;
travelToBuyer();
State available
Exit action
State harassed
Deferred events new GenericEvent()
Entry action isBeingHarassed = true;
Exit action isBeingHarassed = false;
State onShift
Entry action isOnShift = true;
Exit action isOnShift = false;
isWaitingOnBuyer = false;

denyPendingRequests();
if (isOnDeal) {
  sendDealDeniedMsg(dealFromBuyer);
  isOnDeal = false;
}

shiftEndTimer.reset();
State offShift
Entry action currentPlace = Main.PLACE_HOME;
Exit action
State arrested
Entry action currentPlace = Main.PLACE_JAIL;
State active
Entry action isActive = true;
location.currentX = homeX;
location.currentY = homeY;
Exit action isActive = false;

Algorithmic Functions
Name applyArrestMsg
Type void
Arguments
Type Name
ArrestMsg msg
Body location.stopTravel();
numArrests++;
arrestDuration = msg.duration;
updateInventory(-possessionUnits);
possessionUnits = 0;
Name applyDealRequestMsg
Type void
Arguments
Type Name
DealRequestMsg msg
Body dealFromBuyer = msg;
Name applyHarassCompleteMsg
Type void
Arguments
Type Name
HarassCompleteMsg msg
Body // Nothing needs to be done.
Name applyHarassMsg
Type void
Arguments
Type Name
HarassMsg msg
Body location.stopTravel();
lastHarassTime = Engine.getTime();

Name applyWaitCompleteMsg
Type void
Arguments
Type Name
WaitCompleteMsg msg
Body // Nothing needs to be done.
Name denyPendingRequests
Type void
Body List pendingEvents = simulationState.getEvents();

for (int i = 0; i < pendingEvents.size(); i++) {
  Object event = pendingEvents.get(i);

  if (event instanceof DealRequestMsg) {
    sendDealDeniedMsg((DealRequestMsg)event);
  }
}
Name endDeal
Type void
Arguments
Type Name
boolean buyerWaiting
Body // Record that we completed a deal with this customer. We do not
// need to maintain the customer's location, so pass the dummy
// coordinates (-1, -1).

AgentHistory history = customerHistories.find(dealFromBuyer.customer);
history.madeDeal(buyerWaiting, -1, -1);


if (buyerWaiting) {
  // Notify the buyer that the deal is complete.
  
  DealCompleteMsg dealToBuyer = new DealCompleteMsg();
  dealToBuyer.dealID = dealFromBuyer.dealID;
  dealToBuyer.units = dealFromBuyer.units;
  dealToBuyer.price = dealFromBuyer.price;
  dealFromBuyer.buyer.receiveDealCompleteMsg(dealToBuyer);
  
  
  // Apply the deal to the dealer's inventory and money.
  
  updateInventory(-dealToBuyer.units);
  updateMoney(dealToBuyer.price);
  

  // If we have made enough successful deals with this customer,
  // invite him to make direct deals.
  
  if (history.numGoodDeals == invitationDeals) {
    InviteBuyerMsg invitation = new InviteBuyerMsg();
    invitation.privateDealer = self;
    dealFromBuyer.customer.receiveInviteBuyerMsg(invitation);
  }
}


// Note that we are no longer on a deal.
isOnDeal = false;
Name getDrugsOnPerson
Type real
Body if (currentPlace == Main.PLACE_HOME) {
  return inventory;
}
else {
  return possessionUnits;
}
Name getLastHarassTime
Type real
Body return lastHarassTime;
Name getLocation
Type Location
Body return location;
Name getQueueLength
Type integer
Body int length = 0;

List pendingEvents = simulationState.getEvents();

for (int i = 0; i < pendingEvents.size(); i++) {
  Object event = pendingEvents.get(i);

  if (event instanceof DealRequestMsg) {
    length++;
  }
}

if (isOnDeal) {
  length++;
}

return length;

Name isAcceptingRequests
Type boolean
Body // isAcceptingRequests: Is the seller currently willing to accept
// a deal request, either for immediate response or to add onto
// his queue for a later response?

return isOnShift && !isBeingHarassed();
Name isBeingHarassed
Type boolean
Body return isBeingHarassed;
Name isInPublic
Type boolean
Body // isInPublic: Can the seller currently be seen in public?

// Private dealers can be seen in public only when they are traveling
// or when they are in the market delivering drugs.

return currentPlace == Main.PLACE_TRANSIT || currentPlace == Main.PLACE_MARKET;
Name isWaitingOnBuyer
Type boolean
Arguments
Type Name
integer dealID
Body // isWaitingOnBuyer: Is this seller waiting for the buyer to take some
// action before he can proceed with his own actions?

return isWaitingOnBuyer && dealFromBuyer.dealID == dealID;
Name quitDeal
Type void
Body endDeal(false);
sendDealDeniedMsg(dealFromBuyer);

Name receiveArrestMsg
Type void
Arguments
Type Name
ArrestMsg msg
Body if (currentPlace != Main.PLACE_JAIL) {
  simulationState.fireEvent(msg);
}
Name receiveDealRequestMsg
Type void
Arguments
Type Name
DealRequestMsg msg
Body if (isAcceptingRequests()) {
  simulationState.fireEvent(msg);
}
else {
  sendDealDeniedMsg(msg);
}
Name receiveHarassCompleteMsg
Type void
Arguments
Type Name
HarassCompleteMsg msg
Body if (isBeingHarassed()) {
  simulationState.fireEvent(msg);
}
else {
  traceln("***WARNING in PrivateDealer.receiveHarassCompleteMsg(): " + self + " ignoring unexpected message @ " + Engine.getTime());
}


Name receiveHarassMsg
Type void
Arguments
Type Name
HarassMsg msg
Body if (!isBeingHarassed()) {
  simulationState.fireEvent(msg);
}

Name receiveWaitCompleteMsg
Type void
Arguments
Type Name
WaitCompleteMsg msg
Body if (isWaitingOnBuyer(msg.dealID)) {
  simulationState.fireEvent(msg);
}
else {
  traceln("***WARNING in PrivateDealer.receiveWaitCompleteMsg(): " + self + " ignoring message about unexpected deal " + msg.dealID + " @ " + Engine.getTime());
}
Name sendDealDeniedMsg
Type void
Arguments
Type Name
DealRequestMsg request
Body if (request.buyer.isWaitingOnSeller(request.dealID)) {
  DealDeniedMsg msg = new DealDeniedMsg();
  msg.dealID = request.dealID;
  request.buyer.receiveDealDeniedMsg(msg);
}
Name sendDealerDeliversMsg
Type void
Arguments
Type Name
boolean delivers
Body DealerDeliversMsg msg = new DealerDeliversMsg();
msg.dealID = dealFromBuyer.dealID;
msg.delivers = delivers;
dealFromBuyer.buyer.receiveDealerDeliversMsg(msg);
Name sendWaitCompleteMsg
Type void
Body WaitCompleteMsg msg = new WaitCompleteMsg();
msg.dealID = dealFromBuyer.dealID;
dealFromBuyer.buyer.receiveWaitCompleteMsg(msg);
Name startDeal
Type void
Body // Note that we are now on a deal.
isOnDeal = true;

// Record the buyer's location.
buyerX = dealFromBuyer.buyer.getLocation().currentX;
buyerY = dealFromBuyer.buyer.getLocation().currentY;

// Make sure the customer is on the known customer list.
customerHistories.add(dealFromBuyer.customer);

// Let the buyer know the deal has started.
DealStartMsg msg = new DealStartMsg();
msg.dealID = dealFromBuyer.dealID;
dealFromBuyer.buyer.receiveDealStartMsg(msg);
Name travelToBuyer
Type void
Body // Begin traveling to the customer. Assume the travel
// takes place at driving speed (car, bus, etc.).
location.travel(buyerX, buyerY, Main.TRAVEL_DRIVE);
Name travelToHome
Type void
Body // Begin traveling home. Assume the travel takes
// place at driving speed (car, bus, etc.).
location.travel(homeX, homeY, Main.TRAVEL_DRIVE);
Name updateInventory
Type void
Arguments
Type Name
real inventoryChange
Body inventory += inventoryChange;

if (inventory < 0) {
  traceln("***ERROR in PrivateDealer.updateInventory(): Negative inventory for " + self + " @ " + Engine.getTime());
}
Name updateMoney
Type void
Arguments
Type Name
real moneyChange
Body money += moneyChange;

if (money < 0) {
  traceln("***ERROR in PrivateDealer.updateMoney(): Negative money for " + self + " @ " + Engine.getTime());
}
Name willAcceptDeal
Type boolean
Arguments
Type Name
Buyer buyer
real units
real price
Body // willAcceptDeal: Will the seller sell the buyer the requested
// amount of drugs at the requested price?

// Private dealers will accept the deal as long as they have enough
// inventory to cover the requested amount of drugs.
//
// They should probably also verify that the price is appropriate
// for the drug amount. For now it's not necessary, though, because
// all deals are initiated using the same pricing structure.

return inventory >= units;
Name willDeliver
Type boolean
Body if (dealFromBuyer.units >= acceptableDeliverUnits) {
  if (dealFromBuyer.buyerPlace == Main.PLACE_MARKET) {
    return randomTrue(probDeliverMarket);
  }
  else if (dealFromBuyer.buyerPlace == Main.PLACE_HOME) {
    return randomTrue(probDeliverHome);
  }
  else if (dealFromBuyer.buyerPlace == Main.PLACE_PRIVATE_DEALER) {
    return false;
  }
  else {
    traceln("***ERROR in PrivateDealer.willDeliver: Unexpected currentPlace (" + dealFromBuyer.buyerPlace + ") for " + dealFromBuyer.buyer + " detected by " + self + " @ " + Engine.getTime());
    return false;
  }
}
else {
  return false;
}

Animation
Name animation
X 0
Y 0
Additional class code public boolean isSelected = false;
Picture
Rectangle agentShape
X main.animation.getAgentLocationX(main.AGENT_PRIVATE_DEALER, isSelected, location.currentX)
Y main.animation.getAgentLocationY(main.AGENT_PRIVATE_DEALER, isSelected, location.currentY)
Width main.animation.getAgentSize(main.AGENT_PRIVATE_DEALER, isSelected)
Height main.animation.getAgentSize(main.AGENT_PRIVATE_DEALER, isSelected)
Fill color main.animation.getAgentFillColor(main.AGENT_PRIVATE_DEALER, isSelected)
Line color main.animation.getAgentLineColor(main.AGENT_PRIVATE_DEALER, isSelected)
Line width main.animation.getAgentLineWidth(main.AGENT_PRIVATE_DEALER, isSelected)
Visible isOnShift && main.animation.getAgentVisibility(main.AGENT_PRIVATE_DEALER, isSelected, location.currentX, location.currentY)

Active Object: StreetBroker

General
Import import message.*;
import misc.*;

Parameters
Name initialMarket
Type Market
Name initialConcentration
Type real
Name initialAddiction
Type real
Name metabolismRate
Type real
Name addictionIncreaseDuration
Type real
Name addictionIncreaseRate
Type real
Name addictionDecreaseOnset
Type real
Name addictionDecreaseRate
Type real
Name visionRadius
Type real
Name probTryPrivateDealer
Type real
Name acceptableSdQueueLength
Type integer
Name acceptablePdQueueLength
Type integer
Name initialTimeSinceLastUse
Type real
Name probUseTogether
Type real
Name initialHIV
Type boolean
Name marketQuitTime
Type real
Name useDelayMin
Type real
Name useDelayMax
Type real

Icon
Picture

Structure
Picture
ChartTimer dailyTimer
Timeout util.Units.days(1)
Expire At Startup Yes
Expiry Action // Each day, street brokers start their "shifts" sometime during
// the time period that they know the street dealers tend to
// enter the market. They end their shifts sometime during the
// time period that they know the street dealers tend to leave
// the market.
//
// They choose these times not so much because that's when street
// dealers will be around, but because that's when customers will
// be around. If the street dealers aren't there, they can still
// go to a private dealer.

if (isActive) {
  double earlyStartHour = util.Params.getDouble(Main.sdShiftStartRange, 0);
  double lateStartHour = util.Params.getDouble(Main.sdShiftStartRange, 1);
  double shiftStartHour = util.Units.hours(uniform(earlyStartHour, lateStartHour));
  shiftStartTimer.restart(shiftStartHour);

  double earlyEndHour = util.Params.getDouble(Main.sdShiftEndRange, 0);
  double lateEndHour = util.Params.getDouble(Main.sdShiftEndRange, 1);
  shiftEndTime = Engine.getTime() + util.Units.hours(uniform(earlyEndHour, lateEndHour));
}
ChartTimer shiftStartTimer
Expire No (manual mode)
Expiry Action simulationState.fireEvent(new ShiftStartMsg());
ChartTimer dealQuitTimer
Expire No (manual mode)
Expiry Action DealDeniedMsg msg = new DealDeniedMsg();
msg.dealID = dealToSeller.dealID;
self.receiveDealDeniedMsg(msg);
Variable shiftEndTime
Variable type real
Variable isUsingAlone
Variable type boolean
Initial value false
Variable isOnDeal
Variable type boolean
Initial value false
Variable isUsingWithBuyer
Variable type boolean
Initial value false
Variable isWaitingOnBuyer
Variable type boolean
Initial value false
Variable dealToSeller
Variable type DealRequestMsg
Initial value null
Variable pendingInvitations
Variable type List
Initial value new ArrayList()
Variable lastHarassTime
Variable type real
Initial value 0
Variable isWaitingOnSeller
Variable type boolean
Initial value false
Variable isBeingHarassed
Variable type boolean
Initial value false
Variable numArrests
Variable type real
Initial value 0
Variable arrestDuration
Variable type real
Variable main
Variable type Main
Initial value (Main)getOwner()
Variable visibleStreetDealersIterator
Variable type Iterator
Initial value null
Variable visibleStreetDealers
Variable type List
Initial value new ArrayList()
Variable currentPlace
Variable type integer
Initial value Main.PLACE_MARKET
Variable marketStartTime
Variable type real
Initial value 0
Variable targetSellerY
Variable type real
Initial value 0
Variable targetSellerX
Variable type real
Initial value 0
Variable money
Variable type real
Initial value 0
Variable targetSellerHistory
Variable type AgentHistory
Initial value null
Variable dealFromSeller
Variable type DealCompleteMsg
Initial value null
Variable isActive
Variable type boolean
Initial value false
Variable customerY
Variable type real
Variable customerX
Variable type real
Variable targetSeller
Variable type Seller
Initial value null
Variable dealFromBuyer
Variable type DealRequestMsg
Initial value null
Variable self
Variable type StreetBroker
Initial value this
Variable market
Variable type Market
Initial value initialMarket
Port travelCompleteListener
Message type TravelCompleteMsg
Port wantFixListener
Message type WantFixMsg
Statechart simulationState
Object allTransactions
Type misc.TransactionCounter
Object sdTransactions
Type misc.TransactionCounter
Object pdTransactions
Type misc.TransactionCounter
Object location
Type misc.Location
Parameters
Name Value
startX market.getRandomX()
startY market.getRandomY()
Object userBehavior
Type misc.UserBehavior
Parameters
Name Value
initialInventory 0
initialConcentration initialConcentration
initialAddiction initialAddiction
randomUseIntervalMin 0
randomUseIntervalMax 0
metabolismRate metabolismRate
addictionIncreaseRate addictionIncreaseRate
addictionDecreaseRate addictionDecreaseRate
addictionIncreaseDuration addictionIncreaseDuration
addictionDecreaseOnset addictionDecreaseOnset
desperateNeedThreshold 1
desperateAddictionThreshold 0
initialTimeSinceLastUse initialTimeSinceLastUse
initialHIV initialHIV
Object privateDealerHistories
Type misc.AgentHistoryList
Object streetDealerHistories
Type misc.AgentHistoryList

Statechart
Name simulationState
Picture
ChoicePoint checkStreetDealer
Action checkStreetDealer();
ChoicePoint seekStreetDealer
Action seekStreetDealer();
ChoicePoint chooseStreetDealer
Action chooseStreetDealer();
ChoicePoint seekPrivateDealer
Action seekPrivateDealer();
History preHarassed
Type Shallow
Action // NOTE:
//
// Due to a bug in AnyLogic 5, a deep history state
// will not transition to a simple state that is
// inside a composite state when the composite state
// is on the same level as the deep history state.
// Instead, it will cause a runtime error. Therefore
// we avoid deep history states and live with the
// limitations of shallow history states.
Transition transition58
Source/target isMarketOpen=>chooseStreetDealer
Fire If guard is open
Guard market.isOpen
Transition transition56
Source/target wasDealMade1=>randomWalk1
Fire If all other guards are closed
Action endCustomerDeal(true, false);
Transition transition50
Source/target wasDealMade1=>giveToCustomer
Fire If guard is open
Guard dealFromSeller != null
Action isWaitingOnBuyer = true;
endCustomerDeal(true, true);
Transition transition53
Source/target wasDealMade=>useAlone
Fire If guard is open
Guard dealFromSeller != null
Action endCustomerDeal(false, true);
isUsingAlone = true;
useAlone();
Transition transition52
Source/target isBuyerWaiting1=>wasDealMade
Fire If all other guards are closed
Transition transition51
Source/target isBuyerWaiting1=>wasDealMade1
Fire If guard is open
Guard dealFromBuyer.buyer.isWaitingOnSeller(dealFromBuyer.dealID)
Transition transition49
Source/target willAcceptDeal=>randomWalk1
Fire If all other guards are closed
Action sendDealDeniedMsg(dealFromBuyer, 0);
Transition transition48
Source/target isBuyerWaiting=>willAcceptDeal
Fire If guard is open
Guard dealFromBuyer.buyer.isWaitingOnSeller(dealFromBuyer.dealID)
Transition transition47
Source/target isBuyerWaiting=>randomWalk1
Fire If all other guards are closed
Transition transition46
Source/target randomWalk1=>isBuyerWaiting
Fire Signal event occurs
Signal event DealRequestMsg
Action applyDealRequestMsg((DealRequestMsg)getEvent());
Transition transition45
Source/target travelToMarket=>isMarketOpen
Fire Signal event occurs
Signal event TravelCompleteMsg
Action currentPlace = Main.PLACE_MARKET;
marketStartTime = Engine.getTime();
setupKnownStreetDealers();
Transition transition44
Source/target harassed=>preHarassed
Fire Signal event occurs
Signal event HarassCompleteMsg
Action applyHarassCompleteMsg((HarassCompleteMsg)getEvent());
Transition transition55
Source/target state=>harassed
Fire Signal event occurs
Signal event HarassMsg
Action applyHarassMsg((HarassMsg)getEvent());
Transition transition43
Source/target waitForPrivateDealer=>seekPrivateDealer
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition57
Source/target arrested=>active
Fire Timeout
Timeout arrestDuration
Transition transition54
Source/target active=>arrested
Fire Signal event occurs
Signal event ArrestMsg
Action applyArrestMsg((ArrestMsg)getEvent());
Transition transition42
Source/target useAlone=>randomWalk1
Fire Timeout
Timeout uniform(useDelayMin, useDelayMax)
Action isUsingAlone = false;
Transition transition41
Source/target seekStreetDealer=>seekStreetDealer
Fire Signal event occurs
Guard targetSeller != null && dealToSeller == null
Action applyDealDeniedMsg(new DealDeniedMsg());
Transition transition40
Source/target startPrivateDeal=>seekPrivateDealer
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition37
Source/target seekPrivateDealer=>startPrivateDeal
Fire Signal event occurs
Guard targetSeller != null && dealToSeller != null
Signal event TargetFoundMsg
Action isWaitingOnSeller = true;
sendDealRequestMsg();

Transition transition39
Source/target startStreetDeal=>isMarketOpen
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition38
Source/target checkStreetDealer=>startStreetDeal
Fire Signal event occurs
Guard dealToSeller != null
Action isWaitingOnSeller = true;
sendDealRequestMsg();

Transition transition36
Source/target seekStreetDealer=>startStreetDeal1
Fire Signal event occurs
Guard targetSeller != null && dealToSeller != null
Action isWaitingOnSeller = true;
sendDealRequestMsg();
Transition transition35
Source/target randomWalk=>seekPrivateDealer
Fire Immediately
Guard !market.isOpen || Engine.getTime() - marketStartTime > marketQuitTime
Action setupKnownPrivateDealers();
Transition transition34
Source/target willSeekPrivateDealer=>travelToMarket
Fire If all other guards are closed
Transition transition33
Source/target willSeekPrivateDealer=>seekPrivateDealer
Fire If guard is open
Guard willSeekPrivateDealer()
Action setupKnownPrivateDealers();
Transition transition32
Source/target onStreetDeal1=>travelToCustomer
Fire Signal event occurs
Signal event DealCompleteMsg
Action applyDealCompleteMsg((DealCompleteMsg)getEvent());
isWaitingOnSeller = false;
Transition transition30
Source/target onStreetDeal1=>seekStreetDealer
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition16
Source/target onStreetDeal=>isMarketOpen
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition25
Source/target onStreetDeal=>travelToCustomer
Fire Signal event occurs
Signal event DealCompleteMsg
Action applyDealCompleteMsg((DealCompleteMsg)getEvent());
isWaitingOnSeller = false;
Transition transition29
Source/target useWithCustomer=>randomWalk1
Fire Signal event occurs
Signal event UseTogetherCompleteMsg
Action applyUseTogetherCompleteMsg((UseTogetherCompleteMsg)getEvent());

// Waiting until we receive a UseTogetherCompleteMsg before calling
// useWithCustomer() means that we will not call useWithCustomer()
// if we are arrested while waiting for the message to come through.
// In other words, if we are arrested in the process of using drugs,
// the model will assume we did not use any drugs at all.
//
// The other option is to call useWithCustomer() as soon as we receive
// the UseTogetherMsg. In this case, if we are arrested in the process
// of using drugs, the model will assume that we used all of our drugs.
// Neither situation is realistic.
//
// The useWithCustomer() call was placed here for a practical reason.
// useWithCustomer() needs to check that the drug partner is also in
// a useTogether state, and by waiting until this point to call
// useWithCustomer(), we can be sure that the drug partner has had
// an opportunity to set his status variables properly.
useWithCustomer();

isWaitingOnBuyer = false;
isUsingWithBuyer = false;
Transition transition27
Source/target giveToCustomer=>useWithCustomer
Fire Signal event occurs
Guard ((UseTogetherMsg)getEvent()).uses
Signal event UseTogetherMsg
Action applyUseTogetherMsg((UseTogetherMsg)getEvent());

isWaitingOnBuyer = true;
isUsingWithBuyer = true;
Transition transition26
Source/target giveToCustomer=>useAlone
Fire Signal event occurs
Guard !((UseTogetherMsg)getEvent()).uses
Signal event UseTogetherMsg
Action applyUseTogetherMsg((UseTogetherMsg)getEvent());

isWaitingOnBuyer = false;
isUsingAlone = true;
useAlone();
Transition transition22
Source/target onPrivateDeal=>seekPrivateDealer
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition21
Source/target checkStreetDealer=>isMarketOpen
Fire Signal event occurs
Guard dealToSeller == null
Action applyDealDeniedMsg(new DealDeniedMsg());
Transition transition14
Source/target startStreetDeal1=>seekStreetDealer
Fire Signal event occurs
Signal event DealDeniedMsg
Action applyDealDeniedMsg((DealDeniedMsg)getEvent());
isWaitingOnSeller = false;
Transition transition2
Source/target seekPrivateDealer=>seekPrivateDealer
Fire Signal event occurs
Guard targetSeller != null && dealToSeller == null
Signal event DealDeniedMsg
Action applyDealDeniedMsg(new DealDeniedMsg());
Transition transition1
Source/target startStreetDeal1=>onStreetDeal1
Fire Signal event occurs
Signal event DealStartMsg
Action applyDealStartMsg((DealStartMsg)getEvent());
isWaitingOnSeller = true;
Transition transition24
Source/target travelToPrivateDealer=>onPrivateDeal
Fire Signal event occurs
Signal event TravelCompleteMsg
Action currentPlace = Main.PLACE_PRIVATE_DEALER;
isWaitingOnSeller = true;
sendWaitCompleteMsgToSeller();
Transition transition23
Source/target waitForPrivateDealer=>onPrivateDeal
Fire Signal event occurs
Signal event WaitCompleteMsg
Action applyWaitCompleteMsg((WaitCompleteMsg)getEvent());
isWaitingOnSeller = true;
Transition transition19
Source/target startPrivateDeal=>waitForPrivateDealer
Fire Signal event occurs
Guard ((DealerDeliversMsg)getEvent()).delivers
Signal event DealerDeliversMsg
Action applyDealerDeliversMsg((DealerDeliversMsg)getEvent());
isWaitingOnSeller = true;
Transition transition20
Source/target startPrivateDeal=>travelToPrivateDealer
Action applyDealerDeliversMsg((DealerDeliversMsg)getEvent());
isWaitingOnSeller = false;
Transition transition18
Source/target isMarketOpen=>travelToCustomer
Transition transition13
Source/target travelToStreetDealer=>checkStreetDealer
Fire Signal event occurs
Signal event TravelCompleteMsg
Transition transition12
Source/target chooseStreetDealer=>seekStreetDealer
Fire Signal event occurs
Guard targetSeller == null
Signal event TargetFoundMsg
Action setupVisibleStreetDealers();
Transition transition11
Source/target chooseStreetDealer=>travelToStreetDealer
Fire Signal event occurs
Guard targetSeller != null
Transition transition8
Source/target idle=>randomWalk1
Fire Signal event occurs
Signal event ShiftStartMsg
Transition transition7
Source/target randomWalk1=>idle
Fire Immediately
Guard !market.isOpen && Engine.getTime() > shiftEndTime
Action denyPendingRequests();
location.travel(location.startX, location.startY, Main.TRAVEL_WALK);
Transition transition9
Source/target randomWalk1=>randomWalk1
Fire Signal event occurs
Signal event TravelCompleteMsg
Transition transition6
Source/target startStreetDeal=>onStreetDeal
Fire Signal event occurs
Signal event DealStartMsg
Action applyDealStartMsg((DealStartMsg)getEvent());
isWaitingOnSeller = true;
Transition transition31
Source/target randomWalk=>seekStreetDealer
Fire Signal event occurs
Signal event TravelCompleteMsg
Action setupVisibleStreetDealers();
Transition transition28
Source/target seekStreetDealer=>randomWalk
Fire Signal event occurs
Guard targetSeller == null
Transition transition5
Source/target wasDealMade=>randomWalk1
Fire If all other guards are closed
Action endCustomerDeal(false, false);
Transition transition4
Source/target travelToCustomer=>isBuyerWaiting1
Fire Signal event occurs
Signal event TravelCompleteMsg
Action currentPlace = Main.PLACE_MARKET;
market.addStreetBroker(self);
Transition transition3
Source/target onPrivateDeal=>travelToCustomer
Fire Signal event occurs
Signal event DealCompleteMsg
Action applyDealCompleteMsg((DealCompleteMsg)getEvent());
isWaitingOnSeller = false;
Transition transition
Source/target willAcceptDeal=>willSeekPrivateDealer
Fire Signal event occurs
Guard willAcceptDeal(dealFromBuyer.buyer, dealFromBuyer.units, dealFromBuyer.price)
Signal event DealRequestMsg
Action startCustomerDeal();
Transition transition15
Source/target seekPrivateDealer=>travelToMarket
Fire Signal event occurs
Guard targetSeller == null
State randomWalk1
Entry action location.wanderInMarket(market);
State travelToStreetDealer
Deferred events new DealRequestEvent()
Entry action travelToStreetDealer();
State travelToPrivateDealer
Deferred events new DealRequestEvent(), new DealDeniedEvent()
Entry action market.removeStreetBroker(self);
travelToPrivateDealer();
State waitForPrivateDealer
Deferred events new DealRequestEvent(), new DealDeniedEvent()
Entry action dealQuitTimer.restart(Main.sbDealQuitTime);
Exit action dealQuitTimer.reset();
State onPrivateDeal
Deferred events new DealRequestEvent()
Entry action dealQuitTimer.restart(Main.sbDealQuitTime);
Exit action dealQuitTimer.reset();
State randomWalk
Deferred events new DealRequestEvent()
Entry action wanderInMarket();
State giveToCustomer
Deferred events new DealRequestEvent()
State travelToCustomer
Deferred events new DealRequestEvent()
Entry action travelToCustomer();
State useWithCustomer
Deferred events new DealRequestEvent()
State onStreetDeal
Deferred events new DealRequestEvent()
Entry action dealQuitTimer.restart(Main.sbDealQuitTime);
Exit action dealQuitTimer.reset();
State onStreetDeal1
Deferred events new DealRequestEvent()
Entry action dealQuitTimer.restart(Main.sbDealQuitTime);
Exit action dealQuitTimer.reset();
State startStreetDeal
Deferred events new DealRequestEvent()
Entry action dealQuitTimer.restart(Main.sbDealQuitTime);
Exit action dealQuitTimer.reset();
State startStreetDeal1
Deferred events new DealRequestEvent()
Entry action dealQuitTimer.restart(Main.sbDealQuitTime);
Exit action dealQuitTimer.reset();
State startPrivateDeal
Deferred events new DealRequestEvent()
Entry action dealQuitTimer.restart(Main.sbDealQuitTime);
Exit action dealQuitTimer.reset();
State useAlone
Deferred events new DealRequestEvent()
Exit action
State travelToMarket
Deferred events new DealRequestEvent()
Entry action travelToMarket();
State harassed
Deferred events new GenericEvent()
Entry action isBeingHarassed = true;
Exit action isBeingHarassed = false;
State arrested
Entry action currentPlace = Main.PLACE_JAIL;
State active
Entry action isActive = true;

currentPlace = Main.PLACE_MARKET;
location.currentX = location.startX;
location.currentY = location.startY;

market.addStreetBroker(self);
Exit action isActive = false;
isOnDeal = false;
isWaitingOnSeller = false;
isWaitingOnBuyer = false;
isUsingAlone = false;
isUsingWithBuyer = false;

market.removeStreetBroker(self);

Algorithmic Functions
Name applyArrestMsg
Type void
Arguments
Type Name
ArrestMsg msg
Body location.stopTravel();
numArrests++;
arrestDuration = msg.duration;
updateInventory(-userBehavior.inventory);
Name applyDealCompleteMsg
Type void
Arguments
Type Name
DealCompleteMsg msg
Body dealFromSeller = msg;

updateInventory(dealFromSeller.units);
updateMoney(-dealFromSeller.price);


// Update the street broker's history with this seller.

targetSellerHistory.madeDeal(true, targetSellerX, targetSellerY);


// Update transaction counters.

allTransactions.addGood(dealFromSeller.units, dealFromSeller.price);
main.sbAllTransactions.addGood(dealFromSeller.units, dealFromSeller.price);

if (targetSeller instanceof StreetDealer) {
  sdTransactions.addGood(dealFromSeller.units, dealFromSeller.price);
  main.sbSdTransactions.addGood(dealFromSeller.units, dealFromSeller.price);
}
else if (targetSeller instanceof PrivateDealer) {
  pdTransactions.addGood(dealFromSeller.units, dealFromSeller.price);
  main.sbPdTransactions.addGood(dealFromSeller.units, dealFromSeller.price);
}
else {
  traceln("***ERROR in StreetBroker.receiveDealCompleteMsg(): Unexpected seller type for " + self + " @ " + Engine.getTime());
}
Name applyDealDeniedMsg
Type void
Arguments
Type Name
DealDeniedMsg msg
Body // Update the street broker's history with this seller.

targetSellerHistory.madeDeal(false, targetSellerX, targetSellerY);


// Update transaction counters.

allTransactions.addFailed();
main.sbAllTransactions.addFailed();

if (targetSeller instanceof StreetDealer) {
  sdTransactions.addFailed();
  main.sbSdTransactions.addFailed();
}
else if (targetSeller instanceof PrivateDealer) {
  pdTransactions.addFailed();
  main.sbPdTransactions.addFailed();
}
else {
  traceln("***ERROR in StreetBroker.receiveDealDeniedMsg(): Unexpected seller type for " + self + " @ " + Engine.getTime());
}
Name applyDealRequestMsg
Type void
Arguments
Type Name
DealRequestMsg msg
Body dealFromBuyer = msg;
Name applyDealStartMsg
Type void
Arguments
Type Name
DealStartMsg msg
Body // Nothing needs to be done.
Name applyDealerDeliversMsg
Type void
Arguments
Type Name
DealerDeliversMsg msg
Body // Nothing needs to be done.
Name applyHarassCompleteMsg
Type void
Arguments
Type Name
HarassCompleteMsg msg
Body // Nothing needs to be done.
Name applyHarassMsg
Type void
Arguments
Type Name
HarassMsg msg
Body location.stopTravel();
lastHarassTime = Engine.getTime();

Name applyUseTogetherCompleteMsg
Type void
Arguments
Type Name
UseTogetherCompleteMsg msg
Body // Nothing needs to be done.
Name applyUseTogetherMsg
Type void
Arguments
Type Name
UseTogetherMsg msg
Body // Nothing needs to be done.
Name applyWaitCompleteMsg
Type void
Arguments
Type Name
WaitCompleteMsg msg
Body // Nothing needs to be done.
Name checkStreetDealer
Type void
Body dealToSeller = null;

StreetDealer sd = (StreetDealer)targetSeller;

// Bail out without creating a deal request under certain conditions.
if (!sd.isInPublic()) return;
if (location.distance(sd.location.currentX, sd.location.currentY) > max(visionRadius, sd.visionRadius)) return;
if (sd instanceof User && ((User)sd).isUsing()) return;
if (((PoliceTarget)sd).isBeingHarassed()) return;
if (!isQueueLengthAcceptable(sd)) return;

createDealRequestMsg();
Name chooseStreetDealer
Type void
Body // We expect this method to be called repeatedly, with each new call
// returning the next dealer in the list until an available dealer is
// found.

targetSeller = null;

if (streetDealerHistories.hasNext()) {
  targetSellerHistory = streetDealerHistories.next();

  if (Main.sbCheckAllKnownSd
      || streetDealerHistories.indexOf(targetSellerHistory.agent) < Main.sbNumKnownSdToCheck
     ) {
    targetSeller = (Seller)targetSellerHistory.agent;
  
    // In this case we do not yet know where the dealer is because
    // we are not standing in front of him. Therefore we have to
    // guess. Compare this to the seekStreetDealer function.
  
    if (targetSellerHistory.lastDeal > 0) {
      // We have made a deal with this dealer since the model started,
      // so guess that he is at the same place he was when we made
      // that deal.
      targetSellerX = targetSellerHistory.lastDealX;
      targetSellerY = targetSellerHistory.lastDealY;
    }
    else {
      // We have not made a deal with this dealer since the model
      // started, so assume that we know his most recent market
      // location and guess that.
      StreetDealer sd = (StreetDealer)targetSellerHistory.agent;
      targetSellerX = sd.marketX;
      targetSellerY = sd.marketY;
    }
  }
}
Name createDealRequestMsg
Type void
Body dealToSeller = new DealRequestMsg();
dealToSeller.dealID = main.getNewDealID();
dealToSeller.units = dealFromBuyer.units;
dealToSeller.price = dealFromBuyer.price;
dealToSeller.customer = dealFromBuyer.customer;
dealToSeller.buyer = self;
dealToSeller.buyerPlace = currentPlace;
Name denyPendingRequests
Type void
Body List pendingEvents = simulationState.getEvents();

for (int i = 0; i < pendingEvents.size(); i++) {
  Object event = pendingEvents.get(i);

  if (event instanceof DealRequestMsg) {
    sendDealDeniedMsg((DealRequestMsg)event, 0);
  }
}
Name endCustomerDeal
Type void
Arguments
Type Name
boolean buyerWaiting
boolean dealMade
Body if (buyerWaiting) {
  if (dealMade) {
    // Calculate the tip for the deal.
    double tip = util.Math.round(dealFromSeller.units * Main.sbTipPercent / 100, Main.useIncrement);
    tip = min(tip, Main.sbTipUpperBound);
    tip = max(tip, Main.sbTipLowerBound);
  
    // The broker is about to give the drugs back to the customer,
    // so subtract the drug amount (minus the tip) from his inventory.
    updateInventory(-(dealFromSeller.units - tip));
  
    // Send the completed deal info to the buyer.
    DealCompleteMsg dealToBuyer = new DealCompleteMsg();
    dealToBuyer.dealID = dealFromBuyer.dealID;
    dealToBuyer.units = dealFromSeller.units - tip;
    dealToBuyer.price = dealFromSeller.price;
    dealFromBuyer.buyer.receiveDealCompleteMsg(dealToBuyer);
  }
  else {
    // The broker was not able to find a seller, so return the
    // customer's money.
    updateMoney(-dealFromBuyer.price);
    sendDealDeniedMsg(dealFromBuyer, dealFromBuyer.price);
  }
}

isOnDeal = false;

Name getDrugsOnPerson
Type real
Body return userBehavior.inventory;
Name getLastHarassTime
Type real
Body return lastHarassTime;
Name getLocation
Type Location
Body return location;
Name getQueueLength
Type integer
Body int length = 0;

List pendingEvents = simulationState.getEvents();

for (int i = 0; i < pendingEvents.size(); i++) {
  Object event = pendingEvents.get(i);

  if (event instanceof DealRequestMsg) {
    length++;
  }
}

if (isOnDeal) {
  length++;
}

return length;
Name hasHIV
Type boolean
Body return userBehavior.hasHIV;
Name isAcceptingRequests
Type boolean
Body // isAcceptingRequests: Is the seller currently willing to accept
// a deal request, either for immediate response or to add onto
// his queue for a later response?

return
     market.isOpen
  && !isBeingHarassed()
  && !(isUsingAlone || isUsingWithBuyer)
  ;
Name isBeingHarassed
Type boolean
Body return isBeingHarassed;
Name isInPublic
Type boolean
Body // isInPublic: Can the seller currently be seen in public?

// Street brokers are in public when they are traveling or when they are
// in the market.

return currentPlace == Main.PLACE_TRANSIT || currentPlace == Main.PLACE_MARKET;
Name isQueueLengthAcceptable
Type boolean
Arguments
Type Name
Seller seller
Body int acceptableQueueLength = 0;

if (seller instanceof StreetDealer) {
  acceptableQueueLength = acceptableSdQueueLength;
}
else if (seller instanceof PrivateDealer) {
  acceptableQueueLength = acceptablePdQueueLength;
}
else {
  traceln("***ERROR in StreetBroker.isQueueLengthAcceptable(): Unexpected seller type for " + self + " @ " + Engine.getTime());
  return false;
}

return targetSeller.getQueueLength() <= acceptableQueueLength;
Name isUsing
Type boolean
Body // isUsing: Is this user currently using drugs?

return isUsingAlone || isUsingWithBuyer;
Name isUsingTogether
Type boolean
Arguments
Type Name
integer dealID
Body // isUsingTogether: Is this user using drugs with another user as
// a result of the specified deal?

return isUsingWithBuyer && dealFromBuyer.dealID == dealID;
Name isWaitingOnBuyer
Type boolean
Arguments
Type Name
integer dealID
Body // isWaitingOnBuyer: Is this seller waiting for the buyer to take some
// action before he can proceed with his own actions?

return isWaitingOnBuyer && dealFromBuyer.dealID == dealID;
Name isWaitingOnSeller
Type boolean
Arguments
Type Name
integer dealID
Body // isWaitingOnSeller: Is this buyer waiting for the seller to take some
// action before he can proceed with his own actions?

return isWaitingOnSeller && dealToSeller.dealID == dealID;
Name processPendingInvitations
Type void
Body // See the discussion in Customer.receiveInviteBuyerMsg() for an
// explanation of this function.

for (int i = 0; i < pendingInvitations.size(); i++) {
  PrivateDealer pd = (PrivateDealer)pendingInvitations.get(i);
  privateDealerHistories.add(pd);
}

pendingInvitations.clear();
Name receiveArrestMsg
Type void
Arguments
Type Name
ArrestMsg msg
Body if (currentPlace != Main.PLACE_JAIL) {
  simulationState.fireEvent(msg);
}
Name receiveDealCom