first commit - 21w07a

This commit is contained in:
Peter 2021-02-20 22:32:09 +08:00
commit ae3a392af3
11 changed files with 271 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
region/
pyanvileditor/
etc/
inputs/
__pycache__/

23
README.md Normal file
View 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

File diff suppressed because one or more lines are too long

BIN
RESULTS/abs_freq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

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

File diff suppressed because one or more lines are too long

25
export_graph.py Normal file
View 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
View 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
View 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))