2048 MP/plugin

리버티게임, 모두가 만들어가는 자유로운 게임
/** 2048 plugin
 * 제작자: Bd3076
 * 용도: 2048 MP에 사용될 플러그인
 */

var api = MediaWikiAPI();

// 보드판 생성
var board = new Array(new Array(4), new Array(4), new Array(4), new Array(4));
for(var i=0; i<4; i++){
	for(var j=0; j<4; j++){
		board[i][j] = 0;
	}
}

// 점수 생성
var myScore = 0;
var maxScore = localStorage.getItem('maxScore');
if(maxScore === null) maxScore = 0;

// 캔버스 구문
document.getElementById('gameArea').innerHTML = '<canvas id="cvs" width="800" height="600"></canvas>';
var canvas = document.getElementById('cvs');
var ctx = canvas.getContext('2d');
ctx.textAlign = "center";
ctx.textBaseline = "middle";

// 비어있는 칸이 있는지 판별하는 함수
var emptyExist = function(){
	for(var i=0; i<4; i++){
		for(var j=0; j<4; j++){
			if(!board[i][j]) return true;
		}
	}
	return false;
};

// 비어있는 칸 중 하나를 랜덤으로 고르는 함수
var randomEmpty = function(){
	var emptyArray = new Array(0); // 비어 있는 칸들의 목록
	for(var i=0; i<4; i++){
		for(var j=0; j<4; j++){
			if(!board[i][j]) emptyArray.push([i, j]);
		}
	}
	if(emptyArray.length === 0){
		console.log("error: no empty slots");
		return null;
	}
	return emptyArray[Math.floor(Math.random() * emptyArray.length)];
};

// 보드판의 상태를 캔버스에 그리는 함수
var drawBoard = function(){
	// 숫자에 따른 색
	var numberColor = ["#CDC1B4", "#EEE4DA", "#EDE0C8", "#F2B179", "#F59563",
					   "#F67C5F", "#F6523B", // ~64
					   "#EDCF72", "#EDCC61", "#EDC850",
					   "#EDC53F", "#EDC22E", "#EBB914", "#D3A612", // ~8192
					   "#BC9410", "#BC9410", "#BC9410", "#BC9410"]; 
	
	// 보드판의 개형 그리기
	ctx.fillStyle = "#BBADA0";
	ctx.beginPath();
	ctx.moveTo(0, 10);
	ctx.lineTo(0, 590);
	ctx.arc(10, 590, 10, Math.PI/2, Math.PI);
	ctx.lineTo(10, 600);
	ctx.lineTo(790, 600);
	ctx.arc(790, 590, 10, 0, Math.PI/2);
	ctx.lineTo(800, 590);
	ctx.lineTo(800, 10);
	ctx.arc(790, 10, 10, Math.PI*3/2, Math.PI*2);
	ctx.lineTo(790, 0);
	ctx.lineTo(10, 0);
	ctx.arc(10, 10, 10, Math.PI, Math.PI*3/2);
	ctx.closePath();
	ctx.fill();
	
	// 각 칸 그리기
	for(var i=0; i<4; i++){
		for(var j=0; j<4; j++){
			// 외형 그리기
			var startX = 16 + 146*i;
			var startY = 16 + 146*j;
			ctx.fillStyle = numberColor[board[i][j] === 0 ? 0 : Math.round(Math.log2(board[i][j]))];
			ctx.beginPath();
			ctx.moveTo(startX, startY+4);
			ctx.lineTo(startX, startY+126);
			ctx.arc(startX+4, startY+126, 4, Math.PI/2, Math.PI);
			ctx.lineTo(startX+4, startY+130);
			ctx.lineTo(startX+126, startY+130);
			ctx.arc(startX+126, startY+126, 4, 0, Math.PI/2);
			ctx.lineTo(startX+130, startY+126);
			ctx.lineTo(startX+130, startY+4);
			ctx.arc(startX+126, startY+4, 4, Math.PI*3/2, Math.PI*2);
			ctx.lineTo(startX+126, startY);
			ctx.lineTo(startX+4, startY);
			ctx.arc(startX+4, startY+4, 4, Math.PI, Math.PI*3/2);
			ctx.closePath();
			ctx.fill();
			
			// 텍스트 그리기
			if(board[i][j] !== 0){
				var numberLength = board[i][j].toString().length;
				var fontSize = numberLength == 1 ? 80 : 160 / numberLength;
				ctx.fillStyle = "#776E65";
				ctx.font = "bold " + fontSize + "px sans-serif";
				console.log("bold " + fontSize + "px sans-serif");
				ctx.fillText(board[i][j], startX+65, startY+65);
			}
		}
	}
	
	// 점수 그리기
	ctx.fillStyle = "#f2ddaf";
	ctx.font = "bold 30px sans-serif";
	ctx.fillText("YOUR SCORE", 697, 33);
	
	ctx.fillStyle = "#EEE4DA";
	ctx.beginPath();
	ctx.moveTo(610, 76);
	ctx.lineTo(610, 140);
	ctx.arc(620, 140, 10, Math.PI/2, Math.PI);
	ctx.lineTo(620, 150);
	ctx.lineTo(774, 150);
	ctx.arc(774, 140, 10, 0, Math.PI/2);
	ctx.lineTo(784, 140);
	ctx.lineTo(784, 76);
	ctx.arc(774, 76, 10, Math.PI*3/2, Math.PI*2);
	ctx.lineTo(774, 66);
	ctx.lineTo(620, 66);
	ctx.arc(620, 76, 10, Math.PI, Math.PI*3/2);
	ctx.closePath();
	ctx.fill();
	ctx.fillStyle = "#776E65";
	ctx.font = "bold 60px sans-serif";
	ctx.fillText(myScore, 697, 108);
	
	// 최고점수 그리기
	ctx.fillStyle = "#f2ddaf";
	ctx.font = "bold 30px sans-serif";
	ctx.fillText("MAX SCORE", 697, 33+180);
	
	ctx.fillStyle = "#EEE4DA";
	ctx.beginPath();
	ctx.moveTo(610, 76+180);
	ctx.lineTo(610, 140+180);
	ctx.arc(620, 140+180, 10, Math.PI/2, Math.PI);
	ctx.lineTo(620, 150+180);
	ctx.lineTo(774, 150+180);
	ctx.arc(774, 140+180, 10, 0, Math.PI/2);
	ctx.lineTo(784, 140+180);
	ctx.lineTo(784, 76+180);
	ctx.arc(774, 76+180, 10, Math.PI*3/2, Math.PI*2);
	ctx.lineTo(774, 66+180);
	ctx.lineTo(620, 66+180);
	ctx.arc(620, 76+180, 10, Math.PI, Math.PI*3/2);
	ctx.closePath();
	ctx.fill();
	ctx.fillStyle = "#776E65";
	ctx.font = "bold 60px sans-serif";
	ctx.fillText(maxScore, 697, 108+180);
};

// 키보드 입력을 받아 판을 이동시키고, 새로운 숫자를 만들거나 게임 오버시키는 함수

$("#left").click(function(){ gameRunner({keyCode: 37})});
$("#up").click(function(){ gameRunner({keyCode: 38})});
$("#right").click(function(){ gameRunner({keyCode: 39})});
$("#down").click(function(){ gameRunner({keyCode: 40})});

var gameRunner = function(e){
	var ek = e.keyCode;
	var cnt;
	var moved = 0;
	var added = new Array(new Array(4), new Array(4), new Array(4), new Array(4));
	for(var i=0; i<4; i++){
		for(var j=0; j<4; j++){
			added[i][j] = 0;
		}
	}
	console.log(ek);
	// 판 움직이기
	if(37 <= ek && ek <= 40) e.preventDefault();
	switch(ek){
		case 37:
			for(var j=0; j<4; j++){
				cnt = 0;
				for(var i=0; i<4; i++){
					if(i>0 && board[i][j] !== 0 && board[i-1][j] === 0){
						board[cnt++][j] = board[i][j];
						board[i][j] = 0;
						moved = 1;
						
						if(cnt > 1 && board[cnt-1][j] == board[cnt-2][j] && !added[cnt-2][j]){
							board[cnt-2][j] *= 2;
							board[cnt-1][j] = 0;
							cnt--;
							added[cnt-1][j] = 1;
							myScore += board[cnt-1][j];
						}
					}
					else if(board[i][j] !== 0){
						cnt++;
						
						if(cnt > 1 && board[cnt-1][j] == board[cnt-2][j] && !added[cnt-2][j]){
							moved = 1;
							board[cnt-2][j] *= 2;
							board[cnt-1][j] = 0;
							cnt--;
							added[cnt-1][j] = 1;
							myScore += board[cnt-1][j];
						}
					}
				}
			}
			if(!moved) return;
			break;
		case 38:
			for(var i=0; i<4; i++){
				cnt = 0;
				for(var j=0; j<4; j++){
					if(j>0 && board[i][j] !== 0 && board[i][j-1] === 0){
						board[i][cnt++] = board[i][j];
						board[i][j] = 0;
						moved = 1;
						
						if(cnt > 1 && board[i][cnt-1] == board[i][cnt-2] && !added[i][cnt-2]){
							board[i][cnt-2] *= 2;
							board[i][cnt-1] = 0;
							cnt--;
							added[i][cnt-1] = 1;
							myScore += board[i][cnt-1];
						}
					}
					else if(board[i][j] !== 0){
						cnt++;
						
						if(cnt > 1 && board[i][cnt-1] == board[i][cnt-2] && !added[i][cnt-2]){
							moved = 1;
							board[i][cnt-2] *= 2;
							board[i][cnt-1] = 0;
							cnt--;
							added[i][cnt-1] = 1;
							myScore += board[i][cnt-1];
						}
					}
				}
			}
			if(!moved) return;
			break;
		case 39:
			for(var j=0; j<4; j++){
				cnt = 0;
				for(var i=3; i>=0; i--){
					if(i<3 && board[i][j] !== 0 && board[i+1][j] === 0){
						board[3-(cnt++)][j] = board[i][j];
						board[i][j] = 0;
						moved = 1;
						
						if(cnt > 1 && board[3-(cnt-1)][j] == board[3-(cnt-2)][j] && !added[3-(cnt-2)][j]){
							board[3-(cnt-2)][j] *= 2;
							board[3-(cnt-1)][j] = 0;
							cnt--;
							added[3-(cnt-1)][j] = 1;
							myScore += board[3-(cnt-1)][j];
						}
					}
					else if(board[i][j] !== 0){
						cnt++;
						
						if(cnt > 1 && board[3-(cnt-1)][j] == board[3-(cnt-2)][j] && !added[3-(cnt-2)][j]){
							moved = 1;
							board[3-(cnt-2)][j] *= 2;
							board[3-(cnt-1)][j] = 0;
							cnt--;
							added[3-(cnt-1)][j] = 1;
							myScore += board[3-(cnt-1)][j];
						}
					}
				}
			}
			if(!moved) return;
			break;
		case 40:
			for(var i=0; i<4; i++){
				cnt = 0;
				for(var j=3; j>=0; j--){
					if(j<3 && board[i][j] !== 0 && board[i][j+1] === 0){
						board[i][3-(cnt++)] = board[i][j];
						board[i][j] = 0;
						moved = 1;
						
						if(cnt > 1 && board[i][3-(cnt-1)] == board[i][3-(cnt-2)] && !added[i][3-(cnt-2)]){
							board[i][3-(cnt-2)] *= 2;
							board[i][3-(cnt-1)] = 0;
							cnt--;
							added[i][3-(cnt-1)] = 1;
							myScore += board[i][3-(cnt-1)];
						}
					}
					else if(board[i][j] !== 0){
						cnt++;
						
						if(cnt > 1 && board[i][3-(cnt-1)] == board[i][3-(cnt-2)] && !added[i][3-(cnt-2)]){
							moved = 1;
							board[i][3-(cnt-2)] *= 2;
							board[i][3-(cnt-1)] = 0;
							cnt--;
							added[i][3-(cnt-1)] = 1;
							myScore += board[i][3-(cnt-1)];
						}
					}
				}
			}
			if(!moved) return;
			break;
		default:
			return;
	}
	// 숫자 추가
	var aNum = (Math.random() < 0.875) ? 2 : 4;
	var re = randomEmpty();
	board[re[0]][re[1]] = aNum;
	
	// 최고점수 갱신
	if(maxScore < myScore){
		maxScore = myScore;
		localStorage.setItem('maxScore', maxScore);
	}
	drawBoard();
	
	
	// 게임 오버 감지
	for(var i=0; i<4; i++){
		for(var j=0; j<4; j++){
			if(board[i][j] === 0) return;
		}
	}
	
	for(var i=0; i<4; i++){
		for(var j=0; j<3; j++){
			if(board[i][j] == board[i][j+1]) return;
		}
	}
	
	for(var i=0; i<3; i++){
		for(var j=0; j<4; j++){
			if(board[i][j] == board[i+1][j]) return;
		}
	}
	
	window.onkeydown = undefined;
	setTimeout(gameOver, 2000);
};

function gameOver(){
	ctx.fillStyle = "rgba(187, 171, 160, 0.2)";
	ctx.fillRect(0, 0, 800, 600);
	ctx.fillStyle = "black";
	ctx.font = "bold 100px sans-serif";
	ctx.fillText("Game Over", 300, 300);
	ctx.font = "bold 20px sans-serif";
	ctx.fillText("다시 시작하려면 새로고침하세요", 300, 450);
	var username = mw.config.get('wgUserName');
	if(username === null){
		ctx.fillText("랭킹에 결과를 등록하기 위해서는 로그인이 필요합니다", 300, 500);
	}
	else{
		updateRanking();
		ctx.fillText("랭킹은 게임 토론 메뉴에서 확인 가능합니다", 300, 500);
	}
	return;
}

function scoreData(){
	var score;
	var name;
	var time;
}

var bigger = function(a, b){
	if(a.score != b.score) return a.score > b.score;
	return a.time < b.time;
}

function updateRanking(){
	var username = mw.config.get('wgUserName');
	var rankingDoc = api.getDocument("2048 MP/Ranking");
	var json = JSON.parse(rankingDoc);
	var myData = new scoreData();
	myData.score = myScore;
	myData.name = mw.config.get('wgUserName');
	myData.time = Date.now();
	var rnum = undefined;
	// 랭킹 구조
	// json[data]: 구조체 데이터
	// json[rank]: 각 사람별 등수
	if(json.data[json.rank[myData.name]-1].score >= myScore) return;
	for(var i=json['data'].length-1; i>=0; i--){
		if(bigger(myData, json['data'][i])){
			json['data'][i+1] = json['data'][i];
			if(json['rank'][json['data'][i].name] === undefined || json['rank'][json['data'][i].name] == i+1){
				json['rank'][json['data'][i].name] = i+2;
			}
			json['data'][i] = null;
		}
		else{
			json['data'][i+1] = myData;
			rnum = i+2;
			break;
		}
	}
	if(rnum === undefined){
		json['data'][0] = myData;
		rnum = 1;
	}
	if(!(json['rank'][myData.name] === undefined && json['rank'][myData.name] < rnum)){
		json['rank'][myData.name] = rnum;
	}
	console.log(json);
	api.changeDocument('2048 MP/Ranking', '랭킹 갱신', JSON.stringify(json), 1, 1);
}

// 초기 보드판 상태: 2 또는 4가 총 2개 나오고, 4가 나올 확률은 각각 1/8
var sNum1 = (Math.random() < 0.875) ? 2 : 4;
var sNum2 = (Math.random() < 0.875) ? 2 : 4;

var re1 = randomEmpty();
board[re1[0]][re1[1]] = sNum1;
var re2 = randomEmpty();
board[re2[0]][re2[1]] = sNum2;

// 판 그리기
drawBoard();

window.onkeydown = gameRunner;