<repo_name>codeneverstop/JavascriptRoad<file_sep>/Tetris/game/connection_manager.js
class ConnectionManager {
	constructor() {
		this.conn = null;
	}

	connect(address) {
		this.conn = new WebSocket(address);

		/* 这个open和new有什么区别？ 应该是 如果new了以后 server没反应，这个open也应该不会有执行*/
		this.conn.addEventListener('open', () => {
			console.log('conn established');
			/*create-session已经是程序自己指定的来*/
			this.initSession();
		});

		this.conn.addEventListener('message', event => {
			console.log("receive message:" + event.data);
			this.receive(event.data);
		});
	}

	initSession() {
		/*获取url的#后面的所有的内容*/
		const sessionId = window.location.hash.split('#')[1];
		if (sessionId) { 
			this.send({
				type: "join-session",
				id: sessionId,
			});
		} else {
			this.send({
				type: 'create-session',
			});
		}
	}

	receive(msg) {
		const data = JSON.parse(msg);
		if (data.type === "session-created") {
			window.location.hash = data.id;
		}
	}

	send(data) {
		const msg = JSON.stringify(data);
		console.log(`Sending msg ${msg}`);
		this.conn.send(msg);
	}
}<file_sep>/Tetris/game/tetris.js
class Tetris
{
	/* 这里传html的document就没意义，最终它是要画不同的tetris，canvas是不同的 包括class是game的div的里面的score等所有元素都是不同的
		所以要改成传特定的game的div或者特定的canvas都可以
	*/
	constructor(canvas, index)
	{
		this.canvas_bkg = canvas;
		this.context_bkg = this.canvas_bkg.getContext('2d');    /*never change*/
		this.context_bkg.scale(20, 20);

		this.arena = new Arena(12, 20);
		this.player = new Player({x: 2, y: 2}, 0, this.arena);
		this.index = index;

		let lastTime = 0;
		
		//必须要用指针，否则在tetrisAddEventListener上下文，this就是不是tetris实例了。
		const update = (time = 0) => {  
			const deltaTime = time - lastTime;
			lastTime = time;
			dropCounter += deltaTime;
			if (dropCounter > dropInterval)
			{
				this.player.drop(1);
			}
			let row = this.arena.eliminate();
			this.player.updateScore(row * 10);
			this.draw();
			requestAnimationFrame(update);
		}

		update();
	}

	_drawMatrix(matrix, offset){
		matrix.forEach((row, y) => {
			row.forEach((value, x) => {
				if (value !== 0){
					this.context_bkg.fillStyle = colorarr[value - 1];
					this.context_bkg.fillRect(x + offset.x, y + offset.y, 1, 1);
				}
			}); 
		});
	}

	draw(){
		/*renew back ground*/
		this.context_bkg.fillStyle = '#000';
		this.context_bkg.fillRect(0, 0, this.canvas_bkg.width, this.canvas_bkg.height);
		this._drawMatrix(this.arena.arena, {x: 0, y: 0});
		this._drawMatrix(this.player.block, this.player.pos);
		document.getElementById("score_" + this.index).innerText = this.player.score;
	}

}<file_sep>/Tetris/game/main.js
const matrix_block = [
[
	[0, 0, 0],
	[1, 1, 1],
	[0, 1, 0],
],
[
	[2, 2],
	[2, 2],
],
[
	[0, 3, 0, 0],
	[0, 3, 0, 0],
	[0, 3, 0, 0],
	[0, 3, 0, 0],
],
[
	[4, 0, 0],
	[4, 0, 0],
	[4, 4, 0],
],
[
	[0, 0, 5],
	[0, 0, 5],
	[0, 5, 5],
],
[
	[6, 6, 0],
	[0, 6, 6],
	[0, 0, 0],
],
[
	[0, 7, 7],
	[7, 7, 0],
	[0, 0, 0],
],

];

const connectionManager = new ConnectionManager();
connectionManager.connect("ws://localhost:9000");

const colorarr = ['red', 'blue', 'yellow', 'pink', 'purple', 'cyan', 'gray'];
const dropInterval = 1000;
let dropCounter = 0;

const tetris = [];
var game_canvas;
const playerElement = document.querySelectorAll('.game');
[...playerElement].forEach((element, index) => {
	game_canvas = element.querySelector('canvas');
	const tetrisInstance = new Tetris(game_canvas, index);
	tetris.push(tetrisInstance);
});

function tetrisAddEventListener() {
	//这里面的this就是Tetris
	var key = [
        [65, 68, 81, 69, 83],
        [72, 75, 89, 73, 74],
	];

	key.forEach((value, i) => {
		document.addEventListener('keydown', event => {
		if (event.keyCode === value[0]) 
	        { // left
	    	    tetris[i].player.moveHorizontally(-1);
	    	    if (tetris[i].arena.isCollideWithPlayer(tetris[i].player)) {                                                                                                                                      	
		    	    tetris[i].player.moveHorizontally(1);
		        }
	        } else if (event.keyCode === value[1]) {
		        tetris[i].player.moveHorizontally(1);
		        if (tetris[i].arena.isCollideWithPlayer(tetris[i].player)) {
		    	    tetris[i].player.moveHorizontally(-1);
		        }
	        } else if (event.keyCode === value[4]) {
		         tetris[i].player.drop(1);
	        } else if (event.keyCode === value[2]) {
		        tetris[i].player.rotate(-1);
	            if (tetris[i].arena.isCollideWithPlayer(tetris[i].player)) {
		            tetris[i].player.rotate(1);
	            }
	        } else if (event.keyCode === value[3]) {
		        tetris[i].player.rotate(1);
		        if (tetris[i].arena.isCollideWithPlayer(tetris[i].player)) {
			        tetris[i].player.rotate(-1);
		        }
	        }
	    });
	});
}
tetrisAddEventListener();<file_sep>/Tetris/server/client.js
class Client
{
	constructor(conn)
	{
		this.conn = conn;
		this.session = null;
	}

	send(data)
	{
		const msg = JSON.stringify(data);
		console.log(`send msg is ${msg}`);
		this.conn.send(msg, function ack(err) {
			if (err) { 
				console.error("send msg failed. session id is " + this.session.id + ", err is " + err);
			}
		});
	}
}

module.exports = Client;<file_sep>/Tetris/game/player.js
class Player
{
	constructor(pos, matrix_type, arena)
	{
		this.pos = pos;
		this.block = matrix_block[matrix_type];
		this.arena = arena;
		this.score = 0;
	}

	reset()
	{
		this.pos.y = 2;
		this.pos.x = 3;//canvas_bkg.width / 20 / 2 - 1;
		var type = Math.random() * colorarr.length| 0;
		this.block = matrix_block[type];
	}

	moveHorizontally(x_len)
	{
		this.pos.x += x_len;
	}

	drop(height)
	{
		this.pos.y += height;

		if (this.arena.isCollideWithPlayer(this)){
			this.pos.y--;
			this.arena.merge(this);
			this.reset();
			if (this.arena.isCollideWithPlayer(this))
			{
				//TODO: back to menu
				for (var i = 0; i < this.arena.arena.length; i++) {
					for (var j = 0; j < this.arena.arena[i].length; j++) {
						this.arena.arena[i][j] = 0;
					}
				}
				this.score = 0;
			}
		}
		dropCounter = 0;
	}

	rotate(dir)
	{
		for (let y = 0; y < this.block.length; y++){
		for (let x = 0; x < y; x++){
				[
					this.block[x][y],
					this.block[y][x],
				] = [
					this.block[y][x],
					this.block[x][y],
				]
			}
		}

		//TODO: dir should be an enum
		if (dir > 0){
			this.block.forEach(row => row.reverse());
		} else {
			this.block.reverse(); 
		}
	}

	updateScore(score)
	{
		this.score += score;
	}
}<file_sep>/Tetris/game/arena.js
class Arena
{
	constructor(width, height)
	{
		this.pos = {x: 0, y: 0};
		this.arena = [];
		for (let y = 0; y < height; y++)
		{
			this.arena.push(new Array(width).fill(0));
		}
	}

	isCollideWithPlayer(player)
	{
		const [pos, matrix] = [player.pos, player.block];
	
		/*
		在forEach里break是没有用的，因为这里forEach是个函数，是肯定都要执行的。 所以这里用forEach不合适。
		*/
		
		for (let y = 0; y < matrix.length; ++y)
		{
			for (let x = 0; x < matrix[y].length; ++x){
				//console.log("-pos origin(" + y + ", " + x + "), matrix pos is " + matrix[y][x]);
				if (matrix[y][x] !== 0 && 
					(this.arena[y + pos.y] && 
					this.arena[y + pos.y][x + pos.x]) !== 0){
					return true;
				}
			}
		}
		
		return false;
	}

	eliminate()
	{
		let row = 0;
		start: for (let y = 0; y < this.arena.length; y++)
		{
			for (let x = 0; x < this.arena[y].length; x++)
			{
				if (this.arena[y][x] === 0)
				{
					continue start;
				}
			}
			this.arena.splice(y, 1);
			this.arena.unshift(new Array(this.arena[0].length).fill(0));
			row += 1;
		}

		return row;
	}

	merge(player)
	{
		const [pos, matrix] = [player.pos, player.block];
		matrix.forEach((row, y) => {
			row.forEach((value, x) => {
				if (value !== 0){
					this.arena[y + pos.y][x + pos.x] = value;
				}
			});
		}); 
	}
}