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.
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.
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.
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.
Dig down (safely) and look for the mossy blocks that show that you’ve found it.
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:
And the R code to implement the solution, using the numbers from the example above, is:
library(tidyverse)
library(corpcor) # for pseudo-inverse
<- tribble( # data frame of origin points and Minecraft 'heading'
df ~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)
<- nrow(df) # number of lines
k <- 2
dimension <- df[, 1:2] %>% as.matrix() %>% t() # *columns* of origin points
a <- df[, 5:6] %>% as.matrix() %>% t() # *columns* of the points' unit direction vectors
n = matrix(data = 0, nrow = dimension, ncol = dimension) # initialize an empty matrix
R = vector(mode = 'numeric', length = dimension) # initialize an empty vector
q
# Generating a system of linear equations, with Rp = q, where p will be the 'best' point
for (i in 1:k) {
<- R + (diag(dimension) - n[, i] %*% t(n[, i]))
R <- q + (diag(dimension) - n[, i] %*% t(n[, i])) %*% a[, i]
q
}# So p_hat = pseudoinverse(R) x q
<- pseudoinverse(R) %*% q # column vector of the least squares fit best point
p_hat
# Turn solution into proper data frame to plot optimal point
<- t(p_hat) %>% data.frame # need to convert it to a row vector for data frame
df_p 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!