Finding Minecraft Strongholds with Math

Find a Minecraft stronghold in just three Eye of Ender throws
R
Just for fun
Published

April 10, 2020

In Mojang’s Minecraft, there are Strongholds that allow travel to the End dimension. Finding the Strongholds requires throwing Eyes of Ender. When an Eye of Ender is thrown, it will zip off in the direction of the nearest Stronhold.

Some players will try to find a Stronghold by throwing an Eye, running for a while in the direction that it goes, and repeating the process until the path of the Eye suggests that you are over the Stronghold. This can consume a large number of Eyes of Ender.

Because it’s possible to display Minecraft information, including current coordinates and the current player heading, it’s possible to more efficiently identify the location of Strongholds by making a more limited number of throws from different locations, and figuring out where the paths of the Eyes of Ender would intersect.

However, because of errors of estimating the direction heading, you can’t assume that multiple rays of Eye headings will intersect precisely at a single point, so some math is needed to estimate the “best” point of intersection of multiple throws. Details of the math are at the bottom of this post.

Example of use

The following is an example of a solution to this problem, written in R, with screenshots showing how to where to find the coordinates and headings. The R code for both a standalone script and an R Shiny app are available on my (rather sparse) GitHub repo. The following screenshots are from Minecraft v1.15.2 and using the Shiny app.

First throw

You need to display the Minecraft Debug screen by pressing F3.

When you throw the Eye of Ender, you’ll want to change the direction you’re facing so that the axis ‘crosshairs’ at the center of the screen are lined up with direction the Eye zipped off in.

In the debug info, XYZ shows your current coordinates (you just want the X and Z coordinates; the Y altitude isn’t needed), and the Facing first number shows the (horizontal) heading you’re facing. Save these 3 numbers.

First throw

After the first throw, go a couple hundred blocks, preferably roughly perpendicular to the direction the Eye of Ender went the first time, and repeat the process. Don’t go thousands of blocks away, because there are multiple Strongholds, and you don’t want the closest one for the second throw to be a different Stronghold from the one found by the first throw.

Second throw

After the second throw, you can already enter the data into the Shiny App, and get a very rough estimate of where the Stronghold is, but it probably won’t be good enough to dig down and successfully find the Stronghold.

Go roughly to the vicinity of the Stronghold and throw a third time.

Third throw

Enter the X, Z, and heading directions (3 of them here) into the Shiny App, and you’ll have a good estimate of the Stronghold location.

Shiny App input

Dig down (safely) and look for the mossy blocks that show that you’ve found it.

Found the Stronghold

Note that the coordinates will NOT necessarily be for the actual End Portal, so you’re still going to need to explore to find that.

Math

If you’re curious about the actual math and calculations, I found a solution to calculate the ‘best’ point of intersection from a work “Least-Squares Intersection of Lines” by Johannes Traa published at UIUC 2013. However, in the intervening years, the links I had to the paper have since broken.

The relevant part of the solution is:

LS intersection of lines

And the R code to implement the solution, using the numbers from the example above, is:

library(tidyverse)
library(corpcor) # for pseudo-inverse

df <- tribble( # data frame of origin points and Minecraft 'heading'
    ~x, ~z, ~heading,
    96, 298, 35.8,
    -304, 127, 20.2,
    -281, 1844, 109.4
  ) %>%
  mutate(
    radians = pi * heading / 180.0,
    unit_x = -sin(radians),
    unit_z = cos(radians)
  )

# Determine the 'best' point for the intersection of the lines,
# by minimizing the perpendicular distances of the point to the lines
#
# From: "Least-Squares Intersection of Lines, by Johannes Traa - UIUC 2013"
# - http://cal.cs.illinois.edu/~johannes/research/LS_line_intersect.pdf (link broken as of 2020-04)

k <- nrow(df) # number of lines
dimension <- 2
a <- df[, 1:2] %>% as.matrix() %>% t() # *columns* of origin points
n <- df[, 5:6] %>% as.matrix() %>% t() # *columns* of the points' unit direction vectors
R = matrix(data = 0, nrow = dimension, ncol = dimension) # initialize an empty matrix
q = vector(mode = 'numeric', length = dimension) # initialize an empty vector

# Generating a system of linear equations, with Rp = q, where p will be the 'best' point
for (i in 1:k) {
  R <- R + (diag(dimension) - n[, i] %*% t(n[, i]))
  q <- q + (diag(dimension) - n[, i] %*% t(n[, i])) %*% a[, i]
}
# So p_hat = pseudoinverse(R) x q
p_hat <- pseudoinverse(R) %*% q # column vector of the least squares fit best point

# Turn solution into proper data frame to plot optimal point
df_p <- t(p_hat) %>% data.frame # need to convert it to a row vector for data frame
names(df_p) <- c('x', 'z') # and match the column names to plot

df_p

#           x        z
#   -864.3211 1637.285

Gaming with math, woot!