TYPESCRIPT: Пример для изучения Часть 1

Как настоящий «фуллстек» все могу все умею программист, я часто пишу обе части кода, и часто это достаточно неприятные минуты «яваскрипт» ненависти.
С этим надо как-то бороться, и отличным примером борьбы является использование typescript. В качестве примера будет использован алгоритм игры «жизнь» (см https://ru.wikipedia.org/wiki/Жизнь_(игра))

В PhpStorm поддержка typescript включается плагином, после этого достаточно создать первый файл *.ts и указать параметры компиляции в tsconfig.json (я предпочитаю складывать код js в отдельную папку, чтобы на него не натыкаться при разработке). Я накручиваю сразу все остальные штучки из npm, так как дальше проект обрастает обычно компиляцией css, дополнительными либами и прочая прочая прочая). Полный пример кода можно скачать тут

Шаг 1. Наивный Javascript

Чистый вариант кода, который абсолютно «чистояваскрипт» и кучка переменных. Недостатков масса, и самый «больной» невозможность создать вторую матрицу рядышком с первой. Просто попробуйте.

var KEEP_ALIVE_MIN = 2;
var KEEP_ALIVE_MAX = 3;
var CREATE_NUMBER = 3;
var matrix_step 	= 0;
var matrix_size_rows	= 10;
var matrix_size_columns = 20;
var matrix_values = [];
var matrix_colors = [];
 
var matrix_div 		= document.createElement('div');
var matrix_div_counter 	= document.createElement('div');
var matrix_div_table	= document.createElement('table');
 
matrix_div.appendChild(matrix_div_counter);
matrix_div.appendChild(matrix_div_table);
document.body.appendChild(matrix_div);
 
matrix_div_counter.innerHTML = " Init step " + matrix_step;
 
initTable();
showTable();
 
setInterval(function(){
	matrix_step++;
	matrix_div_counter.innerHTML = " Current step " + matrix_step;
	stepTable();
	showTable();
}, 10);
 
 
function initTable(){
	matrix_values = [];
	matrix_colors = [];
	for (var i=0; i<matrix_size_rows; i++) {
		matrix_values[i] = {};
		matrix_colors[i] = {};
		for (var j=0; j<matrix_size_columns; j++) {
			matrix_values[i][j] = Math.random() > 0.5 ? 1 : 0;
			matrix_colors[i][j] =  false;
		}
	}
}
function stepTable(){
	for (var i=0; i<matrix_size_rows; i++) {
		for (var j=0; j<matrix_size_columns; j++) {
			var around = 0
			for(var around_i = Math.max(0, i-1); around_i<=Math.min(i+1, matrix_size_rows-1); around_i++) {
				for(var around_j = Math.max(0, j-1); around_j<=Math.min(j+1, matrix_size_columns-1); around_j++) {
					around+= matrix_values[around_i][around_j];
				}
			}
			around-= matrix_values[i][j];
			matrix_colors[i][j] = false;
			if (matrix_values[i][j] == 0) {
				if (around == CREATE_NUMBER) {
					matrix_values[i][j] = 1;
					matrix_colors[i][j] = 'green';
				}
			} else {
				if (around > KEEP_ALIVE_MAX || around < KEEP_ALIVE_MIN) {
					matrix_values[i][j] = 0;
					matrix_colors[i][j] = 'grey';
				}
			}
		}
	}
}
 
function showTable(){
	matrix_div_table.innerHTML = '';
	for (var i=0; i<matrix_size_rows; i++) {
		var row = document.createElement('tr');
		for (var j=0; j<matrix_size_columns; j++) {
			var td = document.createElement('td');
			td.setAttribute('height', '50px');
			td.setAttribute('width', '50px');
			td.setAttribute('valign', 'center');
			td.setAttribute('align', 'center');
			if (matrix_values[i][j] > 0) {
				td.setAttribute('bgcolor', matrix_colors[i][j] ? matrix_colors[i][j] : 'blue');
			}
			row.appendChild(td);
		}
		matrix_div_table.appendChild(row);
	}
}

Шаг 2. Наивный Typescript

Внесем все в класс, но оставим «наивность» работы с dom деревом — и прощай производительность (this.showTable() — полностью перерисовывает таблицу на каждом шаге)

 
const MatrixSettings = {
	ROWS : number = 10,
	COLUMNS : number = 20,
	KEEP_ALIVE_MIN : number = 2,
	KEEP_ALIVE_MAX : number = 3,
	CREATE_NUMBER : number = 3,
};
 
class MatrixCell {
	is_alive : boolean;
	color : string;
	constructor() {
		this.is_alive 	= Math.random() > 0.5;
		this.color 		= '';
	}
	showCell() : HTMLTableCellElement {
		var td = document.createElement('td');
		td.setAttribute('height', '50px');
		td.setAttribute('width', '50px');
		td.setAttribute('valign', 'center');
		td.setAttribute('align', 'center');
		if (this.is_alive) {
			td.setAttribute('bgcolor', this.color ? this.color : 'blue');
		}
		return td;
	}
	dieOrSurvive(around : number) : void {
		this.color = '';
		if (this.is_alive) {
			if (around > MatrixSettings.KEEP_ALIVE_MAX || around < MatrixSettings.KEEP_ALIVE_MIN) {
				this.is_alive = false;
				this.color = 'grey';
			}
		} else {
			if (around == MatrixSettings.CREATE_NUMBER) {
				this.is_alive = true;
				this.color = 'green';
			}
		}
	}
}
class Matrix {
	step 	: number = 0;
	size 	: {rows:number, columns:number};
	cells 	: MatrixCell[][];
	table 	: HTMLTableElement;
	counter : HTMLDivElement;
	constructor (target : HTMLDivElement, size_rows:number = MatrixSettings.ROWS, size_columns:number = MatrixSettings.COLUMNS) {
		this.counter = document.createElement('div');
		this.counter.innerHTML = " Init step ";
		this.table	 = document.createElement('table');
 
		target.appendChild(this.counter);
		target.appendChild(this.table);
		this.size 	= {rows:size_rows, columns:size_columns};
		this.cells  = [];
		for (var i=0; i<this.size.rows; i++) {
			this.cells[i] = [];
			for (var j=0; j<this.size.columns; j++) {
				this.cells[i][j] = new MatrixCell();
			}
		}
	}
	nextStep() : void {
		this.step++;
		this.counter.innerHTML = ` Current step  + ${this.step}`;
		this.stepTable();
		this.showTable();
	}
	stepTable() : void {
		for (var i=0; i<this.size.rows; i++) {
			for (var j=0; j<this.size.columns; j++) {
				var around = 0
				for(var around_i = Math.max(0, i-1); around_i<=Math.min(i+1, this.size.rows-1); around_i++) {
					for(var around_j = Math.max(0, j-1); around_j<=Math.min(j+1, this.size.columns-1); around_j++) {
						around+= this.cells[around_i][around_j].is_alive ? 1 : 0;
					}
				}
				around-= this.cells[i][j].is_alive ? 1 : 0;
				this.cells[i][j].dieOrSurvive(around);
			}
		}
	}
	showTable() : void {
		this.table.innerHTML = '';
		for (var i=0; i<this.size.rows; i++) {
			var row = document.createElement('tr');
			for (var j=0; j<this.size.columns; j++) {
				row.appendChild(this.cells[i][j].showCell());
			}
			this.table.appendChild(row);
		}
	}
}
 
let matrix_div = document.createElement('div');
document.body.appendChild(matrix_div);
 
let matrix = new Matrix(matrix_div);
setInterval(() => {
	matrix.nextStep()
}, 10)

Шаг 3. Экономный Typescript

Вынесем все что можно в константы вообще, а саму клеточку привяжем к ячейке…

const MatrixSettings = {
	ROWS : 100,
	COLUMNS : 200,
	KEEP_ALIVE_MIN : 2,
	KEEP_ALIVE_MAX : 3,
	CREATE_NUMBER : 3
}
 
const MatrixColors = {
	DIE : 'grey',
	ALIVE : 'blue',
	NEWBORN : 'green'
}
 
 
class MatrixCell {
	is_alive : boolean;
	is_newborn : boolean;
	is_newdie : boolean;
	td : HTMLTableCellElement;
	constructor() {
		this.is_alive 	= Math.random() > 0.5;
		this.is_newborn = false;
	}
	showCell() : HTMLTableCellElement {
		//создаем клетку всего раз - дальше мы это не дергаем
		var td = document.createElement('td');
		td.setAttribute('height', '10px');
		td.setAttribute('width', '10px');
		if (this.is_alive) {
			td.setAttribute('bgcolor', MatrixColors.ALIVE);
		}
		this.td = td;
		return td;
	}
	dieOrSurvive(around : number) : void {
		if (this.is_alive) {
			if (around > MatrixSettings.KEEP_ALIVE_MAX || around < MatrixSettings.KEEP_ALIVE_MIN) {
				//жизнь есть - но условия неблагоприятны и клетка умирает
				this.is_alive = false;
				this.is_newdie = true;
				this.td.setAttribute('bgcolor', MatrixColors.DIE);
			} else if (this.is_newborn) {
				//только что родился (на прошлом шаге) - цвет "новорожденного" надо изменить на просто "живой"
				this.is_newborn = false;
				this.td.setAttribute('bgcolor', MatrixColors.ALIVE);
			}
		} else {
			if (around == MatrixSettings.CREATE_NUMBER) {
				//жизни не было - но условия благоприятны для нового рождения
				this.is_alive = true;
				this.is_newborn = true;
				this.td.setAttribute('bgcolor', MatrixColors.NEWBORN);
			} else if (this.is_newdie) {
				//только что умер - у него цвет был "трупный"
				this.is_newdie = false;
				this.td.setAttribute('bgcolor', '');
			}
		}
	}
}
class Matrix {
	step 	: number = 0;
	size 	: {rows:number, columns:number};
	cells 	: MatrixCell[][];
	table 	: HTMLTableElement;
	counter : HTMLDivElement;
	constructor (target : HTMLDivElement, size_rows:number = MatrixSettings.ROWS, size_columns:number = MatrixSettings.COLUMNS) {
		this.counter = document.createElement('div');
		this.counter.innerHTML = " Init step ";
		this.table	 = document.createElement('table');
 
		target.appendChild(this.counter);
		target.appendChild(this.table);
		this.size 	= {rows:size_rows, columns:size_columns};
		this.cells  = [];
		for (var i=0; i<this.size.rows; i++) {
			var row = document.createElement('tr');
			this.cells[i] = [];
			for (var j=0; j<this.size.columns; j++) {
				this.cells[i][j] = new MatrixCell();
				row.appendChild(this.cells[i][j].showCell());
			}
			this.table.appendChild(row);
		}
	}
	nextStep() : void {
		this.step++;
		this.counter.innerHTML = ` Current step  + ${this.step}`;
		this.stepTable();
	}
	stepTable() : void {
		for (var i=0; i<this.size.rows; i++) {
			for (var j=0; j<this.size.columns; j++) {
				var around = 0
				for(var around_i = Math.max(0, i-1); around_i<=Math.min(i+1, this.size.rows-1); around_i++) {
					for(var around_j = Math.max(0, j-1); around_j<=Math.min(j+1, this.size.columns-1); around_j++) {
						around+= this.cells[around_i][around_j].is_alive ? 1 : 0;
					}
				}
				around-= this.cells[i][j].is_alive ? 1 : 0;
				this.cells[i][j].dieOrSurvive(around);
			}
		}
	}
}
 
let matrix_div = document.createElement('div');
document.body.appendChild(matrix_div);
 
 
let matrix = new Matrix(matrix_div, 20, 200);
setInterval(() => {
	matrix.nextStep()
}, 100)
 
let matrix_2 = new Matrix(matrix_div);
setInterval(() => {
	matrix_2.nextStep()
}, 10)
alive

Шаг 4. Наследование и почему собственно Typescript

А теперь сделаем вторую матрицу непохожей — пусть жизнь в ней «расползается» из центра игрового поля. Для этого расширим класс матрицы и вынесем «генератор» начального состояния поля в зависимую от положения функцию.

class Matrix {
	...
		this.cells[i][j] = new MatrixCell(this.initCellIsAlive(i,j));
	...
	initCellIsAlive(i : number, j:number) : boolean {
		return Math.random() > 0.5;
	}
}
class MatrixFromCenter extends Matrix {
	initCellIsAlive(i : number, j:number) : boolean {
		let center_i = this.size.rows / 2;
		if (i < center_i - 10 || i > center_i + 10) {
			return false;
		}
		let center_j = this.size.columns / 2;
		if (j < center_j - 10 || j > center_j + 10) {
			return false;
		}
		return Math.random() > 0.5;
	}
}

Оставить комментарий

XHTML: Вы можете использовать такие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre lang="" line="" escaped="" cssfile="">