Skip to content

Instantly share code, notes, and snippets.

@CyberShadow
Last active May 4, 2019 08:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CyberShadow/d15eefe8b30569988e1752e8717f4824 to your computer and use it in GitHub Desktop.
Save CyberShadow/d15eefe8b30569988e1752e8717f4824 to your computer and use it in GitHub Desktop.
Terraria Compact Crafting Workstation Generator
import std.algorithm.comparison;
import std.algorithm.iteration;
import std.algorithm.searching;
import std.parallelism;
import std.algorithm.comparison : min, max;
import std.range;
import std.random;
import std.stdio;
import std.array;
struct Item
{
char character;
string name;
int width, height;
bool isPlatform; /// The player can stand on this item.
bool isFlat; /// Items with canStandOnFlat can be stacked directly on this item.
bool canStandOnFlat; /// Can be stacked on isFlat items.
bool click; /// Needs to be right-clicked, rather than just be in range
}
immutable Item[] items =
[
Item('w', "Workbench" , 2, 1, true , true , false, false), // E.g. Wood -> 4x Wood Wall
Item('p', "Piggy Bank" , 2, 1, false, false, true , true ), // Right-click range
// Item('s', "Safe" , 2, 2, false, false, true , true ), // Right-click range
Item('a', "Anvil" , 2, 1, true , false, false, false), // E.g. 8x Iron Bar -> Iron Broadsword
Item('l', "Loom" , 3, 2, false, false, false, false), // E.g. 7x Cobweb -> Silk
Item('W', "Sawmill" , 3, 3, false, false, false, false), // E.g. 20x Wood -> Mannequin
Item('T', "Table" , 3, 2, true , true , false, false), // E.g. 10x Copper Bar + Chain -> Copper Watch
Item('c', "Chair" , 1, 2, false, false, false, false), // Ditto
// Item('D', "Dye Vat" , 3, 3, false, false, false, false), // E.g. Black Ink -> Black Dye
Item('b', "Bottle" , 1, 1, false, false, true , false), // E.g. Bottled Water + ... -> potion
Item('k', "Sink" , 2, 2, false, false, true , false), // E.g. Bottle -> Bottled Water
// Item('A', "Alchemy Table" , 3, 3, false, false, false, false), // E.g. Bottled Water + Daybloom + Blinkroot -> Night Owl Potion
Item('F', "Furnace" , 3, 2, false, false, false, false), // E.g. 3x Copper Ore -> Copper Bar
Item('C', "Cooking Pot" , 2, 2, false, false, false, false), // E.g. Bass -> Cooked Fish
// Item('L', "Living Loom" , 3, 3, false, false, false, false), // E.g. 4x Wood -> Living Wood Chair
// Item('I', "Imbuing Station" , 3, 3, false, false, false, false), // E.g. Bottled Water + Stinger -> Flask of Poison
// Item('H', "Heavy Workbench" , 3, 3, false, false, false, false), // E.g. 12x Wood + 4x Any Iron Bar -> Tall Gate
Item('K', "Tinkerer's Workshop" , 3, 2, true , true , false, false), //
// Item('S', "Solidifier" , 3, 3, false, false, false, false), // E.g. Slime -> Slime Block
];
enum nItems = cast(ubyte)items.length;
enum playerWidth = 1;
enum playerHeight = 3;
// Distance between player and object. Objects at or above this distance are too far away.
enum craftRangeSide = 4; // must be 3 tiles away or closer
enum craftRangeUp = 0; // must be overlapping
enum craftRangeDown = 3; // must be 2 tiles below or closer
enum clickRangeSide = 5; // must be 4 tiles away or closer
enum clickRangeUp = 3; // must be 2 tiles above or closer
enum clickRangeDown = 3; // must be 2 tiles below or closer
enum maxCraftObjectWidth = items.filter!(item => !item.click).map!(item => item.width).reduce!max;
enum maxCraftObjectHeight = items.filter!(item => !item.click).map!(item => item.height).reduce!max;
enum maxClickObjectWidth = items.filter!(item => item.click).map!(item => item.width).reduce!max;
enum maxClickObjectHeight = items.filter!(item => item.click).map!(item => item.height).reduce!max;
// Size of placing area
// Coordinates of player's 1x3 rect (top-left corner)
enum playerX =
max(
maxCraftObjectWidth + craftRangeSide - 1,
maxClickObjectWidth + clickRangeSide - 1,
);
enum width = playerX + playerWidth + playerX;
enum playerY =
max(
maxCraftObjectHeight + craftRangeUp - 1,
maxClickObjectHeight + clickRangeUp - 1,
);
enum height =
playerY +
playerHeight +
max(
maxCraftObjectHeight + craftRangeDown - 1,
maxClickObjectHeight + clickRangeDown - 1,
) +
1 ; // Extra row for platforms
// Additional constraints
enum borderX = 0;
enum minX = borderX;
enum maxX = width - borderX + 1;
enum minY = 2;
enum maxY = height - 4;
struct State
{
byte[width][height] grid = [[gridEmpty].replicate(width)].replicate(height);
enum gridEmpty = -1;
enum gridPlatform = -2;
bool placeItem(byte itemIndex)
{
auto p = &items[itemIndex];
int rangeSide = p.click ? clickRangeSide : craftRangeSide;
int x0 = playerX - rangeSide - p.width + 1;
int x1 = playerX + playerWidth + rangeSide - 1 + 1;
x0 = x0.max(minX);
x1 = x1.min(maxX - p.width);
int y0 = playerY - (p.click ? clickRangeUp : craftRangeUp ) - p.height + 1;
int y1 = playerY + playerHeight + (p.click ? clickRangeDown : craftRangeDown) - 1 + 1;
y0 = y0.max(minY);
y1 = y1.min(maxY - p.height);
scope(failure) writeln([x0, y0, x1, y1, width, height, playerX, playerY]);
static struct Coord { int x, y; }
Coord[width*height] coords;
int nCoords;
foreach (y; y0..y1)
coordSearch:
foreach (x; x0..x1)
{
int checkHeight = p.height;
if (!p.canStandOnFlat)
checkHeight++;
foreach (j; 0..checkHeight)
foreach (i; 0..p.width)
if (grid[y+j][x+i] != gridEmpty)
continue coordSearch;
if (p.canStandOnFlat)
{
foreach (i; 0..p.width)
{
auto q = grid[y+p.height][x+i];
if (q >= 0 && !items[q].isFlat)
continue coordSearch;
}
}
coords[nCoords++] = Coord(x, y);
}
if (!nCoords)
return false;
auto coord = coords[uniform(0, nCoords)];
with (coord)
{
foreach (j; 0..p.height)
foreach (i; 0..p.width)
grid[y+j][x+i] = itemIndex;
foreach (i; 0..p.width)
if (grid[y+p.height][x+i] == gridEmpty)
grid[y+p.height][x+i] = gridPlatform;
}
return true;
}
bool ok()
{
// Can the player stand in the designated position?
foreach (int dx; -1 .. 1 + 1)
{
auto x = playerX + dx;
auto y = playerY + playerHeight;
if (grid[y][x] == gridPlatform)
return true;
if (grid[y][x] != grid[y-1][x] && grid[y][x] >= 0 && items[grid[y][x]].isPlatform)
return true;
}
return false;
}
int score()
{
int result = 100;
// Try to put the table and chair next to each other
static immutable itemNames = items.map!(item => item.name).array;
enum gridTable = itemNames.countUntil("Table");
enum gridChair = itemNames.countUntil("Chair");
static if (gridTable >= 0 && gridChair >= 0)
{
tableChair:
foreach (x; 0 .. width-1)
foreach (y; 0 .. height-1)
if (grid[y ][x ] != grid[y ][x+1] &&
grid[y ][x ] == grid[y+1][x ] &&
grid[y ][x+1] == grid[y+1][x+1] &&
grid[y ][x ].among(gridTable, gridChair) &&
grid[y ][x+1].among(gridTable, gridChair))
{
result += 10;
break tableChair;
}
}
foreach (y; playerY .. playerY + playerHeight)
if (grid[y][playerX] == gridEmpty)
result += 2;
else
if (grid[y][playerX] == gridPlatform)
result += 1;
if (grid[playerY + playerHeight - 1][playerX] == gridEmpty)
result += 4;
// foreach (x; playerX - 1 .. playerX + 1 + 1)
// if (grid[playerY + playerHeight][x] == gridPlatform)
// result += 2;
// else
// if (grid[playerY + playerHeight][x] != grid[playerY + playerHeight - 1][x] &&
// grid[playerY + playerHeight][x] >= 0 && items[grid[playerY + playerHeight][x]].isPlatform)
// result += 1;
// Make accidentally climbing higher than needed harder
{
enum y = playerY + 2;
foreach (x; playerX - 1 .. playerX + 1 + 1)
if (grid[y][x] == gridPlatform ||
(
grid[y][x] != grid[y - 1][x] &&
grid[y][x] >= 0 && items[grid[y][x]].isPlatform
)
)
result -= 5;
}
return result;
}
void print()
{
foreach (y; 0..height)
{
foreach (x; 0..width)
{
auto i = grid[y][x];
if (i == gridEmpty)
write('·');
else
if (i == gridPlatform)
write('‾');
else
write(items[i].character);
}
if (y >= playerY && y < playerY + playerHeight)
write('<');
writeln();
}
foreach (x; 0..width)
{
if (x >= playerX && x < playerX + playerWidth)
write('^');
else
write(' ');
}
writeln();
stdout.flush();
}
}
void saveLegend()
{
//auto f = File("legend.txt", "wb");
auto f = stdout;
foreach (item; items)
f.writefln("%s - %s", item.character, item.name);
}
void main()
{
version (Windows) { import win32.winbase; SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS); }
saveLegend();
int bestScore;
searchLoop:
foreach (i; int.max.iota.parallel)
{
State state;
static int bestScoreThread = 0;
ubyte[nItems] itemOrder;
foreach (ubyte n; 0..nItems)
itemOrder[n] = n;
randomShuffle(itemOrder[]);
foreach (n; itemOrder)
if (!state.placeItem(n))
continue searchLoop;
if (!state.ok)
continue;
auto score = state.score;
if (bestScoreThread < score)
{
bestScoreThread = score;
synchronized
{
if (bestScore < score)
{
bestScore = score;
state.print();
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment