Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HeightMap datastructure to avoid returning vectors #43

Merged
merged 6 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions include/mcpp/mcpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,6 @@ class MinecraftConnection {
unflattenBlocksArray(const Coordinate& loc1, const Coordinate& loc2,
const std::vector<BlockType>& inVector);

/**
* @brief Helper function to convert flat height array to 2D.
*
* @param loc1 The first coordinate.
* @param loc2 The second coordinate.
* @param inVector The input flat height array.
* @return A 2D vector representing the heights.
*/
static std::vector<std::vector<int>>
unflattenHeightsArray(const Coordinate& loc1, const Coordinate& loc2,
const std::vector<int>& inVector);

public:
/**
* @brief Represents the main endpoint for interaction with the minecraft
Expand Down Expand Up @@ -174,7 +162,6 @@ class MinecraftConnection {
* @param loc2
* @return Returns a vector of integers representing the 2D area of heights.
*/
std::vector<std::vector<int>> getHeights(const Coordinate& loc1,
const Coordinate& loc2);
const HeightMap getHeights(const Coordinate& loc1, const Coordinate& loc2);
};
} // namespace mcpp
40 changes: 40 additions & 0 deletions include/mcpp/util.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <ostream>
#include <vector>

/** @file
* @brief Coordinate class.
Expand Down Expand Up @@ -85,4 +86,43 @@ class Coordinate {
int y;
int z;
};

/**
* Represents a 2D area of the world with the y coordinates of the highest
* non-air blocks at each (x,z)
*/
struct HeightMap {
HeightMap(const Coordinate& loc1, const Coordinate& loc2,
const std::vector<int>& heights);

/**
* Get the height using an offset from the origin/base point of the heights
* area
* @param x: x offset to access underlying array
* @param z: z offset to access underlying array
* @return: height at specified offset
*/
int get(int x, int z) const;

/**
* Get the height at a Minecraft coordinate if saved inside the height map
* @param loc: Coordinate in Minecraft world to access in the map
* @return: height at specified coordinate
*/
int get_worldspace(const Coordinate& loc) const;

/**
* Fill a coordinate inplace with the highest y coordinate at the `loc`'s x
* and z components.
* @param loc: Coordinate to fill y value for
*/
void fill_coord(Coordinate& out);

private:
int x_len;
int z_len;
Coordinate base_pt;
int* raw_heights;
};

} // namespace mcpp
28 changes: 3 additions & 25 deletions src/mcpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,16 @@ int MinecraftConnection::getHeight(int x, int z) {
return stoi(returnValue);
}

std::vector<std::vector<int>>
MinecraftConnection::getHeights(const Coordinate& loc1,
const Coordinate& loc2) {
const HeightMap MinecraftConnection::getHeights(const Coordinate& loc1,
const Coordinate& loc2) {
std::string returnValue = conn->sendReceiveCommand(
"world.getHeights", loc1.x, loc1.z, loc2.x, loc2.z);

// Returned in format "1,2,3,4,5"
std::vector<int> returnVector;
splitCommaStringToInts(returnValue, returnVector);

return unflattenHeightsArray(loc1, loc2, returnVector);
return HeightMap(loc1, loc2, returnVector);
}

std::vector<std::vector<std::vector<BlockType>>>
Expand Down Expand Up @@ -154,25 +153,4 @@ MinecraftConnection::unflattenBlocksArray(
return returnVector;
}

std::vector<std::vector<int>>
MinecraftConnection::unflattenHeightsArray(const Coordinate& loc1,
const Coordinate& loc2,
const std::vector<int>& inVector) {
// Initialise empty vector of correct size and shape
int x_len = abs(loc2.x - loc1.x) + 1;
int z_len = abs(loc2.z - loc1.z) + 1;

std::vector<std::vector<int>> returnVector(x_len,
std::vector<int>(z_len, 0));

int index = 0;
for (int x = 0; x < x_len; x++) {
for (int z = 0; z < z_len; z++) {
returnVector[x][z] = inVector[index];
index++;
}
}

return returnVector;
}
} // namespace mcpp
37 changes: 37 additions & 0 deletions src/util.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#include "../include/mcpp/util.h"
#include <cstdlib>
#include <stdexcept>
#include <string>

namespace mcpp {

Expand Down Expand Up @@ -46,4 +49,38 @@ std::ostream& operator<<(std::ostream& out, const Coordinate& coord) {
out << "(" << coord.x << ", " << coord.y << ", " << coord.z << ")";
return out;
}

HeightMap::HeightMap(const Coordinate& loc1, const Coordinate& loc2,
const std::vector<int>& heights) {
base_pt = Coordinate{
std::min(loc1.x, loc2.x),
0,
std::min(loc1.z, loc2.z),
};

x_len = std::abs(loc1.x - loc2.x) + 1;
z_len = std::abs(loc1.z - loc2.z) + 1;

raw_heights = new int[heights.size()];
std::copy(heights.begin(), heights.end(), raw_heights);
}

int HeightMap::get(int x, int z) const {
if ((x < 0 || x >= x_len) || (z < 0 || z >= z_len)) {
throw new std::out_of_range(
"Out of range access of heightmap at " + std::to_string(x) + "," +
std::to_string(z) +
" (worldspace x=" + std::to_string(base_pt.x + x) +
",z=" + std::to_string(base_pt.z + z));
}
// Get 2D from flat vector
return raw_heights[x * z_len + z];
}

int HeightMap::get_worldspace(const Coordinate& loc) const {
return get(loc.x - base_pt.x, loc.z - base_pt.z);
}

void HeightMap::fill_coord(Coordinate& out) { out.y = get_worldspace(out); }

} // namespace mcpp
101 changes: 81 additions & 20 deletions test/minecraft_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,27 +110,13 @@ TEST_CASE("Test the main mcpp class") {
}

SUBCASE("getHeight") {
Coordinate height_test_loc(200, 200, 200);
mc.setBlock(height_test_loc, Blocks::DIRT);
int height = mc.getHeight(height_test_loc.x, height_test_loc.z);
CHECK_EQ(height, height_test_loc.y);
Coordinate heightTestLoc(300, 200, 300);
mc.setBlock(heightTestLoc, Blocks::DIRT);
auto height = mc.getHeight(heightTestLoc.x, heightTestLoc.z);
CHECK_EQ(height, heightTestLoc.y);

// Cleanup
mc.setBlock(height_test_loc, Blocks::AIR);
}

SUBCASE("getHeights") {
Coordinate platformCoord1(151, 100, 151);
Coordinate platformCoord2(160, 100, 160);

// Create even heights
mc.setBlocks(platformCoord1, platformCoord2, Blocks::DIRT);

std::vector expected =
std::vector<std::vector<int>>(10, std::vector<int>(10, 100));

auto resultHeights = mc.getHeights(platformCoord1, platformCoord2);
CHECK_EQ(resultHeights, expected);
// Clean up
mc.setBlock(heightTestLoc, Blocks::AIR);
}

// Used for cuboid functions
Expand Down Expand Up @@ -172,6 +158,81 @@ TEST_CASE("Test blocks struct") {
CHECK_EQ(mc.getBlock(testLoc), Blocks::STONE);
}

TEST_CASE("HeightMap functionality") {
// 319 is the build limit in 1.19
mc.setBlocks(Coordinate{200, 300, 200}, Coordinate{210, 319, 210},
Blocks::AIR);
mc.setBlocks(Coordinate{200, 300, 200}, Coordinate{210, 300, 210},
Blocks::STONE);
mc.setBlock(Coordinate{200, 301, 200}, Blocks::STONE);
mc.setBlock(Coordinate{210, 301, 210}, Blocks::STONE);
mc.setBlock(Coordinate{201, 301, 202}, Blocks::STONE);

SUBCASE("get") {
HeightMap data =
mc.getHeights(Coordinate{200, 0, 200}, Coordinate{210, 0, 210});
CHECK_EQ(data.get(0, 0), 301);
CHECK_EQ(data.get(1, 1), 300);
CHECK_EQ(data.get(10, 10), 301);
CHECK_EQ(data.get(1, 2), 301);
}

SUBCASE("get_worldspace") {
HeightMap data =
mc.getHeights(Coordinate{200, 0, 200}, Coordinate{210, 0, 210});
CHECK_EQ(data.get_worldspace(Coordinate{200, 0, 200}), 301);
CHECK_EQ(data.get_worldspace(Coordinate{201, 0, 201}), 300);
CHECK_EQ(data.get_worldspace(Coordinate{210, 0, 210}), 301);
CHECK_EQ(data.get_worldspace(Coordinate{201, 0, 202}), 301);
}

SUBCASE("fill_coord") {
HeightMap data =
mc.getHeights(Coordinate{200, 0, 200}, Coordinate{210, 0, 210});

Coordinate to_fill{200, 0, 200};
data.fill_coord(to_fill);
CHECK_EQ(to_fill.y, 301);
}

SUBCASE("Bounds checking") {
HeightMap data =
mc.getHeights(Coordinate{200, 0, 200}, Coordinate{210, 0, 210});
CHECK_THROWS(data.get(-1, 0));
CHECK_THROWS(data.get(0, -1));
CHECK_THROWS(data.get(11, 0));
CHECK_THROWS(data.get(0, 11));

CHECK_THROWS(data.get_worldspace(Coordinate{199, 0, 200}));
CHECK_THROWS(data.get_worldspace(Coordinate{200, 0, 199}));
CHECK_THROWS(data.get_worldspace(Coordinate{211, 0, 200}));
CHECK_THROWS(data.get_worldspace(Coordinate{200, 0, 211}));

Coordinate to_fill{199, 0, 211};
CHECK_THROWS(data.fill_coord(to_fill));
}

SUBCASE("Negative coord") {
mc.setBlocks(Coordinate{-200, 300, -200}, Coordinate{-210, 319, -210},
Blocks::AIR);
mc.setBlocks(Coordinate{-200, 300, -200}, Coordinate{-210, 300, -210},
Blocks::STONE);
mc.setBlock(Coordinate{-200, 301, -200}, Blocks::STONE);
mc.setBlock(Coordinate{-210, 301, -210}, Blocks::STONE);
mc.setBlock(Coordinate{-201, 301, -202}, Blocks::STONE);

HeightMap data =
mc.getHeights(Coordinate{-200, 0, -200}, Coordinate{-210, 0, -210});
CHECK_EQ(data.get_worldspace(Coordinate{-200, 0, -200}), 301);
CHECK_EQ(data.get_worldspace(Coordinate{-201, 0, -201}), 300);
CHECK_EQ(data.get_worldspace(Coordinate{-210, 0, -210}), 301);
CHECK_EQ(data.get_worldspace(Coordinate{-201, 0, -202}), 301);
}

// Clean up
mc.setBlocks(Coordinate{200, 300, 200}, Coordinate{210, 301, 210},
Blocks::AIR);
}
// Requires player joined to server, will throw serverside if player is not
// joined
#ifdef PLAYER_TEST
Expand Down