News Projects

Friday Facts 20: Java Swing Dial UX

Dial is a Java Swing component designed to create a customizable dial interface. This component allows users to interact using the mouse wheel, click-and-drag actions, or keyboard keys. It features an ActionListener to handle turn commands, making it easy to integrate into various Java applications that require a rotary input method. It can be sometimes be more intuitive than a JSlider, which cannot “roll over” back to the starting value.

Key Features

  • Mouse Wheel Interaction: Turn the dial smoothly with the mouse wheel.
  • Mouse Click+Drag: Click and drag to adjust the dial.
  • Keyboard Control: Use the +/- keys to increment or decrement the dial value.
  • Rollover: Unlike JSlider, the dial can wrap around back to the start. Great for controlling an angle value.

Basic Usage

import com.marginallyclever.dial.Dial;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class DialDemo {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Dial Demo");
        Dial dial = new Dial();
        
        dial.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Dial turned: " + dial.getValue());
            }
        });

        frame.add(dial);
        frame.setSize(200, 200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

Final Thoughts

You can customize the appearance and behavior of the dial through its properties. Adjust the color, range, and initial value as needed to fit your application.

For detailed documentation, visit the GitHub repository.

News

Friday Facts 7: A 2D physics engine in Processing (Java) and the Sunk Cost Fallacy

I have a robot dog being operated on by the Students at Alpha Secondary school in Burnaby, BC, as part of a work experience program. The goal is to make the dog walk as good or better than Spot Micro. Obviously this is not done in a weekend. How do you eat an elephant? One piece at a time. The current plan is to tackle things in order:

  • get it to turn on (done!)
  • move each motor individually (done!)
  • move them together (done!)
  • make it stand up and sit down (working on it!)
  • teach it to roll over and recover from a fall
  • teach it to walk

In a sense I’ve delegated all these steps to the students. Which frees me to do the part that needs experience – making tools that will empower them to do the hard steps at the end.

I don’t want to break the robot because it’s expensive to fix. Therefore I don’t want anyone to drop the robot hundreds of times while testing it. Given the tools I have, what can I do to test the robot without dropping the robot? The same thing I’ve done with all my robots – simulate them! But I’ve never used a phsyics system in Robot Overlord before.

I already have all my code and development tools in Java, so my first instinct is to continue in that direction. I didn’t find any up-to-date physics engines that work in Java. JBullet hasn’t been updated since 2016. Other engines I found were Java ports of C libraries. I don’t have links to them any more but those I found were stale and had no “contact us” system. Two solutions present themselves:

  1. Write my own physics system in Robot Overlord from scratch
  2. Install Gazebo on a linux system, ?, profit.

For your consideration, this is my attempt at number 1.

2D physics with circles and boxes in Processing

This is the first time I’ve had to simulate things with physics in Java.

This is a 2D physics engine written in Processing 4.0b. At tag 1.0, it looks like this:

Much help and inspiration was obtained by reading the code for Impulse Engine by Randy Gaul. Do your homework!

It took a day to write the drawing of shapes and 13 more to debug the physics up to this point. Here’s what it looked like at the half-way point:

The hope is to extend this into 3D, add constraints (think bones connected by joints), add friction, and then make physically approximate ragdoll models of the Spot Mini. They can be dropped, kicked, drop kicked, and beat up over and over until the simulated walking gets them up and balanced. Then the simulation can be tied to the real machine and our goal should be within sight if not in reach.

The sunk cost fallacy

Initially I didn’t even consider option no 2. The cost of setting up a linux box or partition, learning Gazebo, installing the robot model, unknown further mysteries tbd… I choose the devil I know over the devil I don’t!

Discord user M2710 DM’d me to ask “why don’t you just [gazebo]?” and made a compelling case that it might be easier than running my own physics engine. I couldn’t immediately prove the idea wrong so I have to consider it might be right. I’ve booked a day in the near future to try Gazebo and Webots. Look for that in an upcoming FF.

Hiring

If you have experience with Gazebo or Webots, share your story with us on the Discord channel. we really want to eat your juicy brains know what you know.

If you have java/physics skills and you think you can get the Robot Overlord features (3D, constraints, friction, physically approximate ragdoll models of the Spot Mini) working, DM me! I want to help you help me. Make me an offer, I’m eager. I only have so many hours in the day and I need your help.

News

Make a DND Discord Dice Roller in Java (with regular expressions!)

Discord is a popular online service for gaming that really took off during the 2020’s pandemic. My friends and I play Dungeons and Dragons and we wanted a way to roll dice online without giving all our data to some wierdo company. So, being that I’m good with code, I wrote my own. Here’s my steps so that you can learn from what I did. We’re also going to use a really powerful tool called regular expressions to make reading dice requests super easy.

On the shoulders of giants

Writing my own code from scratch and figuring things out is fun and educational, but I’m getting old and I don’t have a lot of time left. To save time I used Java, available libraries, and a slightly out of date tutorial by Oliy Barrett. I recommend you read this for the basics.

Hello, World!

When using someone else’s library there’s always a bit of setup and teardown. Here’s the essentials.

token.txt is the private token for this bot. Never share it. If you check it into Github then Discord will send you a friendly email telling you the token has been rejected forever and you have to make a new one. use your .gitignore to make sure that never happens.

public class DiscordDND extends ListenerAdapter {
    static final String MY_ENTITY_ID = "***";
    static final String MY_ENTITY_NAME = "Dice Roller";
    static public final String ROLL_COMMAND = "~r";

    public static void main( String[] args ) throws LoginException {
        String token = readAllBytesFromFile(DiscordDND.class.getResource("token.txt"));
        JDA jda = JDABuilder.createDefault(token).build();
        jda.addEventListener(new DiscordDND());
    }

    private static String readAllBytesFromFile(URL filePath) {
        String content = "";
        try {
            System.out.println("Token search: "+filePath.toURI());
            content = new String ( Files.readAllBytes( Paths.get( filePath.toURI() ) ) );
        }  catch (Exception e) {
            e.printStackTrace();
	}
        return content;
    }

    @Override
    public void onMessageReceived(MessageReceivedEvent event) {
    	if(event.getAuthor().isBot()) return;

    	String message = event.getMessage().getContentDisplay();
    	
    	if(!message.startsWith(ROLL_COMMAND)) return;
    	// remove the prefix and continue
    	message = message.substring(ROLL_COMMAND.length());

        System.out.println("I heard: "+message);

        // handle the roll here
    }
}

When the app runs it loads the token, connects to Discord with the token, and gets ready to listen to things being said in the discord servers to which it has been invited. How do you invite a bot to a server? Uh… I don’t remember.

Understanding a roll request

The regular expression syntax for a dnd dice roll

The normal format for writing out a dice roll in DND is [number of dice]d(number of sides)[k(number to keep)][+/-(modifier)] with no spaces. I found a regular expression online that pretty closely matches this pattern and modified it.

  • Anything in a () is a group.
  • ? means “zero or one of the previous element” (in this case, the previous group).
  • [\+\-] means “any characters inside the [] braces”. Combined with the ? it means “at most one + or – symbol.” Because of text formatting rules in regular expressions the + and - have to escaped by putting a backslash \ in front of them.
  • \d means digit. \d+ means 1 or more digits.

So putting it all together it says:

  • The first group has a positive or negative whole number. The group is optional.
  • The second group starts with the letter ‘d’, then a positive or negative whole number. The group is required.
  • The third group starts with the letter ‘k’ and then a positive or negative whole number. The group is optional.
  • The fourth group MUST start with +/- and then a whole number. The group is optional.

Hey! Why negative dice and negative sides? It’s not that I want negative dice. But sometimes users type silly things. I thought it would be fun to catch those and deal with them in equally funny ways.

Why keep negative dice? In DND sometimes the player rolls with advantage to keep the highest dice and sometimes they roll with disadvantage to keep the lowest dice. Negative numbers mean keep the low dice.

To use regular expressions in Java (or OpenJDK) I’m using the Pattern and Matcher classes.

// remove all whitespace and the roll command from the start
String saneMessage = sanitizeMessage(event.message);

Pattern p = Pattern.compile("([\\+\\-]?\\d+)?(d[\\+\\-]?\\d+)(k[\\+\\-]?\\d+)?([\\+\\-]\\d+)?");
Matcher m = p.matcher(saneMessage);
id(m.find()) {
	int numDice=1, numSides=20, numKeep, modifier=0;
	if(m.group(1) !=null && !m.group(1).isEmpty()) numDice  = Integer.parseInt(m.group(1));
	if(m.group(2) !=null && !m.group(2).isEmpty()) numSides = Integer.parseInt(m.group(2).substring(1));
	if(m.group(3) !=null && !m.group(3).isEmpty()) numKeep  = Integer.parseInt(m.group(3).substring(1));
	else numKeep=numDice;
	if(m.group(4) !=null && !m.group(4).isEmpty()) modifier = Integer.parseInt(m.group(4));

	roll(event,numDice,numSides,numKeep,modifier);
	return;
}

In Java Strings the \ symbol is special so I have to escape them again – the double backslash is not a mistake.

Matcher returns the original expression in m.group(0). If Matcher does not find a group it returns null for that group index. That means I can reliably expect group 4 is always the modifier and so on.

Rolling and keeping

int [] rolls = rollDice(numDice,numSides);
if(numKeep!=numDice) {
	if(numKeep>0) keepSomeHighRolls(rolls,numKeep);
	else keepSomeLowRolls(rolls,-numKeep);
}
event.reply(event.actorName + ": "+renderResults(rolls,modifier));

The catch here is that I don’t want to sort the rolls into high > low because it would look wrong to the user. Organic rolls do not happen that way! I’m stuck searching for the worst roll, numRolls – numKeep times.

for(int k = numKeep;k<rolls.length;++k) {
	int worst = 0;
	for(int i=0;i<rolls.length;++i) {
		if(rolls[i]>0 && rolls[worst]>rolls[i]) worst = i;
	}
	rolls[worst] *= -1;  // mark it as rejected but keep the value.
}

Final thoughts

As a final bit of flair I add a meme pic for natural 20 and natural 1 rolls. If a natural 1 is not kept I put in Neo’s famous bullet dodge from The Matrix. What would be a good meme for a not-kept-natural-20?

All the code for this project can be found at https://github.com/i-make-robots/DiscordDND/. It’s got way more stuff!

Opinion Projects

Robot Overlord: A puzzle in Java

Robot Overlord Java app contains lots and lots of classes, some of which are robots and their gui.

I want robot developers to have an easy time adding their robots to RO. A simple interface, minimal distraction, and examples to work from are Good Things. I’m told that RO can use a Service Provider Interface (SPI) to load a jar it’s never seen before, a jar that contains an implementation of the interface. I would then

  • make a RobotInterface,
  • make every robot I’ve already got use that interface
  • move each robot to a separate jar and load said jars through SPI
  • make a separate github project for each robot
  • advertise these plugins via tutorials so that you can fork a repo, adjust to taste, and publish your new thing.

What I’m discovering is that SPI is tricky tricky.

  • I can’t find any online examples where someone has done this successfully.
  • I have not yet got RO to load my first robot’s jar file, tho I’m trying. Is the jar packaged wrong? Maybe it doesn’t say “yes, I have that interface!” in the right way.
  • Is RO not even seeing the jar? I’m told SPI looks for any jar on the classpath. I printed out the classpath, then put the robot jar in one of those classpath folders and ran the app again. Nothing.

There are several possible points of failure, none of which can be clearly eliminated as possibilities. Worse, I’m not sure how these plugins would be debugged. Running RO would not give a lot of insight into the plugins’ inner workings. Would I still be able to tweak code in real time? That is a must.

So I ask you, dear reader: am I way off track? What do?

I should note here that I do not want to have to run RO from the command line with a custom classpath. While I’m able to do it, I doubt that the people who buy robots and use them will even know how to open a command line. Imagine a grade school teacher trying to set up for their students, or your aged mother who’s used to OSX. It ain’t happening. You don’t want that tech support phone call and neither do I.

Tutorials

How to Generate a Maze

In this tutorial I’m going to show you how to recursively generate a well formed maze in Java without overflowing your computer’s stack.

Makelangelo v7.2.9 maze generator
Makelangelo v7.2.9 maze generator

(more…)