LuckyColorCalculator/leatherColorCombinationsCalculator.cpp
2022-06-30 22:19:59 +08:00

404 lines
12 KiB
C++

#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <bitset>
#include <map>
#include <thread>
#include <mutex>
template<typename T>
void printVector(std::vector<T> vec){
for (size_t i = 0; i < vec.size(); i++)
{
std::cout<<vec[i];
if (i < vec.size() - 1) {
std::cout<<", ";
}
}
std::cout<<std::endl;
}
template<typename T>
void printMatrix(std::vector<std::vector<T>> mat){
for (size_t i = 0; i < mat.size(); i++)
{
printVector(mat[i]);
}
}
class Color
{
public:
Color():r(0), g(0), b(0){}
Color(const Color& color) {
r = color.r;
g = color.g;
b = color.b;
}
Color(uint32_t color){
Color c = Color::decode(color);
r = c.r;
g = c.g;
b = c.b;
}
Color(int _r, int _g, int _b){
r = uint8_t(_r);
g = uint8_t(_g);
b = uint8_t(_b);
}
uint32_t encode(void) const{
return (uint32_t(r)<<16) + (uint32_t(g)<<8) + uint32_t(b);
}
static Color decode(uint32_t val) {
uint8_t r = (val>>16) & 0xff;
uint8_t g = (val>>8) & 0xff;
uint8_t b = val & 0xff;
return Color(r, g, b);
}
void show(void) {
std::cout<<"(r, g, b) = ("<<uint32_t(r)<<", "<<uint32_t(g)<<", "<<uint32_t(b)<<")"<<std::endl;
}
bool operator==(const Color& color) const {
return (r == color.r) && (g == color.g) && (b ==color.b);
}
public:
uint8_t r;
uint8_t g;
uint8_t b;
};
class ColorCraftingTable {
public:
ColorCraftingTable(void){
totalRed = 0;
totalGreen = 0;
totalBlue = 0;
maxSum = 0;
numColors = 0;
}
public:
void addColor(const Color& color) {
totalRed += color.r;
totalGreen += color.g;
totalBlue += color.b;
maxSum += std::max(color.r, std::max(color.g, color.b));
numColors += 1;
}
Color craft() {
uint32_t avgRed = totalRed / numColors;
uint32_t avgGreen = totalGreen / numColors;
uint32_t avgBlue = totalBlue / numColors;
float avgOfMax = maxSum*1.0 / numColors;
uint32_t maxAvg = std::max(avgRed, std::max(avgGreen, avgBlue));
// std::cout<<numColors<<","<<avgRed<<","<<avgGreen<<","<<avgBlue<<","<<avgOfMax<<","<<maxAvg<<","<<std::endl;
return Color(
uint8_t(float(avgRed)*avgOfMax/maxAvg),
uint8_t(float(avgGreen)*avgOfMax/maxAvg),
uint8_t(float(avgBlue)*avgOfMax/maxAvg)
);
}
private:
uint32_t totalRed;
uint32_t totalGreen;
uint32_t totalBlue;
uint32_t maxSum;
uint32_t numColors;
};
void generateCompose(int total, int choice, std::vector<int>& current, std::vector<std::vector<int>>& ans){
if (choice <= 0) {
std::vector<int> compose;
for (size_t i = 0; i < current.size(); i++)
{
compose.push_back(current[current.size() - 1 - i]);
}
ans.push_back(compose);
return;
}
if (total<=0 || choice <=0 || choice > total) return;
// do not choose current number
generateCompose(total-1, choice, current, ans);
// choose current number.
current.push_back(total);
generateCompose(total-1, choice-1, current, ans);
current.pop_back();
}
std::vector<int> composeToRecipe(const std::vector<int>& compose, int num) {
std::vector<int> pre = compose;
std::vector<int> post = compose;
pre.insert(pre.begin(), 0);
post.push_back(num);
std::vector<int> recipe;
for (size_t i = 1; i < pre.size(); i++)
{
recipe.push_back(post[i] - pre[i] - 1);
}
return recipe;
}
class Recipe {
public:
Recipe(const std::vector<int>& _counts){
counts = _counts;
}
public:
uint64_t encode(void) {
uint64_t val = 0;
uint64_t bits = 1;
for (size_t i = 0; i < counts.size(); i++)
{
val += bits * counts[i];
bits *= 9;
}
return val;
}
static Recipe decode(uint64_t encoded) {
std::vector<int> counts;
for (size_t i = 0; i < 16; i++)
{
counts.push_back(int(encoded%9));
encoded /= 9;
}
return Recipe(counts);
}
void preCraft(const std::vector<Color>& colorlist) {
for (size_t i = 0; i < counts.size(); i++)
{
for (int j = 0; j < counts[i]; j++)
{
table.addColor(colorlist[i]);
}
}
}
Color getTableColor(void) {
return table.craft();
}
Color getCraftedArmorColor(const Color armorColor) {
ColorCraftingTable temp = table;
if (!(armorColor == Color(0, 0, 0))) {
temp.addColor(armorColor);
}
return temp.craft();
}
std::vector<int> getCounts(void){
return counts;
}
private:
std::vector<int> counts;
ColorCraftingTable table;
};
void dumpPossibleRecipe(const std::string& path) {
std::vector<std::vector<int>> ans;
std::vector<int> current;
std::fstream recipes;
recipes.open(path, std::fstream::out|std::fstream::binary);
generateCompose(24, 16, current, ans);
size_t counts = 0;
for (size_t i = 0; i < ans.size(); i++)
{
Recipe recipe(composeToRecipe(ans[i], 25));
uint64_t encoded = recipe.encode();
if (encoded == 0) continue; // this recipe is empty.
recipes.write((char*) &encoded, sizeof(encoded));
counts += 1;
}
recipes.close();
std::cout<<"we have write "<<counts<<" possible recipes."<<std::endl;
}
template<int N>
class SyncholizedHeapBitset {
public:
SyncholizedHeapBitset(void) {
bitmap = new std::bitset<N>;
}
~SyncholizedHeapBitset(void) {
delete bitmap;
}
bool test(size_t pos) {return bitmap->test(pos);}
void set(size_t pos, bool val) {bitmap->set(pos, val);}
size_t count(void) {return bitmap->count();}
private:
std::bitset<N>* bitmap;
};
void searchBlock(
std::mutex& lock,
std::vector<Recipe>& recipes,// readonly
SyncholizedHeapBitset<(2<<24)>& colormap, // thread-safe
std::vector<Color>& oldColors, // read-only
std::vector<Color>& newColors, //write-only
std::fstream& searched, // write-only.
size_t start, size_t end
) {
for (size_t i = start; i < end; i++)
{
for(size_t j = 0; j < recipes.size(); j++) {
Color crafted = recipes[j].getCraftedArmorColor(oldColors[i]);
uint32_t encodedColor = crafted.encode();
if (!colormap.test(encodedColor)) {
lock.lock(); // for thread safety.
if (!colormap.test(encodedColor)) { // double check after locked to avoid duplication.
colormap.set(encodedColor, true);
newColors.push_back(crafted);
uint32_t encodedOldColor = oldColors[i].encode();
searched.write((const char*) &encodedOldColor, sizeof(encodedOldColor));
uint64_t encodedRecipe = recipes[j].encode();
searched.write((const char*) &encodedRecipe, sizeof(encodedRecipe));
searched.write((const char*) &encodedColor, sizeof(encodedColor));
}
lock.unlock();
}
}
}
}
void searchAllColors(const std::vector<Color>& colorlist, int numThreads=8, int blockSize=32) {
std::cout<<"preparing..."<<std::endl;
// generate all possible recipes for a leather.
std::vector<Recipe> recipes;
std::vector<std::vector<int>> ans;
std::vector<int> current;
std::cout<<"Generating compose..."<<std::endl;
generateCompose(24, 16, current, ans);
size_t counts = 0;
std::cout<<"Converting compose to recipe..."<<std::endl;
for (size_t i = 0; i < ans.size(); i++)
{
Recipe recipe(composeToRecipe(ans[i], 25));
if (recipe.encode() == 0) continue; // this recipe is empty.
recipes.push_back(recipe);
counts += 1;
}
std::cout<<"we have find "<<counts<<" possible recipes."<<std::endl;
// pre-craft all possible recipes to save time.
std::cout<<"Pre-crafting recipe..."<<std::endl;
for (size_t i = 0; i < recipes.size(); i++)
{
recipes[i].preCraft(colorlist);
}
// lets start to search.
SyncholizedHeapBitset<(2<<24)> colormap;
std::vector<Color> oldColors;
// color #0x000000 do not exist in minecraft, so it is okay to be used as empty color.
oldColors.push_back(Color(uint32_t(0)));
std::mutex lock;
size_t layer = 1;
while (oldColors.size() > 0) {
std::cout<<"processing layer "<<layer<<std::endl;
std::fstream output;
std::string savepath = std::string("layer") + std::to_string(layer) + std::string(".bin");
std::cout<<"writing generated recipe in this layer to file "<<savepath<<"..."<<std::endl;
output.open(
savepath,
std::fstream::out|std::fstream::binary
);
std::vector<Color> newColors;
auto start = std::chrono::high_resolution_clock::now();
auto last = start;
for (size_t i = 0; i < oldColors.size(); i+=numThreads*blockSize)
{
// create threads and dispatch jobs.
std::vector<std::thread*> threads;
for(int j = 0; j < numThreads; j++) {
int start = i + j*blockSize;
int end = i + (j+1)*blockSize;
if (end > (int) oldColors.size()) {
end = (int) oldColors.size();
}
if (start > end) {start = end;}
std::thread* t = new std::thread(
searchBlock,
std::ref(lock),
std::ref(recipes),
std::ref(colormap),
std::ref(oldColors),
std::ref(newColors),
std::ref(output),
start, end
);
threads.push_back(t);
}
// wait for all threads to finish.
for(size_t j = 0; j < threads.size(); j++) {
threads[j]->join();
delete threads[j];
threads[j] = nullptr;
}
auto now = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = now - start;
std::chrono::duration<double> interval = now - last;
if (interval.count() >= 5) {
last = now;
std::cout<<"progress: "<<double((i+1)*100)/oldColors.size()<<"%, ";
std::cout<<colormap.count()<<" colors found.";
double totalTime = diff.count()*oldColors.size()/(i+1);
double remeaningTime = totalTime * (1 - (i+1)*1.0/oldColors.size());
std::cout<<"estimating "<<totalTime<<" s in total and "<<remeaningTime<<" s left."<<std::endl;
}
}
output.close();
oldColors = newColors;
std::cout<<"layer "<<layer<<" finished, found "<<oldColors.size()<<" new colors."<<std::endl;
layer++;
}
}
int main(int argc, char const *argv[])
{
std::vector<Color> colors = {
Color(176, 46, 38),
Color(94, 124, 22),
Color(137, 50, 184),
Color(22, 156, 156),
Color(157, 157, 151),
Color(71, 79, 82),
Color(243, 139, 170),
Color(128, 199, 31),
Color(254, 216, 61),
Color(58, 179, 218),
Color(199, 78, 189),
Color(249, 128, 29),
Color(29, 29, 33),
Color(131, 84, 50),
Color(60, 68, 170),
Color(249, 255, 254),
};
// useless in this script, but useful for interpreting encoded recipe.
std::vector<std::string> keys = {
"red", "green", "purple", "cyan",
"silver", "gray", "pink", "lime",
"yellow", "lightBlue", "magenta", "orange",
"black", "brown", "blue", "white"
};
// parse numThreads.
if (argc == 2) {
int numThreads = std::stoi(std::string(argv[1]));
searchAllColors(colors, numThreads);
} else {
std::cout<<"Usage: ./leatherColorCombinationsCalculator <numThreads>"<<std::endl;
}
return 0;
}