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