1175 строки
45 KiB
JavaScript
1175 строки
45 KiB
JavaScript
Math.randint = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
|
|
|
|
String.prototype.subscript = function () {
|
|
return this
|
|
.replaceAll(/0/g, '\u2080')
|
|
.replaceAll(/1/g, '\u2081')
|
|
.replaceAll(/2/g, '\u2082')
|
|
.replaceAll(/3/g, '\u2083')
|
|
.replaceAll(/4/g, '\u2084')
|
|
.replaceAll(/5/g, '\u2085')
|
|
.replaceAll(/6/g, '\u2086')
|
|
.replaceAll(/7/g, '\u2087')
|
|
.replaceAll(/8/g, '\u2088')
|
|
.replaceAll(/9/g, '\u2089');
|
|
};
|
|
|
|
class Menu {
|
|
#application;
|
|
#root;
|
|
#items;
|
|
|
|
constructor(application) {
|
|
this.#application = application;
|
|
this.#root = document.querySelector('#menu');
|
|
this.#items = this.#root.querySelectorAll('input[data-fragment]');
|
|
this.#items.forEach(item => {
|
|
item.addEventListener('click', () => this.#application.open(item.dataset.fragment));
|
|
});
|
|
};
|
|
|
|
setActive(fragmentId) {
|
|
this.#items.forEach(item => item.classList.remove('active'));
|
|
this.#items.forEach(item => item.dataset.fragment === fragmentId ? item.classList.add('active') : null);
|
|
};
|
|
}
|
|
|
|
class BottomBar {
|
|
#copyrightYears;
|
|
|
|
constructor() {
|
|
this.#copyrightYears = document.querySelector('#copyright-years');
|
|
if (this.copyrightToYear > this.copyrightFromYear) this.copyrightYears = `${this.copyrightFromYear}—${this.copyrightToYear}`;
|
|
};
|
|
|
|
get copyrightFromYear() {
|
|
return Number(this.#copyrightYears.dataset.from);
|
|
};
|
|
|
|
get copyrightToYear() {
|
|
const date = new Date();
|
|
return date.getFullYear();
|
|
};
|
|
|
|
set copyrightYears(value) {
|
|
this.#copyrightYears.innerHTML = value;
|
|
};
|
|
}
|
|
|
|
class Fragment1 {
|
|
#matrixTable;
|
|
#widthInput;
|
|
#heightInput;
|
|
#generateMatrixButton;
|
|
#minRandomValueInput;
|
|
#maxRandomValueInput;
|
|
#fillRandomValuesButton;
|
|
#clearMatrixButton;
|
|
#optimismCriterionInput;
|
|
#fractionDigitsInput;
|
|
#lowestPriceInput;
|
|
#highestPriceInput;
|
|
#saddlePointInput;
|
|
#laplaceCriterionInput;
|
|
#waldCriterionInput;
|
|
#maxOfMaxCriterionInput;
|
|
#minOfMinCriterionInput;
|
|
#savageCriterionInput;
|
|
#hurwitzCriterionInput;
|
|
#regretMatrixTable;
|
|
#extra;
|
|
|
|
constructor() {
|
|
this.#matrixTable = document.querySelector('#fragment-1-matrix');
|
|
this.#widthInput = document.querySelector('#fragment-1-width');
|
|
this.#heightInput = document.querySelector('#fragment-1-height');
|
|
this.#generateMatrixButton = document.querySelector('#fragment-1-generate-matrix');
|
|
this.#minRandomValueInput = document.querySelector('#fragment-1-min-random-value');
|
|
this.#maxRandomValueInput = document.querySelector('#fragment-1-max-random-value');
|
|
this.#fillRandomValuesButton = document.querySelector('#fragment-1-fill-random-values');
|
|
this.#clearMatrixButton = document.querySelector('#fragment-1-clear-matrix');
|
|
this.#optimismCriterionInput = document.querySelector('#fragment-1-optimism-criterion');
|
|
this.#fractionDigitsInput = document.querySelector('#fragment-1-fraction-digits');
|
|
this.#lowestPriceInput = document.querySelector('#fragment-1-lowest-price');
|
|
this.#highestPriceInput = document.querySelector('#fragment-1-highest-price');
|
|
this.#saddlePointInput = document.querySelector('#fragment-1-saddle-point');
|
|
this.#laplaceCriterionInput = document.querySelector('#fragment-1-laplace-criterion');
|
|
this.#waldCriterionInput = document.querySelector('#fragment-1-wald-criterion');
|
|
this.#maxOfMaxCriterionInput = document.querySelector('#fragment-1-max-of-max-criterion');
|
|
this.#minOfMinCriterionInput = document.querySelector('#fragment-1-min-of-min-criterion');
|
|
this.#savageCriterionInput = document.querySelector('#fragment-1-savage-criterion');
|
|
this.#hurwitzCriterionInput = document.querySelector('#fragment-1-hurwitz-criterion');
|
|
this.#regretMatrixTable = document.querySelector('#fragment-1-regret-matrix');
|
|
this.#extra = document.querySelectorAll('.extra');
|
|
this.setMatrixSize();
|
|
this.generateMatrix();
|
|
this.#generateMatrixButton.addEventListener('click', () => {
|
|
this.setMatrixSize();
|
|
this.generateMatrix();
|
|
});
|
|
this.#fillRandomValuesButton.addEventListener('click', () => this.generateMatrix(true));
|
|
this.#clearMatrixButton.addEventListener('click', () => this.generateMatrix());
|
|
[
|
|
this.#lowestPriceInput,
|
|
this.#highestPriceInput,
|
|
this.#saddlePointInput,
|
|
this.#laplaceCriterionInput,
|
|
this.#waldCriterionInput,
|
|
this.#maxOfMaxCriterionInput,
|
|
this.#minOfMinCriterionInput,
|
|
this.#savageCriterionInput,
|
|
this.#hurwitzCriterionInput,
|
|
].forEach(input => input.addEventListener('input', () => input.value = input.dataset.value));
|
|
};
|
|
|
|
getMatrixWidth() {
|
|
return Number(this.#widthInput.dataset.value);
|
|
};
|
|
|
|
getMatrixHeight() {
|
|
return Number(this.#heightInput.dataset.value);
|
|
};
|
|
|
|
getMinRandomValue() {
|
|
return Number(this.#minRandomValueInput.value);
|
|
};
|
|
|
|
getMaxRandomValue() {
|
|
return Number(this.#maxRandomValueInput.value);
|
|
};
|
|
|
|
getOptimismCriterion() {
|
|
return Number(this.#optimismCriterionInput.value);
|
|
}
|
|
|
|
getFractionDigits() {
|
|
return Number(this.#fractionDigitsInput.value);
|
|
};
|
|
|
|
getRows() {
|
|
const rows = [];
|
|
const width = this.getMatrixWidth();
|
|
const height = this.getMatrixHeight();
|
|
for (let i = 0; i < height; i++) {
|
|
const row = {
|
|
index: this.#matrixTable.querySelector(`tr:nth-child(${i + 2}) > th`).textContent,
|
|
cols: [],
|
|
};
|
|
for (let j = 0; j < width; j++) {
|
|
row.cols.push({
|
|
index: this.#matrixTable.querySelector(`tr:first-child > th:nth-child(${j + 2})`).textContent,
|
|
value: Number(this.#matrixTable.querySelector(`tr:nth-child(${i + 2}) > td:nth-child(${j + 2}) > input`).value),
|
|
});
|
|
}
|
|
rows.push(row);
|
|
}
|
|
return rows;
|
|
};
|
|
|
|
transposeMatrix(rows) {
|
|
return rows[0].cols.map((col, i) => {
|
|
return {
|
|
index: col.index,
|
|
rows: rows.map(row => {
|
|
return {
|
|
index: row.index,
|
|
value: row.cols[i].value,
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
};
|
|
|
|
getRegretMatrix(cols, fractionDigits) {
|
|
const maxes = cols.map(col => col.rows.reduce((row1, row2) => {
|
|
return {
|
|
index: col.index,
|
|
value: Math.max(row1.value, row2.value),
|
|
};
|
|
}));
|
|
const transposedRegretMatrix = cols.map((col, i) => {
|
|
return {
|
|
index: col.index,
|
|
rows: col.rows.map(row => {
|
|
return {
|
|
index: row.index,
|
|
value: parseFloat((maxes[i].value - row.value).toFixed(fractionDigits)),
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
return transposedRegretMatrix[0].rows.map((row, i) => {
|
|
return {
|
|
index: row.index,
|
|
cols: transposedRegretMatrix.map(col => {
|
|
return {
|
|
index: col.index,
|
|
value: col.rows[i].value,
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
};
|
|
|
|
getLowestPrice(rows, fractionDigits) {
|
|
return rows.map(row => row.cols.reduce((col1, col2) => {
|
|
return {
|
|
index: row.index,
|
|
value: Math.min(col1.value, col2.value),
|
|
};
|
|
})).reduce((row1, row2) => {
|
|
return {
|
|
index: row1.value > row2.value ? row1.index : row1.value < row2.value ? row2.index : `${row1.index}, ${row2.index}`,
|
|
value: parseFloat(Math.max(row1.value, row2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
set lowestPrice(lowestPrise) {
|
|
const value = `${lowestPrise.index} (${lowestPrise.value})`;
|
|
this.#lowestPriceInput.dataset.value = String(value);
|
|
this.#lowestPriceInput.value = String(value);
|
|
};
|
|
|
|
getHighestPrice(cols, fractionDigits) {
|
|
return cols.map(col => col.rows.reduce((row1, row2) => {
|
|
return {
|
|
index: col.index,
|
|
value: Math.max(row1.value, row2.value),
|
|
};
|
|
})).reduce((col1, col2) => {
|
|
return {
|
|
index: col1.value < col2.value ? col1.index : col1.value > col2.value ? col2.index : `${col1.index}, ${col2.index}`,
|
|
value: parseFloat(Math.min(col1.value, col2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
set highestPrice(highestPrice) {
|
|
const value = `${highestPrice.index} (${highestPrice.value})`;
|
|
this.#highestPriceInput.dataset.value = String(value);
|
|
this.#highestPriceInput.value = String(value);
|
|
};
|
|
|
|
getSaddlePoint(lowestPrice, highestPrice) {
|
|
return lowestPrice.value === highestPrice.value ? lowestPrice.value : null;
|
|
};
|
|
|
|
set saddlePoint(value) {
|
|
this.#saddlePointInput.dataset.value = String(value);
|
|
this.#saddlePointInput.value = String(value);
|
|
};
|
|
|
|
getLaplaceCriterion(rows, fractionDigits) {
|
|
return rows.map(row => row.cols.reduce((col1, col2) => {
|
|
return {
|
|
index: row.index,
|
|
value: col1.value + col2.value,
|
|
length: row.cols.length,
|
|
};
|
|
})).map(row => {
|
|
return {
|
|
index: row.index,
|
|
value: row.value / row.length,
|
|
};
|
|
}).reduce((row1, row2) => {
|
|
return {
|
|
index: row1.value > row2.value ? row1.index : row1.value < row2.value ? row2.index : `${row1.index}, ${row2.index}`,
|
|
value: parseFloat(Math.max(row1.value, row2.value).toFixed(fractionDigits)),
|
|
}
|
|
});
|
|
};
|
|
|
|
set laplaceCriterion(criterion) {
|
|
const value = `${criterion.index} (${criterion.value})`;
|
|
this.#laplaceCriterionInput.dataset.value = value;
|
|
this.#laplaceCriterionInput.value = value;
|
|
};
|
|
|
|
getWaldCriterion(rows, fractionDigits) {
|
|
return rows.map(row => row.cols.reduce((col1, col2) => {
|
|
return {
|
|
index: row.index,
|
|
value: Math.min(col1.value, col2.value),
|
|
};
|
|
})).reduce((row1, row2) => {
|
|
return {
|
|
index: row1.value > row2.value ? row1.index : row1.value < row2.value ? row2.index : `${row1.index}, ${row2.index}`,
|
|
value: parseFloat(Math.max(row1.value, row2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
set waldCriterion(criterion) {
|
|
const value = `${criterion.index} (${criterion.value})`;
|
|
this.#waldCriterionInput.dataset.value = String(value);
|
|
this.#waldCriterionInput.value = `${value}`;
|
|
};
|
|
|
|
getMaxOfMaxCriterion(rows, fractionDigits) {
|
|
return rows.map(row => row.cols.reduce((col1, col2) => {
|
|
return {
|
|
index: row.index,
|
|
value: Math.max(col1.value, col2.value),
|
|
};
|
|
})).reduce((row1, row2) => {
|
|
return {
|
|
index: row1.value > row2.value ? row1.index : row1.value < row2.value ? row2.index : `${row1.index}, ${row2.index}`,
|
|
value: parseFloat(Math.max(row1.value, row2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
set maxOfMaxCriterion(criterion) {
|
|
const value = `${criterion.index} (${criterion.value})`;
|
|
this.#maxOfMaxCriterionInput.dataset.value = String(value);
|
|
this.#maxOfMaxCriterionInput.value = `${value}`;
|
|
};
|
|
|
|
getMinOfMinCriterion(rows, fractionDigits) {
|
|
return rows.map(row => row.cols.reduce((col1, col2) => {
|
|
return {
|
|
index: row.index,
|
|
value: Math.min(col1.value, col2.value),
|
|
};
|
|
})).reduce((row1, row2) => {
|
|
return {
|
|
index: row1.value < row2.value ? row1.index : row1.value > row2.value ? row2.index : `${row1.index}, ${row2.index}`,
|
|
value: parseFloat(Math.min(row1.value, row2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
set minOfMinCriterion(criterion) {
|
|
const value = `${criterion.index} (${criterion.value})`;
|
|
this.#minOfMinCriterionInput.dataset.value = String(value);
|
|
this.#minOfMinCriterionInput.value = `${value}`;
|
|
};
|
|
|
|
getSavageCriterion(regretMatrix, fractionDigits) {
|
|
return regretMatrix.map(row => row.cols.reduce((col1, col2) => {
|
|
return {
|
|
index: row.index,
|
|
value: Math.max(col1.value, col2.value),
|
|
};
|
|
})).reduce((row1, row2) => {
|
|
return {
|
|
index: row1.value < row2.value ? row1.index : row1.value > row2.value ? row2.index : `${row1.index}, ${row2.index}`,
|
|
value: parseFloat(Math.min(row1.value, row2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
set savageCriterion(criterion) {
|
|
const value = `${criterion.index} (${criterion.value})`;
|
|
this.#savageCriterionInput.dataset.value = String(value);
|
|
this.#savageCriterionInput.value = `${value}`;
|
|
};
|
|
|
|
getHurwitzCriterion(rows, optimismCriterion, fractionDigits) {
|
|
return rows.map(row => row.cols.map(col => {
|
|
col.min = col.value;
|
|
col.max = col.value;
|
|
return col;
|
|
}).reduce((col1, col2) => {
|
|
return {
|
|
index: row.index,
|
|
min: Math.min(col1.min, col2.min),
|
|
max: Math.max(col1.max, col2.max),
|
|
};
|
|
})).map(row => {
|
|
return {
|
|
index: row.index,
|
|
value: optimismCriterion * row.min + (1 - optimismCriterion) * row.max,
|
|
};
|
|
}).reduce((row1, row2) => {
|
|
return {
|
|
index: row1.value > row2.value ? row1.index : row1.value < row2.value ? row2.index : `${row1.index}, ${row2.index}`,
|
|
value: parseFloat(Math.max(row1.value, row2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
set hurwitzCriterion(criterion) {
|
|
const value = `${criterion.index} (${criterion.value})`;
|
|
this.#hurwitzCriterionInput.dataset.value = String(value);
|
|
this.#hurwitzCriterionInput.value = `${value}`;
|
|
};
|
|
|
|
set regretMatrix(rows) {
|
|
this.#regretMatrixTable.replaceChildren();
|
|
const width = this.getMatrixWidth();
|
|
const height = this.getMatrixHeight();
|
|
const tr = document.createElement('tr');
|
|
const th = document.createElement('th');
|
|
tr.appendChild(th);
|
|
for (let i = 0; i < width; i++) {
|
|
const th = document.createElement('th');
|
|
th.textContent = `B${i + 1}`.subscript();
|
|
tr.appendChild(th);
|
|
}
|
|
this.#regretMatrixTable.appendChild(tr);
|
|
for (let i = 0; i < height; i++) {
|
|
const tr = document.createElement('tr');
|
|
const th = document.createElement('th');
|
|
th.textContent = `A${i + 1}`.subscript();
|
|
tr.appendChild(th);
|
|
this.#regretMatrixTable.appendChild(tr);
|
|
for (let j = 0; j < width; j++) {
|
|
const td = document.createElement('td');
|
|
const input = document.createElement('input');
|
|
input.type = 'number';
|
|
input.value = rows[i].cols[j].value;
|
|
input.dataset.value = rows[i].cols[j].value;
|
|
input.ariaLabel = `Выделить ячейку A${i + 1}B${j + 1}`;
|
|
input.addEventListener('input', () => input.value = input.dataset.value);
|
|
input.dataset.a = String(i);
|
|
input.dataset.b = String(j);
|
|
input.addEventListener('keydown', (event) => {
|
|
const selectCell = (a, b) => this.#regretMatrixTable.querySelector(`input[type="number"][data-a="${a}"][data-b="${b}"]`).select();
|
|
switch (event.code) {
|
|
case 'ArrowUp':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell((Number(input.dataset.a) - 1 + height) % height, input.dataset.b);
|
|
break;
|
|
|
|
case 'ArrowDown':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell((Number(input.dataset.a) + 1 + height) % height, input.dataset.b);
|
|
break;
|
|
|
|
case 'ArrowLeft':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell(input.dataset.a, (Number(input.dataset.b) - 1 + width) % width);
|
|
break;
|
|
|
|
case 'ArrowRight':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell(input.dataset.a, (Number(input.dataset.b) + 1 + width) % width);
|
|
break;
|
|
}
|
|
});
|
|
td.appendChild(input);
|
|
tr.appendChild(td);
|
|
}
|
|
}
|
|
};
|
|
|
|
setMatrixSize() {
|
|
this.#widthInput.dataset.value = this.#widthInput.value;
|
|
this.#heightInput.dataset.value = this.#heightInput.value;
|
|
};
|
|
|
|
generateMatrix(randomValues) {
|
|
this.#matrixTable.replaceChildren();
|
|
const width = this.getMatrixWidth();
|
|
const height = this.getMatrixHeight();
|
|
const minRandomValue = this.getMinRandomValue();
|
|
const maxRandomValue = this.getMaxRandomValue();
|
|
const tr = document.createElement('tr');
|
|
const th = document.createElement('th');
|
|
tr.appendChild(th);
|
|
for (let i = 0; i < width; i++) {
|
|
const th = document.createElement('th');
|
|
th.textContent = `B${i + 1}`.subscript();
|
|
tr.appendChild(th);
|
|
}
|
|
this.#matrixTable.appendChild(tr);
|
|
for (let i = 0; i < height; i++) {
|
|
const tr = document.createElement('tr');
|
|
const th = document.createElement('th');
|
|
th.textContent = `A${i + 1}`.subscript();
|
|
tr.appendChild(th);
|
|
this.#matrixTable.appendChild(tr);
|
|
for (let j = 0; j < width; j++) {
|
|
const td = document.createElement('td');
|
|
const input = document.createElement('input');
|
|
input.type = 'number';
|
|
input.ariaLabel = `Изменить ячейку A${i + 1}B${j + 1}`;
|
|
input.dataset.a = String(i);
|
|
input.dataset.b = String(j);
|
|
input.addEventListener('keydown', (event) => {
|
|
const selectCell = (a, b) => this.#matrixTable.querySelector(`input[type="number"][data-a="${a}"][data-b="${b}"]`).select();
|
|
switch (event.code) {
|
|
case 'ArrowUp':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell((Number(input.dataset.a) - 1 + height) % height, input.dataset.b);
|
|
break;
|
|
|
|
case 'ArrowDown':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell((Number(input.dataset.a) + 1 + height) % height, input.dataset.b);
|
|
break;
|
|
|
|
case 'ArrowLeft':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell(input.dataset.a, (Number(input.dataset.b) - 1 + width) % width);
|
|
break;
|
|
|
|
case 'ArrowRight':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell(input.dataset.a, (Number(input.dataset.b) + 1 + width) % width);
|
|
break;
|
|
}
|
|
});
|
|
if (randomValues) input.value = `${Math.randint(minRandomValue, maxRandomValue)}`;
|
|
td.appendChild(input);
|
|
tr.appendChild(td);
|
|
}
|
|
}
|
|
};
|
|
|
|
solve() {
|
|
const rows = this.getRows();
|
|
const cols = this.transposeMatrix(rows);
|
|
const fractionDigits = this.getFractionDigits();
|
|
const regretMatrix = this.getRegretMatrix(cols, fractionDigits);
|
|
const optimismCriterion = this.getOptimismCriterion();
|
|
const lowestPrice = this.getLowestPrice(rows, fractionDigits);
|
|
const highestPrice = this.getHighestPrice(cols, fractionDigits);
|
|
const saddlePoint = this.getSaddlePoint(lowestPrice, highestPrice);
|
|
this.lowestPrice = lowestPrice;
|
|
this.highestPrice = highestPrice;
|
|
this.saddlePoint = saddlePoint === null ? 'Нет' : saddlePoint;
|
|
this.laplaceCriterion = this.getLaplaceCriterion(rows, fractionDigits);
|
|
this.waldCriterion = this.getWaldCriterion(rows, fractionDigits);
|
|
this.maxOfMaxCriterion = this.getMaxOfMaxCriterion(rows, fractionDigits);
|
|
this.minOfMinCriterion = this.getMinOfMinCriterion(rows, fractionDigits);
|
|
this.savageCriterion = this.getSavageCriterion(regretMatrix, fractionDigits);
|
|
this.hurwitzCriterion = this.getHurwitzCriterion(rows, optimismCriterion, fractionDigits);
|
|
this.regretMatrix = regretMatrix;
|
|
};
|
|
}
|
|
|
|
class Fragment2 {
|
|
#matrixTable;
|
|
#minRandomValueInput;
|
|
#maxRandomValueInput;
|
|
#fillRandomValuesButton;
|
|
#clearMatrixButton;
|
|
#fractionDigitsInput;
|
|
#graphSizeInput;
|
|
#lowestPriceInput;
|
|
#highestPriceInput;
|
|
#saddlePointInput;
|
|
#p1Input;
|
|
#p2Input;
|
|
#q1Input;
|
|
#q2Input;
|
|
#vInput;
|
|
#aPlayerGraph;
|
|
#bPlayerGraph;
|
|
#extra;
|
|
|
|
constructor() {
|
|
this.#matrixTable = document.querySelector('#fragment-2-matrix');
|
|
this.#minRandomValueInput = document.querySelector('#fragment-2-min-random-value');
|
|
this.#maxRandomValueInput = document.querySelector('#fragment-2-max-random-value');
|
|
this.#fillRandomValuesButton = document.querySelector('#fragment-2-fill-random-values');
|
|
this.#clearMatrixButton = document.querySelector('#fragment-2-clear-matrix');
|
|
this.#fractionDigitsInput = document.querySelector('#fragment-2-fraction-digits');
|
|
this.#graphSizeInput = document.querySelector('#fragment-2-graph-size');
|
|
this.#lowestPriceInput = document.querySelector('#fragment-2-lowest-price');
|
|
this.#highestPriceInput = document.querySelector('#fragment-2-highest-price');
|
|
this.#saddlePointInput = document.querySelector('#fragment-2-saddle-point');
|
|
this.#p1Input = document.querySelector('#fragment-2-p1');
|
|
this.#p2Input = document.querySelector('#fragment-2-p2');
|
|
this.#q1Input = document.querySelector('#fragment-2-q1');
|
|
this.#q2Input = document.querySelector('#fragment-2-q2');
|
|
this.#vInput = document.querySelector('#fragment-2-v');
|
|
this.#aPlayerGraph = new Graph(document.querySelector('#fragment-2-a-player-graph'));
|
|
this.#bPlayerGraph = new Graph(document.querySelector('#fragment-2-b-player-graph'));
|
|
this.#extra = document.querySelectorAll('.extra');
|
|
this.generateMatrix();
|
|
this.#fillRandomValuesButton.addEventListener('click', () => this.generateMatrix(true));
|
|
this.#clearMatrixButton.addEventListener('click', () => this.generateMatrix());
|
|
[
|
|
this.#lowestPriceInput,
|
|
this.#highestPriceInput,
|
|
this.#saddlePointInput,
|
|
this.#p1Input,
|
|
this.#p2Input,
|
|
this.#q1Input,
|
|
this.#q2Input,
|
|
this.#vInput,
|
|
].forEach(input => input.addEventListener('input', () => input.value = input.dataset.value));
|
|
};
|
|
|
|
getMatrixWidth() {
|
|
return 2;
|
|
};
|
|
|
|
getMatrixHeight() {
|
|
return 2;
|
|
};
|
|
|
|
getMinRandomValue() {
|
|
return Number(this.#minRandomValueInput.value);
|
|
};
|
|
|
|
getMaxRandomValue() {
|
|
return Number(this.#maxRandomValueInput.value);
|
|
};
|
|
|
|
getFractionDigits() {
|
|
return Number(this.#fractionDigitsInput.value);
|
|
};
|
|
|
|
getGraphSize() {
|
|
return Number(this.#graphSizeInput.value);
|
|
};
|
|
|
|
getRows() {
|
|
const rows = [];
|
|
const width = this.getMatrixWidth();
|
|
const height = this.getMatrixHeight();
|
|
for (let i = 0; i < height; i++) {
|
|
const row = {
|
|
index: this.#matrixTable.querySelector(`tr:nth-child(${i + 2}) > th`).textContent,
|
|
cols: [],
|
|
};
|
|
for (let j = 0; j < width; j++) {
|
|
row.cols.push({
|
|
index: this.#matrixTable.querySelector(`tr:first-child > th:nth-child(${j + 2})`).textContent,
|
|
value: Number(this.#matrixTable.querySelector(`tr:nth-child(${i + 2}) > td:nth-child(${j + 2}) > input`).value),
|
|
});
|
|
}
|
|
rows.push(row);
|
|
}
|
|
return rows;
|
|
};
|
|
|
|
transposeMatrix(rows) {
|
|
return rows[0].cols.map((col, i) => {
|
|
return {
|
|
index: col.index,
|
|
rows: rows.map(row => {
|
|
return {
|
|
index: row.index,
|
|
value: row.cols[i].value,
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
};
|
|
|
|
getLowestPrice(rows, fractionDigits) {
|
|
return rows.map(row => row.cols.reduce((col1, col2) => {
|
|
return {
|
|
index: row.index,
|
|
value: Math.min(col1.value, col2.value),
|
|
};
|
|
})).reduce((row1, row2) => {
|
|
return {
|
|
index: row1.value > row2.value ? row1.index : row1.value < row2.value ? row2.index : `${row1.index}, ${row2.index}`,
|
|
value: parseFloat(Math.max(row1.value, row2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
set lowestPrice(lowestPrise) {
|
|
const value = `${lowestPrise.index} (${lowestPrise.value})`;
|
|
this.#lowestPriceInput.dataset.value = String(value);
|
|
this.#lowestPriceInput.value = String(value);
|
|
};
|
|
|
|
getHighestPrice(cols, fractionDigits) {
|
|
return cols.map(col => col.rows.reduce((row1, row2) => {
|
|
return {
|
|
index: col.index,
|
|
value: Math.max(row1.value, row2.value),
|
|
};
|
|
})).reduce((col1, col2) => {
|
|
return {
|
|
index: col1.value < col2.value ? col1.index : col1.value > col2.value ? col2.index : `${col1.index}, ${col2.index}`,
|
|
value: parseFloat(Math.min(col1.value, col2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
set highestPrice(highestPrice) {
|
|
const value = `${highestPrice.index} (${highestPrice.value})`;
|
|
this.#highestPriceInput.dataset.value = String(value);
|
|
this.#highestPriceInput.value = String(value);
|
|
};
|
|
|
|
getSaddlePoint(lowestPrice, highestPrice) {
|
|
return lowestPrice.value === highestPrice.value ? lowestPrice.value : null;
|
|
};
|
|
|
|
set saddlePoint(value) {
|
|
this.#saddlePointInput.dataset.value = String(value);
|
|
this.#saddlePointInput.value = String(value);
|
|
};
|
|
|
|
showExtra() {
|
|
this.#extra.forEach(element => element.classList.remove('hidden'));
|
|
};
|
|
|
|
hideExtra() {
|
|
this.#extra.forEach(element => element.classList.add('hidden'));
|
|
};
|
|
|
|
getDenominator(rows, fractionDigits) {
|
|
return parseFloat((rows[0].cols[0].value + rows[1].cols[1].value - rows[1].cols[0].value - rows[0].cols[1].value).toFixed(fractionDigits));
|
|
};
|
|
|
|
getP1(rows, denominator) {
|
|
return (rows[1].cols[1].value - rows[1].cols[0].value) / denominator;
|
|
};
|
|
|
|
setP1(p1, fractionDigits) {
|
|
const value = String(parseFloat(p1.toFixed(fractionDigits)));
|
|
this.#p1Input.dataset.value = value;
|
|
this.#p1Input.value = value;
|
|
};
|
|
|
|
getP2(rows, denominator) {
|
|
return (rows[0].cols[0].value - rows[0].cols[1].value) / denominator;
|
|
};
|
|
|
|
setP2(p2, fractionDigits) {
|
|
const value = String(parseFloat(p2.toFixed(fractionDigits)));
|
|
this.#p2Input.dataset.value = value;
|
|
this.#p2Input.value = value;
|
|
};
|
|
|
|
getQ1(rows, denominator) {
|
|
return (rows[1].cols[1].value - rows[0].cols[1].value) / denominator;
|
|
};
|
|
|
|
setQ1(q1, fractionDigits) {
|
|
const value = String(parseFloat(q1.toFixed(fractionDigits)));
|
|
this.#q1Input.dataset.value = value;
|
|
this.#q1Input.value = value;
|
|
};
|
|
|
|
getQ2(rows, denominator) {
|
|
return (rows[0].cols[0].value - rows[1].cols[0].value) / denominator;
|
|
};
|
|
|
|
setQ2(q2, fractionDigits) {
|
|
const value = String(parseFloat(q2.toFixed(fractionDigits)));
|
|
this.#q2Input.dataset.value = value;
|
|
this.#q2Input.value = value;
|
|
};
|
|
|
|
getV(rows, denominator) {
|
|
return (rows[0].cols[0].value * rows[1].cols[1].value - rows[0].cols[1].value * rows[1].cols[0].value) / denominator;
|
|
};
|
|
|
|
setV(v, fractionDigits) {
|
|
const value = String(parseFloat(v.toFixed(fractionDigits)));
|
|
this.#vInput.dataset.value = value;
|
|
this.#vInput.value = value;
|
|
};
|
|
|
|
getMaxOfMaxCriterion(rows, fractionDigits) {
|
|
return rows.map(row => row.cols.reduce((col1, col2) => {
|
|
return {
|
|
index: row.index,
|
|
value: Math.max(col1.value, col2.value),
|
|
};
|
|
})).reduce((row1, row2) => {
|
|
return {
|
|
index: row1.value > row2.value ? row1.index : row1.value < row2.value ? row2.index : `${row1.index}, ${row2.index}`,
|
|
value: parseFloat(Math.max(row1.value, row2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
getMinOfMinCriterion(rows, fractionDigits) {
|
|
return rows.map(row => row.cols.reduce((col1, col2) => {
|
|
return {
|
|
index: row.index,
|
|
value: Math.min(col1.value, col2.value),
|
|
};
|
|
})).reduce((row1, row2) => {
|
|
return {
|
|
index: row1.value < row2.value ? row1.index : row1.value > row2.value ? row2.index : `${row1.index}, ${row2.index}`,
|
|
value: parseFloat(Math.min(row1.value, row2.value).toFixed(fractionDigits)),
|
|
};
|
|
});
|
|
};
|
|
|
|
generateMatrix(randomValues) {
|
|
this.#matrixTable.replaceChildren();
|
|
const width = this.getMatrixWidth();
|
|
const height = this.getMatrixHeight();
|
|
const minRandomValue = this.getMinRandomValue();
|
|
const maxRandomValue = this.getMaxRandomValue();
|
|
const tr = document.createElement('tr');
|
|
const th = document.createElement('th');
|
|
tr.appendChild(th);
|
|
for (let i = 0; i < width; i++) {
|
|
const th = document.createElement('th');
|
|
th.textContent = `B${i + 1}`.subscript();
|
|
tr.appendChild(th);
|
|
}
|
|
this.#matrixTable.appendChild(tr);
|
|
for (let i = 0; i < height; i++) {
|
|
const tr = document.createElement('tr');
|
|
const th = document.createElement('th');
|
|
th.textContent = `A${i + 1}`.subscript();
|
|
tr.appendChild(th);
|
|
this.#matrixTable.appendChild(tr);
|
|
for (let j = 0; j < width; j++) {
|
|
const td = document.createElement('td');
|
|
const input = document.createElement('input');
|
|
input.type = 'number';
|
|
input.ariaLabel = `Изменить ячейку A${i + 1}B${j + 1}`;
|
|
input.dataset.a = String(i);
|
|
input.dataset.b = String(j);
|
|
input.addEventListener('keydown', (event) => {
|
|
const selectCell = (a, b) => this.#matrixTable.querySelector(`input[type="number"][data-a="${a}"][data-b="${b}"]`).select();
|
|
switch (event.code) {
|
|
case 'ArrowUp':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell((Number(input.dataset.a) - 1 + height) % height, input.dataset.b);
|
|
break;
|
|
|
|
case 'ArrowDown':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell((Number(input.dataset.a) + 1 + height) % height, input.dataset.b);
|
|
break;
|
|
|
|
case 'ArrowLeft':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell(input.dataset.a, (Number(input.dataset.b) - 1 + width) % width);
|
|
break;
|
|
|
|
case 'ArrowRight':
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
selectCell(input.dataset.a, (Number(input.dataset.b) + 1 + width) % width);
|
|
break;
|
|
}
|
|
});
|
|
if (randomValues) input.value = `${Math.randint(minRandomValue, maxRandomValue)}`;
|
|
td.appendChild(input);
|
|
tr.appendChild(td);
|
|
}
|
|
}
|
|
};
|
|
|
|
solve() {
|
|
const rows = this.getRows();
|
|
const cols = this.transposeMatrix(rows);
|
|
const fractionDigits = this.getFractionDigits();
|
|
const graphSize = this.getGraphSize();
|
|
const lowestPrice = this.getLowestPrice(rows, fractionDigits);
|
|
const highestPrice = this.getHighestPrice(cols, fractionDigits);
|
|
const saddlePoint = this.getSaddlePoint(lowestPrice, highestPrice);
|
|
const denominator = this.getDenominator(rows, fractionDigits);
|
|
const p1 = this.getP1(rows, denominator);
|
|
const p2 = this.getP2(rows, denominator);
|
|
const q1 = this.getQ1(rows, denominator);
|
|
const q2 = this.getQ2(rows, denominator);
|
|
const v = this.getV(rows, denominator);
|
|
const maxOfMaxCriterion = this.getMaxOfMaxCriterion(rows, fractionDigits);
|
|
const minOfMinCriterion = this.getMinOfMinCriterion(rows, fractionDigits);
|
|
this.lowestPrice = lowestPrice;
|
|
this.highestPrice = highestPrice;
|
|
this.saddlePoint = saddlePoint === null ? 'Нет' : saddlePoint;
|
|
if (saddlePoint === null) {
|
|
this.setP1(p1, fractionDigits);
|
|
this.setP2(p2, fractionDigits);
|
|
this.setQ1(q1, fractionDigits);
|
|
this.setQ2(q2, fractionDigits);
|
|
this.setV(v, fractionDigits);
|
|
this.#aPlayerGraph.size = graphSize;
|
|
this.#aPlayerGraph.min = minOfMinCriterion.value;
|
|
this.#aPlayerGraph.max = maxOfMaxCriterion.value;
|
|
this.#aPlayerGraph.clear();
|
|
const aPlayerGraphWorkspace = this.#aPlayerGraph.getWorkspace();
|
|
this.#aPlayerGraph.drawLine(aPlayerGraphWorkspace, cols[0].rows[0].value, cols[0].rows[1].value, Graph.blue, 'B\u2081');
|
|
this.#aPlayerGraph.drawLine(aPlayerGraphWorkspace, cols[1].rows[0].value, cols[1].rows[1].value, Graph.red, 'B\u2082');
|
|
this.#aPlayerGraph.drawShadows(aPlayerGraphWorkspace, p1, p2, v, fractionDigits, 'p\u2081', 'p\u2082', 'V');
|
|
this.#bPlayerGraph.size = graphSize;
|
|
this.#bPlayerGraph.min = minOfMinCriterion.value;
|
|
this.#bPlayerGraph.max = maxOfMaxCriterion.value;
|
|
this.#bPlayerGraph.clear();
|
|
const bPlayerGraphWorkspace = this.#bPlayerGraph.getWorkspace();
|
|
this.#bPlayerGraph.drawLine(bPlayerGraphWorkspace, rows[0].cols[0].value, rows[0].cols[1].value, Graph.blue, 'A\u2081');
|
|
this.#bPlayerGraph.drawLine(bPlayerGraphWorkspace, rows[1].cols[0].value, rows[1].cols[1].value, Graph.red, 'A\u2082');
|
|
this.#bPlayerGraph.drawShadows(bPlayerGraphWorkspace, q1, q2, v, fractionDigits, 'q\u2081', 'q\u2082', 'V');
|
|
this.showExtra();
|
|
} else
|
|
this.hideExtra();
|
|
};
|
|
}
|
|
|
|
class Graph {
|
|
static white = '#FFF';
|
|
static black = '#1E1F22';
|
|
static gray = '#575757';
|
|
static blue = '#3574F0';
|
|
static red = '#C94F4F';
|
|
|
|
min = 0;
|
|
max = 10;
|
|
|
|
#canvas;
|
|
#context;
|
|
|
|
constructor(canvas) {
|
|
this.#canvas = canvas;
|
|
this.#context = this.#canvas.getContext('2d');
|
|
}
|
|
|
|
set size(value) {
|
|
this.#canvas.width = value;
|
|
this.#canvas.height = value * 9 / 16;
|
|
};
|
|
|
|
getWidth() {
|
|
return this.#canvas.width;
|
|
};
|
|
|
|
getHeight() {
|
|
return this.#canvas.height;
|
|
};
|
|
|
|
getPenSize(width) {
|
|
return Math.floor(Math.max(width / 500, 1));
|
|
};
|
|
|
|
getScalePitch(width) {
|
|
return width / 100;
|
|
};
|
|
|
|
getPadding(scalePitch) {
|
|
return scalePitch * 5;
|
|
};
|
|
|
|
getWorkspace() {
|
|
const canvasWidth = this.getWidth();
|
|
const canvasHeight = this.getHeight();
|
|
const scalePitch = this.getScalePitch(canvasWidth);
|
|
const padding = this.getPadding(scalePitch);
|
|
const bottomPadding = scalePitch * 3;
|
|
const x1 = padding + scalePitch;
|
|
const y1 = padding + scalePitch * 5;
|
|
const width = canvasWidth - padding * 2 - scalePitch * 6;
|
|
const height = canvasHeight - padding * 2 - scalePitch * 6;
|
|
const x2 = x1 + width;
|
|
const y2 = y1 + height;
|
|
return {
|
|
x1: x1,
|
|
y1: y2,
|
|
x2: x2,
|
|
y2: y2,
|
|
width: width,
|
|
height: height,
|
|
scalePitch: scalePitch,
|
|
bottomPadding: bottomPadding,
|
|
};
|
|
};
|
|
|
|
clear() {
|
|
const width = this.getWidth();
|
|
const height = this.getHeight();
|
|
const scalePitch = this.getScalePitch(width);
|
|
const penSize = this.getPenSize(width);
|
|
const padding = this.getPadding(scalePitch);
|
|
|
|
this.#context.fillStyle = Graph.white;
|
|
this.#context.beginPath();
|
|
this.#context.rect(0, 0, width, height);
|
|
this.#context.fill();
|
|
|
|
this.drawAxes(width, height, padding, scalePitch, penSize);
|
|
};
|
|
|
|
drawLine(workspace, y1, y2, color, title) {
|
|
const getHeight = (value) => (workspace.height - workspace.bottomPadding) * (value - this.min) / (this.max - this.min) + workspace.bottomPadding;
|
|
|
|
this.#context.beginPath();
|
|
this.#context.strokeStyle = color;
|
|
|
|
y1 = workspace.y2 - getHeight(y1);
|
|
y2 = workspace.y2 - getHeight(y2);
|
|
|
|
this.#context.moveTo(workspace.x1, y1);
|
|
this.#context.lineTo(workspace.x2, y2);
|
|
|
|
this.#context.stroke();
|
|
|
|
this.#context.beginPath();
|
|
this.#context.fillStyle = color;
|
|
this.#context.arc(workspace.x1, y1, workspace.scalePitch * .5, 0, 2 * Math.PI);
|
|
this.#context.arc(workspace.x2, y2, workspace.scalePitch * .5, 0, 2 * Math.PI);
|
|
this.#context.fill();
|
|
|
|
this.#context.beginPath();
|
|
this.#context.fillStyle = Graph.black;
|
|
this.#context.font = `${workspace.scalePitch * .15}rem "Exo 2", sans-serif`;
|
|
this.#context.textAlign = 'end';
|
|
this.#context.fillText(title, workspace.x1 - workspace.scalePitch * .75, y1 + workspace.scalePitch * .8);
|
|
|
|
this.#context.textAlign = 'start';
|
|
this.#context.fillText(`${title}'`, workspace.x2 + workspace.scalePitch * .75, y2 + workspace.scalePitch * .8);
|
|
|
|
this.#context.stroke();
|
|
};
|
|
|
|
drawShadows(workspace, p1, p2, v, fractionDigits, p1Text, p2Text, vText) {
|
|
const getHeight = (value) => (workspace.height - workspace.bottomPadding) * (value - this.min) / (this.max - this.min) + workspace.bottomPadding;
|
|
|
|
this.#context.beginPath();
|
|
this.#context.strokeStyle = Graph.gray;
|
|
this.#context.setLineDash([workspace.scalePitch * .75, workspace.scalePitch * .75]);
|
|
|
|
const a = workspace.x1 + workspace.width * p2;
|
|
const b = workspace.y2 - getHeight(v);
|
|
|
|
this.#context.moveTo(a, b);
|
|
this.#context.lineTo(a, workspace.y2);
|
|
this.#context.moveTo(workspace.x1, b);
|
|
this.#context.lineTo(a, b);
|
|
this.#context.stroke();
|
|
|
|
this.#context.fillStyle = Graph.gray;
|
|
|
|
this.#context.beginPath();
|
|
this.#context.arc(a, b, workspace.scalePitch * .5, 0, 2 * Math.PI);
|
|
this.#context.fill();
|
|
|
|
this.#context.beginPath();
|
|
this.#context.arc(a, workspace.y2, workspace.scalePitch * .5, 0, 2 * Math.PI);
|
|
this.#context.fill();
|
|
|
|
this.#context.beginPath();
|
|
this.#context.arc(workspace.x1, b, workspace.scalePitch * .5, 0, 2 * Math.PI);
|
|
this.#context.fill();
|
|
|
|
this.#context.fillStyle = Graph.black;
|
|
this.#context.font = `${workspace.scalePitch * .1}rem "Exo 2", sans-serif`;
|
|
|
|
this.#context.beginPath();
|
|
this.#context.textAlign = 'end';
|
|
this.#context.fillText(String(parseFloat(v.toFixed(fractionDigits))), workspace.x1 - workspace.scalePitch * .75, b + workspace.scalePitch * .5);
|
|
this.#context.textAlign = 'center';
|
|
this.#context.fillText(String(parseFloat(p2.toFixed(fractionDigits))), a, workspace.y2 + workspace.scalePitch * 2);
|
|
|
|
this.#context.font = `${workspace.scalePitch * .15}rem "Exo 2", sans-serif`;
|
|
this.#context.textAlign = 'center';
|
|
this.#context.fillText(vText, a, b - workspace.scalePitch);
|
|
this.#context.fillText(p1Text, a + (workspace.width * p1) * .5, workspace.y2 + workspace.scalePitch * 2);
|
|
this.#context.fillText(p2Text, workspace.x1 + (workspace.width * p2) * .5, workspace.y2 + workspace.scalePitch * 2);
|
|
|
|
this.#context.stroke();
|
|
};
|
|
|
|
drawAxes(width, height, padding, scalePitch, penSize) {
|
|
this.#context.strokeStyle = Graph.black;
|
|
this.#context.fillStyle = Graph.black;
|
|
this.#context.lineWidth = penSize;
|
|
|
|
const a = padding + scalePitch;
|
|
const b = width - padding;
|
|
const c = height - padding;
|
|
const d = padding * .7;
|
|
const e = height - d;
|
|
const f = width - d;
|
|
|
|
this.#context.beginPath();
|
|
this.#context.moveTo(a, d + scalePitch * 2.5);
|
|
this.#context.lineTo(a, e);
|
|
this.#context.stroke();
|
|
|
|
this.#context.beginPath();
|
|
this.#context.moveTo(a, d);
|
|
this.#context.lineTo(padding + scalePitch * .25, d + scalePitch * 3);
|
|
this.#context.lineTo(padding + scalePitch, d + scalePitch * 2.5);
|
|
this.#context.lineTo(padding + scalePitch * 1.75, d + scalePitch * 3);
|
|
this.#context.fill();
|
|
|
|
this.#context.beginPath();
|
|
this.#context.font = `${scalePitch * .15}rem "Exo 2", sans-serif`;
|
|
this.#context.textAlign = 'start';
|
|
this.#context.fillText('y\u2081', padding + scalePitch * 2.25, d + scalePitch * 2);
|
|
|
|
this.#context.beginPath();
|
|
this.#context.moveTo(b - scalePitch * 5, d + scalePitch * 2.5);
|
|
this.#context.lineTo(b - scalePitch * 5, e);
|
|
this.#context.stroke();
|
|
|
|
this.#context.beginPath();
|
|
this.#context.moveTo(b - scalePitch * 5, d);
|
|
this.#context.lineTo(b - scalePitch * 5.75, d + scalePitch * 3);
|
|
this.#context.lineTo(b - scalePitch * 5, d + scalePitch * 2.5);
|
|
this.#context.lineTo(b - scalePitch * 4.25, d + scalePitch * 3);
|
|
this.#context.fill();
|
|
|
|
this.#context.beginPath();
|
|
this.#context.font = `${scalePitch * .15}rem "Exo 2", sans-serif`;
|
|
this.#context.textAlign = 'start';
|
|
this.#context.fillText('y\u2082', b - scalePitch * 3.75, d + scalePitch * 2);
|
|
|
|
this.#context.beginPath();
|
|
this.#context.moveTo(d, c - scalePitch);
|
|
this.#context.lineTo(f - scalePitch * 2.5, c - scalePitch);
|
|
this.#context.stroke();
|
|
|
|
this.#context.beginPath();
|
|
this.#context.moveTo(f, c - scalePitch);
|
|
this.#context.lineTo(f - scalePitch * 3, c - scalePitch * .25);
|
|
this.#context.lineTo(f - scalePitch * 2.5, c - scalePitch);
|
|
this.#context.lineTo(f - scalePitch * 3, c - scalePitch * 1.75);
|
|
this.#context.fill();
|
|
|
|
this.#context.beginPath();
|
|
this.#context.font = `${scalePitch * .15}rem "Exo 2", sans-serif`;
|
|
this.#context.textAlign = 'center';
|
|
this.#context.fillText('x', f - scalePitch * 1.5, c - scalePitch + scalePitch * 2.25);
|
|
};
|
|
}
|
|
|
|
class Application {
|
|
static DEFAULT_FRAGMENT = '1';
|
|
|
|
#inputContainer;
|
|
#outputContainer;
|
|
#solveButton;
|
|
#menu;
|
|
#bottomBar;
|
|
#fragmentId;
|
|
#fragments = {
|
|
'1': new Fragment1(),
|
|
'2': new Fragment2(),
|
|
};
|
|
|
|
constructor() {
|
|
this.#inputContainer = document.querySelector('#input');
|
|
this.#outputContainer = document.querySelector('#output');
|
|
this.#solveButton = document.querySelector('#solve');
|
|
this.#menu = new Menu(this);
|
|
this.#bottomBar = new BottomBar();
|
|
|
|
this.#solveButton.addEventListener('click', () => {
|
|
this.#fragments[this.#fragmentId].solve();
|
|
document.querySelector(`#output [data-fragment="${this.#fragmentId}"]`).classList.add('active');
|
|
});
|
|
};
|
|
|
|
open(fragmentId) {
|
|
this.#fragmentId = fragmentId;
|
|
document.querySelectorAll('#input [data-fragment]').forEach(fragment => fragment.classList.remove('active'));
|
|
document.querySelector(`#input [data-fragment="${fragmentId}"]`).classList.add('active');
|
|
document.querySelectorAll('#output [data-fragment]').forEach(fragment => fragment.classList.remove('active'));
|
|
this.#menu.setActive(fragmentId);
|
|
};
|
|
}
|