Minecraft Calculus: River Crossing Escape

This is a classic introductory calculus problem, with a Minecraft theme.  This particular variation is inspired by example 4 in section 4.5 of Stewart's calculus.

I'll set the scene:  You're weaponless.  You have half a heart and you're being chased by a skeleton archer.  There's a river between you and your cabin.  You need to get to your cabin as quickly as possible!

In more "mathy" terms, you want to get from A to B.  There are three main ways you can do this:

  1. A to B directly, diagonally across the river
  2. A to C and then C to B
  3. At slight angle from A to D and then D to B

We know that swimming is slower than running, so option 1 isn't the best.  Option 2 involves the smallest time in the water, but also the longest distance traveled.

Option 3 is somewhere in between - you spend a bit more time in the water, but the total distance is decreased.  The travel time in this case depends on where exactly point D is.  To find the optimal path, we must find the position of D which minimizes the travel time.  This sounds like calculus!

Let x be the distance between points C and D.  In math terms, |CD|=x.

We first need an expression for the total time traveled in terms of x.  The basic equation for time traveled at constant speed is

t=\frac{distance}{speed}.

The first part of the trip is in the water where we travel from A to D.  We can use Pythagorean's theorem to get this distance as |AD|=\sqrt{x^2+w^2}.  Assuming that we can travel at a speed of v_w in the water, the time for this part is

t_w=\frac{\sqrt{x^2+w^2}}{v_w}.

The land part of the trip involves traveling what's left over of l after having already traveled x, so |DB|=l-x.  Traveling at v_l on land, the time for this part of the trip is

t_l=\frac{l-x}{v_l}.

The total trip time, T(x), is just the sum of these two.

T(x)=\frac{\sqrt{x^2+w^2}}{v_w}+\frac{l-x}{v_l}     (1)

To optimize this function with respect to x, we need to find where it is stationary and then verify that this point is a minimum.  This means we want to find a place where the function isn't changing with small changes in x.  In other words, we want to find a spot where the derivative is zero.

Taking the derivative gives:

T'(x)=\frac{x}{v_w\sqrt{x^2+w^2}}-\frac{1}{v_l}

Setting this equal to zero, we can solve for x:

x=\frac{v_ww}{\sqrt{v_l^2-v_w^2}}     (2)

Let's plug in some numbers!  In Minecraft, you can swim at about 2.2 m/s and sprint at 5.6 m/s.  Let's take the river width w to be 7 blocks (1 block = 1 meter).  Plugging in, we find that x=2.9.  At this point, though, we can't tell if this is a maximum or a minimum.  One way to find out is to examine the curvature of the function at this point by using the 2nd derivative:

T''(x)=\frac{1}{v_w\sqrt{x^2+w^2}}(1-\frac{x^2}{x^2+w^2})

Plugging in x=2.9, we see that T''(2.9)=0.04 which, being positive, means that x=2.9 is a minimum point for T(x).  Visually:

crossing

 

So if you want to get to your cabin as quickly as possible, the fastest route is to swim across the river to a point D that is 2.9 meters from point C and then run the rest of the way.

This is actually a general problem.  Try replacing the speeds I used with speeds for soul sand, crouching and walking, a boat, etc. and see what happens!  Particularly, what happens to (2) if you can travel more quickly in the water than on land?

 

Scratch Work

Finding T'(x):  First, rewrite the square root as a power,  T(x)=\frac{(x^2+w^2)^{\frac{1}{2}}}{v_w}+\frac{l-x}{v_l}

Differentiating, using the chain rule on the first term,  T'(x)=\frac{\frac{1}{2}(x^2+w^2)^{-\frac{1}{2}}(2x)}{v_w}-\frac{1}{v_l}=\frac{x}{v_w\sqrt{x^2+w^2}}-\frac{1}{v_l}

Finding T''(x):  Starting with T'(x)=\frac{(x^2+w^2)^{-\frac{1}{2}}x}{v_w}+\frac{1}{v_l}, we differentiate with respect to x.  Note that the derivative of \frac{1}{v_l} with respect to x is 0, so we only have to deal with the first term.  Using the product rule and chain rule:

T''(x)=\frac{-\frac{1}{2}(x^2+w^2)^{-\frac{3}{2}}(2x^2)}{v_w}+\frac{(x^2+w^2)^{-\frac{1}{2}}}{v_w}

Cleaning up with some algebra:

T''(x)=\frac{-x^2}{v_w(x^2+w^2)^{\frac{3}{2}}}+\frac{1}{v_w\sqrt{x^2+w^2}} =\frac{1}{v_w\sqrt{x^2+w^2}}(1-\frac{x^2}{x^2+w^2})

 

Anti Rage-Induced-Accidental-Disconnect Minecraft Client Mod

We've all been there: You're playing on your favorite Minecraft PvP server, ready to get revenge on the player who just killed you, when suddenly, you're staring blankly at Minecraft's title screen.  How could such a thing happen?  Well, maybe Mr. McMillan has something to say about the matter:

 

Yes, Mr. McMillan, precisely.  The death screen GUI has "Respawn" and "Title screen" buttons that are way too close to each other, resulting in players sometimes unintentionally leaving the game when they actually want to respawn.  Now, this isn't something that happens all the time, but it happens occasionally and it's annoying.

So, in the first and only installment of the new (and old, by the time you read this) series "Weekly 1 Minute Minecraft Mods", we can fix the problem.

We will change this:

 

Before

into this:

 

After

(Texture pack is the beautiful Plast Pack.)

The new layout isn't pretty, but in the case of PvP I'll definitely take function over fashion (with the exception of gold pants).

Making the Mod

You need to download and install Minecraft Coder Pack (MCP).  There is a lot of information about how to do this online.

Once MCP is ready to go, find the file "GuiGameOver.java" in the minecraft/src subdirectory.  In this file, there are only two lines we need to modify:

1
2
this.buttonList.add(new GuiButton(1, this.width / 2 - 100, this.height / 4 + 72, I18n.getString("deathScreen.respawn")));
this.buttonList.add(new GuiButton(2, this.width / 2 - 100, this.height / 4 + 96, I18n.getString("deathScreen.titleScreen")));

These files add the "Respawn" and "Title screen" buttons.  The only thing we need to change is the third argument to the GuiButton constructor.  We can shift the buttons up and down by changing the values of 72 and 96, with larger values corresponding to a lower position on the screen.  The "after" photo shown above uses values of 70 and 120:

1
2
this.buttonList.add(new GuiButton(1, this.width / 2 - 100, this.height / 4 + 70, I18n.getString("deathScreen.respawn")));
this.buttonList.add(new GuiButton(2, this.width / 2 - 100, this.height / 4 + 120, I18n.getString("deathScreen.titleScreen")));

Once this is changed, save it and then proceed as you would with any other mod (recompile, reobfuscate, and drop the class in the appropriate Minecraft .jar file after backing everything up, etc.). Now you can crank up your mouse's sensitivity and fail to unintentionally disconnect from your favorite server with ease!

Note that this does involve changing core Minecraft files and so may result in issues if there is an overlap with another mod.  Also, it seems this won't be an issue when 1.7 comes out.

Minecraft Minigame Match Dynamics

As a physics student, it can be difficult to not think of things as point particles - even players engaging in minecraft minigame matches on the Overcast Network.

So as a Saturday afternoon project, I made a simple client mod that allows you to write all of the player positions on the current world to a CSV file. Then, using  Mathematica, I processed the data and made some simple visualizations:

This shows the player positions, colored by team.  This match takes place on a map called Warlock (one of my favorites), and the goal is to be the first team to break the other team's monument (which is made out of two pieces of obsidian).  Red team won this match, and you can see a red guy sneaking near the blue's monument (probably underground) in the bottom left for a few minutes before finally breaking it.

See more visualizations here (I don't want to lag up the page with gifs), and view the source code here.

wools++: Major update!

I've totally revamped wools++.  Major changes include:

I have ideas for plenty of other features as well, and I'd be happy to take further suggestions.

Related, Overcast Network recently updated their stats system as well, providing statistics breakdowns based on game mode and playing time which is really cool (except, oh god, now I can easily see how much I play each day).  I plan on incorporating some of this new information into wools++ in the future.

New project: wools++

I've taken on another minecraft related project in an attempt to learn some more python as well as some basic web development.

 

Introducing wools++

Centered on the Overcast Network (formerly Project Ares) collection of minecraft servers, wools++ collects data from a user's profile a few times per day and produces more sophisticated statistics (such as rolling values) and even some time series plots.  Here's a couple from my profile:

kd
rkd

 

With:

  • KD = kills/deaths
  • RK7 = rolling kills (kills over the last 7 days)
  • RD7 = rolling deaths
  • RKD7 = rolling KD

The name itself comes from a capture-the-flag type game often played on the servers, where the flag is replaced by a minecraft wool block.  If you successfully capture a wool block, your "wools" count is incremented.

The project is hosted on Google's app engine for a couple reasons.

  • It can be free (if your app is small enough)
  • Built in datastore (no need to worry about setting up my own SQL database or anything)
  • Python support.  This is great because python is generally a super useful language to know, particularly in the computational sciences.

In the future I'll definitely spend some time discussing the process I went through building this app, because in some cases Google's documentation was a little bit light on the details.  For now, check out the about page if you'd like to read about more details, and you can find the source here.

 

Minecraft Bukkit Server Ascii Chat Filter

I took on another small Minecraft bukkit plugin project this past Friday night (yes, it was a fun way to spend a Friday night, thanks for asking).  The server I primarily play on, Project Ares, has been getting extremely popular over the past several months, due mostly to how awesome and addicting it is.  There are hundreds of people from around the world divided among the 12 servers at any given time.  This is great, but one downside is that the chat gets very busy, and it's often filled with different languages that most of the users can't speak.  There are tons of chat related bukkit plugins that already exist, many of which have fancy features like multiple channels for different languages.

I decided to create a very simple and lightweight plugin that, when toggled on, filters out all non-ascii characters (so, most foreign languages).  Of course, it would be silly (and kind of a jerk move) if this simply muted all non English speakers server-wide.  It would have to work on a per-person basis, meaning that those with it toggled on see only ascii characters, and those with it toggled off see all characters.  I ended up with this.   You can check out the source there, but I'll briefly go through it here (because it's short, and also because I just got a WordPress plugin with allows you to insert code into posts and it's pretty neat).

 

The plugin is composed of three classes: one that registers the toggle command, one that actually filters the chat message, and one main class that puts the two together.  The main class is quite boring as it only registers the event handler and command executor (lines 6 and 7):

1
2
3
4
5
6
7
8
9
10
11
public class AsciiOnlyChatFilter extends JavaPlugin{
 
	public static ArrayList<Player> enabledPlayers = new ArrayList<Player>();
 
	public void onEnable(){
		getCommand("asciionly").setExecutor(new AsciiOnlyChatCommand());		
		this.getServer().getPluginManager().registerEvents(new AsciiOnlyChatListener(), this);
	}
	public void onDisable(){		
	}	
}

It also creates a list called enabledPlayers (line 3), which is where those who have the filter turned on are stored.  The second class handles the command ("/asciionly") if someone toggles it on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AsciiOnlyChatCommand implements CommandExecutor{
 
	public boolean onCommand(CommandSender sender, Command command,String label, String[] args) {
		if(sender instanceof Player == false){
			sender.sendMessage(ChatColor.RED + "This command can only be used in-game.");
			return true;
		}
		Player p = (Player) sender;
 
		if(command.getName().equalsIgnoreCase("asciionly")){
			if(AsciiOnlyChatFilter.enabledPlayers.contains(p)){
				AsciiOnlyChatFilter.enabledPlayers.remove(p);
				p.sendMessage(ChatColor.GREEN + "AsciiOnly filter deactivated");	
			}else{
				AsciiOnlyChatFilter.enabledPlayers.add(p);
				p.sendMessage(ChatColor.GREEN + "AsciiOnly filter activated");
			}
			return true;
		}
		return false;
	}
}

It basically just does some boring checks and then adds the player to the enabledPlayers list if they are turning the filter on, or removes them if they are turning it off.  The final class actually filters the chat for those players with the filter on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AsciiOnlyChatListener implements Listener {
 
	@EventHandler 
	public void onAsyncPlayerChatEvent(AsyncPlayerChatEvent event){
		String msg = event.getMessage();
		Set&lt;Player&gt; msgRecipients =  event.getRecipients();
		CharsetEncoder asciiEncoder = Charset.forName("US-ASCII").newEncoder();
 
		//If chat message is not ascii, do not display it to those with /asciionly activated
		if(!asciiEncoder.canEncode(msg)){
			for(Player enabledPlayer : AsciiOnlyChatFilter.enabledPlayers){
		    	if(msgRecipients.contains(enabledPlayer) && enabledPlayer != event.getPlayer()){
		    		event.getRecipients().remove(enabledPlayer);
		    	}		    	
			}
		}
	}
}

When it intercepts a chat message, it checks to see if it contains only ascii characters (line 11).  After this, it modifies the set that contains the message recipients, removing all of those that are on the enabledPlayers list (lines 12-14).  This seems to do the job pretty well!

Project Ares Beta Tournament: Predictions vs. Outcome

Update and Reflection (October 2015):

WOW! This was almost two years ago. It's pretty funny looking back at these old blog posts. This is probably the first actual "data science" project I ever worked on, and now I get paid to do silly things like this which is pretty cool. So yeah, if you're looking to get into software or data science, you have to find random little projects to work through. It might be embarrassing to look back, but in the end if you're learning stuff that's all that matters. Thanks for letting me scrape your website for all of these years, guys! Cheers!

I've added some additional comments at the bottom of the article.

Original Article:

Project Ares held their first full tournament yesterday.  This included 16 teams playing a variety of maps, and it lasted a whopping 8 hours!

Anyway, I thought it would be fun to compare all of the teams competing using some of the statistical data available on the Project Ares site to see how well of a performance predictor it is.  There are three main statistics: KD, KK and OD.  They are defined as follows:

  • KD = kills/deaths
  • KK = kills/times killed by another player
  • OD = objectives completed / deaths

KD and KK are directly related to fighting skills, where as OD has more to do with completing objectives (capturing wools and leaking cores).

I compared teams by taking the average of each of these three quantities for each team.

Results

Predicted Bracket
Actual Bracket (link now broken, October 2015)

The KD and KK ratios give identical predictions.  The OD ratio, on the other hand, is a very poor indicator of performance.

Using the team's average KD (or KK) alone, I was able to predict 12 out of 15 matches (accuracy of 80%), including the semifinals and tournament winner and most match outcomes (there were only three upsets: two pictured and one when YoloSwag beat Impact in Round II).  Of course, minecraft gameplay is complex and the three statistics alone do not represent all of the skills required to win a match, but it does seem to be a pretty strong indicator of overall performance.

Looking at the numbers, it is quite clear that the winning team, Badlion, is totally stacked.  Their average KD was 3 times that of the overall average.

Other Stuff

I decided this would be a neat idea late last night after the tourney ended, so the way I built the bracket is a bit of a mess (a combination of an extremely inefficient Mathematica script and google docs).  But, I plan on playing with this more and automating it better for future use.

More Update Stuff (Still October 2015)

So what's changed? An accuracy of 80% using only player's KD ratios is pretty crazy. In fact, since the first tournament, it is no longer possible to make such a "good" prediction. I think there are two reasons why KD has lost its predictive power:

  1. Since the first tournament, players have begun boosting their stats to get on teams. This includes playing differently than you would in a tournament (e.g. cautiously farming), making them less reflective of actual ability and more of patience.
  2. People strat the hell out of maps now. Players knew maps during the first tournament, but now teams are in general much more organized and practice a lot more, trying to exploit every little opportunity each map has to offer.

I think point 1 isn't as important, because most good players know that stats aren't necessarily an indicator of skill level. At the end of the day everyone does a little sword fighting, but map-specific knowledge and team organization still plays an enormous role in winning matches.

Now that Overcast Network (which was named "Project Ares" when this post was originally written) has introduced a more sophisticated ranking system, I'm looking forward to squeezing some information out of that data next time around.