Custom GOL (#731)

Co-authored-by: jacob1 <jfu614@gmail.com>
This commit is contained in:
Tamás Bálint Misius 2020-10-08 20:23:59 +02:00 committed by GitHub
parent 02b26a9da3
commit ba72dc7a22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 849 additions and 356 deletions

View File

@ -1298,6 +1298,16 @@ void GameSave::readOPS(char * data, int dataLength)
particles[newIndex].tmp = 0; particles[newIndex].tmp = 0;
} }
break; break;
case PT_LIFE:
if (savedVersion < 95 || minorVersion < 1)
{
if (particles[newIndex].ctype >= 0 && particles[newIndex].ctype < NGOL)
{
particles[newIndex].tmp2 = particles[newIndex].tmp;
particles[newIndex].dcolour = builtinGol[particles[newIndex].ctype].colour;
particles[newIndex].tmp = builtinGol[particles[newIndex].ctype].colour2;
}
}
} }
//note: PSv was used in version 77.0 and every version before, add something in PSv too if the element is that old //note: PSv was used in version 77.0 and every version before, add something in PSv too if the element is that old
newIndex++; newIndex++;
@ -1359,10 +1369,6 @@ void GameSave::readPSv(char * saveDataChar, int dataLength)
char tempSignText[255]; char tempSignText[255];
sign tempSign("", 0, 0, sign::Left); sign tempSign("", 0, 0, sign::Left);
//Gol data used to read older saves
std::vector<int> goltype = LoadGOLTypes();
std::vector<std::array<int, 10> > grule = LoadGOLRules();
std::vector<Element> elements = GetElements(); std::vector<Element> elements = GetElements();
//New file header uses PSv, replacing fuC. This is to detect if the client uses a new save format for temperatures //New file header uses PSv, replacing fuC. This is to detect if the client uses a new save format for temperatures
@ -1658,9 +1664,9 @@ void GameSave::readPSv(char * saveDataChar, int dataLength)
ttv |= (data[p++]); ttv |= (data[p++]);
particles[i-1].tmp = ttv; particles[i-1].tmp = ttv;
if (ver<53 && !particles[i-1].tmp) if (ver<53 && !particles[i-1].tmp)
for (q = 1; q<=NGOL; q++) { for (q = 0; q < NGOL; q++) {
if (particles[i-1].type==goltype[q-1] && grule[q][9]==2) if (particles[i-1].type==builtinGol[q].oldtype && (builtinGol[q].ruleset >> 17)==0)
particles[i-1].tmp = grule[q][9]-1; particles[i-1].tmp = (builtinGol[q].ruleset >> 17)+1;
} }
if (ver>=51 && ver<53 && particles[i-1].type==PT_PBCN) if (ver>=51 && ver<53 && particles[i-1].type==PT_PBCN)
{ {
@ -1844,7 +1850,7 @@ void GameSave::readPSv(char * saveDataChar, int dataLength)
//Replace old GOL //Replace old GOL
particles[i-1].type = PT_LIFE; particles[i-1].type = PT_LIFE;
for (gnum = 0; gnum<NGOL; gnum++){ for (gnum = 0; gnum<NGOL; gnum++){
if (ty==goltype[gnum]) if (ty==builtinGol[gnum].oldtype)
particles[i-1].ctype = gnum; particles[i-1].ctype = gnum;
} }
ty = PT_LIFE; ty = PT_LIFE;
@ -1852,13 +1858,23 @@ void GameSave::readPSv(char * saveDataChar, int dataLength)
if(ver<52 && (ty==PT_CLNE || ty==PT_PCLN || ty==PT_BCLN)){ if(ver<52 && (ty==PT_CLNE || ty==PT_PCLN || ty==PT_BCLN)){
//Replace old GOL ctypes in clone //Replace old GOL ctypes in clone
for (gnum = 0; gnum<NGOL; gnum++){ for (gnum = 0; gnum<NGOL; gnum++){
if (particles[i-1].ctype==goltype[gnum]) if (particles[i-1].ctype==builtinGol[gnum].oldtype)
{ {
particles[i-1].ctype = PT_LIFE; particles[i-1].ctype = PT_LIFE;
particles[i-1].tmp = gnum; particles[i-1].tmp = gnum;
} }
} }
} }
if (particles[i-1].type == PT_LIFE)
{
particles[i-1].tmp2 = particles[i-1].tmp;
particles[i-1].tmp = 0;
if (particles[i-1].ctype >= 0 && particles[i-1].ctype < NGOL)
{
particles[i-1].dcolour = builtinGol[particles[i-1].ctype].colour;
particles[i-1].tmp = builtinGol[particles[i-1].ctype].colour2;
}
}
if(ty==PT_LCRY){ if(ty==PT_LCRY){
if(ver<67) if(ver<67)
{ {
@ -2269,7 +2285,7 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
} }
//Dcolour (optional), 4 bytes //Dcolour (optional), 4 bytes
if(particles[i].dcolour && (particles[i].dcolour & 0xFF000000)) if(particles[i].dcolour && (particles[i].dcolour & 0xFF000000 || particles[i].type == PT_LIFE))
{ {
fieldDesc |= 1 << 6; fieldDesc |= 1 << 6;
partsData[partsDataLen++] = (particles[i].dcolour&0xFF000000)>>24; partsData[partsDataLen++] = (particles[i].dcolour&0xFF000000)>>24;
@ -2411,6 +2427,10 @@ char * GameSave::serialiseOPS(unsigned int & dataLength)
RESTRICTVERSION(95, 0); RESTRICTVERSION(95, 0);
} }
} }
if (particles[i].type == PT_LIFE)
{
RESTRICTVERSION(95, 1);
}
//Get the pmap entry for the next particle in the same position //Get the pmap entry for the next particle in the same position
i = partsPosLink[i]; i = partsPosLink[i];

214
src/gui/game/GOLTool.cpp Normal file
View File

@ -0,0 +1,214 @@
#include "Tool.h"
#include "simulation/GOLString.h"
#include "client/Client.h"
#include "common/tpt-rand.h"
#include "gui/Style.h"
#include "gui/game/GameModel.h"
#include "gui/interface/Window.h"
#include "gui/interface/Button.h"
#include "gui/interface/Label.h"
#include "gui/interface/Textbox.h"
#include "gui/dialogues/ErrorMessage.h"
#include "gui/colourpicker/ColourPickerActivity.h"
#include "graphics/Graphics.h"
class GOLWindow: public ui::Window
{
void UpdateGradient();
public:
ui::Colour highColour, lowColour;
ui::Button *highColourButton, *lowColourButton;
ui::Textbox *nameField, *ruleField;
GOLTool * tool;
Simulation *sim;
int toolSelection;
GOLWindow(GOLTool *tool_, Simulation *sim, int toolSelection, int rule, int colour1, int colour2);
void Validate();
void OnDraw() override;
void OnTryExit(ExitMethod method) override;
virtual ~GOLWindow() {}
};
GOLWindow::GOLWindow(GOLTool * tool_, Simulation *sim_, int toolSelection, int rule, int colour1, int colour2):
ui::Window(ui::Point(-1, -1), ui::Point(200, 108)),
tool(tool_),
sim(sim_),
toolSelection(toolSelection)
{
ui::Label * messageLabel = new ui::Label(ui::Point(4, 5), ui::Point(Size.X-8, 14), "Edit custom GOL type");
messageLabel->SetTextColour(style::Colour::InformationTitle);
messageLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
messageLabel->Appearance.VerticalAlign = ui::Appearance::AlignTop;
AddComponent(messageLabel);
auto *okayButton = new ui::Button(ui::Point(0, Size.Y-17), ui::Point(Size.X, 17), "OK");
okayButton->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
okayButton->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
okayButton->Appearance.BorderInactive = ui::Colour(200, 200, 200);
okayButton->SetActionCallback({ [this] {
CloseActiveWindow();
if (nameField->GetText().length() && ruleField->GetText().length())
{
Validate();
}
SelfDestruct();
} });
AddComponent(okayButton);
SetOkayButton(okayButton);
nameField = new ui::Textbox(ui::Point(8, 25), ui::Point(Size.X-16, 16), "", "[name]");
nameField->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
nameField->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
AddComponent(nameField);
FocusComponent(nameField);
ruleField = new ui::Textbox(ui::Point(8, 46), ui::Point(Size.X-16, 16), "", "[rule]");
ruleField->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
ruleField->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
AddComponent(ruleField);
FocusComponent(ruleField);
highColourButton = new ui::Button(ui::Point(8, 67), ui::Point(16, 16), "");
highColourButton->SetActionCallback({ [this] {
new ColourPickerActivity(highColour, [this](ui::Colour colour) {
highColour = colour;
UpdateGradient();
});
} });
AddComponent(highColourButton);
lowColourButton = new ui::Button(ui::Point(Size.X - 24, 67), ui::Point(16, 16), "");
lowColourButton->SetActionCallback({ [this] {
new ColourPickerActivity(lowColour, [this](ui::Colour colour) {
lowColour = colour;
UpdateGradient();
});
} });
AddComponent(lowColourButton);
if (rule)
{
ruleField->SetText(SerialiseGOLRule(rule));
nameField->SetText("");
highColour.Red = PIXR(colour1);
highColour.Green = PIXG(colour1);
highColour.Blue = PIXB(colour1);
lowColour.Red = PIXR(colour2);
lowColour.Green = PIXG(colour2);
lowColour.Blue = PIXB(colour2);
}
else
{
ruleField->SetText(Client::Ref().GetPrefString("CustomGOL.Rule", "B3/S23"));
nameField->SetText(Client::Ref().GetPrefString("CustomGOL.Name", "CGOL"));
highColour.Red = RNG::Ref().between(0x80, 0xFF);
highColour.Green = RNG::Ref().between(0x80, 0xFF);
highColour.Blue = RNG::Ref().between(0x80, 0xFF);
lowColour.Red = RNG::Ref().between(0x00, 0x7F);
lowColour.Green = RNG::Ref().between(0x00, 0x7F);
lowColour.Blue = RNG::Ref().between(0x00, 0x7F);
}
highColour.Alpha = 255;
lowColour.Alpha = 255;
UpdateGradient();
MakeActiveWindow();
}
void GOLWindow::UpdateGradient()
{
highColourButton->Appearance.BackgroundInactive = highColour;
highColourButton->Appearance.BackgroundHover = highColour;
lowColourButton->Appearance.BackgroundInactive = lowColour;
lowColourButton->Appearance.BackgroundHover = lowColour;
}
void GOLWindow::Validate()
{
tool->selectGOLType.clear();
auto nameString = nameField->GetText();
auto ruleString = ruleField->GetText();
if (!ValidateGOLName(nameString))
{
new ErrorMessage("Could not add GOL type", "Invalid name provided");
return;
}
nameString = nameString.ToUpper();
int rule = ParseGOLString(ruleString);
if (rule == -1)
{
new ErrorMessage("Could not add GOL type", "Invalid rule provided");
return;
}
ruleString = SerialiseGOLRule(rule); // * Make it canonical.
Client::Ref().SetPrefUnicode("CustomGOL.Name", nameString);
Client::Ref().SetPrefUnicode("CustomGOL.Rule", ruleString);
auto customGOLTypes = Client::Ref().GetPrefByteStringArray("CustomGOL.Types");
Json::Value newCustomGOLTypes(Json::arrayValue);
bool nameTaken = false;
for (auto gol : customGOLTypes)
{
auto parts = gol.FromUtf8().PartitionBy(' ');
if (parts.size())
{
if (parts[0] == nameString)
{
nameTaken = true;
}
}
newCustomGOLTypes.append(gol);
}
if (nameTaken)
{
new ErrorMessage("Could not add GOL type", "Name already taken");
return;
}
StringBuilder sb;
auto colour1 = (((highColour.Red << 8) | highColour.Green) << 8) | highColour.Blue;
auto colour2 = (((lowColour.Red << 8) | lowColour.Green) << 8) | lowColour.Blue;
sb << nameString << " " << ruleString << " " << colour1 << " " << colour2;
newCustomGOLTypes.append(sb.Build().ToUtf8());
Client::Ref().SetPref("CustomGOL.Types", newCustomGOLTypes);
tool->gameModel->SelectNextIdentifier = "DEFAULT_PT_LIFECUST_" + nameString.ToAscii();
tool->gameModel->SelectNextTool = toolSelection;
}
void GOLWindow::OnTryExit(ExitMethod method)
{
CloseActiveWindow();
SelfDestruct();
}
void GOLWindow::OnDraw()
{
Graphics * g = GetGraphics();
g->clearrect(Position.X-2, Position.Y-2, Size.X+3, Size.Y+3);
g->drawrect(Position.X, Position.Y, Size.X, Size.Y, 200, 200, 200, 255);
int width = Size.X - 60;
for (int xx = 0; xx < width; ++xx)
{
auto f = xx / (float)width;
for (int yy = 0; yy < 16; ++yy)
{
int rr = highColour.Red * (1.f - f) + lowColour.Red * f;
int gg = highColour.Green * (1.f - f) + lowColour.Green * f;
int bb = highColour.Blue * (1.f - f) + lowColour.Blue * f;
g->blendpixel(Position.X + xx + 30, Position.Y + yy + 67, rr, gg, bb, 255);
}
}
}
void GOLTool::OpenWindow(Simulation *sim, int toolSelection, int rule, int colour1, int colour2)
{
new GOLWindow(this, sim, toolSelection, rule, colour1, colour2);
}

View File

@ -779,6 +779,12 @@ void GameController::Tick()
#endif #endif
firstTick = false; firstTick = false;
} }
if (gameModel->SelectNextIdentifier.length())
{
gameModel->BuildMenus();
gameModel->SetActiveTool(gameModel->SelectNextTool, gameModel->GetToolFromIdentifier(gameModel->SelectNextIdentifier));
gameModel->SelectNextIdentifier.clear();
}
for(std::vector<DebugInfo*>::iterator iter = debugInfo.begin(), end = debugInfo.end(); iter != end; iter++) for(std::vector<DebugInfo*>::iterator iter = debugInfo.begin(), end = debugInfo.end(); iter != end; iter++)
{ {
if ((*iter)->debugID & debugFlags) if ((*iter)->debugID & debugFlags)
@ -1114,6 +1120,10 @@ void GameController::SetActiveTool(int toolSelection, Tool * tool)
} }
if(tool->GetIdentifier() == "DEFAULT_UI_PROPERTY") if(tool->GetIdentifier() == "DEFAULT_UI_PROPERTY")
((PropertyTool *)tool)->OpenWindow(gameModel->GetSimulation()); ((PropertyTool *)tool)->OpenWindow(gameModel->GetSimulation());
if(tool->GetIdentifier() == "DEFAULT_UI_ADDLIFE")
{
((GOLTool *)tool)->OpenWindow(gameModel->GetSimulation(), toolSelection);
}
} }
void GameController::SetActiveTool(int toolSelection, ByteString identifier) void GameController::SetActiveTool(int toolSelection, ByteString identifier)
@ -1672,3 +1682,8 @@ bool GameController::GetMouseClickRequired()
{ {
return gameModel->GetMouseClickRequired(); return gameModel->GetMouseClickRequired();
} }
void GameController::RemoveCustomGOLType(const ByteString &identifier)
{
gameModel->RemoveCustomGOLType(identifier);
}

View File

@ -174,6 +174,8 @@ public:
void NotifyNewNotification(Client * sender, std::pair<String, ByteString> notification) override; void NotifyNewNotification(Client * sender, std::pair<String, ByteString> notification) override;
void RunUpdater(); void RunUpdater();
bool GetMouseClickRequired(); bool GetMouseClickRequired();
void RemoveCustomGOLType(const ByteString &identifier);
}; };
#endif // GAMECONTROLLER_H #endif // GAMECONTROLLER_H

View File

@ -27,11 +27,13 @@
#include "simulation/Gravity.h" #include "simulation/Gravity.h"
#include "simulation/ElementGraphics.h" #include "simulation/ElementGraphics.h"
#include "simulation/ElementClasses.h" #include "simulation/ElementClasses.h"
#include "simulation/GOLString.h"
#include "gui/game/DecorationTool.h" #include "gui/game/DecorationTool.h"
#include "gui/interface/Engine.h" #include "gui/interface/Engine.h"
#include <iostream> #include <iostream>
#include <algorithm>
GameModel::GameModel(): GameModel::GameModel():
clipboard(NULL), clipboard(NULL),
@ -303,9 +305,55 @@ void GameModel::BuildMenus()
//Build menu for GOL types //Build menu for GOL types
for(int i = 0; i < NGOL; i++) for(int i = 0; i < NGOL; i++)
{ {
Tool * tempTool = new ElementTool(PT_LIFE|PMAPID(i), sim->gmenu[i].name, sim->gmenu[i].description, PIXR(sim->gmenu[i].colour), PIXG(sim->gmenu[i].colour), PIXB(sim->gmenu[i].colour), "DEFAULT_PT_LIFE_"+sim->gmenu[i].name.ToAscii()); Tool * tempTool = new ElementTool(PT_LIFE|PMAPID(i), builtinGol[i].name, builtinGol[i].description, PIXR(builtinGol[i].colour), PIXG(builtinGol[i].colour), PIXB(builtinGol[i].colour), "DEFAULT_PT_LIFE_"+builtinGol[i].name.ToAscii());
menuList[SC_LIFE]->AddTool(tempTool); menuList[SC_LIFE]->AddTool(tempTool);
} }
{
auto customGOLTypes = Client::Ref().GetPrefByteStringArray("CustomGOL.Types");
Json::Value validatedCustomLifeTypes(Json::arrayValue);
std::vector<Simulation::CustomGOLData> newCustomGol;
for (auto gol : customGOLTypes)
{
auto parts = gol.FromUtf8().PartitionBy(' ');
if (parts.size() != 4)
{
continue;
}
Simulation::CustomGOLData gd;
gd.nameString = parts[0];
gd.ruleString = parts[1];
auto &colour1String = parts[2];
auto &colour2String = parts[3];
if (!ValidateGOLName(gd.nameString))
{
continue;
}
gd.rule = ParseGOLString(gd.ruleString);
if (gd.rule == -1)
{
continue;
}
try
{
gd.colour1 = colour1String.ToNumber<int>();
gd.colour2 = colour2String.ToNumber<int>();
}
catch (std::exception &)
{
continue;
}
newCustomGol.push_back(gd);
validatedCustomLifeTypes.append(gol);
}
// All custom rules that fail validation will be removed
Client::Ref().SetPref("CustomGOL.Types", validatedCustomLifeTypes);
for (auto &gd : newCustomGol)
{
Tool * tempTool = new ElementTool(PT_LIFE|PMAPID(gd.rule), gd.nameString, "Custom GOL type: " + gd.ruleString, PIXR(gd.colour1), PIXG(gd.colour1), PIXB(gd.colour1), "DEFAULT_PT_LIFECUST_"+gd.nameString.ToAscii(), NULL);
menuList[SC_LIFE]->AddTool(tempTool);
}
sim->SetCustomGOL(newCustomGol);
}
//Build other menus from wall data //Build other menus from wall data
for(int i = 0; i < UI_WALLCOUNT; i++) for(int i = 0; i < UI_WALLCOUNT; i++)
@ -331,9 +379,10 @@ void GameModel::BuildMenus()
} }
//Add special sign and prop tools //Add special sign and prop tools
menuList[SC_TOOL]->AddTool(new WindTool(0, "WIND", "Creates air movement.", 64, 64, 64, "DEFAULT_UI_WIND")); menuList[SC_TOOL]->AddTool(new WindTool(0, "WIND", "Creates air movement.", 64, 64, 64, "DEFAULT_UI_WIND"));
menuList[SC_TOOL]->AddTool(new PropertyTool()); menuList[SC_TOOL]->AddTool(new PropertyTool(this));
menuList[SC_TOOL]->AddTool(new SignTool(this)); menuList[SC_TOOL]->AddTool(new SignTool(this));
menuList[SC_TOOL]->AddTool(new SampleTool(this)); menuList[SC_TOOL]->AddTool(new SampleTool(this));
menuList[SC_LIFE]->AddTool(new GOLTool(this));
//Add decoration tools to menu //Add decoration tools to menu
menuList[SC_DECO]->AddTool(new DecorationTool(ren, DECO_ADD, "ADD", "Colour blending: Add.", 0, 0, 0, "DEFAULT_DECOR_ADD")); menuList[SC_DECO]->AddTool(new DecorationTool(ren, DECO_ADD, "ADD", "Colour blending: Add.", 0, 0, 0, "DEFAULT_DECOR_ADD"));
@ -1374,3 +1423,27 @@ void GameModel::SetPerfectCircle(bool perfectCircle)
BuildBrushList(); BuildBrushList();
} }
} }
void GameModel::RemoveCustomGOLType(const ByteString &identifier)
{
auto customGOLTypes = Client::Ref().GetPrefByteStringArray("CustomGOL.Types");
Json::Value newCustomGOLTypes(Json::arrayValue);
for (auto gol : customGOLTypes)
{
auto parts = gol.PartitionBy(' ');
bool remove = false;
if (parts.size())
{
if ("DEFAULT_PT_LIFECUST_" + parts[0] == identifier)
{
remove = true;
}
}
if (!remove)
{
newCustomGOLTypes.append(gol);
}
}
Client::Ref().SetPref("CustomGOL.Types", newCustomGOLTypes);
BuildMenus();
}

View File

@ -219,6 +219,11 @@ public:
std::vector<Notification*> GetNotifications(); std::vector<Notification*> GetNotifications();
void AddNotification(Notification * notification); void AddNotification(Notification * notification);
void RemoveNotification(Notification * notification); void RemoveNotification(Notification * notification);
void RemoveCustomGOLType(const ByteString &identifier);
ByteString SelectNextIdentifier;
int SelectNextTool;
}; };
#endif // GAMEMODEL_H #endif // GAMEMODEL_H

View File

@ -585,8 +585,19 @@ void GameView::NotifyToolListChanged(GameModel * sender)
} }
else if (tempButton->GetSelectionState() == 1) else if (tempButton->GetSelectionState() == 1)
{ {
Favorite::Ref().RemoveFavorite(tool->GetIdentifier()); auto identifier = tool->GetIdentifier();
c->RebuildFavoritesMenu(); if (Favorite::Ref().IsFavorite(identifier))
{
Favorite::Ref().RemoveFavorite(identifier);
c->RebuildFavoritesMenu();
}
else if (identifier.BeginsWith("DEFAULT_PT_LIFECUST_"))
{
if (ConfirmPrompt::Blocking("Remove custom GOL type", "Are you sure you want to remove " + identifier.Substr(20).FromUtf8() + "?"))
{
c->RemoveCustomGOLType(identifier);
}
}
} }
} }
else else
@ -2154,6 +2165,8 @@ void GameView::OnDraw()
// Some elements store extra LIFE info in upper bits of ctype, instead of tmp/tmp2 // Some elements store extra LIFE info in upper bits of ctype, instead of tmp/tmp2
else if (type == PT_CRAY || type == PT_DRAY || type == PT_CONV) else if (type == PT_CRAY || type == PT_DRAY || type == PT_CONV)
sampleInfo << " (" << c->ElementResolve(TYP(ctype), ID(ctype)) << ")"; sampleInfo << " (" << c->ElementResolve(TYP(ctype), ID(ctype)) << ")";
else if (type == PT_CLNE || type == PT_BCLN || type == PT_PCLN || type == PT_PBCN)
sampleInfo << " (" << c->ElementResolve(ctype, sample.particle.tmp) << ")";
else if (c->IsValidElement(ctype)) else if (c->IsValidElement(ctype))
sampleInfo << " (" << c->ElementResolve(ctype, -1) << ")"; sampleInfo << " (" << c->ElementResolve(ctype, -1) << ")";
else else

View File

@ -1,7 +1,9 @@
#include "Tool.h" #include "Tool.h"
#include "client/Client.h" #include "client/Client.h"
#include "Menu.h"
#include "gui/game/GameModel.h"
#include "gui/Style.h" #include "gui/Style.h"
#include "gui/game/Brush.h" #include "gui/game/Brush.h"
#include "gui/interface/Window.h" #include "gui/interface/Window.h"
@ -12,7 +14,10 @@
#include "gui/interface/Keys.h" #include "gui/interface/Keys.h"
#include "gui/dialogues/ErrorMessage.h" #include "gui/dialogues/ErrorMessage.h"
#include "simulation/GOLString.h"
#include "simulation/BuiltinGOL.h"
#include "simulation/Simulation.h" #include "simulation/Simulation.h"
#include "simulation/SimulationData.h"
#include "graphics/Graphics.h" #include "graphics/Graphics.h"
@ -83,7 +88,7 @@ void PropertyWindow::SetProperty()
{ {
if(property->GetOption().second!=-1 && textField->GetText().length() > 0) if(property->GetOption().second!=-1 && textField->GetText().length() > 0)
{ {
String value = textField->GetText(); String value = textField->GetText().ToUpper();
try { try {
switch(properties[property->GetOption().second].Type) switch(properties[property->GetOption().second].Type)
{ {
@ -91,7 +96,7 @@ void PropertyWindow::SetProperty()
case StructProperty::ParticleType: case StructProperty::ParticleType:
{ {
int v; int v;
if(value.length() > 2 && value.BeginsWith("0x")) if(value.length() > 2 && value.BeginsWith("0X"))
{ {
//0xC0FFEE //0xC0FFEE
v = value.Substr(2).ToNumber<unsigned int>(Format::Hex()); v = value.Substr(2).ToNumber<unsigned int>(Format::Hex());
@ -101,18 +106,32 @@ void PropertyWindow::SetProperty()
//#C0FFEE //#C0FFEE
v = value.Substr(1).ToNumber<unsigned int>(Format::Hex()); v = value.Substr(1).ToNumber<unsigned int>(Format::Hex());
} }
else if (value.length() > 1 && value.BeginsWith("B") && value.Contains("/"))
{
v = ParseGOLString(value);
if (v == -1)
{
class InvalidGOLString : public std::exception
{
};
throw InvalidGOLString();
}
}
else else
{ {
int type; v = sim->GetParticleType(value.ToUtf8());
if ((type = sim->GetParticleType(value.ToUtf8())) != -1) if (v == -1)
{ {
v = type; for (auto *elementTool : tool->gameModel->GetMenuList()[SC_LIFE]->GetToolList())
{
#ifdef DEBUG if (elementTool && elementTool->GetName() == value)
std::cout << "Got type from particle name" << std::endl; {
#endif v = ID(elementTool->GetToolID());
break;
}
}
} }
else if (v == -1)
{ {
v = value.ToNumber<int>(); v = value.ToNumber<int>();
} }
@ -134,7 +153,7 @@ void PropertyWindow::SetProperty()
case StructProperty::UInteger: case StructProperty::UInteger:
{ {
unsigned int v; unsigned int v;
if(value.length() > 2 && value.BeginsWith("0x")) if(value.length() > 2 && value.BeginsWith("0X"))
{ {
//0xC0FFEE //0xC0FFEE
v = value.Substr(2).ToNumber<unsigned int>(Format::Hex()); v = value.Substr(2).ToNumber<unsigned int>(Format::Hex());

View File

@ -38,36 +38,37 @@ void SampleTool::Draw(Simulation * sim, Brush * brush, ui::Point position)
} }
else else
{ {
int particleType = 0; Particle *part = nullptr;
int particleCtype = 0;
if (sim->photons[position.Y][position.X]) if (sim->photons[position.Y][position.X])
{ {
particleType = sim->parts[ID(sim->photons[position.Y][position.X])].type; part = &sim->parts[ID(sim->photons[position.Y][position.X])];
particleCtype = sim->parts[ID(sim->pmap[position.Y][position.X])].ctype;
} }
else if (sim->pmap[position.Y][position.X]) else if (sim->pmap[position.Y][position.X])
{ {
particleType = sim->parts[ID(sim->pmap[position.Y][position.X])].type; part = &sim->parts[ID(sim->pmap[position.Y][position.X])];
particleCtype = sim->parts[ID(sim->pmap[position.Y][position.X])].ctype;
} }
if (part)
if(particleType)
{ {
if(particleType == PT_LIFE) if (part->type == PT_LIFE)
{ {
Menu * lifeMenu = gameModel->GetMenuList()[SC_LIFE]; bool found = false;
std::vector<Tool*> elementTools = lifeMenu->GetToolList(); for (auto *elementTool : gameModel->GetMenuList()[SC_LIFE]->GetToolList())
for(std::vector<Tool*>::iterator iter = elementTools.begin(), end = elementTools.end(); iter != end; ++iter)
{ {
Tool * elementTool = *iter; if (elementTool && ID(elementTool->GetToolID()) == part->ctype)
if(elementTool && ID(elementTool->GetToolID()) == particleCtype) {
gameModel->SetActiveTool(0, elementTool); gameModel->SetActiveTool(0, elementTool);
found = true;
break;
}
}
if (!found)
{
((GOLTool *)(gameModel->GetToolFromIdentifier("DEFAULT_UI_ADDLIFE")))->OpenWindow(gameModel->GetSimulation(), 0, part->ctype, part->dcolour, part->tmp);
} }
} }
else else
{ {
Tool * elementTool = gameModel->GetElementTool(particleType); Tool * elementTool = gameModel->GetElementTool(part->type);
if(elementTool) if(elementTool)
gameModel->SetActiveTool(0, elementTool); gameModel->SetActiveTool(0, elementTool);
} }

View File

@ -81,8 +81,10 @@ public:
class PropertyTool: public Tool class PropertyTool: public Tool
{ {
public: public:
PropertyTool(): GameModel * gameModel;
Tool(0, "PROP", "Property Drawing Tool. Use to alter the properties of elements in the field.", 0xfe, 0xa9, 0x00, "DEFAULT_UI_PROPERTY", NULL) PropertyTool(GameModel *model):
Tool(0, "PROP", "Property Drawing Tool. Use to alter the properties of elements in the field.", 0xfe, 0xa9, 0x00, "DEFAULT_UI_PROPERTY", NULL),
gameModel(model)
{ {
} }
StructProperty::PropertyType propType; StructProperty::PropertyType propType;
@ -100,6 +102,25 @@ public:
void DrawFill(Simulation * sim, Brush * brush, ui::Point position) override; void DrawFill(Simulation * sim, Brush * brush, ui::Point position) override;
}; };
class GOLTool: public Tool
{
public:
String selectGOLType;
GameModel * gameModel;
GOLTool(GameModel * gameModel):
Tool(0, "CUST", "Add a new custom GOL type. (Use ctrl+shift+rightclick to remove them)", 0xfe, 0xa9, 0x00, "DEFAULT_UI_ADDLIFE", NULL),
gameModel(gameModel)
{
}
void OpenWindow(Simulation *sim, int toolSelection, int rule = 0, int colour1 = 0, int colour2 = 0);
virtual ~GOLTool() {}
void Click(Simulation * sim, Brush * brush, ui::Point position) override { }
void Draw(Simulation *sim, Brush *brush, ui::Point position) override { };
void DrawLine(Simulation * sim, Brush * brush, ui::Point position1, ui::Point position2, bool dragging = false) override { };
void DrawRect(Simulation * sim, Brush * brush, ui::Point position1, ui::Point position2) override { };
void DrawFill(Simulation * sim, Brush * brush, ui::Point position) override { };
};
class ElementTool: public Tool class ElementTool: public Tool
{ {

View File

@ -0,0 +1,14 @@
#pragma once
#include "graphics/Pixel.h"
#include "common/String.h"
struct BuiltinGOL
{
String name;
int oldtype;
int ruleset;
pixel colour, colour2;
int goltype;
String description;
};

View File

@ -250,7 +250,7 @@ bool Element::ctypeDrawVInTmp(CTYPEDRAW_FUNC_ARGS)
{ {
return false; return false;
} }
if (t == PT_LIFE && v >= 0 && v < NGOL) if (t == PT_LIFE)
{ {
sim->parts[i].tmp = v; sim->parts[i].tmp = v;
} }
@ -263,7 +263,7 @@ bool Element::ctypeDrawVInCtype(CTYPEDRAW_FUNC_ARGS)
{ {
return false; return false;
} }
if (t == PT_LIFE && v >= 0 && v < NGOL) if (t == PT_LIFE)
{ {
sim->parts[i].ctype |= PMAPID(v); sim->parts[i].ctype |= PMAPID(v);
} }

View File

@ -1,12 +0,0 @@
#ifndef The_Powder_Toy_GOLMenu_h
#define The_Powder_Toy_GOLMenu_h
struct gol_menu
{
String name;
pixel colour;
int goltype;
String description;
};
#endif

View File

@ -0,0 +1,91 @@
#include "GOLString.h"
int ParseGOLString(const String &value)
{
// * Most likely a GOL string.
auto it = value.begin() + 1;
auto begin = 0U;
auto stay = 0U;
auto states = 2U;
// Scan 'B' section, must be between 1 and 8
for (; it != value.end() && it[0] >= '1' && it[0] <= '8'; ++it)
{
begin |= 1U << (it[0] - '0');
}
// Must have a /S immediately afterwards
if (it < value.end() - 1 && it[0] == '/' && it[1] == 'S')
{
it += 2;
}
else
{
return -1;
}
// Scan 'S' section, must be between 0 and 8
for (; it != value.end() && it[0] >= '0' && it[0] <= '8'; ++it)
{
stay |= 1U << (it[0] - '0');
}
// Optionally can have a 3rd section, with the number of frames to remain after dying
if (it != value.end())
{
if (it[0] == '/')
{
it += 1;
}
else
{
return -1;
}
states = String(it, value.end()).ToNumber<unsigned int>(true);
if (states < 2 || states > 17)
{
return -1;
}
}
return stay | (begin << 8) | ((states - 2) << 17);
}
bool ValidateGOLName(const String &value)
{
bool nameOk = true;
for (auto ch : value)
{
if (!((ch >= '0' && ch < '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch == '-')))
{
nameOk = false;
}
}
return nameOk;
}
String SerialiseGOLRule(int rule)
{
StringBuilder golName;
golName << "B";
for (int i = 1; i < 9; ++i)
{
if ((rule >> (i + 8)) & 1)
{
golName << char('0' + i);
}
}
golName << "/S";
for (int i = 0; i < 9; ++i)
{
if ((rule >> i) & 1)
{
golName << char('0' + i);
}
}
if ((rule >> 17) & 0xF)
{
golName << "/" << ((rule >> 17) & 0xF) + 2;
}
return golName.Build();
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "common/String.h"
#include <exception>
bool ValidateGOLName(const String &value);
int ParseGOLString(const String &value);
String SerialiseGOLRule(int rule);

View File

@ -21,6 +21,7 @@
#include "ToolClasses.h" #include "ToolClasses.h"
#include "Config.h" #include "Config.h"
#include "SimulationData.h" #include "SimulationData.h"
#include "GOLString.h"
#include "graphics/Renderer.h" #include "graphics/Renderer.h"
@ -2250,7 +2251,7 @@ void Simulation::clear_sim(void)
memset(fvy, 0, sizeof(fvy)); memset(fvy, 0, sizeof(fvy));
memset(photons, 0, sizeof(photons)); memset(photons, 0, sizeof(photons));
memset(wireless, 0, sizeof(wireless)); memset(wireless, 0, sizeof(wireless));
memset(gol2, 0, sizeof(gol2)); memset(gol, 0, sizeof(gol));
memset(portalp, 0, sizeof(portalp)); memset(portalp, 0, sizeof(portalp));
memset(fighters, 0, sizeof(fighters)); memset(fighters, 0, sizeof(fighters));
std::fill(elementCount, elementCount+PT_NUM, 0); std::fill(elementCount, elementCount+PT_NUM, 0);
@ -4659,120 +4660,6 @@ int Simulation::GetParticleType(ByteString type)
return -1; return -1;
} }
void Simulation::SimulateGoL()
{
CGOL = 0;
//TODO: maybe this should only loop through active particles
for (int ny = CELL; ny < YRES-CELL; ny++)
{
//go through every particle and set neighbor map
for (int nx = CELL; nx < XRES-CELL; nx++)
{
int r = pmap[ny][nx];
if (!r)
{
gol[ny][nx] = 0;
continue;
}
if (TYP(r) == PT_LIFE)
{
int golnum = parts[ID(r)].ctype + 1;
if (golnum <= 0 || golnum > NGOL)
{
kill_part(ID(r));
continue;
}
gol[ny][nx] = golnum;
if (parts[ID(r)].tmp == grule[golnum][9]-1)
{
for (int nnx = -1; nnx < 2; nnx++)
{
//it will count itself as its own neighbor, which is needed, but will have 1 extra for delete check
for (int nny = -1; nny < 2; nny++)
{
int adx = ((nx+nnx+XRES-3*CELL)%(XRES-2*CELL))+CELL;
int ady = ((ny+nny+YRES-3*CELL)%(YRES-2*CELL))+CELL;
int rt = pmap[ady][adx];
if (!rt || TYP(rt) == PT_LIFE)
{
//the total neighbor count is in 0
gol2[ady][adx][0] ++;
//insert golnum into neighbor table
for (int i = 1; i < 9; i++)
{
if (!gol2[ady][adx][i])
{
gol2[ady][adx][i] = (golnum<<4)+1;
break;
}
else if((gol2[ady][adx][i]>>4)==golnum)
{
gol2[ady][adx][i]++;
break;
}
}
}
}
}
}
else
{
if (!(bmap[ny/CELL][nx/CELL] == WL_STASIS && emap[ny/CELL][nx/CELL] < 8))
{
parts[ID(r)].tmp --;
}
}
}
}
}
for (int ny = CELL; ny < YRES-CELL; ny++)
{
//go through every particle again, but check neighbor map, then update particles
for (int nx = CELL; nx < XRES-CELL; nx++)
{
int r = pmap[ny][nx];
if (r && TYP(r)!=PT_LIFE)
continue;
int neighbors = gol2[ny][nx][0];
if (neighbors)
{
if (!(bmap[ny/CELL][nx/CELL] == WL_STASIS && emap[ny/CELL][nx/CELL] < 8))
{
int golnum = gol[ny][nx];
if (!r)
{
//Find which type we can try and create
int creategol = 0xFF;
for (int i = 1; i < 9; i++)
{
if (!gol2[ny][nx][i]) break;
golnum = (gol2[ny][nx][i]>>4);
if (grule[golnum][neighbors]>= 2 && (gol2[ny][nx][i]&0xF) >= (neighbors%2)+neighbors/2)
{
if (golnum < creategol)
creategol = golnum;
}
}
if (creategol < 0xFF)
create_part(-1, nx, ny, PT_LIFE, creategol-1);
}
else if (grule[golnum][neighbors-1] == 0 || grule[golnum][neighbors-1] == 2)//subtract 1 because it counted itself
{
if (parts[ID(r)].tmp == grule[golnum][9]-1)
parts[ID(r)].tmp--;
}
}
for (int z = 0; z < 9; z++)
gol2[ny][nx][z] = 0;//this improves performance A LOT compared to the memset, i was getting ~23 more fps with this.
}
//we still need to kill things with 0 neighbors (higher state life)
if (r && parts[ID(r)].tmp <= 0)
kill_part(ID(r));
}
}
//memset(gol2, 0, sizeof(gol2));
}
void Simulation::RecalcFreeParticles(bool do_life_dec) void Simulation::RecalcFreeParticles(bool do_life_dec)
{ {
int x, y, t; int x, y, t;
@ -4870,6 +4757,175 @@ void Simulation::RecalcFreeParticles(bool do_life_dec)
elementRecount = false; elementRecount = false;
} }
void Simulation::SimulateGoL()
{
CGOL = 0;
for (int i = 0; i <= parts_lastActiveIndex; ++i)
{
auto &part = parts[i];
if (part.type != PT_LIFE)
{
continue;
}
auto x = int(part.x + 0.5f);
auto y = int(part.y + 0.5f);
if (x < CELL || y < CELL || x >= XRES - CELL || y >= YRES - CELL)
{
continue;
}
unsigned int golnum = part.ctype;
unsigned int ruleset = golnum;
if (golnum < NGOL)
{
ruleset = builtinGol[golnum].ruleset;
golnum += 1;
}
if (part.tmp2 == int((ruleset >> 17) & 0xF) + 1)
{
for (int yy = -1; yy <= 1; ++yy)
{
for (int xx = -1; xx <= 1; ++xx)
{
if (xx || yy)
{
// * Calculate address of the neighbourList, taking wraparound
// into account. The fact that the GOL space is 2 CELL's worth
// narrower in both dimensions than the simulation area makes
// this a bit awkward.
int ax = ((x + xx + XRES - 3 * CELL) % (XRES - 2 * CELL)) + CELL;
int ay = ((y + yy + YRES - 3 * CELL) % (YRES - 2 * CELL)) + CELL;
unsigned int (&neighbourList)[5] = gol[ay][ax];
// * Bump overall neighbour counter (bits 30..28) for the entire list.
neighbourList[0] += 1U << 28;
for (int l = 0; l < 5; ++l)
{
auto neighbourRuleset = neighbourList[l] & 0x001FFFFFU;
if (neighbourRuleset == golnum)
{
// * Bump population counter (bits 23..21) of the
// same kind of cell.
neighbourList[l] += 1U << 21;
break;
}
if (neighbourRuleset == 0)
{
// * Add the new kind of cell to the population. Both counters
// have a bias of -1, so they're intentionally initialised
// to 0 instead of 1 here. This is all so they can both
// fit in 3 bits.
neighbourList[l] = ((yy & 3) << 26) | ((xx & 3) << 24) | golnum;
break;
}
// * If after 5 iterations the cell still hasn't contributed
// to a list entry, it's surely a 6th kind of cell, meaning
// there could be at most 3 of it in the neighbourhood,
// as there are already 5 other kinds of cells present in
// the list. This in turn means that it couldn't possibly
// win the population ratio-based contest later on.
}
}
}
}
}
else
{
if (!(bmap[y / CELL][x / CELL] == WL_STASIS && emap[y / CELL][x / CELL] < 8))
{
part.tmp2 -= 1;
}
}
}
for (int y = CELL; y < YRES - CELL; ++y)
{
for (int x = CELL; x < XRES - CELL; ++x)
{
int r = pmap[y][x];
if (r && TYP(r) != PT_LIFE)
{
continue;
}
unsigned int (&neighbourList)[5] = gol[y][x];
auto nl0 = neighbourList[0];
if (r || nl0)
{
// * Get overall neighbour count (bits 30..28).
unsigned int neighbours = nl0 ? ((nl0 >> 28) & 7) + 1 : 0;
if (!(bmap[y / CELL][x / CELL] == WL_STASIS && emap[y / CELL][x / CELL] < 8))
{
if (r)
{
auto &part = parts[ID(r)];
unsigned int ruleset = part.ctype;
if (ruleset < NGOL)
{
ruleset = builtinGol[ruleset].ruleset;
}
if (!((ruleset >> neighbours) & 1) && part.tmp2 == int(ruleset >> 17) + 1)
{
// * Start death sequence.
part.tmp2 -= 1;
}
}
else
{
unsigned int golnumToCreate = 0xFFFFFFFFU;
unsigned int createFromEntry = 0U;
unsigned int majority = neighbours / 2 + neighbours % 2;
for (int l = 0; l < 5; ++l)
{
auto golnum = neighbourList[l] & 0x001FFFFFU;
if (!golnum)
{
break;
}
auto ruleset = golnum;
if (golnum - 1 < NGOL)
{
ruleset = builtinGol[golnum - 1].ruleset;
golnum -= 1;
}
if ((ruleset >> (neighbours + 8)) & 1 && ((neighbourList[l] >> 21) & 7) + 1 >= majority && golnum < golnumToCreate)
{
golnumToCreate = golnum;
createFromEntry = neighbourList[l];
}
}
if (golnumToCreate != 0xFFFFFFFFU)
{
// * 0x200000: No need to look for colours, they'll be set later anyway.
int i = create_part(-1, x, y, PT_LIFE, golnumToCreate | 0x200000);
int xx = (createFromEntry >> 24) & 3;
int yy = (createFromEntry >> 26) & 3;
if (xx == 3) xx = -1;
if (yy == 3) yy = -1;
int ax = ((x - xx + XRES - 3 * CELL) % (XRES - 2 * CELL)) + CELL;
int ay = ((y - yy + YRES - 3 * CELL) % (YRES - 2 * CELL)) + CELL;
auto &sample = parts[ID(pmap[ay][ax])];
parts[i].dcolour = sample.dcolour;
parts[i].tmp = sample.tmp;
}
}
}
for (int l = 0; l < 5 && neighbourList[l]; ++l)
{
neighbourList[l] = 0;
}
}
}
}
for (int y = CELL; y < YRES - CELL; ++y)
{
for (int x = CELL; x < XRES - CELL; ++x)
{
int r = pmap[y][x];
if (r && TYP(r) == PT_LIFE && parts[ID(r)].tmp2 <= 0)
{
kill_part(ID(r));
}
}
}
}
void Simulation::CheckStacking() void Simulation::CheckStacking()
{ {
bool excessive_stacking_found = false; bool excessive_stacking_found = false;
@ -5205,8 +5261,6 @@ Simulation::Simulation():
platent = LoadLatent(); platent = LoadLatent();
std::copy(GetElements().begin(), GetElements().end(), elements.begin()); std::copy(GetElements().begin(), GetElements().end(), elements.begin());
tools = GetTools(); tools = GetTools();
grule = LoadGOLRules();
gmenu = LoadGOLMenu();
player.comm = 0; player.comm = 0;
player2.comm = 0; player2.comm = 0;
@ -5217,11 +5271,39 @@ Simulation::Simulation():
grav->gravity_mask(); grav->gravity_mask();
} }
const Simulation::CustomGOLData *Simulation::GetCustomGOLByRule(int rule) const
{
// * Binary search. customGol is already sorted, see SetCustomGOL.
auto it = std::lower_bound(customGol.begin(), customGol.end(), rule, [](const CustomGOLData &item, int rule) {
return item.rule < rule;
});
if (it != customGol.end() && !(rule < it->rule))
{
return &*it;
}
return nullptr;
}
void Simulation::SetCustomGOL(std::vector<CustomGOLData> newCustomGol)
{
std::sort(newCustomGol.begin(), newCustomGol.end());
customGol = newCustomGol;
}
String Simulation::ElementResolve(int type, int ctype) String Simulation::ElementResolve(int type, int ctype)
{ {
if (type == PT_LIFE && ctype >= 0 && ctype < NGOL) if (type == PT_LIFE)
{ {
return gmenu[ctype].name; if (ctype >= 0 && ctype < NGOL)
{
return builtinGol[ctype].name;
}
auto *cgol = GetCustomGOLByRule(ctype);
if (cgol)
{
return cgol->nameString;
}
return SerialiseGOLRule(ctype);
} }
else if (type >= 0 && type < PT_NUM) else if (type >= 0 && type < PT_NUM)
{ {

View File

@ -11,7 +11,7 @@
#include "WallType.h" #include "WallType.h"
#include "Sign.h" #include "Sign.h"
#include "ElementDefs.h" #include "ElementDefs.h"
#include "GOLMenu.h" #include "BuiltinGOL.h"
#include "MenuSection.h" #include "MenuSection.h"
#include "CoordStack.h" #include "CoordStack.h"
@ -46,9 +46,6 @@ public:
std::vector<SimTool> tools; std::vector<SimTool> tools;
std::vector<unsigned int> platent; std::vector<unsigned int> platent;
std::vector<wall_type> wtypes; std::vector<wall_type> wtypes;
std::vector<gol_menu> gmenu;
std::vector<int> goltype;
std::vector<std::array<int, 10> > grule;
std::vector<menu_section> msections; std::vector<menu_section> msections;
int currentTick; int currentTick;
@ -83,8 +80,7 @@ public:
//Gol sim //Gol sim
int CGOL; int CGOL;
int GSPEED; int GSPEED;
unsigned char gol[YRES][XRES]; unsigned int gol[YRES][XRES][5];
unsigned short gol2[YRES][XRES][9];
//Air sim //Air sim
float (*vx)[XRES/CELL]; float (*vx)[XRES/CELL];
float (*vy)[XRES/CELL]; float (*vy)[XRES/CELL];
@ -224,6 +220,25 @@ public:
String ElementResolve(int type, int ctype); String ElementResolve(int type, int ctype);
String BasicParticleInfo(Particle const &sample_part); String BasicParticleInfo(Particle const &sample_part);
struct CustomGOLData
{
int rule, colour1, colour2;
String nameString, ruleString;
inline bool operator <(const CustomGOLData &other) const
{
return rule < other.rule;
}
};
private:
std::vector<CustomGOLData> customGol;
public:
const CustomGOLData *GetCustomGOLByRule(int rule) const;
void SetCustomGOL(std::vector<CustomGOLData> newCustomGol);
private: private:
CoordStack& getCoordStackSingleton(); CoordStack& getCoordStackSingleton();
}; };

View File

@ -4,106 +4,48 @@
#include "ElementDefs.h" #include "ElementDefs.h"
#include "ElementClasses.h" #include "ElementClasses.h"
#include "GOLMenu.h" #include "BuiltinGOL.h"
#include "WallType.h" #include "WallType.h"
#include "MenuSection.h" #include "MenuSection.h"
#include "graphics/Renderer.h" #include "graphics/Renderer.h"
std::vector<gol_menu> LoadGOLMenu() const BuiltinGOL builtinGol[NGOL] = {
{ // * Ruleset:
return // * bits x = 8..0: stay if x neighbours present
std::vector<gol_menu>{ // * bits x = 16..9: begin if x-8 neighbours present
{"GOL", PIXPACK(0x0CAC00), 0, String("Game Of Life: Begin 3/Stay 23")}, // * bits 20..17: 4-bit unsigned int encoding the number of states minus 2; 2 states is
{"HLIF", PIXPACK(0xFF0000), 1, String("High Life: B36/S23")}, // encoded as 0, 3 states as 1, etc.
{"ASIM", PIXPACK(0x0000FF), 2, String("Assimilation: B345/S4567")}, // * states are kind of long until a cell dies; normal ones use two states (living and dead),
{"2x2", PIXPACK(0xFFFF00), 3, String("2x2: B36/S125")}, // for others the intermediate states live but do nothing
{"DANI", PIXPACK(0x00FFFF), 4, String("Day and Night: B3678/S34678")}, // * the ruleset constants below look 20-bit, but rulesets actually consist of 21
{"AMOE", PIXPACK(0xFF00FF), 5, String("Amoeba: B357/S1358")}, // bits of data; bit 20 just happens to not be set for any of the built-in types,
{"MOVE", PIXPACK(0xFFFFFF), 6, String("'Move' particles. Does not move things.. it is a life type: B368/S245")}, // as none of them have 10 or more states
{"PGOL", PIXPACK(0xE05010), 7, String("Pseudo Life: B357/S238")}, { "GOL", GT_GOL , 0x0080C, PIXPACK(0x0CAC00), PIXPACK(0x0CAC00), NGT_GOL, String("Game Of Life: Begin 3/Stay 23") },
{"DMOE", PIXPACK(0x500000), 8, String("Diamoeba: B35678/S5678")}, { "HLIF", GT_HLIF, 0x0480C, PIXPACK(0xFF0000), PIXPACK(0xFF0000), NGT_HLIF, String("High Life: B36/S23") },
{"34", PIXPACK(0x500050), 9, String("34: B34/S34")}, { "ASIM", GT_ASIM, 0x038F0, PIXPACK(0x0000FF), PIXPACK(0x0000FF), NGT_ASIM, String("Assimilation: B345/S4567") },
{"LLIF", PIXPACK(0x505050), 10, String("Long Life: B345/S5")}, { "2X2", GT_2x2 , 0x04826, PIXPACK(0xFFFF00), PIXPACK(0xFFFF00), NGT_2x2, String("2X2: B36/S125") },
{"STAN", PIXPACK(0x5000FF), 11, String("Stains: B3678/S235678")}, { "DANI", GT_DANI, 0x1C9D8, PIXPACK(0x00FFFF), PIXPACK(0x00FFFF), NGT_DANI, String("Day and Night: B3678/S34678") },
{"SEED", PIXPACK(0xFBEC7D), 12, String("Seeds: B2/S")}, { "AMOE", GT_AMOE, 0x0A92A, PIXPACK(0xFF00FF), PIXPACK(0xFF00FF), NGT_AMOE, String("Amoeba: B357/S1358") },
{"MAZE", PIXPACK(0xA8E4A0), 13, String("Maze: B3/S12345")}, { "MOVE", GT_MOVE, 0x14834, PIXPACK(0xFFFFFF), PIXPACK(0xFFFFFF), NGT_MOVE, String("'Move' particles. Does not move things.. it is a life type: B368/S245") },
{"COAG", PIXPACK(0x9ACD32), 14, String("Coagulations: B378/S235678")}, { "PGOL", GT_PGOL, 0x0A90C, PIXPACK(0xE05010), PIXPACK(0xE05010), NGT_PGOL, String("Pseudo Life: B357/S238") },
{"WALL", PIXPACK(0x0047AB), 15, String("Walled cities: B45678/S2345")}, { "DMOE", GT_DMOE, 0x1E9E0, PIXPACK(0x500000), PIXPACK(0x500000), NGT_DMOE, String("Diamoeba: B35678/S5678") },
{"GNAR", PIXPACK(0xE5B73B), 16, String("Gnarl: B1/S1")}, { "3-4", GT_34 , 0x01818, PIXPACK(0x500050), PIXPACK(0x500050), NGT_34, String("3-4: B34/S34") },
{"REPL", PIXPACK(0x259588), 17, String("Replicator: B1357/S1357")}, { "LLIF", GT_LLIF, 0x03820, PIXPACK(0x505050), PIXPACK(0x505050), NGT_LLIF, String("Long Life: B345/S5") },
{"MYST", PIXPACK(0x0C3C00), 18, String("Mystery: B3458/S05678")}, { "STAN", GT_STAN, 0x1C9EC, PIXPACK(0x5000FF), PIXPACK(0x5000FF), NGT_STAN, String("Stains: B3678/S235678") },
{"LOTE", PIXPACK(0xFF0000), 19, String("Living on the Edge: B37/S3458/4")}, { "SEED", GT_SEED, 0x00400, PIXPACK(0xFBEC7D), PIXPACK(0xFBEC7D), NGT_SEED, String("Seeds: B2/S") },
{"FRG2", PIXPACK(0x00FF00), 20, String("Like Frogs rule: B3/S124/3")}, { "MAZE", GT_MAZE, 0x0083E, PIXPACK(0xA8E4A0), PIXPACK(0xA8E4A0), NGT_MAZE, String("Maze: B3/S12345") },
{"STAR", PIXPACK(0x0000FF), 21, String("Like Star Wars rule: B278/S3456/6")}, { "COAG", GT_COAG, 0x189EC, PIXPACK(0x9ACD32), PIXPACK(0x9ACD32), NGT_COAG, String("Coagulations: B378/S235678") },
{"FROG", PIXPACK(0x00AA00), 22, String("Frogs: B34/S12/3")}, { "WALL", GT_WALL, 0x1F03C, PIXPACK(0x0047AB), PIXPACK(0x0047AB), NGT_WALL, String("Walled cities: B45678/S2345") },
{"BRAN", PIXPACK(0xCCCC00), 23, String("Brian 6: B246/S6/3")} { "GNAR", GT_GNAR, 0x00202, PIXPACK(0xE5B73B), PIXPACK(0xE5B73B), NGT_GNAR, String("Gnarl: B1/S1") },
}; { "REPL", GT_REPL, 0x0AAAA, PIXPACK(0x259588), PIXPACK(0x259588), NGT_REPL, String("Replicator: B1357/S1357") },
} { "MYST", GT_MYST, 0x139E1, PIXPACK(0x0C3C00), PIXPACK(0x0C3C00), NGT_MYST, String("Mystery: B3458/S05678") },
{ "LOTE", GT_LOTE, 0x48938, PIXPACK(0xFF0000), PIXPACK(0xFFFF00), NGT_LOTE, String("Living on the Edge: B37/S3458/4") },
std::vector<std::array<int, 10> > LoadGOLRules() { "FRG2", GT_FRG2, 0x20816, PIXPACK(0x006432), PIXPACK(0x00FF5A), NGT_FRG2, String("Like Frogs rule: B3/S124/3") },
{ { "STAR", GT_STAR, 0x98478, PIXPACK(0x000040), PIXPACK(0x0000E6), NGT_STAR, String("Like Star Wars rule: B278/S3456/6") },
return { "FROG", GT_FROG, 0x21806, PIXPACK(0x006400), PIXPACK(0x00FF00), NGT_FROG, String("Frogs: B34/S12/3") },
std::vector<std::array<int, 10> >{ { "BRAN", GT_BRAN, 0x25440, PIXPACK(0xFFFF00), PIXPACK(0x969600), NGT_BRAN, String("Brian 6: B246/S6/3" )}
// 0,1,2,3,4,5,6,7,8,STATES live=1 spawn=2 spawn&live=3 States are kind of how long until it dies, normal ones use two states(living,dead) for others the intermediate states live but do nothing };
{0,0,0,0,0,0,0,0,0,2},//blank
{0,0,1,3,0,0,0,0,0,2},//GOL
{0,0,1,3,0,0,2,0,0,2},//HLIF
{0,0,0,2,3,3,1,1,0,2},//ASIM
{0,1,1,2,0,1,2,0,0,2},//2x2
{0,0,0,3,1,0,3,3,3,2},//DANI
{0,1,0,3,0,3,0,2,1,2},//AMOE
{0,0,1,2,1,1,2,0,2,2},//MOVE
{0,0,1,3,0,2,0,2,1,2},//PGOL
{0,0,0,2,0,3,3,3,3,2},//DMOE
{0,0,0,3,3,0,0,0,0,2},//34
{0,0,0,2,2,3,0,0,0,2},//LLIF
{0,0,1,3,0,1,3,3,3,2},//STAN
{0,0,2,0,0,0,0,0,0,2},//SEED
{0,1,1,3,1,1,0,0,0,2},//MAZE
{0,0,1,3,0,1,1,3,3,2},//COAG
{0,0,1,1,3,3,2,2,2,2},//WALL
{0,3,0,0,0,0,0,0,0,2},//GNAR
{0,3,0,3,0,3,0,3,0,2},//REPL
{1,0,0,2,2,3,1,1,3,2},//MYST
{0,0,0,3,1,1,0,2,1,4},//LOTE
{0,1,1,2,1,0,0,0,0,3},//FRG2
{0,0,2,1,1,1,1,2,2,6},//STAR
{0,1,1,2,2,0,0,0,0,3},//FROG
{0,0,2,0,2,0,3,0,0,3},//BRAN
};
}
std::vector<int> LoadGOLTypes()
{
return
std::vector<int>{
GT_GOL,
GT_HLIF,
GT_ASIM,
GT_2x2,
GT_DANI,
GT_AMOE,
GT_MOVE,
GT_PGOL,
GT_DMOE,
GT_34,
GT_LLIF,
GT_STAN,
GT_SEED,
GT_MAZE,
GT_COAG,
GT_WALL,
GT_GNAR,
GT_REPL,
GT_MYST,
GT_LOTE,
GT_FRG2,
GT_STAR,
GT_FROG,
GT_BRAN,
};
}
std::vector<wall_type> LoadWalls() std::vector<wall_type> LoadWalls()
{ {

View File

@ -137,17 +137,13 @@ struct part_type;
struct part_transition; struct part_transition;
struct wall_type; struct wall_type;
struct gol_menu; struct BuiltinGOL;
struct menu_section; struct menu_section;
class SimTool; class SimTool;
class Element; class Element;
std::vector<gol_menu> LoadGOLMenu(); extern const BuiltinGOL builtinGol[];
std::vector<int> LoadGOLTypes();
std::vector<std::array<int, 10> > LoadGOLRules();
std::vector<wall_type> LoadWalls(); std::vector<wall_type> LoadWalls();

View File

@ -57,7 +57,7 @@ static int update(UPDATE_FUNC_ARGS)
parts[i].vx += ADVECTION*sim->vx[y/CELL][x/CELL]; parts[i].vx += ADVECTION*sim->vx[y/CELL][x/CELL];
parts[i].vy += ADVECTION*sim->vy[y/CELL][x/CELL]; parts[i].vy += ADVECTION*sim->vy[y/CELL][x/CELL];
} }
if (parts[i].ctype<=0 || parts[i].ctype>=PT_NUM || !sim->elements[parts[i].ctype].Enabled || (parts[i].ctype==PT_LIFE && (parts[i].tmp<0 || parts[i].tmp>=NGOL))) if (parts[i].ctype<=0 || parts[i].ctype>=PT_NUM || !sim->elements[parts[i].ctype].Enabled)
{ {
int r, rx, ry, rt; int r, rx, ry, rt;
for (rx=-1; rx<2; rx++) for (rx=-1; rx<2; rx++)

View File

@ -48,7 +48,7 @@ void Element::Element_CLNE()
static int update(UPDATE_FUNC_ARGS) static int update(UPDATE_FUNC_ARGS)
{ {
if (parts[i].ctype<=0 || parts[i].ctype>=PT_NUM || !sim->elements[parts[i].ctype].Enabled || (parts[i].ctype==PT_LIFE && (parts[i].tmp<0 || parts[i].tmp>=NGOL))) if (parts[i].ctype<=0 || parts[i].ctype>=PT_NUM || !sim->elements[parts[i].ctype].Enabled)
{ {
int r, rx, ry, rt; int r, rx, ry, rt;
for (rx=-1; rx<2; rx++) for (rx=-1; rx<2; rx++)

View File

@ -49,8 +49,8 @@ void Element::Element_CONV()
static int update(UPDATE_FUNC_ARGS) static int update(UPDATE_FUNC_ARGS)
{ {
int r, rx, ry; int r, rx, ry;
int ctype = TYP(parts[i].ctype), ctypeExtra = ID(parts[i].ctype); int ctype = TYP(parts[i].ctype);
if (ctype<=0 || ctype>=PT_NUM || !sim->elements[ctype].Enabled || ctype==PT_CONV || (ctype==PT_LIFE && (ctypeExtra<0 || ctypeExtra>=NGOL))) if (ctype<=0 || ctype>=PT_NUM || !sim->elements[ctype].Enabled || ctype==PT_CONV)
{ {
for (rx=-1; rx<2; rx++) for (rx=-1; rx<2; rx++)
for (ry=-1; ry<2; ry++) for (ry=-1; ry<2; ry++)

View File

@ -1,8 +1,5 @@
#include "simulation/ElementCommon.h" #include "simulation/ElementCommon.h"
bool Element_GOL_colourInit = false;
pixel Element_GOL_colour[NGOL];
static int graphics(GRAPHICS_FUNC_ARGS); static int graphics(GRAPHICS_FUNC_ARGS);
static void create(ELEMENT_CREATE_FUNC_ARGS); static void create(ELEMENT_CREATE_FUNC_ARGS);
@ -49,82 +46,59 @@ void Element::Element_LIFE()
Graphics = &graphics; Graphics = &graphics;
Create = &create; Create = &create;
if (!Element_GOL_colourInit)
{
Element_GOL_colourInit = true;
std::vector<gol_menu> golMenuT = LoadGOLMenu();
for(int i = 0; i < NGOL; i++)
{
Element_GOL_colour[i] = golMenuT[i].colour;
}
}
} }
static int graphics(GRAPHICS_FUNC_ARGS) static int graphics(GRAPHICS_FUNC_ARGS)
{ {
pixel pc; auto colour1 = cpart->dcolour;
if (cpart->ctype==NGT_LOTE)//colors for life states auto colour2 = cpart->tmp;
if (!colour1)
{ {
if (cpart->tmp==2) colour1 = PIXPACK(0xFFFFFF);
pc = PIXRGB(255, 128, 0); }
else if (cpart->tmp==1) auto ruleset = cpart->ctype;
pc = PIXRGB(255, 255, 0); if (ruleset >= 0 && ruleset < NGOL)
{
ruleset = builtinGol[ruleset].ruleset;
}
if (!ren->blackDecorations)
{
auto states = ((ruleset >> 17) & 0xF) + 2;
if (states == 2)
{
*colr = PIXR(colour1);
*colg = PIXG(colour1);
*colb = PIXB(colour1);
}
else else
pc = PIXRGB(255, 0, 0); {
auto mul = (cpart->tmp2 - 1) / float(states - 2);
*colr = PIXR(colour1) * mul + PIXR(colour2) * (1.f - mul);
*colg = PIXG(colour1) * mul + PIXG(colour2) * (1.f - mul);
*colb = PIXB(colour1) * mul + PIXB(colour2) * (1.f - mul);
}
} }
else if (cpart->ctype==NGT_FRG2)//colors for life states *pixel_mode |= NO_DECO;
{
if (cpart->tmp==2)
pc = PIXRGB(0, 100, 50);
else
pc = PIXRGB(0, 255, 90);
}
else if (cpart->ctype==NGT_STAR)//colors for life states
{
if (cpart->tmp==4)
pc = PIXRGB(0, 0, 128);
else if (cpart->tmp==3)
pc = PIXRGB(0, 0, 150);
else if (cpart->tmp==2)
pc = PIXRGB(0, 0, 190);
else if (cpart->tmp==1)
pc = PIXRGB(0, 0, 230);
else
pc = PIXRGB(0, 0, 70);
}
else if (cpart->ctype==NGT_FROG)//colors for life states
{
if (cpart->tmp==2)
pc = PIXRGB(0, 100, 0);
else
pc = PIXRGB(0, 255, 0);
}
else if (cpart->ctype==NGT_BRAN)//colors for life states
{
if (cpart->tmp==1)
pc = PIXRGB(150, 150, 0);
else
pc = PIXRGB(255, 255, 0);
}
else if (cpart->ctype >= 0 && cpart->ctype < NGOL)
{
pc = Element_GOL_colour[cpart->ctype];
}
else
pc = ren->sim->elements[cpart->type].Colour;
*colr = PIXR(pc);
*colg = PIXG(pc);
*colb = PIXB(pc);
return 0; return 0;
} }
static void create(ELEMENT_CREATE_FUNC_ARGS) static void create(ELEMENT_CREATE_FUNC_ARGS)
{ {
if (v >= 0 && v < NGOL) sim->parts[i].ctype = v & 0x1FFFFF;
if (v < NGOL)
{ {
sim->parts[i].tmp = sim->grule[v+1][9] - 1; sim->parts[i].dcolour = builtinGol[v].colour;
sim->parts[i].ctype = v; sim->parts[i].tmp = builtinGol[v].colour2;
v = builtinGol[v].ruleset;
} }
else if (!(v & 0x200000)) // * 0x200000: No need to look for colours, they'll be set later anyway.
{
auto *cgol = sim->GetCustomGOLByRule(v & 0x1FFFFF);
if (cgol)
{
sim->parts[i].dcolour = cgol->colour1;
sim->parts[i].tmp = cgol->colour2;
}
}
sim->parts[i].tmp2 = ((v >> 17) & 0xF) + 1;
} }

View File

@ -66,7 +66,7 @@ static int update(UPDATE_FUNC_ARGS)
return 1; return 1;
} }
} }
if (parts[i].ctype<=0 || parts[i].ctype>=PT_NUM || !sim->elements[parts[i].ctype].Enabled || (parts[i].ctype==PT_LIFE && (parts[i].tmp<0 || parts[i].tmp>=NGOL))) if (parts[i].ctype<=0 || parts[i].ctype>=PT_NUM || !sim->elements[parts[i].ctype].Enabled)
for (rx=-1; rx<2; rx++) for (rx=-1; rx<2; rx++)
for (ry=-1; ry<2; ry++) for (ry=-1; ry<2; ry++)
if (BOUNDS_CHECK) if (BOUNDS_CHECK)

View File

@ -79,7 +79,7 @@ static int update(UPDATE_FUNC_ARGS)
parts[i].life = 10; parts[i].life = 10;
} }
} }
if (parts[i].ctype<=0 || parts[i].ctype>=PT_NUM || !sim->elements[parts[i].ctype].Enabled || (parts[i].ctype==PT_LIFE && (parts[i].tmp<0 || parts[i].tmp>=NGOL))) if (parts[i].ctype<=0 || parts[i].ctype>=PT_NUM || !sim->elements[parts[i].ctype].Enabled)
for (rx=-1; rx<2; rx++) for (rx=-1; rx<2; rx++)
for (ry=-1; ry<2; ry++) for (ry=-1; ry<2; ry++)
if (BOUNDS_CHECK) if (BOUNDS_CHECK)