コード例 #1
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
  public void draw() {
    background(
        theme.getColor(Types.BACKGROUND)); // Draws background, basically refreshes the screen
    win.setBackground(theme.getColor(Types.BACKGROUND));
    sidePanel.setBackground(theme.getColor(Types.SIDEPANEL1));

    for (int i = 0; i < SIM_TICKS; i++) {
      env.updateAllAgents(); // 'Ticks' for the new frame, sensors sense, networks network and
      // collisions are checked.
      env.updateCollisions(); // update the environment with the new collisions
      env.updateAgentsSight(); // update all the agents to everything they can see in their field
      // of view
      handleCollisions();
      if (!env.fitnessOnly) {
        checkDeaths();
      }
      updateUI();

      autoSnapshot();
    }

    // move drawn region
    if (arrowsPressed[0] && !arrowsPressed[1]) offsetY -= moveSpeed * zoomLevel; // UP
    if (arrowsPressed[1] && !arrowsPressed[0]) offsetY += moveSpeed * zoomLevel; // DOWN
    if (arrowsPressed[2] && !arrowsPressed[3]) offsetX -= moveSpeed * zoomLevel; // LEFT
    if (arrowsPressed[3] && !arrowsPressed[2]) offsetX += moveSpeed * zoomLevel; // RIGHT

    win.draw();

    fill(255);
    text("FrameRate: " + frameRate, 10, 10); // Displays framerate in the top left hand corner
    text("Mouse X: " + mouseX + "Mouse Y: " + mouseY, 10, 30);
    text("Fish Generation: " + env.getFishGeneration(), 10, 50);
    text("Ticks to next fish generation: " + (1000 - (env.getTick() % 1000)), 10, 70);
  }
コード例 #2
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
  public void mousePressed() {
    ArrayList<Agent> agents = env.getAllAgents();
    Agent agent_clicked = null;

    if (win.mousePressed()) return;
    if (!Utilities.isPointInBox(mouseX, mouseY, 0, 0, draw_width, draw_height)) return;

    /*
     * Mouse Modes are as follows:
     * 0 = Click tool - Select agents to see information on them in the top left hand corner
     * 1 = Food tool - Place food where you click
     * 2 = Agent tool - Place agent where you click
     * 3 = Thrust tool - Thrusts the agent clicked on by 2
     * 4 = Wall tool - Place a new wall in the environment
     */

    // coordinates of the mouse within the simulation environment
    int simMouseX = (int) ((float) (mouseX - offsetX) / zoomLevel);
    int simMouseY = (int) ((float) (mouseY - offsetY) / zoomLevel);
    if (!Utilities.isPointInBox(simMouseX, simMouseY, 0, 0, Environment.width, Environment.height))
      return;

    // A wall has been placed -> add to the world
    if (PLACE_MODE) {
      PLACE_MODE = false;
      ArrayList<Wall> walls = env.getAllWalls();
      Wall wl = walls.get(walls.size() - 1);
      wl.addToWorld();
      return;
    }
    switch (mouseMode) {
      case 0:
        agent_clicked = getClickedAgent(agents, simMouseX, simMouseY);
        if (agent_clicked != null) { // agent was clicked so update selected
          selectedAgent = agent_clicked;
        }
        break;

      case 1:
        env.addSeaweed(new Point2D.Double(simMouseX, simMouseY));
        break;

      case 2:
        int heading = (int) Math.floor(Math.random() * 360);
        env.addFish(new Point2D.Double(simMouseX, simMouseY), heading);
        break;
      case 3:
        agent_clicked = getClickedAgent(agents, simMouseX, simMouseY);
        if (agent_clicked != null) { // agent was clicked so update selected
          agent_clicked.thrust(2);
        }
        break;
      case 4:
        env.addWall(
            new Point2D.Double(simMouseX, simMouseY), new Point2D.Double(simMouseX, simMouseY));
        PLACE_MODE = true;
    }
  }
コード例 #3
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
  public void mouseWheel(MouseWheelEvent event) {
    if (win.mouseWheel(event)) return;

    if (!Utilities.isPointInBox(mouseX, mouseY, 0, 0, draw_width, draw_height)) return;

    if (zoomLevel > minZoom || event.getWheelRotation() > 0) {
      zoomLevel = Math.max(minZoom, (zoomLevel + 0.1f * event.getWheelRotation()));
      offsetX -= (int) (((mouseX - offsetX) * (0.1f * event.getWheelRotation()))) / zoomLevel;
      offsetY -= (int) (((mouseY - offsetY) * (0.1f * event.getWheelRotation()))) / zoomLevel;
    }
  } // SIM_TICKS = (int) (slider.getValue() * SIM_TPS_MAX);
コード例 #4
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
 private void addNEATParamInput(String name, int offset) {
   UILabel label = new UILabel(this, 0, offset * 50, name);
   UITextField input = new UITextField(this, 0, offset * 50 + 20, 250, getNEATParam(name));
   input.setEventHandler(
       new UIAction() {
         public void change(UITextField input) {
           setNEATParam(input.getName(), input.getText());
         }
       });
   input.setName(name);
   neatParams.addObject(label);
   neatParams.addObject(input);
   neatParamInputs.add(input);
 }
コード例 #5
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
  // lblSimTPS.setText("Ticks: " + SIM_TICKS);
  public void keyReleased() { // Hotkeys for buttons
    if (win.keyReleased()) return;

    if (!Utilities.isPointInBox(mouseX, mouseY, 0, 0, draw_width, draw_height)) return;

    switch (key) {
      case ('e'):
        mouseMode = 2;
        btnGroupModes.selectButton(btnAddAgent);
        break;
      case ('q'):
        mouseMode = 0;
        btnGroupModes.selectButton(btnSelectAgent);
        break;
      case ('r'):
        mouseMode = 3;
        btnGroupModes.selectButton(btnThrust);
        break;
      case ('w'):
        mouseMode = 1;
        btnGroupModes.selectButton(btnAddFood);
        break;
      case ('t'):
        mouseMode = 4;
        btnGroupModes.selectButton(btnAddWall);
        break;
      case ('s'):
        saveEnvironment();
        break;
      case ('o'):
        restoreEnvironment();
        break;
      case (' '):
        SIM_TICKS = (SIM_TICKS > 0 ? 0 : 1);
        sliderTPS.setValue(SIM_TICKS / SIM_TPS_MAX);
        lblSimTPS.setText("Ticks: " + SIM_TICKS);
        break;
      case ('a'):
        changeNeuralLayout();
        break;
    }

    switch (keyCode) {
      case (UP):
        arrowsPressed[0] = false;
        break;
      case (DOWN):
        arrowsPressed[1] = false;
        break;
      case (LEFT):
        arrowsPressed[2] = false;
        break;
      case (RIGHT):
        arrowsPressed[3] = false;
        break;
    }
  }
コード例 #6
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
 private UIButton addModeButton(final int mode, String txt, int r, int g, int b) {
   UIButton btn =
       new UIButton(
           this, 10 + 60 * (mode % 4), 10 + (35 * (int) Math.floor(mode / 4)), 50, 30, txt);
   btn.setEventHandler(
       new UIAction() {
         public void click(UIButton btn) {
           btnGroupModes.selectButton(btn);
           mouseMode = mode;
         }
       });
   btn.setColor(r, g, b);
   btnGroupModes.addObject(btn);
   return btn;
 }
コード例 #7
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
  // We are unsure whether the God will be a NeatGod or a HyperNeatGod.
  @SuppressWarnings("rawtypes")
  private void setupNEATParams() {
    neatParamInputs = new ArrayList<UITextField>();
    godDrop = new UIDropdown<God>(this, 10, 10, 230, env.getAllGods());
    godDrop.setEventHandler(
        new UIAction() {
          public void change(UIDropdown drop) {
            setAllNEATParams();
          }
        });

    for (int i = 0; i < neatParameters.length; i++) {
      addNEATParamInput(neatParameters[i], i + 1);
    }

    neatParams.addObject(godDrop);
  }
コード例 #8
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
  public void keyPressed() { // Hotkeys for buttons
    if (win.keyPressed()) return;
    if (!Utilities.isPointInBox(mouseX, mouseY, 0, 0, draw_width, draw_height)) return;

    switch (keyCode) {
      case (UP):
        arrowsPressed[0] = true;
        break;
      case (DOWN):
        arrowsPressed[1] = true;
        break;
      case (LEFT):
        arrowsPressed[2] = true;
        break;
      case (RIGHT):
        arrowsPressed[3] = true;
        break;
    }
  }
コード例 #9
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
  private void updateUI() {
    agentHeading.setVisible(selectedAgent != null);
    winStats.setVisible(selectedAgent != null);

    if (selectedAgent != null) {
      // agentHeading.setAngle(selectedAgent.getViewHeading());
      agentHeading.setAgent(selectedAgent);
      if (env.fitnessOnly) {
        progHealth.setValue(selectedAgent.getFitness() / 10);
        progHealth.setColor(
            (int) (255 - 255 * selectedAgent.getFitness()),
            (int) (255 * selectedAgent.getFitness()),
            0);
      } else {
        progHealth.setValue(selectedAgent.getHealth());
        progHealth.setColor(
            (int) (255 - 255 * selectedAgent.getHealth()),
            (int) (255 * selectedAgent.getHealth()),
            0);
      }
      sliderX.setValue((double) selectedAgent.getX() / Environment.width);
      sliderY.setValue((double) selectedAgent.getY() / Environment.height);

      lblX.setText("x = " + selectedAgent.getX());
      lblY.setText("y = " + selectedAgent.getY());
      lblHeading.setText(
          "heading = " + Math.round(selectedAgent.getViewHeading() * 100.0) / 100.0 + "°");
      lblAngle.setText(
          "moving angle = " + Math.round(selectedAgent.getMovingAngle() * 100.0) / 100.0 + "°");
      lblHealth.setText("fitness = " + Math.round(selectedAgent.getFitness() * 1000.0) / 1000.0);
      lblSpeed.setText("speed = " + Math.round((selectedAgent.getMovingSpeed() / 1000)) + " m/s");
      // lblX.setText("Selected agent see = "+selectedAgent.getCanSee().size()+ " "+tmp, 10, 70);
      // lblX.setText("Selected agent see food (seg 0)=
      // "+selectedAgent.viewingObjectOfTypeInSegment(0, SightInformation.TYPE_FOOD), 10, 85);
    }
  }
コード例 #10
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
  private void setupUI() {
    // Set colors of each element of simulation
    theme = new UITheme();
    theme.setColor(Types.BACKGROUND, color(0));
    theme.setColor(Types.SIDEPANEL1, color(50));
    theme.setColor(Types.FOOD, color(0, 255, 0));
    theme.setColor(Types.FISH, color(255, 127, 0));
    theme.setColor(Types.SHARK, color(255, 0, 0));
    theme.setColor(Types.WALL, color(255, 255, 0));
    theme.setColor(Types.NEURON, color(200));
    theme.setColor(Types.NEURON_FIRED, color(0, 255, 0));

    // setup main window for UI elements
    win = new UIWindow(this, 0, 0, screen.width, screen.height);
    sideTabs = new UITab(this, 250, 0, 250, screen.height);
    sideTabs.setIsLeft(false);
    sideTabs.setBackground(50);
    sideTabs.setFixedBackground(true);

    sidePanel = sideTabs.addTab("Information"); // new UIWindow(this, 250, 0, 250, screen.height);
    neatParams = sideTabs.addTab("NEAT Params");
    neatParams.setBackground(30);
    setupNEATParams();

    // Simulation draw region
    UIDrawable sim = new UIDrawable(this, 0, 0, draw_width, draw_height);
    sim.setBackground(color(255));
    sim.setFixedBackground(true);
    sim.setEventHandler(
        new UIAction() {
          public void draw(PApplet canvas) {
            drawSimulation(canvas);
          }
        });
    win.addObject(sim);
    win.addObject(sideTabs);

    // Buttons to change the current mode
    btnGroupModes = new UIWindow(this, 0, 0, 250, 150);
    btnGroupModes.setBackground(30);
    btnGroupModes.setFixedBackground(true);
    sidePanel.addObject(btnGroupModes);

    btnSelectAgent = addModeButton(0, "Select", 2, 118, 255);
    btnAddFood = addModeButton(1, "Seaweed", 84, 255, 159);
    btnAddAgent = addModeButton(2, "Agent", 255, 127, 0);
    btnThrust = addModeButton(3, "Thrust", 0, 231, 125);
    btnAddWall = addModeButton(4, "Wall", 255, 255, 0);

    btnGroupModes.selectButton(btnSelectAgent);

    // Change number of ticks updated each frame
    sliderTPS = new UISlider(this, 10, 90, 170, 30);
    sliderTPS.setEventHandler(
        new UIAction() {
          public void change(UISlider slider) {
            SIM_TICKS = (int) (slider.getValue() * SIM_TPS_MAX);
            lblSimTPS.setText("Ticks: " + SIM_TICKS);
          }
        });
    sliderTPS.setValue(1.0 / SIM_TPS_MAX);
    btnGroupModes.addObject(sliderTPS);

    lblSimTPS = new UILabel(this, 190, 92, "Ticks: " + SIM_TICKS);
    btnGroupModes.addObject(lblSimTPS);

    // Statistics window for the currently selected agent
    winStats = new UIWindow(this, 0, 165, 300, 500);
    winStats.setBackground(30);
    winStats.setFixedBackground(true);
    sidePanel.addObject(winStats);

    // control to change the selected agents heading
    agentHeading = new UIVision(this, 25, 270, 200);
    agentHeading.setTheme(theme);
    agentHeading.setEventHandler(
        new UIAction() {
          public void change(UIAngle ang) {
            if (selectedAgent != null) {
              selectedAgent.changeViewHeading(ang.getAngle() - selectedAgent.getViewHeading());
            }
          }
        });
    winStats.addObject(agentHeading);

    // progress bar of the selected agents current health
    progHealth = new UIProgress(this, 10, 93, 230, 10);
    winStats.addObject(progHealth);

    // sliders to move agents position
    sliderX = new UISlider(this, 10, 35, 230, 15);
    sliderX.setEventHandler(
        new UIAction() {
          public void change(UISlider slider) {
            if (selectedAgent != null) {
              selectedAgent.setX((int) (slider.getValue() * Environment.width));
            }
          }
        });
    winStats.addObject(sliderX);

    sliderY = new UISlider(this, 10, 60, 230, 15);
    sliderY.setEventHandler(
        new UIAction() {
          public void change(UISlider slider) {
            if (selectedAgent != null) {
              selectedAgent.setY((int) (slider.getValue() * Environment.height));
            }
          }
        });
    winStats.addObject(sliderY);

    // boost agent health back to 100%
    btnSelectHealth = new UIButton(this, 10, 115, 65, 20, "100%");
    btnSelectHealth.setColor(84, 255, 159);
    btnSelectHealth.setEventHandler(
        new UIAction() {
          public void click(UIButton btn) {
            if (selectedAgent != null) {
              selectedAgent.updateHealth(1);
            }
          }
        });
    winStats.addObject(btnSelectHealth);

    // thrust selected agent
    btnSelectThrust = new UIButton(this, 93, 115, 65, 20, "Thrust");
    btnSelectThrust.setColor(251, 150, 20);
    btnSelectThrust.setEventHandler(
        new UIAction() {
          public void click(UIButton btn) {
            if (selectedAgent != null) {
              selectedAgent.thrust(5);
            }
          }
        });
    winStats.addObject(btnSelectThrust);

    // kill poor agent
    btnSelectKill = new UIButton(this, 175, 115, 65, 20, "KILL");
    btnSelectKill.setColor(210, 50, 50);
    btnSelectKill.setEventHandler(
        new UIAction() {
          public void click(UIButton btn) {
            if (selectedAgent != null) {
              // env.removeAgent(selectedAgent);
              env.scheduledRemove.add(selectedAgent);
              selectedAgent = null;
            }
          }
        });
    winStats.addObject(btnSelectKill);

    // Toggle focused / tracking mode for selected agent
    btnToggleFocused = new UIButton(this, 120, 5, 65, 15, (agentFocused ? "Unfocus" : "Focus"));
    btnToggleFocused.setIsLeft(false);
    btnToggleFocused.setColor(50, 100, 255);
    btnToggleFocused.setEventHandler(
        new UIAction() {
          public void click(UIButton btn) {
            agentFocused = !agentFocused;
            btn.setText(agentFocused ? "Unfocus" : "Focus");
          }
        });
    winStats.addObject(btnToggleFocused);

    // 3D neural network visual

    UITab bottomWindow = new UITab(this, 0, 300, 250, 300);
    bottomWindow.setIsTop(false);
    sidePanel.addObject(bottomWindow);

    UIWindow tabNeural = bottomWindow.addTab("Network");
    UIWindow tabTheme = bottomWindow.addTab("Theme");
    tabTheme.setBackground(30);

    neuralVisual = new UIDrawable3D(this, 0, 0, 250, 250);
    neuralVisual.setBackground(30);
    neuralVisual.setFixedBackground(true);
    tabNeural.addObject(neuralVisual);

    neuralVisual.setEventHandler(
        new UIAction() {
          private float zoom = 0.5f;
          private int offX = 0;
          private int offY = 0;
          boolean arrows[] = new boolean[4];
          private boolean rotating = true;

          public void draw(PApplet canvas) {
            if (selectedAgent == null) return;

            if (arrows[0] && !arrows[1]) offY -= moveSpeed / 3; // UP
            if (arrows[1] && !arrows[0]) offY += moveSpeed / 3; // DOWN

            MNetwork net = selectedAgent.getNetwork();

            noStroke();
            pushMatrix();

            rotateY(neuralRotation);
            scale(zoom, zoom, zoom);
            translate(offX, offY);
            for (MNeuron n : net.getNeurons()) { // draw the neurons
              int isFired = (n.isFiring() ? 255 : 60);
              if (n.getID() >= 3 && n.getID() < 3 + Agent.configNumSegments)
                fill(theme.getColor(Types.FOOD), isFired);
              else if (n.getID() >= 3 + Agent.configNumSegments
                  && n.getID() < 3 + Agent.configNumSegments * 2)
                fill(theme.getColor(Types.WALL), isFired);
              else if (n.getID() >= 3 + Agent.configNumSegments * 2
                  && n.getID() < 3 + Agent.configNumSegments * 3)
                fill(theme.getColor(Types.SHARK), isFired);
              else if (n.getID() < 3) fill(0, 255, 255, isFired);
              else fill(theme.getColor(Types.NEURON), isFired);

              MVec3f vec = n.getCoords();
              // clip node if off the display
              if ((vec.y + offY) * zoom < -135) continue;

              translate(vec.x, vec.y, vec.z);
              sphere(3);
              translate(-vec.x, -vec.y, -vec.z);
            }

            for (MSynapse s : net.getSynapses()) { // draw the links between the neurons
              MNeuron pre = s.getPreNeuron();
              MNeuron post = s.getPostNeuron();
              MVec3f n1 = pre.getCoords();
              MVec3f n2 = post.getCoords();

              // clip edge if both nodes above clipping
              if ((n1.y + offY) * zoom < -135 && (n2.y + offY) * zoom < -135) continue;

              int isFired = (pre.isFiring() ? 100 : 10);
              if (pre.getID() >= 3 && pre.getID() < 3 + Agent.configNumSegments)
                stroke(theme.getColor(Types.FOOD), isFired);
              else if (pre.getID() >= 3 + Agent.configNumSegments
                  && pre.getID() < 3 + Agent.configNumSegments * 2)
                stroke(theme.getColor(Types.WALL), isFired);
              else if (pre.getID() >= 3 + Agent.configNumSegments * 2
                  && pre.getID() < 3 + Agent.configNumSegments * 3)
                stroke(theme.getColor(Types.SHARK), isFired);
              else if (pre.getID() < 3) stroke(0, 255, 255, isFired);
              else stroke(255, isFired);

              // partial clipping when one node if above line
              if ((n1.y + offY) * zoom < -135) {
                double t = (((-135 / zoom) - offY) - n2.y) / (n1.y - n2.y);
                int x = (int) ((int) (n2.x + t * (n1.x - n2.x)) / zoom);
                line(
                    (int) (x),
                    (int) (-135 / zoom) - offY,
                    0,
                    (int) (n2.x),
                    (int) (n2.y),
                    (int) n2.z);
              } else if ((n2.y + offY) * zoom < -135) {
                double t = (((-135.0 / zoom) - offY) - (n1.y)) / (double) ((n2.y - n1.y));
                int x = (int) (n1.x + t * (n2.x - n1.x));
                line(
                    (int) (n1.x),
                    (int) (n1.y),
                    (int) n1.z,
                    (int) (x),
                    (int) (-135 / zoom) - offY,
                    0);
              } else {
                line((int) n1.x, (int) n1.y, (int) n1.z, (int) n2.x, (int) n2.y, (int) n2.z);
              }
            }

            popMatrix();
            if (rotating) neuralRotation -= 0.02;
          }

          public boolean mouseWheel(MouseWheelEvent event) {
            if (!Utilities.isPointInBox(
                mouseX, mouseY, screen.width - 250, screen.height - 250, 250, 250)) return false;

            if (zoom > minZoom || event.getWheelRotation() > 0) {
              zoom = Math.max(minZoom, (zoom + 0.1f * event.getWheelRotation()));
            }

            return true;
          }

          public boolean mousePressed() {
            if (!Utilities.isPointInBox(
                mouseX, mouseY, screen.width - 250, screen.height - 250, 250, 250)) return false;
            rotating = !rotating;
            return true;
          }

          public boolean keyReleased() { // Hotkeys for buttons
            if (!Utilities.isPointInBox(
                mouseX, mouseY, screen.width - 250, screen.height - 250, 250, 250)) return false;

            switch (keyCode) {
              case (UP):
                arrows[0] = false;
                return true;
              case (DOWN):
                arrows[1] = false;
                return true;
              case (LEFT):
                arrows[2] = false;
                return true;
              case (RIGHT):
                arrows[3] = false;
                return true;
            }
            return false;
          }

          public boolean keyPressed() { // Hotkeys for buttons
            if (!Utilities.isPointInBox(
                mouseX, mouseY, screen.width - 250, screen.height - 250, 250, 250)) return false;

            switch (keyCode) {
              case (UP):
                arrows[0] = true;
                return true;
              case (DOWN):
                arrows[1] = true;
                return true;
              case (LEFT):
                arrows[2] = true;
                return true;
              case (RIGHT):
                arrows[3] = true;
                return true;
            }
            return false;
          }
        });

    // printout of selected agents stats
    lblStatTitle = addStatLabel("Selected Agent", 5);
    lblX = addStatLabel("X", 155);
    lblY = addStatLabel("X", 170);
    lblHeading = addStatLabel("X", 185);
    lblHealth = addStatLabel("X", 200);
    lblAngle = addStatLabel("X", 215);
    lblSpeed = addStatLabel("X", 230);

    // Themes window
    themeColorWheel = new UIColorWheel(this, 45, 40);
    themeColorWheel.setEventHandler(
        new UIAction() {
          public void change(UIColorWheel wheel) {
            theme.setColor((Types) themeDrop.getSelected(), wheel.getColor());
          }
        });
    tabTheme.addObject(themeColorWheel);

    themeDrop = new UIDropdown<Types>(this, 25, 10, 200, theme.getKeys());
    themeDrop.setEventHandler(
        new UIAction() {
          public void change(@SuppressWarnings("rawtypes") UIDropdown drop) {
            themeColorWheel.setColor(theme.getColor((Types) drop.getSelected()));
          }
        });
    tabTheme.addObject(themeDrop);

    // adds mouse scrolling listener to the applet
    addMouseWheelListener(
        new MouseWheelListener() {
          public void mouseWheelMoved(MouseWheelEvent event) {
            mouseWheel(event);
          }
        });
  }
コード例 #11
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
 public void mouseDragged() {
   if (win.mouseDragged()) return;
 }
コード例 #12
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
 public void mouseReleased() {
   if (win.mouseReleased()) return;
 }
コード例 #13
0
ファイル: Simulation.java プロジェクト: RuthRainbow/anemone
 private UILabel addStatLabel(String value, int pos) {
   UILabel lbl = new UILabel(this, 10, pos, value);
   winStats.addObject(lbl);
   return lbl;
 }