Skip to content
195 changes: 144 additions & 51 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
<label for="vent-relative-rate">ventRelativeRate: </label>
<input type="number" id="vent-relative-rate-input" name="vent-relative-rate" value="100">
</p>
<p>
<label for="stack-height">Reactor stack height: </label>
<input type="number" id="stack-height-input" name="stack-height" min="1" max="64" value="5">
</p>
<button onclick="updateConfig()">Update config</button>
</div>
<div>
Expand All @@ -30,11 +34,9 @@
<label for="height">Reactor inner height: </label>
<input type="number" id="height-input" name="height" min="3" max="64" value="5">
</p>
<p>
<label for="stack-height">Reactor stack height: </label>
<input type="number" id="stack-height-input" name="stack-height" min="1" max="64" value="5">
</p>
<button onclick="replaceLayout()">Replace layout</button>
<button onclick="importLayout()">Import layout</button>
<button onclick="exportLayout()">Export layout</button>
</div>
</div>
<p>
Expand All @@ -61,6 +63,7 @@
let ABSORBER_RATE = 16;
let VENT_BASE_RATE = 4;
let VENT_RELATIVE_RATE = 100;
let STACK_HEIGHT = 1;

// ATM10 values
// const RF_PER_PULSE = 1100;
Expand Down Expand Up @@ -134,11 +137,10 @@
}

class ReactorLayout {
constructor(width, height, stackHeight) {
constructor(width, height) {
this.layout = [];
this.width = width;
this.height = height;
this.stackHeight = stackHeight;

for (let j = 0; j < height; j++) {
const row = [];
Expand All @@ -150,6 +152,40 @@
this.layout.push(row);
}
}

fromBase64(base64) {
let tmp = JSON.parse(atob(base64));
if (typeof tmp.width !== "number" || typeof tmp.height !== "number")
throw "Invalid reactor layout: " + ((typeof tmp.width !== "number") ? "width" : "height") + " must be a number.";
// only continue if every value in every row is a number
if (!tmp.layout.every(row => {
row.every(value => typeof value === "number");
})) throw "Invalid reactor layout: layout Array malformed.";
for (var i = 0; i < tmp.layout.length; i++) {
for (var j = 0; j < tmp.layout[i].length; j++) {
// since we serialize by way of an enum, we need to re-instantiate the actual components so they can simulate when deserializing
tmp.layout[i][j] = ReactorComponentFactory(tmp.layout[i][j]);
}
}
// once we've properly deserialized the layout it can just be dropped into place
this.width = tmp.width;
this.height = tmp.height;
this.layout = tmp.layout;
document.querySelector("#reactor-layout-wrapper").replaceChildren(reactorLayout.getHtmlLayout());
}

getBase64() {
let tmp = JSON.parse(JSON.stringify(this));
// Since the above process can't prserve Class prototypes, we need to transform the reactor components into an intermediary form
for (var i = 0; i < tmp.layout.length; i++) {
for (var j = 0; j < tmp.layout[i].length; j++) {
// this can be achieved simply by keeping an "enum" and replacing the Objects with numbers based on the image being used
var comp = tmp.layout[i][j];
tmp.layout[i][j] = ReactorComponentImageEnum[comp.imageUrl];
}
}
return btoa(JSON.stringify(tmp));
}

getComponent(position) {
return this.layout[position.y][position.x];
Expand All @@ -158,19 +194,17 @@
setComponent(position, reactorComponent) {
this.layout[position.y][position.x] = reactorComponent;
}

getComponentNeighbours(position) {
const neighbours = [];

for (const [xOffset, yOffset] of [[0, -1], [1, 0], [0, 1], [-1, 0]]) {
for (const [xOffset, yOffset] of[[0, -1], [1, 0], [0, 1], [-1, 0]]) {
const neighbourPosition = new ReactorComponentPosition(
position.x + xOffset,
position.y + yOffset
);
position.x + xOffset,
position.y + yOffset);

if (neighbourPosition.x < 0 || neighbourPosition.x >= this.width ||
neighbourPosition.y < 0 || neighbourPosition.y >= this.height
) {
neighbourPosition.y < 0 || neighbourPosition.y >= this.height) {
continue;
}

Expand All @@ -190,6 +224,7 @@
for (let i = 0; i < this.width; i++) {
const layoutTableCell = document.createElement("td");
layoutTableCell.classList.add("reactor-component");
layoutTableCell.style.backgroundImage = `url('${reactorLayout.layout[j][i].imageUrl}')`;

layoutTableCell.addEventListener("click", () => {
const newReactorComponent = selectedReactorComponentFactory();
Expand All @@ -212,7 +247,7 @@
this.imageUrl = imageUrl;
}

tick(reactorLayout, componentPosition, simulator) { }
tick(reactorLayout, componentPosition, simulator) {}
}

class ReactorEmptyTile extends ReactorComponent {
Expand All @@ -224,31 +259,31 @@
class ReactorFuelRod extends ReactorComponent {
constructor(rodCount) {
switch (rodCount) {
case 1:
super("single rod.png");
break;
case 2:
super("duo rod.png");
break;
case 4:
super("quad rod.png");
break;
default:
super(null);
case 1:
super("single rod.png");
break;
case 2:
super("duo rod.png");
break;
case 4:
super("quad rod.png");
break;
default:
super(null);
}
this.rodCount = rodCount;
}

getInternalPulses() {
switch (this.rodCount) {
case 1:
return 1;
case 2:
return 4;
case 4:
return 12;
default:
return 0;
case 1:
return 1;
case 2:
return 4;
case 4:
return 12;
default:
return 0;
}
}

Expand All @@ -265,10 +300,10 @@
}
}

simulator.rfGenerated += RF_PER_PULSE * receivedPulses * reactorLayout.stackHeight;
simulator.rfGenerated += RF_PER_PULSE * receivedPulses * STACK_HEIGHT;
simulator.addComponentHeat(componentPosition, receivedPulses / 2 * receivedPulses + 4);

simulator.fuelConsumed += this.rodCount * reactorLayout.stackHeight;
simulator.fuelConsumed += this.rodCount * STACK_HEIGHT;
}
}

Expand Down Expand Up @@ -319,7 +354,7 @@
}

if (sumRemovedHeat > 0) {
simulator.coolantConsumed += reactorLayout.stackHeight;
simulator.coolantConsumed += STACK_HEIGHT;
}
}
}
Expand All @@ -345,15 +380,25 @@
if (hottestNeighbourHeat != 0) {
simulator.addComponentHeat(
hottestNeighbourPosition,
-Math.min(hottestNeighbourHeat / VENT_RELATIVE_RATE + VENT_BASE_RATE, hottestNeighbourHeat)
);
-Math.min(hottestNeighbourHeat / VENT_RELATIVE_RATE + VENT_BASE_RATE, hottestNeighbourHeat));
}
}
}

let selectedReactorComponentFactory = () => new ReactorEmptyTile();
let reactorLayout = null;
let heatHistoryChart = null;

const ReactorComponentImageEnum = {
"empty.png": 0,
"single rod.png": 1,
"duo rod.png": 2,
"quad rod.png": 3,
"reflector.png": 4,
"absorber.png": 5,
"vent.png": 6,
"heat pipe.png": 7
}

function updateSelectedReactorComponentFactory() {
const componentSelectEle = document.querySelector("#component-select");
Expand All @@ -370,6 +415,29 @@
() => new ReactorHeatPipe(),
][selectedValue];
}

function ReactorComponentFactory(index) {
switch (index) {
case 0:
return new ReactorEmptyTile();
case 1:
return new ReactorFuelRod(1);
case 2:
return new ReactorFuelRod(2);
case 3:
return new ReactorFuelRod(4);
case 4:
return new ReactorNeutronReflector();
case 5:
return new ReactorHeatAbsorber();
case 6:
return new ReactorHeatVent();
case 7:
return new ReactorHeatPipe();
default:
return new ReactorEmptyTile();
}
}

function runSimulationAndPostResults() {
const reactorSimulator = new ReactorSimulator(reactorLayout);
Expand All @@ -379,11 +447,11 @@
const simulationSeconds = 1000 / 20;

const simulationResultsText = `${reactorSimulator.rfGenerated / 1000} RF/t\n` +
`${(reactorSimulator.coolantConsumed / 1000 / simulationSeconds).toFixed(2)} ice blocks per second\n\n` +
`${(reactorSimulator.fuelConsumed / 4000 / simulationSeconds).toFixed(2)} uranium pellets per second, OR\n` +
`${(reactorSimulator.fuelConsumed / 40000 / simulationSeconds).toFixed(2)} plutonium pellets per second, OR\n` +
`${(reactorSimulator.fuelConsumed / 400 / simulationSeconds).toFixed(2)} small uranium pellets per second, OR\n` +
`${(reactorSimulator.fuelConsumed / 4000 / simulationSeconds).toFixed(2)} small plutonium pellets per second\n`;
`${(reactorSimulator.coolantConsumed / 1000 / simulationSeconds).toFixed(2)} ice blocks per second\n\n` +
`${(reactorSimulator.fuelConsumed / 4000 / simulationSeconds).toFixed(2)} uranium pellets per second, OR\n` +
`${(reactorSimulator.fuelConsumed / 40000 / simulationSeconds).toFixed(2)} plutonium pellets per second, OR\n` +
`${(reactorSimulator.fuelConsumed / 400 / simulationSeconds).toFixed(2)} small uranium pellets per second, OR\n` +
`${(reactorSimulator.fuelConsumed / 4000 / simulationSeconds).toFixed(2)} small plutonium pellets per second\n`;

simulationResults.textContent = simulationResultsText;

Expand All @@ -409,12 +477,14 @@
type: "line",
data: {
labels: Array.from(reactorSimulator.maxHeatHistory.keys()),
datasets: [
{
datasets: [{
label: "Heat",
data: reactorSimulator.maxHeatHistory,
borderColor: function (context) {
const {ctx, chartArea} = context.chart;
const {
ctx,
chartArea
} = context.chart;

if (!chartArea) {
return;
Expand All @@ -441,28 +511,51 @@
function replaceLayout() {
const widthInput = document.querySelector("#width-input");
const heightInput = document.querySelector("#height-input");
const stackHeightInput = document.querySelector("#stack-height-input");
// const stackHeightInput = document.querySelector("#stack-height-input");

reactorLayout = new ReactorLayout(
parseInt(widthInput.value),
parseInt(heightInput.value),
parseInt(stackHeightInput.value)
);
parseInt(widthInput.value),
parseInt(heightInput.value));//,
//parseInt(stackHeightInput.value));

document.querySelector("#reactor-layout-wrapper").replaceChildren(reactorLayout.getHtmlLayout());
runSimulationAndPostResults();
}

function importLayout() {
const base64 = prompt("Paste base64 encoded reactor layout here.");
try {
atob(base64);
} catch {
alert("Invalid base64 string.");
}
try {
reactorLayout.fromBase64(base64);
} catch (e) {
alert(e);
}
}

function exportLayout() {
const base64 = reactorLayout.getBase64();
navigator.clipboard.writeText(base64);
alert("Reactor layout copied to clipboard!");
}

function updateConfig() {
const rfPerPulseInput = document.querySelector("#rf-per-pulse-input");
const absorberRateInput = document.querySelector("#absorber-rate-input");
const ventBaseRateInput = document.querySelector("#vent-base-rate-input");
const ventRelativeRateInput = document.querySelector("#vent-relative-rate-input");
const stackHeightInput = document.querySelector("#stack-height-input");

RF_PER_PULSE = parseInt(rfPerPulseInput.value);
ABSORBER_RATE = parseInt(absorberRateInput.value);
VENT_BASE_RATE = parseInt(ventBaseRateInput.value);
VENT_RELATIVE_RATE = parseInt(ventRelativeRateInput.value);
STACK_HEIGHT = parseInt(stackHeightInput.value);

runSimulationAndPostResults();
}

replaceLayout();
Expand Down