risk-calc/static/scripts/vendor.js

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);
};
}