mirror of
https://github.com/peter-tanner/minecraft-ore-analysis.git
synced 2024-11-30 11:00:16 +08:00
first commit - 21w07a
This commit is contained in:
commit
ae3a392af3
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
region/
|
||||||
|
pyanvileditor/
|
||||||
|
etc/
|
||||||
|
inputs/
|
||||||
|
__pycache__/
|
23
README.md
Normal file
23
README.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Minecraft ore analysis
|
||||||
|
|
||||||
|
## Let's see how the new cave update ore generation compares to the old!
|
||||||
|
|
||||||
|
## Go to https://npc-strider.github.io/cave-update-ore-analysis for interactive graphs!
|
||||||
|
|
||||||
|
### Both tests performed with seed 0 and single biome mode enabled (Extreme hills biome only, to include emerald ore statistics). Size of 1024*1024 scanned.
|
||||||
|
|
||||||
|
### Original world files used for research are located at https://drive.google.com/drive/folders/1RsmfDp4nl5KaFWfCpL5sanK6cGUp45TO?usp=sharing
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
The scripts I've created take advantage of a slightly modified [PyAnvilEditor](https://github.com/DonoA/PyAnvilEditor) to parse the region files.
|
||||||
|
|
||||||
|
I've replaced the code in the `276` (and everything else under `def close(self):`) of the `world` class of PyAnvilEditor with `true` - this is because I'm not writing to the world file, I only intend to read from it. This saves a large amount of time spent writing that would go to waste.
|
||||||
|
|
||||||
|
To generate the graphs I've used Matplotlib and I use mpld3 to convert these graphs into interactive html files for use on my website.
|
||||||
|
|
||||||
|
We load a large section of the world into memory - ideally, you want to set the block size to the maximum as this is quicker than loading small sections of the world repeatedly. Then we iterate through each coordinate in the block - this takes about 90 seconds with a block radius of `128`. At each coordinate we iterate each count for a tile in a particular layer. Once a whole block has been processed, we add it to the total.
|
||||||
|
|
||||||
|
The 'sum' represents the total amount of a particular ore block within the whole sample (A 1024*1024 square meter area). The relative frequency represents the proportion of the total amount of a particular ore that occurs at a particular y-level.
|
||||||
|
|
||||||
|
This sample size can be improved on but it would obviously take longer given that my RAM is limited.
|
18
RESULTS/abs_freq.html
Normal file
18
RESULTS/abs_freq.html
Normal file
File diff suppressed because one or more lines are too long
BIN
RESULTS/abs_freq.png
Normal file
BIN
RESULTS/abs_freq.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 264 KiB |
18
RESULTS/rel_freq.html
Normal file
18
RESULTS/rel_freq.html
Normal file
File diff suppressed because one or more lines are too long
BIN
RESULTS/rel_freq.png
Normal file
BIN
RESULTS/rel_freq.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 256 KiB |
1
RESULTS_ALL__1.16.5.json
Normal file
1
RESULTS_ALL__1.16.5.json
Normal file
File diff suppressed because one or more lines are too long
1
RESULTS_ALL__21w07a.json
Normal file
1
RESULTS_ALL__21w07a.json
Normal file
File diff suppressed because one or more lines are too long
25
export_graph.py
Normal file
25
export_graph.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from graph import main
|
||||||
|
import mpld3
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
output_path = Path('RESULTS')
|
||||||
|
output_path.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
plot = main(style='default',
|
||||||
|
show_bounds=False,
|
||||||
|
linewidth=2,
|
||||||
|
dpi=100,
|
||||||
|
size=[16,10])
|
||||||
|
mpld3.save_html(plot[1][0], str(output_path / Path('abs_freq.html')), template_type='simple')
|
||||||
|
mpld3.save_html(plot[1][1], str(output_path / Path('rel_freq.html')), template_type='simple')
|
||||||
|
plt.close("all")
|
||||||
|
|
||||||
|
plot = main(style='dark_background',
|
||||||
|
show_bounds=True,
|
||||||
|
linewidth=0.75,
|
||||||
|
dpi=100,
|
||||||
|
size=[16,10])
|
||||||
|
plot[1][0].savefig(str(output_path / Path('abs_freq.png')))
|
||||||
|
plot[1][1].savefig(str(output_path / Path('rel_freq.png')))
|
||||||
|
plot[0].show()
|
132
graph.py
Normal file
132
graph.py
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from cycler import cycler
|
||||||
|
|
||||||
|
from matplotlib.ticker import MultipleLocator
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
with open('./RESULTS_ALL__NEW_ORGEN_TEST.json', 'r') as file:
|
||||||
|
NEW_DATA = json.loads(file.read())
|
||||||
|
with open('./RESULTS_ALL__OLD_ORE_GEN.json', 'r') as file:
|
||||||
|
OLD_DATA = json.loads(file.read())
|
||||||
|
|
||||||
|
def list_distribution(block, data):
|
||||||
|
return [ y[block] if block in y else 0 for y in data ]
|
||||||
|
|
||||||
|
ABSOLUTE_BLOCKS_OLD = [
|
||||||
|
'minecraft:diamond_ore',
|
||||||
|
'minecraft:redstone_ore',
|
||||||
|
'minecraft:emerald_ore',
|
||||||
|
'minecraft:gold_ore',
|
||||||
|
'minecraft:lapis_ore',
|
||||||
|
'minecraft:iron_ore',
|
||||||
|
'minecraft:coal_ore',
|
||||||
|
# 'minecraft:lava'
|
||||||
|
]
|
||||||
|
ABSOLUTE_BLOCKS_NEW = ABSOLUTE_BLOCKS_OLD+[
|
||||||
|
'minecraft:copper_ore',
|
||||||
|
'minecraft:amethyst_block',
|
||||||
|
]
|
||||||
|
|
||||||
|
RELATIVE_BLOCKS_OLD = ABSOLUTE_BLOCKS_OLD+[ #BLOCKS to show in the relative frequency distribution.
|
||||||
|
'minecraft:lava'
|
||||||
|
]
|
||||||
|
RELATIVE_BLOCKS_NEW = RELATIVE_BLOCKS_OLD+[
|
||||||
|
'minecraft:copper_ore',
|
||||||
|
'minecraft:amethyst_block',
|
||||||
|
]
|
||||||
|
|
||||||
|
x = list(range(-64,321))
|
||||||
|
|
||||||
|
def cfg_naxis(axes):
|
||||||
|
for axis in axes:
|
||||||
|
axis.grid(True, color='white', linewidth=0.2, linestyle='--', which="major")
|
||||||
|
for line in axis.legend(loc='upper right').get_lines():
|
||||||
|
line.set_linewidth(2.0)
|
||||||
|
axis.set_xticks(x[::8]+[x[-1]])
|
||||||
|
axis.minorticks_on()
|
||||||
|
axis.xaxis.set_tick_params(labelbottom=True)
|
||||||
|
axis.yaxis.set_tick_params(labelbottom=True)
|
||||||
|
axis.set_ylim(bottom=0.0)
|
||||||
|
axis.set_xlim(left=-64,right=320)
|
||||||
|
axis.xaxis.set_minor_locator(MultipleLocator(1))
|
||||||
|
|
||||||
|
# Conversions between ore/chunk and total (based on sample size.)
|
||||||
|
# Radius of 512
|
||||||
|
sample_r = 512
|
||||||
|
def orechunk(y):
|
||||||
|
return y / (2*sample_r/16)**2
|
||||||
|
def sigmaore(x):
|
||||||
|
return x * (2*sample_r/16)**2
|
||||||
|
def gen_maxis(axis):
|
||||||
|
secax = axis.secondary_yaxis('right', functions=(orechunk, sigmaore))
|
||||||
|
secax.minorticks_on()
|
||||||
|
secax.set_ylabel(r'$ore\cdot chunk\_layer^{-1}$')
|
||||||
|
|
||||||
|
def set_figure(fig, dpi, size):
|
||||||
|
fig.set_dpi(100)
|
||||||
|
fig.set_figwidth(size[0])
|
||||||
|
fig.set_figheight(size[1])
|
||||||
|
|
||||||
|
def add_bounds(axis):
|
||||||
|
axis.axvspan(-64, 0, alpha=0.5, color='gray', hatch="/")
|
||||||
|
axis.axvspan(256, 320, alpha=0.5, color='gray', hatch="/")
|
||||||
|
|
||||||
|
def plot_absolute(axis, blocks, data, padl, padu, width):
|
||||||
|
[ axis.plot(x, [0 for x in range(padl[0],padl[1])]+list_distribution(ore, data)+[0 for x in range(padu[0],padu[1])], label=ore, linewidth=width) for ore in blocks]
|
||||||
|
def plot_relative(axis, blocks, data, padl, padu, width):
|
||||||
|
[ axis.plot(x, [0 for x in range(padl[0],padl[1])]+[p/sum(tmp)*100 for p in tmp]+[0 for x in range(padu[0],padu[1])], label=ore, linewidth=width) for ore in blocks if (tmp := list_distribution(ore, data))]
|
||||||
|
|
||||||
|
# quick/hackily converted the script to a function
|
||||||
|
def main(style, show_bounds, linewidth, dpi, size):
|
||||||
|
plt.style.use(style)
|
||||||
|
|
||||||
|
#
|
||||||
|
# abs frequency.
|
||||||
|
#
|
||||||
|
fig0, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
|
||||||
|
set_figure(fig0, dpi, size)
|
||||||
|
plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.05)
|
||||||
|
fig0.suptitle('New ore generation - Absolute frequency - 1024*1024 area sample size')
|
||||||
|
|
||||||
|
plot_absolute(ax1, ABSOLUTE_BLOCKS_OLD, OLD_DATA, [-64, 1], [256, 320], linewidth)
|
||||||
|
plot_absolute(ax2, ABSOLUTE_BLOCKS_NEW, NEW_DATA, [0, 0], [0, 0], linewidth)
|
||||||
|
|
||||||
|
gen_maxis(ax1)
|
||||||
|
gen_maxis(ax2)
|
||||||
|
|
||||||
|
if show_bounds: add_bounds(ax1)
|
||||||
|
ax1.set_title("1.16.5 generation")
|
||||||
|
ax1.set_ylabel(r'$\Sigma ore$')
|
||||||
|
ax1.set_xlabel('Elevation above void (m)')
|
||||||
|
|
||||||
|
ax2.set_title("21w07a generation")
|
||||||
|
ax2.set_ylabel(r'$\Sigma ore$')
|
||||||
|
ax2.set_xlabel('Elevation above grimstone (m)')
|
||||||
|
|
||||||
|
cfg_naxis([ax1, ax2])
|
||||||
|
|
||||||
|
#
|
||||||
|
# Let's also show relative frequency.
|
||||||
|
#
|
||||||
|
fig1, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
|
||||||
|
set_figure(fig1, dpi, size)
|
||||||
|
plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.05)
|
||||||
|
fig1.suptitle('New ore generation - Relative frequency - 1024*1024 area sample size')
|
||||||
|
|
||||||
|
plot_relative(ax1, RELATIVE_BLOCKS_OLD, OLD_DATA, [-64, 1], [256, 320], linewidth)
|
||||||
|
plot_relative(ax2, RELATIVE_BLOCKS_NEW, NEW_DATA, [0, 0], [0, 0], linewidth)
|
||||||
|
|
||||||
|
if show_bounds: add_bounds(ax1)
|
||||||
|
ax1.set_title("1.16.5 generation")
|
||||||
|
ax1.set_ylabel('Relative Frequency (%)')
|
||||||
|
ax1.set_xlabel('Elevation above void (m)')
|
||||||
|
|
||||||
|
ax2.set_title("21w07a generation")
|
||||||
|
ax2.set_ylabel('Relative Frequency (%)')
|
||||||
|
ax2.set_xlabel('Elevation above grimstone (m)')
|
||||||
|
|
||||||
|
cfg_naxis([ax1, ax2])
|
||||||
|
|
||||||
|
return [plt, [fig0, fig1]]
|
48
pyanvil_read.py
Normal file
48
pyanvil_read.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from PyAnvilEditor.pyanvil import world
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import timeit
|
||||||
|
|
||||||
|
TIME_START_GLOBAL = timeit.default_timer()
|
||||||
|
|
||||||
|
# Load the world folder relative to the current working dir
|
||||||
|
WORLD_NAME = 'NEW_ORGEN_TEST'
|
||||||
|
total_r = 512
|
||||||
|
r = 128 # Split ops into these 'blocks' to save memory.
|
||||||
|
block_fac = math.floor(total_r/r)
|
||||||
|
|
||||||
|
output_path = Path('etc/'+WORLD_NAME+'__output')
|
||||||
|
output_path.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
input_path = Path('Inputs/'+WORLD_NAME)
|
||||||
|
|
||||||
|
height_range = range(-64,321)
|
||||||
|
# height_range = range(0,256)
|
||||||
|
|
||||||
|
distribution = [ {} for x in height_range ]
|
||||||
|
for blk_i in range(-block_fac,block_fac):
|
||||||
|
for blk_k in range(-block_fac,block_fac):
|
||||||
|
print("\n\nBLOCK ",blk_i,blk_k, flush=True)
|
||||||
|
time_start_block = timeit.default_timer()
|
||||||
|
with world.World(input_path, write=False) as WORLD:
|
||||||
|
z = 0
|
||||||
|
for j in height_range: #Adjust if you're using different worldheights.
|
||||||
|
print(j, end=' ', flush=True)
|
||||||
|
for i in range(-r+blk_i*r,r+blk_i*r):
|
||||||
|
for k in range(-r+blk_k*r,r+blk_k*r):
|
||||||
|
block = WORLD.get_block((i, j, k))._state.name
|
||||||
|
if block in distribution[z]:
|
||||||
|
distribution[z][block] += 1
|
||||||
|
else:
|
||||||
|
distribution[z].update({block:1})
|
||||||
|
z += 1
|
||||||
|
# print("\nDone iterating. ", blk_i, blk_k, 'done in', (timeit.default_timer() - time_start_block), 'seconds')
|
||||||
|
print("\nBLOCK ", blk_i, blk_k, 'done in', (timeit.default_timer() - time_start_block), 'seconds')
|
||||||
|
with open(output_path / Path('SUM_leq_block_'+str(blk_i)+'.'+str(blk_k)+'.json'), 'w') as file:
|
||||||
|
file.write(json.dumps(distribution))
|
||||||
|
|
||||||
|
print('Done in', (timeit.default_timer() - TIME_START_GLOBAL), 'seconds')
|
||||||
|
|
||||||
|
with open('./RESULTS_ALL__'+WORLD_NAME+'.json', 'w') as file:
|
||||||
|
file.write(json.dumps(distribution))
|
Loading…
Reference in New Issue
Block a user