Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

1682
LINES

< > BotCompany Repo | #1031123 // Smarty [stable preview]

JavaX source code (desktop) [tags: use-pretranspiled] - run with: x30.jar

Download Jar. Uses 1177K of libraries. Click here for Pure Java version (36804L/240K).

!7

set flag Reparse.

set flag OurSyncCollections. // allow to run on newer JDKs without illegal-access command line flags

import javax.swing.filechooser.*;

!include #1031138 // Smarty AI Include

sbool debugValues = false; // debug sensor output
static int defaultSpacing = 10, spacing;
static JFrame mainFrame;
static LL<JCell> gridComponents;
static JComponent mainGrid;
static JCheckBoxMenuItem miShowLocations;
static SingleComponentPanel scpMainGrid, scpVisionConfig, scpWheelsConfig, scpCurrentSmarty,
  scpStimuliAndResponse;
static JFrame historyFrame, memoryFrame;
static JSpinner worldSizeSpinner;
static JTextArea historyTextArea1, historyTextArea2;
static JTextArea taSensorValues, taDeviceValues;

// originally: BinonDisplay.ObjList.Text 
static JTextArea memoryTextArea1;

// originally: BinonDisplay.ChngResults.Text
static JTextArea memoryTextArea2;

static Smarty smarty; // AI instance

static Q smartyQ = startQ("Smarty Q"); // queue for Smarty operations

sS wallSymbol = unicode_blackSquare(), noWallSymbol = unicode_dottedSquare();

static Color lightBlue = colorFromHex("4169e1");

sS locationLetters = charRange('A', 'Z') + "[\\]^_`" + charRange('a', 'z') + charRange(123, 128);
sS smartyUnicode = unicode_slightlySmilingFace();
static int smartyFontSize = 40;

sS windowsFontID = #1400468;
sS emojiFontID = #1400470;
sS frameIconID = #1102983;
sS monospacedFontID = /*#1400479*/#1400483;
static double monospacedFontSize = 12;

sbool smartyBrainPersistence = false;

sS smartyImageID = #1102987;
sS helpTextID = #1031105;

sS worldFileExtension = "WLD";
sS bodyFileExtension = "BDY";

sS historyHelp = unindent_mls([[
  A percept is the stimulus or simultaneous combination of stimuli observed.
  An action is the response or simultaneous combination of responses produced.
  Binon # numbers refer to the binons in memory that represent the percepts, actions, expectations, and perceptual or action sequences.
  P = Percept, E = Expectation, PP = Perceptual sequence A = Action, AA = Action sequence
  Read the paper: "The Perception-Action Hierarchy and its Implementation Using Binons (Binary Neurons)"  on the www.adaptroninc.com website for more information.
]]);

// translate Smarty's windows characters to unicode
static SS winToUnicode = litmap(
  Smarty.Smarty, unicode_slightlySmilingFace(),
  Smarty.UpArrow, unicode("2191"),
  Smarty.LeftArrow, unicode("2190"),
  Smarty.RightArrow, unicode("2192"),
  Smarty.DownArrow, unicode("2193"),
  Smarty.Wall, unicode_blackVerticalRectangle(),
  Smarty.EmptySquare, unicode_whiteVerticalRectangle(),
  Smarty.chr(Smarty.TurnRght), unicode("21B1"),
  Smarty.chr(Smarty.TurnLft), unicode("21B0"),
  Smarty.chr(Smarty.TurnArnd), unicode("21B7"),
  Smarty.chr(254), unicode_blackSmallSquare(), // "Novel"
);

sbool showTabs = true;

static int defaultWorldSize = 7, maxWorldSize = 8;

static ReliableSingleThread rstUpdateChngResults = rstWithPreDelay(250, r {
  setText(memoryTextArea2, winToUnicode(state.binonDisplayChngResults));
});

svoid rearrangeLetters {
  int iLetter = 0;
  for (int y = 0; y < state.gridRows; y++)
    for (int x = 0; x < state.gridCols; x++) {
      JCell cell = getCell(x, y);
      if (cell.obstacle())
        cell.letter = ' ';
      else
        cell.letter = locationLetters.charAt(iLetter++);
    }
}

sclass VisionMode {
  sS nextSquare = "next square";
  sS distanceToWall = "distance to wall";
  sS wallOrNot = "wall or not";
}

sclass CameraDirection {
  sS down = "down";
  sS left = "left";
  sS right = "right";
  sS inFront = "in front";
  sS behind = "behind";
  
  static LS all = ll(down, left, right, inFront, behind);
}

concept SmartyBody {
  S name = "Smarty0";
  Set<S> cameraDirections = litset(CameraDirection.down);
  S visionMode = VisionMode.nextSquare; // see VisionMode
  Set<S> wheels = litset("MoveForward", "MoveLeft", "MoveRight", "MoveBackward");
  bool changed; // changed w.r.t. file
  bool used; // used in Smarty execution

  // for saving
  S wheelsFlag(S flag) { ret wheels.contains(flag) ? "1" : "0"; }

  void userCausedChange {
    print("Body: User caused change");
    changed = true;
    change();
  }
}

concept State {
  float fontScale = 1.5f;
  S worldName = "Empty";
  int gridRows = defaultWorldSize, gridCols = defaultWorldSize; // actually these are always identical, could merge into one
  bool showLocations;
  bool worldChanged;
  int startX, startY; // Smarty's start position
  int smartyX, smartyY;
  S smartyDirection = Smarty.Smarty; // which way Smarty is looking - this or smarty.LeftArrow etc.
  Rect selection = rect(1, 1, 1, 1);
  Pt cursorDirection = pt(1, 0);
  new BitSet obstacles;
  
  S history1 = "", history2 = "", memory1 = "", sensorValuesText = "", deviceValuesText = "";
  S binonDisplayChngResults = ""; // memory2  

  new Ref<SmartyBody> body;

  // value is just true
  Map<File, Bool> recentWorldFiles = synchronizedMRUCache(5);
  Map<File, Bool> recentBodyFiles = synchronizedMRUCache(5);

  void setSelection(Rect r) {
    if (!cset_trueIfChanged(this, selection := r)) ret;
    if (selection.h == 1 && selection.w > 1) _setField(cursorDirection := pt(1, 0));
    else if (selection.w == 1 && selection.h > 1) _setField(cursorDirection := pt(0, 1));
    repaintGrid();
  }

  void clearSelectedArea {
    fillSelection(false);
  }
  
  void invertSelection {
    JCell cell1 = getCell(selection.x, selection.y);
    fillSelection(!cell1.obstacle());
  }

  void fillSelection(bool b) {
    for (Pt p : pointsInRect(selection))
      if (!(b && state.smartyX == p.x && state.smartyY == p.y)) // don't put an obstacle over Smarty
        getCell(p.x, p.y).setObstacle(b);
    rearrangeLetters();
    moveCursor();
  }

  void moveCursor {
    setCursor(selection.x+cursorDirection.x, selection.y+cursorDirection.y);
  }
  
  void setCursor(int x, int y) {
    setSelection(rect(mod(x, gridCols), mod(y, gridRows), 1, 1));
  }
  
  void moveCursor(int dx, int dy) {
    _setField(cursorDirection := pt(dx, dy));
    setCursor(selection.x+cursorDirection.x, selection.y+cursorDirection.y);
  }

  void placeSmarty {
    getCell(selection.x, selection.y).setObstacle(false);
    cset(state, startX := selection.x, startY := selection.y);
    moveSmarty(selection.x, selection.y);
    moveCursor();
  }

  void moveSmarty(int x, int y) {
    if (!inRange(x, 0, gridCols) || !inRange(y, 0, gridRows))
      fail("Smarty position not in range: " + x + "/" + y);
      
    if (cset_trueIfChanged(this, smartyX := x, smartyY := y)) {
      cset(this, worldChanged := true);
      repaintGrid();
    }
  }

  void moveSmartyRelative(int x, int y) {
    moveSmarty(smartyX+x, smartyY+y);
  }
} // end of State

static State state;

sclass JCell > JComponent {
  int x, y;
  char letter;

  *(int *x, int *y) {}
  
  public void paint(Graphics _g) {
    Graphics2D g = cast _g;
    int w = getWidth(), h = getHeight();
    Color color = Color.white;
    if (state.selection.contains(x, y)) color = lightBlue;
    else if (obstacle()) color = Color.black;

    fillRect(g, 0, 0, w, h, color);
    
    if (state.showLocations && !obstacle()) {
      temp tempSetFont(g, loadFont_cached(windowsFontID));
      drawTextWithTopLeftCornerAt(g, str(letter), Pt(2, 0), Color.black);
    }
    
    if (state.smartyX == x && state.smartyY == y) {
      temp tempSetFontSize(g, smartyFontSize);
      /*temp tempSetFont(g, loadFont_cached(emojiFontID));
      drawCenteredText(g, smartyUnicode, 0, 0, w, h, Color.black);*/
      double size = 0.6;
      int dx = iround((1-size)/2*w);
      int dy = iround((1-size)/2*h);
      if (eq(state.smartyDirection, smarty.Smarty))
        drawScaledImage(g, loadImage2_cached(smartyImageID), rectFromPoints(dx, dy, w-dx, h-dy));
      else {
        //temp tempSetFont(g, loadFont_cached(windowsFontID));
        S icon = lookupOrKeep(winToUnicode, state.smartyDirection);
        drawCenteredText(g, icon, 0, 0, w, h, Color.black);
      }
    }
  }

  int index() { ret y*state.gridCols+x; }
  bool obstacle() { ret main contains(state.obstacles, index()); }
  void setObstacle(bool b) {
    if (setBit_trueIfChanged(state.obstacles, index(), b)) {
      cset(state, worldChanged := true);
      state.change();
    }
  }

  {
    addMouseListener(new MouseAdapter {
      public void mousePressed(MouseEvent e) {
        main requestFocus(mainGrid);
        if (!isLeftButton(e)) ret;
        state.setSelection(new Rect(x, y, 1, 1));
      }
    });
    
    addMouseMotionListener(new MouseMotionAdapter {
      public void mouseDragged(MouseEvent e) {
        //_print("Drag in " + x + "/" + y + ": " + e.getPoint());
        Component dest = componentAtScreenLocationInWindow(mainFrame, e);
        if (dest cast JCell) {
          //_print("Target: " + dest);
          state.setSelection(rectFromPointsInclusiveSorted(x, y, dest.x, dest.y));
        }
      }
    });
  }

  toString { ret x + "/" + y; }
}

static JCell getCell(int x, int y) {
  ret gridComponents.get(y).get(x);
}

static JCell smartyCell() {
  ret getCell(state.smartyX, state.smartyY);
}

sbool inGrid(int x, int y) {
  ret inRange(y, state.gridRows) && inRange(x, state.gridCols);
}

svoid makeMainGrid {
  gridComponents = map(iotaZeroList(state.gridRows), y
    -> map(iotaZeroList(state.gridCols), x
    -> new JCell(x, y)));
  mainGrid = setFocusable(setBackground(Color.black, hvgrid(gridComponents, 1)));
  scpMainGrid.setComponent(mainGrid);
  rearrangeLetters();
}

// always clears the world
svoid setWorldSize(int size) {
  state.obstacles.clear();
  state.gridCols = state.gridRows = size;
  setSpinnerValue(worldSizeSpinner, size);
  state.selection = rect(1, 1);
  cset(state, startX := 0, startY := 0);
  state.moveSmarty(0, 0);
  cset(state, worldChanged := false);
  state.change();
  makeMainGrid();
  resetSmarty();
}

svoid repaintGrid() {
  repaint(mainGrid);
}

// add more functions here

ifndef NoMainMethod
p { smartyMain(); }
endifndef

svoid smartyMain {
  System.out.println("Main class: " + strWithIdentityHashCode(mc()) + ", class loader: " + getClassLoader(mc()));
  
  autoRestart(2.0);

  // print all vm bus msgs from concepts
  //vmBus_snoopToPrint((msg, arg) -> firstOfArrayOrSame(arg) instanceof Concepts);
  
  setProgramDir(print("Using directory: ", mainDir()));
  logProgramOutput();
  _handleException_showThreadCancellations = true;

  if (!isDevVersion()) hideConsole();
  else veryBigConsole();
  
  db();
  uniq(SmartyBody); // make at least one instance
  state = uniq(State);
  smarty = uniq(Smarty);
  initSmarty();
  if (!state.body.has()) cset(state, body := conceptWhere(SmartyBody)); // default body
  //cset(state.body, used := false); // XXX

  // prep GUI
  
  jtattoo_mcWin(); // styling
  //set swingFontScale_debug;
  updateFontScale(state.fontScale); // scaling
  disableSpaceBarForAllButtons(); // no space bar for buttons

  loadLibrary(windowsFontID);
  defaultFrameIcon(frameIconID);
  mainFrame = showFrame("Smarty");
  //exitOnClose(mainFrame);
  onWindowClosing(mainFrame, rThread {
    cleanUp(mc()); // proper clean-up for standalone mode
    cleanKillVM();
  });

  historyTextArea1 = monospaced(uneditableBlackTextArea_noUndo());
  historyTextArea2 = monospaced(uneditableBlackTextArea_noUndo());
  memoryTextArea1 = /*withTypeWriterFont*/monospaced(uneditableBlack(swing(() -> new JLineHeightTextArea(0.8))));
  memoryTextArea2 = monospaced(uneditableBlackTextArea_noUndo(winToUnicode(state.binonDisplayChngResults)));

  makeMenus();
    
  registerCtrlKey(mainFrame, KeyEvent.VK_N, "New world", r { print("new") });
  registerCtrlKey(mainFrame, KeyEvent.VK_R, "Retrieve world", r {});
  registerCtrlKey(mainFrame, KeyEvent.VK_S, "Save world", r {});
    
  addMenu(mainFrame, "View",
    miShowLocations = jCheckBoxMenuItem("Locations (Ctrl+L)", state.showLocations, b -> {
       cset(state, showLocations := b);
       repaintGrid();
    }),
    jMenu("UI Zoom",
      "100%", r { uiZoom(100) },
      "112%", r { uiZoom(112) },
      "125%", r { uiZoom(125) },
      "140%", r { uiZoom(140) }),

    "---",
    "Self-Test", rThread selfTest,
    "Console", r activateConsole,
  );

  registerCtrlKey(mainFrame, KeyEvent.VK_L, "Locations", r {
      cset(state, showLocations := !state.showLocations);
      setChecked(miShowLocations, state.showLocations);
      repaintGrid();
    });

  JMenuBar menuBar = getMenuBar(mainFrame);
  JMenuItem helpItem = jMenuItem("Help F1", rThread showHelp);
  /*print(minSize := getMinimumSize(helpItem));
  print(preferredSize := getPreferredSize(helpItem));*/
  addMenuItem(menuBar, jPreferredWidthToMaxWidth(helpItem));
  addAndRevalidate(menuBar, jHorizontalGlue()); // spacer
  
  registerFunctionKey(mainFrame, 1, rThread showHelp);

  registerKeyCode(mainFrame, KeyEvent.VK_SPACE, r { state.invertSelection() });
  registerKeyCode(mainFrame, KeyEvent.VK_UP, r { state.moveCursor(0, -1) });
  registerKeyCode(mainFrame, KeyEvent.VK_LEFT, r { state.moveCursor(-1, 0) });
  registerKeyCode(mainFrame, KeyEvent.VK_DOWN, r { state.moveCursor(0, 1) });
  registerKeyCode(mainFrame, KeyEvent.VK_RIGHT, r { state.moveCursor(1, 0) });

  // react to any letter from A to Z
  for (int key = KeyEvent.VK_A; key <= KeyEvent.VK_Z; key++)
    registerKeyCode(mainFrame, key, r { state.placeSmarty() });

  worldSizeSpinner = jSpinner(state.gridCols, 1, maxWorldSize);
  onChange(worldSizeSpinner, r {
    int size = intFromSpinner(worldSizeSpinner);
    if (state.gridCols != size) {
      if (state.worldChanged) {
        int result = swingShowConfirmDialog(mainFrame,
          "World has changed! - Save it before resizing?", "Smarty",
          JOptionPane.YES_NO_CANCEL_OPTION);
        if (result == JOptionPane.YES_OPTION) {
          saveWorldDialog(r { newWorldWithoutConfirmation(size) });
          ret;
        } else if (result == JOptionPane.CANCEL_OPTION) {
          setSpinnerValue(worldSizeSpinner, state.gridCols);
          ret;
        }
      }
        
      newWorldWithoutConfirmation(size);
    }
  });

  scpCurrentSmarty = singleComponentPanel();

  JComponent middleArea = westAndCenterWithMargin(spacing,
     jvstackWithSpacing(spacing,
        withTitle("World size (max=\*maxWorldSize*/)", worldSizeSpinner),
        jbutton("Clear Selected Area", rThread { state.clearSelectedArea() }),
        withTitle("Current world", disableTextField(jLiveValueTextField(conceptFieldLiveValue worldName(state)))),
        showTabs ? null : fontSizePlus(2, setForeground(Color.ORANGE, jCenteredLabel("Design or Select a body for Smarty"))),
        showTabs ? null : jbutton("Design or Select Smarty's Body", r showSmartyConfig),
        withTitle("Current Smarty", scpCurrentSmarty),
     ),
     jvgridWithSpacing(spacing,
       withTitle(monospaced(jLabel("Sensors: Values:  are:")), taSensorValues = monospaced(uneditableBlackTextArea_noUndo())),
       withTitle(monospaced(jLabel("Devices: Values:  are:")), taDeviceValues = monospaced(uneditableBlackTextArea_noUndo())),
     )
   );

  JComponent controlArea = northAndCenterWithMargin(spacing,
    vstackWithSpacing(
      fontSizePlus(2, setForeground(Color.blue, jlabel("Design the World - Read the Help F1"))),
      jMinHeight(swingScale(200), middleArea)),
    jCenteredSection("Run Smarty",
      jflow(
        jbutton("Go / Continue", rThread goCmd),
        jButton("See History", r showHistory),
        jbutton("See Memory", r showMemory),
        jbutton("Reset", r resetSmarty),
        jbutton("EXIT", rThread killVM),
        ))
  );

  scpMainGrid = singleComponentPanel();
  makeMainGrid();

  var mainPanel = westAndCenterWithMargins(spacing,
    jvstackWithSpacing(spacing,
      jFixedSize(500, scpMainGrid),
      scpStimuliAndResponse = singleComponentPanel()
    ),
    controlArea);

  if (showTabs) {
    scpVisionConfig = singleComponentPanel();
    scpWheelsConfig = singleComponentPanel();
    updateTabs();
    setFrameContents(mainFrame, jtabs(
      "Smarty", mainPanel,
      "Vision", scpVisionConfig,
      "Wheels", scpWheelsConfig,
      ));
  } else
    setFrameContents(mainFrame, mainPanel);
  
  centerPackFrame(mainFrame);

  // set minimum width
  jMinWidth_pure(mainFrame, max(1000, swingScale(800)));
  centerFrameWithMinWidth(mainFrame);

  // set min frame height
  jMinHeight_pure(mainFrame, getHeight(mainFrame));
  
  // disable height changes entirely (not good, may cut off at the bottom)
  //frameMaxHeight(mainFrame, getHeight(mainFrame));

  //smarty.World.Update_View();
  setText(historyTextArea1, winToUnicode(state.history1));
  setText(historyTextArea2, winToUnicode(state.history2));
  setText(memoryTextArea1, winToUnicode(state.memory1));
  setText(taSensorValues, winToUnicode(state.sensorValuesText));
  setText(taDeviceValues, winToUnicode(state.deviceValuesText));

  enableBodyControls(!state.body->used);

  if (!smartyBrainPersistence)
    resetSmarty();

  if (!isDevVersion()) showHelp();
  else {
    //goCmd();
    //selfTest();
  }
} // end of main function

sbool isDevVersion() {
  ret isProgramID(#1031104);
}

svoid updateTabs {
  scpVisionConfig.setComponent(visionConfigPanel(state.body!));
  scpWheelsConfig.setComponent(wheelsConfigPanel(state.body!));
  scpCurrentSmarty.setComponent(disableTextField(jLiveValueTextField(conceptFieldLiveValue name(state.body!))));
  updateFrameTitles();
}

svoid enableBodyControls(bool b) {
  var panels = ll(scpVisionConfig, scpWheelsConfig);
  for (var c : concatMap(panel -> allChildrenOfType JComponent(panel), panels))
    if (isInstanceOfAny(c, JCheckBox.class, JRadioButton.class, JTextField.class)
      && !c instanceof BodyNameField)
      setEnabled(c, b);

  // Modify body button is enabled iff controls are not enabled
  for (var c : concatMap(panel -> allChildrenOfType ModifyBodyButton(panel), panels))
    setEnabled(c, !b);
}

svoid showHelp() {
  S text = loadSnippet(helpTextID);
  S heading = firstLine(text);
  trim(dropFirstLine(text));
  showCenterFrame(swingScale(650), swingScale(500), heading, makeUneditableWithTextColor(Color.black, wordWrapTextArea(text)));
}

// run in Swing thread
svoid showHistory {
  Font ttFont = loadFont_cached(monospacedFontID, 12), helpFont = sansSerifFont(13);
  if (historyFrame == null)
    historyFrame = getFrame(showCenterFrame(maxPt(pt(800, 600), scalePt(600, 350, swingFontScale())),
      "History",
      withMargin(centerAndSouthWithMargin(spacing,
        centerAndSouthWithMargin(
          historyTextArea1,
          westAndCenterWithMargin(spacing, setFont(ttFont, topAlignLabel(jMultiLineLabel(
            linesLL("Step #", "Percept", "Binon #", "", "Expect", "Binon #",
              "", "Action", "Binon #", "", "Action", "Binon #", "")))),
            setFont(ttFont, historyTextArea2))
        ),
        centerAndEastWithMargin(
          setFont(helpFont, jMultiLineLabel(historyHelp)),
          jvstackWithSpacing(
            jButton("See World", r showWorld),
            jButton("See Memory", r showMemory))
        )))));
  else
    frameToFront(historyFrame);
  updateFrameTitles();
}

// run in Swing thread
svoid showMemory {
  Font ttFont = typeWriterFont(12), helpFont = sansSerifFont(13);

  S Interestng;
  if (smarty.InterestLevels == 3)
    Interestng = smarty.IntSymb(smarty.INTERESTING) +  "=Interesting, ";
  else  // Only 2 interest levels
    Interestng = "";

  S IntSymbol = "    " + smarty.IntSymb(smarty.NOVEL) + " = Novel, " + Interestng + smarty.IntSymb(smarty.FAMILIAR) + " = Familiar";

  // originally: BinonDisplay.ObjListTitle.Caption
  S title = "Bn# OL Ty TL BL TR BR   V L R In LQ RQ Pattern "
    + "  l = Left target, r = Right target," + IntSymbol;

  if (memoryFrame == null)
    memoryFrame = getFrame(showCenterFrame(scalePt(600, 350, swingFontScale()),
      "Smarty's Memory",
        jvsplit(0.5, 100,
          centerAndEastWithMargins(
            northAndCenterWithMargin(
              setFont(ttFont, jLabel(title)),
              setFont(ttFont, memoryTextArea1)),
            jvstackWithSpacing(
              jButton("See World", r showWorld),
              jButton("See History", r showHistory))
          ),
          memoryTextArea2,
        )
        ));
  else
    frameToFront(memoryFrame);
  updateFrameTitles();
}

svoid showWorld {
  frameToFront(mainFrame);
}

static JRadioButton visionModeRadioButton(SmartyBody body, ButtonGroup buttonGroup, S visionMode, S text) {
  var rb = jradiobutton(buttonGroup, text, eq(body.visionMode, visionMode));
  onChange(rb, r { if (isChecked(rb)) { cset(body, +visionMode); body.userCausedChange(); } });
  ret rb;
}

sclass ModifyBodyButton extends JButton {
  *() {
    super("Modify Smarty's Body");
    main addActionListener(this, r modifyBody);
  }
}

// enable the controls to edit the body, after confirmation
svoid modifyBody {
  if (state.body->used) {
    if (!confirmOKCancel("Modifying Smarty's body will reset its state. Do you wish to continue?"))
      ret;
    resetSmarty();
    cset(state.body, used := false);
  }
  enableBodyControls(true);
}

sclass BodyNameField extends JTextField {
  *() {
    setEnabled(false);
  }
}

static JComponent bodyNamePanel(SmartyBody body) {
  SimpleLiveValue<S> lv = conceptFieldLiveValue name(body);
  lv.onChange(r { body.userCausedChange() });
  ret hstackWithSpacing(
    jMinWidth(swingScale(250), withLabel("Body name:",
      bindTextComponentToLiveValue(lv, swing(() -> new BodyNameField)))),
    swing(() -> new ModifyBodyButton));
}

static JComponent visionConfigPanel(SmartyBody body) {
  var buttonGroup = buttonGroup();
  ret northAndCenterWithMargins(spacing,
    bodyNamePanel(body),
    westAndCenterWithMargin(spacing,
      jCenteredSection("Camera",
        withLeftMargin(spacing, jvstackWithSpacing(spacing,
          itemPlus(jlabel("Select the camera directions"),
            map(CameraDirection.all, direction -> {
              var cb = jCheckBox(direction, body.cameraDirections.contains(direction));
              onChange(cb, r { addOrRemove(body.cameraDirections, direction, isChecked(cb)); body.userCausedChange(); });
              ret cb;
            }))))),
            
      jCenteredSection("Vision mode",
        jvstack(
          withLeftMargin(spacing*2, jMultiLineLabel(unindentMLS(replaceSymbols([[
            Square locations are identified with a letter.
            Looking down provides the letter of the current square.
            Looking in other directions provides the letter in the next square
            depending on the direction Smarty is facing.
            If it is a wall then the symbolic stimulus is a $wallSymbol
          ]])))),
          visionModeRadioButton(body, buttonGroup, VisionMode.nextSquare,
            "Looking at the next square (symbolic values = letter in square)"),
          verticalStrut(20),
  
          withLeftMargin(spacing*2, jMultiLineLabel(unindentMLS([[
            Looking at the distance to the nearest wall will provide the
            number of squares from the current position to the wall in
            the direction the camera is facing. A wall next to the robot
            is one square away. Looking down will always return 1.
          ]]))),
          visionModeRadioButton(body, buttonGroup, VisionMode.distanceToWall,
          "Distance to wall (magnitude values = number of squares)"),
          verticalStrut(20),
          
          withLeftMargin(spacing*2, jMultiLineLabel(unindentMLS([[
            Looking at the next square will either see a wall or an empty square
          ]]))),
          visionModeRadioButton(body, buttonGroup, VisionMode.wallOrNot,
            replaceSymbols("Wall or not (symbolic values = $wallSymbol for a wall and $noWallSymbol for no wall)")),
        ))));
}

svoid showVisionConfig(SmartyBody body) {
  showPackedFrame("Configure Smarty's Vision",
    visionConfigPanel(body));
}

static JCheckBox wheelsConfigCheckBox(SmartyBody body, S symbol, S text) {
  var cb = jCheckBox(text, body.wheels.contains(symbol));
  onChange(cb, r { addOrRemove(body.wheels, symbol, isChecked(cb)); body.userCausedChange(); });
  ret cb;
}

static JComponent wheelsConfigPanel(SmartyBody body) {
  ret northAndCenterWithMargins(spacing,
    bodyNamePanel(body),
    vstackWithSpacing(spacing,
      westAndCenterWithMargin(spacing,
        jCenteredSection("Movement",
          withLeftMargin(spacing, jvstackWithSpacing(
            jlabel("Select the wheel motions"),
            wheelsConfigCheckBox(body, "MoveForward", "Forward = f"),
            wheelsConfigCheckBox(body, "MoveLeft", "Left = l"),
            wheelsConfigCheckBox(body, "MoveRight", "Right = r"),
            wheelsConfigCheckBox(body, "MoveBackward", "Backward = b"),
          ))),
          
        jCenteredSection("Help", withLeftMargin(
          jTopLabel(hhtml(nlToBr(mls_replaceDollarVars(winToUnicode([[
            Motion in any direction will move one square.
            If there is a wall in the way it will not move. A "-" indicates no motion.
            The direction moved depends on the direction Smarty is facing.
            Smarty will appear as a $smiley if it cannot turn.  It will then always be
            facing North (up) so moving right will move to the East (right).
          ]]), smiley := himgForJLabel(#1102987, width := swingScale(15), height := swingScale(15), align := "middle")))))))),
  
      westAndCenterWithMargin(spacing,
        jCenteredSection("Turning",
          withLeftMargin(spacing, vstackWithSpacing(
            wheelsConfigCheckBox(body, "TurnRight", "Turn to the right = Rotate clockwise 90 degrees = " + winToUnicode(Smarty.TurnRght)),
            wheelsConfigCheckBox(body, "TurnLeft", "Turn to the left = Rotate anti-clockwise 90 degrees = " + winToUnicode(Smarty.TurnLft)),
            wheelsConfigCheckBox(body, "TurnAround", "Turn around = Rotate 180 degrees = " + winToUnicode(Smarty.TurnArnd)),
          ))),
          
        jCenteredSection("Help", withLeftMargin(jMultiLineLabel(winToUnicode(mls_replaceDollarVars([[
          Turning will stay on the same square. If Smarty is able to turn then it will appear as $left, $right, $up or $down to indicate the direction it is facing.
        ]],
          left := Smarty.LeftArrow,
          right := Smarty.RightArrow,
          up := Smarty.UpArrow,
          down := Smarty.DownArrow)))))),
  
      jCenteredSection("Sensing", 
        withLeftMargin(spacing, wheelsConfigCheckBox(body, "AddAWheelSensor", "Add a wheel sensor to detect if it moved or failed because it was up against a wall"))
      )
    ));
}

svoid showWheelsConfig(SmartyBody body) {
  var buttonGroup = buttonGroup();
  showPackedFrame("Configure Smarty's Wheels",
    wheelsConfigPanel(body));
}

svoid showSmartyConfig {
  showPackedFrameMinWidth(400, "Design or Select a body for Smarty",
    withMargin(centeredButtons(
      "Vision", r { showVisionConfig(state.body!) },
      "Wheels", r { showWheelsConfig(state.body!) },
    ))
  );
}

// e.g. $wallSymbol and $noWallSymbol
sS replaceSymbols(S s) {
  ret replaceDollarVars(s, +wallSymbol, +noWallSymbol);
}

sS exportWorldForSaving() {
  new LS lines;
  lines.add(" " + state.gridRows);
  
  for (int y = 0; y < state.gridRows; y++)
    for (int x = 0; x < state.gridCols; x++) {
      JCell cell = getCell(x, y);
      lines.add(state.smartyX == x && state.smartyY == y ? "A" : cell.obstacle() ? "#" : " ");
    }

  ret lines(lines);
}

svoid saveWorld(File f) {
  S text = exportWorldForSaving();
  saveTextFile(f, text);
  infoBox("World saved: " + fileName(f));
  cset(state, worldName := fileNameWithoutExtension(f), worldChanged := false);
  addRecentWorldFile(f);
}

svoid addRecentWorldFile(File f) swing {
  removeAndPut(state.recentWorldFiles, f, true);
  state.change();
  makeMenus();  
}

svoid addRecentBodyFile(File f) swing {
  removeAndPut(state.recentBodyFiles, f, true);
  state.change();
  makeMenus();  
}

svoid loadWorld(File f) {
  pcall-messagebox {
    LS lines = linesFromFile_list(f);

    if (eqAfterRtrim(lines(lines), exportWorldForSaving()))
      ret with infoBox("World is the same as current one");
    
    int i = 0;
    int gridRows = parseInt(trim(lines.get(i++)));
    if (gridRows < 1 || gridRows > maxWorldSize) fail("Bad world size: " + gridRows);
    setWorldSize(gridRows);
    for y to gridRows:
      for x to gridRows: {
        S line = lines.get(i++);
        JCell cell = getCell(x, y);
        if (eq(line, "#"))
          cell.setObstacle(true);
        else if (eq(line, "A"))
          state.moveSmarty(x, y);
      }
    rearrangeLetters();
    repaintGrid();
    cset(state, worldName := fileNameWithoutExtension(f));
    cset(state, worldChanged := false);
    infoBox("World loaded: " + fileName(f));
    addRecentWorldFile(f);
  }
}

svoid saveWorldDialog(Runnable whenDone default null) {
  swing {
    JFileChooser fileChooser = new(mainDir());
    fileChooser.setFileFilter(new FileNameExtensionFilter("Smarty World files (*.\*worldFileExtension*/)", worldFileExtension));
          
    if (fileChooser.showSaveDialog(mainFrame) == JFileChooser.APPROVE_OPTION) {
      File f = defaultExtension(worldFileExtension, fileChooser.getSelectedFile());
      saveWorld(f);
      infoBox("World saved as " + f.getName());
    } else
      messageBox("World NOT saved");

    callF(whenDone);
  }
}

svoid withWorldSave(Runnable r) {
  if (state.worldChanged) {
    int result = swingShowConfirmDialog(mainFrame,
      "World has changed! - Save it?", "Smarty",
      JOptionPane.YES_NO_CANCEL_OPTION);
    if (result == JOptionPane.YES_OPTION) {
      saveWorldDialog(r);
      ret;
    } else if (result == JOptionPane.CANCEL_OPTION)
      ret;
    else
      cset(state, worldChanged := false);
  }

  r.run();
}

svoid loadWorldDialog {
  withWorldSave(r loadWorldDialogWithoutConfirmation);
}

svoid loadWorldDialogWithoutConfirmation swing {
  JFileChooser fileChooser = new(mainDir());
  fileChooser.setFileFilter(new FileNameExtensionFilter("Smarty World files (*.\*worldFileExtension*/)", worldFileExtension));
        
  if (fileChooser.showOpenDialog(mainFrame) == JFileChooser.APPROVE_OPTION)
    loadWorld(fileChooser.getSelectedFile());
}

svoid newWorld {
  withWorldSave(r newWorldWithoutConfirmation);
}

svoid newWorldWithoutConfirmation(int size default defaultWorldSize) {
  setWorldSize(size);
  cset(state, worldName := "Empty", worldChanged := false);
  updateFrameTitles();
}

static File mainDir() {
  ret mkdirs(userDir("Adaptron/Smarty"));
}

sS exportBodyForSaving() {
  S[] Body = new[22];
  bodyToArray(state.body!, Body);
  ret lines(dropFirst(Body));
}

svoid saveBody(File f) {
  S text = exportBodyForSaving();
  saveTextFile(f, text);

  cset(state.body!, changed := false);
  addRecentBodyFile(f);
}

svoid bodyToArray(SmartyBody b, S[] Body) {
  Body[1] = escapeNewLines(b.name);
  // TODO: remaining values from AI?
  Body[2] = "1"; //Vision.Value = 1 - checked
  Body[3] = "0"; //Touch.Value
  Body[4] = b.wheelsFlag("AddAWheelSensor");
  Body[5] = "1"; //Wheels.Value
  Body[6] = "0"; //Arms.Value
  Body[7] = b.cameraDirections.contains(CameraDirection.down) ? "1" : "0"; //VisionConfig.VisionDown
  Body[8] = b.cameraDirections.contains(CameraDirection.left) ? "1" : "0"; //VisionConfig.VisionLeft
  Body[9] = b.cameraDirections.contains(CameraDirection.right) ? "1" : "0"; //VisionConfig.VisionRight
  Body[10] = b.cameraDirections.contains(CameraDirection.inFront) ? "1" : "0"; //VisionConfig.VisionInFront
  Body[11] = b.cameraDirections.contains(CameraDirection.behind) ? "1" : "0"; //VisionConfig.VisionBehind
  Body[12] = eq(b.visionMode, VisionMode.nextSquare) ? "True" : "False"; //VisionConfig.VisionSymbolicOrMagnitude(0)
  Body[13] = eq(b.visionMode, VisionMode.distanceToWall) ? "True" : "False"; //VisionConfig.VisionSymbolicOrMagnitude(1)
  Body[14] = b.wheelsFlag("MoveForward");
  Body[15] = b.wheelsFlag("MoveLeft");
  Body[16] = b.wheelsFlag("MoveRight");
  Body[17] = b.wheelsFlag("MoveBackward");
  Body[18] = b.wheelsFlag("TurnRight");
  Body[19] = b.wheelsFlag("TurnLeft");
  Body[20] = b.wheelsFlag("TurnAround");
  Body[21] = eq(b.visionMode, VisionMode.wallOrNot) ? "True" : "False"; //VisionConfig.VisionSymbolicOrMagnitude(2)
}

svoid withBodySave(Runnable r) {
  if (state.body->changed) {
    int result = swingShowConfirmDialog(mainFrame,
      "Body has changed! - Save it?", "Smarty",
      JOptionPane.YES_NO_CANCEL_OPTION);
    if (result == JOptionPane.YES_OPTION) {
      saveBodyDialog(r);
      ret;
    } else if (result == JOptionPane.CANCEL_OPTION)
      ret;
    else
      cset(state.body, changed := false);
  }

  r.run();
}

svoid newBody {
  withBodySave(r newBodyWithoutConfirmation);
}

svoid newBodyWithoutConfirmation {
  cdelete(state.body!);
  cset(state, body := cnew(SmartyBody));
  finishBodySetup();
}

svoid loadBody(File f) {
  pcall-messagebox {
    LS lines = linesFromFile_list(f);

    if (eqAfterRtrim(lines(lines), exportBodyForSaving()))
      ret with infoBox("Body is the same as current one");
   
    S[] Body = asStringArray(itemPlusList("", lines));
    newBody();
    var b = state.body!;
    // TODO: remaining values
    cset(b, name := Body[1]);
    if (eq(Body[4], "1")) b.wheels.add("AddAWheelSensor");
    if (eq(Body[7], "1")) b.cameraDirections.add(CameraDirection.down);
    if (eq(Body[8], "1")) b.cameraDirections.add(CameraDirection.left);
    if (eq(Body[9], "1")) b.cameraDirections.add(CameraDirection.right);
    if (eq(Body[10], "1")) b.cameraDirections.add(CameraDirection.inFront);
    if (eq(Body[11], "1")) b.cameraDirections.add(CameraDirection.behind);
    if (eq(Body[12], "True")) b.visionMode = VisionMode.nextSquare;
    if (eq(Body[13], "True")) b.visionMode = VisionMode.distanceToWall;
    if (eq(Body[14], "1")) b.wheels.add("MoveForward");
    if (eq(Body[15], "1")) b.wheels.add("MoveLeft");
    if (eq(Body[16], "1")) b.wheels.add("MoveRight");
    if (eq(Body[17], "1")) b.wheels.add("MoveBackward");
    if (eq(Body[18], "1")) b.wheels.add("TurnRight");
    if (eq(Body[19], "1")) b.wheels.add("TurnLeft");
    if (eq(Body[20], "1")) b.wheels.add("TurnAround");
    if (eq(Body[21], "True")) b.visionMode = VisionMode.wallOrNot;
    cset(b, changed := false);
    b.change();
    updateTabs();
    infoBox("Body loaded: " + fileName(f));
    addRecentBodyFile(f);
    
    finishBodySetup();
  }
}

svoid saveBodyDialog(Runnable whenDone default null) {
  swing {
    new JFileChooser fileChooser;
    fileChooser.setSelectedFile(newFile(mainDir(), sanitizeFileName(state.body->name)));
    fileChooser.setFileFilter(new FileNameExtensionFilter("Smarty Body files (*.\*bodyFileExtension*/)", bodyFileExtension));
          
    if (fileChooser.showSaveDialog(mainFrame) == JFileChooser.APPROVE_OPTION) {
      File f = defaultExtension(bodyFileExtension, fileChooser.getSelectedFile());
      cset(state.body, name := fileNameWithoutExtension(f));
      updateTabs();
      saveBody(f);
      infoBox("Body saved as " + f.getName());
    }

    callF(whenDone);
  }
}

svoid loadBodyDialog {
  withBodySave(r loadBodyDialogWithoutConfirmation);
}

svoid loadBodyDialogWithoutConfirmation swing {
  JFileChooser fileChooser = new(mainDir());
  fileChooser.setFileFilter(new FileNameExtensionFilter("Smarty Body files (*.\*bodyFileExtension*/)", bodyFileExtension));
        
  if (fileChooser.showOpenDialog(mainFrame) == JFileChooser.APPROVE_OPTION)
    loadBody(fileChooser.getSelectedFile());
}

sS infosForWindowTitle() {
  ret " - World= " + state.worldName + ", Body= " + state.body->name
    + ", Run done on " + simpleDateFormat_defaultTZ_now("MM-dd-YYYY") + " (mm-dd-yyyy) "
    + simpleDateFormat_defaultTZ_now("HH:mm:ss");
}

svoid updateFrameTitles {
  frameTitle(mainFrame, "Smarty's World" + infosForWindowTitle());
  frameTitle(historyFrame, "Stimulus/Response, Percept/Action History" + infosForWindowTitle());
  frameTitle(memoryFrame, "Smarty's Memory" + infosForWindowTitle());
}

// Make the dynamic menus
svoid makeMenus {
  addMenu(mainFrame, "Worlds",
    "New... (Ctrl+N)", r newWorld,
    "Retrieve... (Ctrl+R)", r loadWorldDialog,
    "Save... (Ctrl+S)", r saveWorldDialog,
    "---",
    mapWithIndexStartingAt1(reversed(keys(state.recentWorldFiles)), (idx, f)
      -> jMenuItem(idx + " " + fileName(f), r { withWorldSave(r { loadWorld(f) }) })),
    empty(state.recentWorldFiles) ? null : "---",
    "Exit", rThread killVM);
  
  addMenu(mainFrame, "Bodies",
    "New... (Ctrl+N)", r newBody,
    "Retrieve... (Ctrl+R)", r loadBodyDialog,
    "Save... (Ctrl+S)", r saveBodyDialog,
    "---",
    mapWithIndexStartingAt1(reversed(keys(state.recentBodyFiles)), (idx, f)
      -> jMenuItem(idx + " " + fileName(f), r { withBodySave(r { loadBody(f) }) })),
    empty(state.recentBodyFiles) ? null : "---",
    "Exit", rThread killVM);   
}

svoid goCmd { smartyQ.add(r goCmd_impl); }

svoid goCmd_impl {
  pcall-messagebox {
    evalWithTimeout(10.0, r {
      print("goCmd");
      
      if (!state.body->used) {
        finishBodySetup();
        cset(state.body, used := true);
        enableBodyControls(false);
      }
    
      smarty.bodies.BodyFunction2();
    });
  }
}

static int Indx(int row, int col) {
  // C is the across / column location - 0 based
  // R is the down / row location - 0 based
  // the function gives the index into NG and CWP linear arrays - 0 based
  // can not be greater than 199 because 200+ used for wall letters

  ret state.gridCols * row + col;
}

// NG(I).Caption is contents of cell x/y
// with I = WallNo(x, y, direction)

// NG Caption is either "" or smarty.Smarty
// or Chr$(64 + InNG - 200) [location letter?]

// WP$ seems to hold the playing field
// WP$(R, C) can be ABlock$

/* From Brett:

WP$ contains either a space, a block or the smarty symbol.

The smarty symbol may be an S,   >,   <,   ^,  v or  + depending on which way smarty is pointing.

NG().captions contain the letter for each location. The LookUnder$() routine returns the letter
for the camera that looks down at the current square. LookNorth$() etc. and other Look routines
return the cell letter that is one cell away.

The higher index values into NG() can contain letter for the walls set by the IDWalls() routine.
But this feature is not in use and the higher index entries of NG are invisible so a mouse down
cannot be done on them.

Note also that ScopeLeft$() and other Scope routines that would return wall letters are not in use.
*/

static int InRow() { ret state.smartyY; }
static int InCol() { ret state.smartyX; }

svoid setSmartyDirection(S smartyDirection) {
  if (cset_trueIfChanged(state, +smartyDirection))
    repaintGrid();
}

// query cell for Smarty's brain
sS WP(int y, int x) {
  if (x == InCol() && y == InRow()) 
    ret state.smartyDirection;
  else
    ret getCell(x, y).obstacle() ? smarty.ABlock : smarty.ASpace;
}

// put more static functions here

sclass TheWorld extends Smarty.TheWorld {

  // find what letter we are standing on
  public String LookUnder() {
     ret str(smartyCell().letter);
  }
  
  void moveRelative(int x, int y) {
    x += state.smartyX; y += state.smartyY;
    
    if (!inGrid(x, y) || getCell(x, y).obstacle())
      smarty.Motion = smarty.NoMove;
    else {
      smarty.Motion = "Y";
      state.moveSmarty(x, y);
    }
  }
  
  public void MoveWest() {
    moveRelative(-1, 0);
  }

  public void MoveSouth() {
    moveRelative(0, 1);
  }

  public void MoveEast() {
    moveRelative(1, 0);
  }

  public void MoveNorth() {
    moveRelative(0, -1);
  }

  public void DisplayStimuliAndResponse(int[][][] inputValues, char response) {
    new L<JComponent> leftStack;
    new L<JComponent> rightStack;
    
    for (int Sens = 1; Sens <= smarty.NumSenses; Sens++) {
      int NumSensors = smarty.Senses[Sens][smarty.NumberOfSensors];
      if (NumSensors == 0) continue;
      if (NumSensors != 1) warn("More than one sensor in sense");

      S label = smarty.Sense[Sens] + ":";
      
      var inputValue = inputValues[Sens][smarty.Valu][smarty.Fired];
      S text;
      if (smarty.Senses[Sens][smarty.SymbolicOrMagnitude] == smarty.Magnitude)
        text = str(charPlus(inputValue , '0'))
          + (debugValues ? " [magn " + inputValue + "]" : "");
      else
        text = lookupOrKeep(winToUnicode, str((char) inputValue))
          + (debugValues ? " [char " + inputValue + "]" : ""); // A, B, C etc

      leftStack.add(withLabel(withTypeWriterFont(jlabel(label)), jlabel(text)));
    }

    for (int Devs = 1; Devs <= smarty.NumDevices; Devs++) {
      S label = smarty.Device[Devs] + ":";

      rightStack.add(withLabel(withTypeWriterFont(jlabel(label)), jlabel(winToUnicode(response))));
    }

    scpStimuliAndResponse.set(jhgridWithSpacing(
      vstack(leftStack),
      vstack(rightStack)));
  }
  
  public void Update_View() {           // called by World.LookInside_Click() to SeeHistory and Bodies.BodyFunction2()
                           // refreshes all the subwindows
    historyDisplay();
    dumpLTM();
  }

  public void setBinonDisplayObjList(S text) {
    cset(state, memory1 := text);
    setText(memoryTextArea1, winToUnicode(text));
  }
  
  public void setBinonDisplayChngResults(S text) {
    cset(state, binonDisplayChngResults := text);
    rstUpdateChngResults.trigger();
  }

  public void appendBinonDisplayChngResults(S text) {
    cset(state, binonDisplayChngResults := state.binonDisplayChngResults+text);
    rstUpdateChngResults.trigger();
  }

  S lookDirection(int dx, int dy) {
    int x = InCol()+dx, y = InRow()+dy;
    // If off top of board or next one is a block
    if (inGrid(x, y)) // must be on board
      if (eq(WP(y, x), smarty.ABlock))
        ret smarty.Wall; // looking at a wall
      else // valid square to move into
        ret str(getCell(x, y).letter);
    else // looking beyond grid
      ret smarty.Wall;
  }

  public S LookNorth() { ret lookDirection(0, -1); }
  public S LookSouth() { ret lookDirection(0,  1); }
  public S LookWest()  { ret lookDirection(-1, 0); }
  public S LookEast()  { ret lookDirection( 1, 0); }

  public void PointUnder() {
    setSmartyDirection(smarty.Smarty);
  }

  public void PointNorth() {
    setSmartyDirection(smarty.UpArrow);
  }

  public void PointSouth() {
    setSmartyDirection(smarty.DownArrow);
  }

  public void PointWest() {
    setSmartyDirection(smarty.LeftArrow);
  }

  public void PointEast() {
    setSmartyDirection(smarty.RightArrow);
  }

  public int RangeNorth() { ret rangeDirection(0, -1); }
  public int RangeSouth() { ret rangeDirection(0,  1); }
  public int RangeWest()  { ret rangeDirection(-1, 0); }
  public int RangeEast()  { ret rangeDirection( 1, 0); }
  
  public int rangeDirection(int dx, int dy) { // Distance to wall
    int x = InCol(), y = InRow();
    int i = 0;
    do {
      ++i;
      x += dx;
      y += dy;
      // End if off rightside of board or next one is a block
    } while (inGrid(x, y) && !getCell(x, y).obstacle());
    ret i;
  }

  public void showSensorValues(S text) {
    cset(state, sensorValuesText := text);
    setText(taSensorValues, winToUnicode(text));
  }

  public void showDeviceValues(S text) {
    cset(state, deviceValuesText := text);
    setText(taDeviceValues, winToUnicode(text));
  }
} // end of TheWorld

svoid initSmarty {
  smarty.World = new TheWorld;
  smarty.Initialize_Adaptron();
}

svoid resetSmarty aka finishBodySetup() {
  print("Finishing body setup");
  
  ResetSmartyLocation();
  smarty.Reset_Adaptron(); // set all memory, S-List, Habit stack

  cset(state, history1 := "", history2 := "", memory1 := "");
  setText(historyTextArea1, "");
  setText(historyTextArea2, "");
  setText(memoryTextArea1, "");
  setText(memoryTextArea2, "");

  bodyToArray(state.body!, smarty.BodySetting);
  //pnl("BodySetting", smarty.BodySetting);
      
  /*if (Vision.Value = 1)*/ smarty.SetupVision();
  //if (Touch.Value = 1) smarty.SetupTouch();
    
  /*if (Wheels.Value = 1)*/ smarty.SetupWheels();
  //if (Arms.Value = 1) smarty.SetupArms();
    
  SetupStimulusResponseDisplay(); //Setup display of stimuli at bottom of World screen

  updateTabs();
}

// not needed
svoid SetupStimulusResponseDisplay {}

// from Adapt.frm / HISTORYDISPLAY
svoid historyDisplay {
  new LS lines;
  StringBuilder[] ILines$ = new[10+1];
  int GroupSize = 250;               // After 250 events the IO.Text area wraps the lines
  new StringBuilder NumberLine$;
  new StringBuilder OLine1$;
  
  for (int Group = 0; Group <= (smarty.LIN - 1) / GroupSize; Group++) {  
    int EndLIN = min(GroupSize * Group + GroupSize, smarty.LIN);
  
    for (int E = GroupSize * Group + 1; E <= EndLIN; E++) {  
      if (E == GroupSize * Group + 1) { // If it is the 1st position / event in the line then
        NumberLine$ = new StringBuilder("Step # : ");
        for (int Sn = 1; Sn <= smarty.NumSenses; Sn++) // Add labels to start of lines
          ILines$[Sn] = new StringBuilder(takeFirst(smarty.Sense[Sn] + ":    ", 9));        // The name of the sense
        OLine1$ = new StringBuilder(takeFirst(smarty.Device[1] + ":   ", 9));  // The name of the action device
      }

      S E_str = " " + E; // visual-basic style int-to-string!
      NumberLine$.append(E_str);           // the event number
      int Wdth = l(E_str);  // the space taken up by the event number
      
      for (int Sn = 1; Sn <= smarty.NumSenses; Sn++)
        ILines$[Sn].append(takeLast("     " + smarty.INPUTS[E][Sn], Wdth));
    
      OLine1$.append(takeLast("     " + smarty.OUTPUTS[E][1], Wdth));
    }

    lines.add(str(NumberLine$));
    for (int Sn = 1; Sn <= smarty.NumSenses; Sn++)
      lines.add(str(ILines$[Sn]));
    lines.add(str(OLine1$));
    lines.add("");  
  }

  S text = rtrim(lines(lines));
  cset(state, history1 := text);
  setText(historyTextArea1, winToUnicode(text));
} // end of historyDisplay

svoid dumpLTM { //Called by Update_View
  String MemValT = null;
  int[] MemSize = new[2000 + 1];
  String Sen = null;
  String Att = null;
  String Pe = null;
  String Ob = null;
  Object BinTyp = null;

  boolean DisplayIt = false; //a Spac$,

//'--------------------  Long Term Memory (LTM) - Monitoring Info. -------------------------
//    'For Display purposes only by Adapt.DUMPLTM in Adapt.LTMem
//    'Long term memory contains a list of the stimuli that have been paid attention to.
//    'It stores Perceptual sequences as a pair of sequential memory locations.
//
//  Public LTMEMORY&(1 To 2000, 1 To 6)
//                                '1st index is StoreAt cycle number
//                                '2nd index is what type of information is in the LTM
//
//                '---- LTMEMORY() 2nd index values - type of information in LTMEMORY()
//  Public LtmPerceptTyp           '1 Percept binon type
//  Public LtmPerceptBin           '2 Percept binon
//  Public LtmESType              '3 Type of ES Binon - may be an SS
//  Public LtmESActBin            '4 ES Actbin
//  Public LtmARType              '5 Type of AR Binon - may be an RR
//  Public LtmARActBin            '6 AR ActBin
//  Public LtmActionTyp           '7 Action binon type
//  Public LtmActionBin           '8 Action binon

  for (var I = 1; I <= smarty.StoreAt; I++) //find the widest binon # for all the StoreAt positions
  {
    MemSize[I] = intMax(
      l(str(I))+1,
      3,
      l(vbToStr(smarty.LTMEMORY[I][smarty.LtmPerceptBin])),
      l(vbToStr(smarty.LTMEMORY[I][smarty.LtmESActBin])),
      l(vbToStr(smarty.LTMEMORY[I][smarty.LtmARActBin])),
      l(vbToStr(smarty.LTMEMORY[I][smarty.LtmActionBin])));
  }

  new StringBuffer text;

  int GroupSize = 250;

  var tempVar = (smarty.StoreAt - 1) / GroupSize;
  
  for (int Group = 0; Group <= (smarty.StoreAt - 1) / GroupSize; Group++) {
    int EndLIN = GroupSize * Group + GroupSize; //Only put up the available values

    if (EndLIN > smarty.StoreAt) EndLIN = smarty.StoreAt;

    new StringBuilder MemLine;

    for (int E = GroupSize * Group + 1; E <= EndLIN; E++) //For each event in LTM display the event number
      MemLine.append(pad(MemSize[E], str(E)));

    text.append(MemLine + "\r\n");

    //------Percepts(.., Fired) Type

    MemLine = new StringBuilder;

    for (int E = GroupSize * Group + 1; E <= EndLIN; E++) //For each event in LTM
    {
      Att = smarty.BinonTypeName[smarty.LTMEMORY[E][smarty.LtmPerceptTyp]][smarty.BinAbbreviation]; //Att$ = Str$(STM(1, BinTp, Current))
      MemLine.append(pad(MemSize[E], Att));
    }

    text.append(MemLine + "\r\n");

         //------Percepts(.., Fired) Binon

    MemLine = new StringBuilder; //Trigger STIMULUS intensity

    for (int E = GroupSize * Group + 1; E <= EndLIN; E++) //For each event in LTM
    {
      Att = str(smarty.LTMEMORY[E][smarty.LtmPerceptBin]); //Att$ = Str$(STM(1, BinId, Current))
      MemLine.append(pad(MemSize[E], Att));
    }

    text.append(MemLine + "\r\n\r\n");

         //------ES ActBin type

    MemLine = new StringBuilder;

    for (int E = GroupSize * Group + 1; E <= EndLIN; E++) //For each event in LTM
    {
      Att = smarty.AESR(smarty.LTMEMORY[E][smarty.LtmESActBin]);
      MemLine.append(pad(MemSize[E], Att));
    }

    text.append(MemLine + "\r\n");


         //------ LTMEMORY ActBin Object binon

    MemLine = new StringBuilder;

    for (int E = GroupSize * Group + 1; E <= EndLIN; E++) //For each event in LTM
    {
      Att = String.valueOf(smarty.LTMEMORY[E][smarty.LtmESActBin]);
      MemLine.append(pad(MemSize[E], Att));
    }

    text.append(MemLine + "\r\n\r\n");
    
         //------AR ActBin type

    MemLine = new StringBuilder;

    for (int E = GroupSize * Group + 1; E <= EndLIN; E++) //For each event in LTM
    {
      Att = smarty.AESR(smarty.LTMEMORY[E][smarty.LtmARActBin]);
      MemLine.append(pad(MemSize[E], Att));
    }

    text.append(MemLine + "\r\n");



        //------ LTMEMORY ActBin Object binon

    MemLine = new StringBuilder;

    for (int E = GroupSize * Group + 1; E <= EndLIN; E++) //For each event in LTM
    {
      Att = String.valueOf(smarty.LTMEMORY[E][smarty.LtmARActBin]);
      MemLine.append(pad(MemSize[E], Att));
    }

    text.append(MemLine + "\r\n\r\n");

         //------Actions(.., Fired) Type

    MemLine = new StringBuilder;

    for (int E = GroupSize * Group + 1; E <= EndLIN; E++) //For each event in LTM
    {

      Att = smarty.BinonTypeName[smarty.LTMEMORY[E][smarty.LtmActionTyp]][smarty.BinAbbreviation];
      MemLine.append(pad(MemSize[E], Att));
    }

    text.append(MemLine + "\r\n");

         //------Actions(.., Fired) Binon

    MemLine = new StringBuilder;

    for (int E = GroupSize * Group + 1; E <= EndLIN; E++) //For each event in LTM
    {
      Att = String.valueOf(smarty.LTMEMORY[E][smarty.LtmActionBin]);
      MemLine.append(pad(MemSize[E], Att));
    }

    text.append(MemLine + "\r\n\r\n");
  }

  S s = rtrim(str(text));
  cset(state, history2 := s);
  setText(historyTextArea2, winToUnicode(s));
} //End of dumpLTM

svoid uiZoom(double percent) {
  if (cset_trueIfChanged(state, fontScale := toFloat(percent/100)))
    restart();
}

svoid updateFontScale(float scale) {
  swingFontScale(scale);
  spacing = iround(defaultSpacing*scale);
}

svoid selfTest { new SelfTest().run(); }

sclass SelfTest {
  new Scorer scorer;

  void loadWorldAndBody(S worldText, S bodyText) {
    File bodyFile = programFile("body.tmp");
    File worldFile = programFile("world.tmp");
  
    saveTextFile(worldFile, dropLeadingEmptyLines(worldText));
    loadWorld(worldFile);
    
    saveTextFile(bodyFile, dropLeadingEmptyLines(bodyText));
    loadBody(bodyFile);
  }
  
  run {
    activateConsole();
  
    print("Self-Test START");
    
    S text = loadSnippet(#1031220);
    LS parts = splitAtMultipleEqualsLines(text);
    
    assertEqualsVerbose(6, l(parts));
    loadWorldAndBody(parts.get(0), parts.get(1));
    LS outputTexts = subList(parts, 2);

    once {
      // step 1
      if (!step(outputTexts)) break;
        
      text = loadSnippet(#1031228);
      outputTexts = getMulti(splitAtMultipleEqualsLines(text), 2, 3, 0, 1);

      // step 2
      if (!step(outputTexts)) break;
      
      // step 10
      text = loadSnippet(#1031229);
      outputTexts = getMulti(splitAtMultipleEqualsLines(text), 0, 1, 2, 3);

      repeat 7 { goCmd_impl(); }
      if (!step(outputTexts)) break;
      
      // step 50
      text = loadSnippet(#1031230);
      outputTexts = getMulti(splitAtMultipleEqualsLines(text), 2, 3, 0, 1);

      repeat 39 { goCmd_impl(); }
      if (!step(outputTexts)) break;
    }

    // second test case

    text = loadSnippet(#1031231);
    parts = splitAtMultipleEqualsLines(text);
    
    assertEqualsVerbose(6, l(parts));
    loadWorldAndBody(parts.get(0), parts.get(1));

    once {
      // step 30
      outputTexts = getMulti(subList(parts, 2), 2, 3, 0, 1);

      repeat 29 { goCmd_impl(); }
      if (!step(outputTexts)) break;
    }
  
    // third test case

    text = loadSnippet(#1031238);
    parts = splitAtMultipleEqualsLines(text);
    
    assertEqualsVerbose(6, l(parts));
    loadWorldAndBody(parts.get(0), parts.get(1));

    once {
      // step 50
      outputTexts = getMulti(subList(parts, 2), 2, 3, 0, 1);

      repeat 49 { goCmd_impl(); }
      if (!step(outputTexts)) break;
    }
  
    print("SELF-TEST RESULT: " + scorer);
  }

  // returns false if something failed
  bool step(LS outputTexts) {
    goCmd_impl();
  
    new LS diffs;
  
    printAsciiHeading("HISTORY 1");
    print(addAndReturn(diffs, unidiff2(historyTextArea1, outputTexts.get(2))));
  
    printAsciiHeading("HISTORY 2");
    print(addAndReturn(diffs, unidiff2(historyTextArea2, outputTexts.get(3))));
  
    printAsciiHeading("MEMORY 1");
    print(addAndReturn(diffs, unidiff2(memoryTextArea1, outputTexts.get(0))));
  
    printAsciiHeading("MEMORY 2");
    print(addAndReturn(diffs, unidiff2(state.binonDisplayChngResults, outputTexts.get(1))));

    bool ok = allStringsEmpty(diffs);
    scorer.add(ok, nOutputs(nempties(diffs)) + " differ");
    ret ok;
  }
}

sS unidiff2(JTextComponent ta, S text) {
  ret unidiff2(getText(ta), text);
}

sS unidiff2(S actual, S text) {
  ret unidiff(cleanUpForDiff(text), cleanUpForDiff(actual));
}

sS cleanUpForDiff(S text) {
  ret fixNewLines(rtrim(dropLeadingEmptyLines(text)));
}

svoid ResetSmartyLocation {
  if (!getCell(state.startX, state.startY).obstacle())
    state.moveSmarty(state.startX, state.startY);
}

sS winToUnicode(O text) {
  ret mapCharacters(str(text), c -> lookupOrKeep(winToUnicode, str(c)));
}

static <A extends JComponent> A monospaced(A a) {
  ret setFont(monospacedFontID, swingScale(monospacedFontSize), a);
}

Author comment

Began life as a copy of #1031104

download  show line numbers  debug dex  old transpilations   

Travelled to 5 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt, ymdyxumozjwr

No comments. add comment

Snippet ID: #1031123
Snippet name: Smarty [stable preview]
Eternal ID of this version: #1031123/29
Text MD5: 9021c2e87e2209c7658da189fd4bf473
Transpilation MD5: 4aabad26cb231133ac6de6fdcfdc0d22
Author: stefan
Category:
Type: JavaX source code (desktop)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2021-06-06 23:50:57
Source code size: 56388 bytes / 1682 lines
Pitched / IR pitched: No / No
Views / Downloads: 505 / 1574
Version history: 28 change(s)
Referenced in: #1031235 - Package Smarty [also deploys from dev to stable preview]
#1031236 - Smarty [runner for standalone packaging]
#1031241 - Smarty [runner for IDEA project]