Skip to content

Commit

Permalink
Применён паттерн "Наблюдатель"
Browse files Browse the repository at this point in the history
  • Loading branch information
Geksanit committed Dec 20, 2017
1 parent 077dc57 commit 09b9188
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 65 deletions.
87 changes: 39 additions & 48 deletions frontend/mvc/controller/Controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,89 +12,80 @@ const autobind = (self) => {

class Controller {
constructor() {
this.model = new Board(10, 10);
this.view = new Painter(this.model);
this.running = false;
this.board = new Board(10, 10);
this.painter = new Painter(this.board);
this.table = this.painter.table;
this.controls = this.painter.controls;
this.fps = 1;
autobind(this);
this.painter.newTable();// начальная отрисовка
this.showStatus();
this.table.onclick = this.toggleCell;
this.controls.onclick = this.setRunning;
this.controls.onchange = this.resizeBoard;
this.setSubscription();
this.view.newTable(this.model.matrix);// начальная отрисовка
this.setRunning(false);
}
showStatus() {
this.painter.setButtons(this.running);
this.painter.setStatus(this.running);
setSubscription() {
this.view.tableClicked.attach((sender, event) => {
this.toggleCell(event);
});
this.view.buttonClicked.attach((sender, event) => {
this.handlerButtons(event);
});
this.view.sliderChanged.attach((sender, event) => {
this.handlerSliders(event);
});
}
toggleCell({ target }) {
if (target.tagName !== 'TD') return;
const cell = target.cellIndex;
const row = target.parentElement.sectionRowIndex;
this.painter.toggleCell(target);
this.board.toggleCell(row, cell);
this.view.toggleCell(target);
this.model.toggleCell(row, cell);
}
setRunning(value) {
this.running = value;
this.view.setButtons(this.running);
this.view.setStatus(this.running);
}
anim(callback) {
// останавливается и вызывет аргумент, когда матрица перестает меняться(для тестов)
let oldMatrix;
// останавливается и вызывет аргумент callback(для тестов), когда матрица перестает меняться
function loop() {
const { fps } = this;
setTimeout(() => {
if (this.running) {
requestAnimationFrame(loop.bind(this));// не блокирует поток!
this.board.worker();
this.painter.repaintTable();
// если матрица не меняется, ссылка остаетя актуальной
if (oldMatrix === this.board.matrix) {
this.running = false;
this.showStatus();
} else oldMatrix = this.board.matrix;
requestAnimationFrame(loop.bind(this));
const flag = this.model.worker();
if (!flag) { // изменилась ли матрица ?
this.setRunning(false);
}
} else if (callback) {
callback();
}
}, 1000 / fps);
}, 1000 / this.fps);
}
loop.call(this);
}
setRunning({ target }) {
if (target.tagName !== 'BUTTON') return;
handlerButtons({ target }) {
switch (target.innerHTML) {
case 'start':
this.running = true;
this.showStatus();
this.setRunning(true);
this.anim();
break;
case 'pause':
this.running = false;
this.showStatus();
this.setRunning(false);
break;
case 'clear':
this.board.clear();
this.running = false;
this.showStatus();
this.painter.repaintTable();
this.model.clear();
this.setRunning(false);
}
}
resizeBoard({ target }) {
if (target.tagName !== 'INPUT') return;
handlerSliders({ target }) {
const value = target.valueAsNumber;
switch (target.parentElement.previousElementSibling.innerText) {
case 'speed':
this.fps = value;
break;
case 'width':
this.running = false;
this.showStatus();
this.board.resize(this.board.rows, value);
this.painter.newTable();
this.setRunning(false);
this.model.resize(this.model.rows, value);
break;
case 'height':
this.running = false;
this.showStatus();
this.board.resize(value, this.board.columns);
this.painter.newTable();
this.setRunning(false);
this.model.resize(value, this.model.columns);
}
}
}
Expand Down
49 changes: 40 additions & 9 deletions frontend/mvc/model/Board.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
class Event {
constructor(sender) {
this.sender = sender;
this.listeners = [];
}
attach(listener) {
this.listeners.push(listener);
}
notify(args) {
this.listeners.forEach((listener) => {
listener(this.sender, args);
});
}
}

class Board {
constructor(rows = 10, columns = 10) {
// матрица m на n заполненная false
this.matrix = [];
this.rows = rows;// строки
this.columns = columns;// столбцы
this.rows = rows;
this.columns = columns;
this.listOldMatrix = [];
this.matrixChanged = new Event(this);
this.cellToggled = new Event(this);
for (let i = 0; i < rows; i += 1) {
const row = [];
for (let j = 0; j < columns; j += 1) {
row.push(false);
}

this.matrix.push(row);
}
}
resize(m, n) {
const { matrix } = this;
const o = matrix.length;
const p = matrix[0].length;

// console.log('resize',o,p,' to ',m,n);
// убираем столбцы
if (p > n) {
for (let i = 0; i < o; i += 1) {
Expand Down Expand Up @@ -52,15 +67,17 @@ class Board {

this.rows = m;
this.columns = n;
return this;
this.listOldMatrix = [];
this.matrixChanged.notify({ matrix: this.matrix, resized: true });
}
clear() {
this.matrix.forEach((row) => {
row.forEach((item, i, arr) => {
arr[i] = false;
});
});
return this;
this.listOldMatrix = [];
this.matrixChanged.notify({ matrix: this.matrix, clear: true });
}
worker() {
// обход всех ячеек с записью нового состояния
Expand All @@ -76,8 +93,21 @@ class Board {
newMatrix.push(newRow);
});

if (flag) this.matrix = newMatrix;
return this;
if (flag) { // изменилась ли матрица в сравнении с предыдущей ?
this.matrix = newMatrix;
flag = !this.isRepeat(newMatrix); // а в сравнении со всеми предыдущими ?
}
this.matrixChanged.notify({ matrix: this.matrix, isChanged: !flag });
return flag;
}
isRepeat(newMatrix) {
const flag = this.listOldMatrix.some((matrix) => {
if (matrix.length !== newMatrix.length) return false;
if (matrix[0].length !== newMatrix[0].length) return false;
return matrix.every((row, i) => row.every((cell, j) => (cell === newMatrix[i][j])));
});
this.listOldMatrix.push(newMatrix);
return flag;
}
calculateCell(i, j) {
// соседи за пределами поля считаются мертвыми
Expand Down Expand Up @@ -105,6 +135,7 @@ class Board {
}
toggleCell(i, j) {
this.matrix[i][j] = !this.matrix[i][j];
this.cellToggled.notify(i, j);
}
}
export default Board;
58 changes: 50 additions & 8 deletions frontend/mvc/view/Painter.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,52 @@
class Event {
constructor(sender) {
this.sender = sender;
this.listeners = [];
}
attach(listener) {
this.listeners.push(listener);
}
notify(args) {
this.listeners.forEach((listener) => {
listener(this.sender, args);
});
}
}

class Painter {
constructor(board) {
this.board = board;
constructor(model) {
this.model = model;
this.table = document.getElementById('board');
this.controls = document.getElementById('controls');
this.buttons = this.controls.querySelectorAll('button');
this.tableClicked = new Event(this);
this.buttonClicked = new Event(this);
this.sliderChanged = new Event(this);
this.setHandlers();
this.setSubscription();
}
setHandlers() {
this.table.onclick = (event) => {
if (event.target.tagName === 'TD') {
this.tableClicked.notify(event);
}
};
this.controls.onclick = (event) => {
if (event.target.tagName === 'BUTTON') {
this.buttonClicked.notify(event);
}
};
this.controls.onchange = (event) => {
if (event.target.tagName === 'INPUT') {
this.sliderChanged.notify(event);
}
};
}
setSubscription() {
this.model.matrixChanged.attach((sender, obj) => {
if (obj.resized) this.newTable(obj.matrix);
else this.repaintTable(obj.matrix);
});
}
setButtons(running) {
if (!this.buttons) return;
Expand All @@ -27,9 +70,9 @@ class Painter {
toggleCell(target) {
target.classList.toggle('live');
}
paintTbody(tableWidth) {
paintTbody(matrix, tableWidth) {
// заполнение тела таблицы
const { matrix, columns } = this.board;
const columns = matrix[0].length;
const size = tableWidth / columns;
const tbody = document.createElement('tbody');
matrix.forEach((row) => {
Expand All @@ -45,17 +88,16 @@ class Painter {
});
return tbody;
}
newTable() {
newTable(matrix) {
// для создания и ресайза таблицы
const { table } = this;
const tbody = this.paintTbody(table.clientWidth);
const tbody = this.paintTbody(matrix, table.clientWidth);
if (table.children.length) table.replaceChild(tbody, table.children[0]);
else table.appendChild(tbody);
}
repaintTable() {
repaintTable(matrix) {
// изменение класса у ячеек таблицы
const { table } = this;
const { matrix } = this.board;
const tbody = table.children[0];
matrix.forEach((row, i) => {
row.forEach((cell, j) => {
Expand Down

1 comment on commit 09b9188

@Geksanit
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.