Windows сапер в 50 строк, или как я не уложился в 30 строк

в 12:10, , рубрики: javascript, Веб-разработка, игра, ненормальное программирование, сапёр, метки: , ,

На хабре стали выкладывать небольшие приложения на JavaScript в 30 строк, и я тоже решил попробовать, и написал всем известную игру сапер. Если вам еще не надоела эта тема, то прошу под кат.

Перед тем как писать код, я поставил перед собой условия, игра должна соответствовать следующим требованиям, чтобы было более похоже на подлинную версию игры.

  • Бомбы генерируются после первого хода. Взорваться на первом ходу нельзя
  • Должна быть возможность ставить метки. (Флажки и вопросительные знаки)
  • Игра должна работать на всех актуальных версиях браузеров
  • В игре должна быть возможность, похожая на удерживание двух кнопкой мыши из оригинальной версии игры
  • Динамичность. Количество клеток и бобм, можно изменять
  • Алгоритм максимально похожий на алгоритм из оригинальной версии игры

Большинство из перечисленных пунктов, мне все же удалось соблюсти, но от этого код ожирел до 50 строк кода. Жертвовать читабельностью не хотелось, от нее и так следа не осталось.

Функционал из подлинной игры, когда удерживаешь две кнопки мыши, показываются соседние клетки, я его в таком виде реализовать не смог. Поэтому я сделал, чтобы при наведении на блок, вокруг него и его соседних блоков, появлялась рамка, отделяющая эти блоки от остальных.

Изменить количество блоков, можно в аргументах вызове функции, где.

function(Количество_блоков_по_горизонтали, Количество_блоков_по_вертикали, Количество_мин, is_generate)

К сожалению, если менять количество блоков, то придется менять и разметку HTML кода.
Вот собственно код

(function(width, height, count_mins, ar, is_generate) {
	function $(el) { return document.getElementById(el); }
	function get_key(e) { $d = (e.which == null) ? ((e.button == 2) ? 3 : 1) : e.which; console.warn($d); return $d; }
	function generate(nx,ny) {
		for(var i = 0,x=0,y=0; i < count_mins; x=Math.round(Math.random()*width),y=Math.round(Math.random()*height)) {
			if(('p' + x.toString() + '_' + y.toString()) in ar || (nx == x && ny == y)) continue;
			else ar['p' + x.toString() + '_' + y.toString()] = 'X', i = i+1;
		}
		for(var x = 0; x < width; x++) 
		for(var y = 0,c=0; y < height; y++,c=0) {
			if(ar['p'+x.toString()+'_'+y.toString()]=='X') continue;
			for(var ix = -1; ix < 2; ix++) for(var iy = -1; iy < 2; iy++)
				c += ar['p'+(x+ix).toString() + '_' + (y+iy).toString()] == 'X' ? 1 : 0;
			ar['p'+x.toString()+'_'+y.toString()]=c.toString();
		}
	}
	function game_over() {
		for(var x = 0; x < width; x++)
			for(var y = 0, z=0,g=0; y < height; y++) {
				z=ar['p'+x.toString()+'_'+y.toString()];
				if(z != '#') {
					$('p_'+x.toString()+'_'+y.toString()).innerHTML = z;
					$('p_'+x.toString()+'_'+y.toString()).className = 'dig'+z;
				}
			}
		$('saper_game').className = 'game_over';
	}
	function flag(x,y) {
		var i = 'p_'+x.toString()+'_'+y.toString(), c = $(i), cn=c.className, d = cn=='flag' ? (c.className = 'quest') : (cn == 'quest' ? c.className = '' : (cn == '' ? c.className = 'flag' : ''));
	}
	function open(x,y) {
		var tmp = ar['p' + x.toString() + '_' + y.toString()],$el=$('p_' + x.toString() + '_' + y.toString());
		if(tmp == []._ || tmp == '#' || (tmp == 'X' && alert('Game Over') == []._ && game_over() == []._ && ($el.className += ' open_lose') )) return;
		$el.className = 'is_open dg'+(dg=ar['p'+x.toString()+'_'+y.toString()]);
		$el.innerHTML = dg,	ar['p' + x.toString() + '_' + y.toString()] = '#';
		for(var i = 0; i <= 7 && tmp == '0'; i++) {
		   var p = Math.PI/4*i, c = Math.cos(p).toFixed(2), xx = (c < 0 ? Math.floor(c) : Math.ceil(c)), c = Math.sin(p).toFixed(2), 
		   yy = (c < 0 ? -Math.floor(c) : -Math.ceil(c)), p = open(x+xx, y+yy);
		}
	}
	for(var x = 0; x < width; x++) 
		for(var y = 0,c=0; y < height; y++,c=0) {
			$('p_'+x.toString()+'_'+y.toString()).onclick = function(e) { 
				if(!is_generate) { generate(parseInt(this.id.split('_')[1]), parseInt(this.id.split('_')[2])); is_generate = true; }
				if(this.className != 'flag' && $('saper_game').className != 'game_over') open(parseInt(this.id.split('_')[1]), parseInt(this.id.split('_')[2])); 
			}
			$('p_'+x.toString()+'_'+y.toString()).onmousedown = function(e) { var evt = (e==null ? event:e), p=[parseInt(this.id.split('_')[1]), parseInt(this.id.split('_')[2])], d = get_key(evt) == 3 ? flag(p[0], p[1]) : ''; }
		}
	document.body.oncontextmenu = function() { return false; };
})(10,10,10,{},false);

Код разметки

<div id="saper_game" class="gaming">
		<div class="row">
			<div id="p_0_0"></div>
			<div id="p_1_0"></div>
			<div id="p_2_0"></div>
			<div id="p_3_0"></div>
			<div id="p_4_0"></div>
			<div id="p_5_0"></div>
			<div id="p_6_0"></div>
			<div id="p_7_0"></div>
			<div id="p_8_0"></div>
			<div id="p_9_0"></div>
		</div>
		<div class="row">
			<div id="p_0_1"></div>
			<div id="p_1_1"></div>
			<div id="p_2_1"></div>
			<div id="p_3_1"></div>
			<div id="p_4_1"></div>
			<div id="p_5_1"></div>
			<div id="p_6_1"></div>
			<div id="p_7_1"></div>
			<div id="p_8_1"></div>
			<div id="p_9_1"></div>
		</div>
		<div class="row">
			<div id="p_0_2"></div>
			<div id="p_1_2"></div>
			<div id="p_2_2"></div>
			<div id="p_3_2"></div>
			<div id="p_4_2"></div>
			<div id="p_5_2"></div>
			<div id="p_6_2"></div>
			<div id="p_7_2"></div>
			<div id="p_8_2"></div>
			<div id="p_9_2"></div>
		</div>
		<div class="row">
			<div id="p_0_3"></div>
			<div id="p_1_3"></div>
			<div id="p_2_3"></div>
			<div id="p_3_3"></div>
			<div id="p_4_3"></div>
			<div id="p_5_3"></div>
			<div id="p_6_3"></div>
			<div id="p_7_3"></div>
			<div id="p_8_3"></div>
			<div id="p_9_3"></div>
		</div>
		<div class="row">
			<div id="p_0_4"></div>
			<div id="p_1_4"></div>
			<div id="p_2_4"></div>
			<div id="p_3_4"></div>
			<div id="p_4_4"></div>
			<div id="p_5_4"></div>
			<div id="p_6_4"></div>
			<div id="p_7_4"></div>
			<div id="p_8_4"></div>
			<div id="p_9_4"></div>
		</div>
		<div class="row">
			<div id="p_0_5"></div>
			<div id="p_1_5"></div>
			<div id="p_2_5"></div>
			<div id="p_3_5"></div>
			<div id="p_4_5"></div>
			<div id="p_5_5"></div>
			<div id="p_6_5"></div>
			<div id="p_7_5"></div>
			<div id="p_8_5"></div>
			<div id="p_9_5"></div>
		</div>
		<div class="row">
			<div id="p_0_6"></div>
			<div id="p_1_6"></div>
			<div id="p_2_6"></div>
			<div id="p_3_6"></div>
			<div id="p_4_6"></div>
			<div id="p_5_6"></div>
			<div id="p_6_6"></div>
			<div id="p_7_6"></div>
			<div id="p_8_6"></div>
			<div id="p_9_6"></div>
		</div>
		<div class="row">
			<div id="p_0_7"></div>
			<div id="p_1_7"></div>
			<div id="p_2_7"></div>
			<div id="p_3_7"></div>
			<div id="p_4_7"></div>
			<div id="p_5_7"></div>
			<div id="p_6_7"></div>
			<div id="p_7_7"></div>
			<div id="p_8_7"></div>
			<div id="p_9_7"></div>
		</div>
		<div class="row">
			<div id="p_0_8"></div>
			<div id="p_1_8"></div>
			<div id="p_2_8"></div>
			<div id="p_3_8"></div>
			<div id="p_4_8"></div>
			<div id="p_5_8"></div>
			<div id="p_6_8"></div>
			<div id="p_7_8"></div>
			<div id="p_8_8"></div>
			<div id="p_9_8"></div>
		</div>
		<div class="row">
			<div id="p_0_9"></div>
			<div id="p_1_9"></div>
			<div id="p_2_9"></div>
			<div id="p_3_9"></div>
			<div id="p_4_9"></div>
			<div id="p_5_9"></div>
			<div id="p_6_9"></div>
			<div id="p_7_9"></div>
			<div id="p_8_9"></div>
			<div id="p_9_9"></div>
		</div>
	</div>

И стиль, получился довольно объемный
body
{
	background: #252525;
}
#saper_game
{
	margin: 0 auto;
	width: 430px;
}
div.row div
{
	border: 3px solid black;
	margin: 3px;
	width: 30px;
	height: 28px;
	display: inline-block;
	border-radius: 4px;
	background: #242424;
	background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJod…EiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
	background: -moz-linear-gradient(top, rgba(36, 36, 36, 1) 0%, rgba(15, 15, 15, 1) 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(36, 36, 36, 1)), color-stop(100%,rgba(15, 15, 15, 1)));
	background: -webkit-linear-gradient(top, rgba(36, 36, 36, 1) 0%,rgba(15, 15, 15, 1) 100%);
	background: -o-linear-gradient(top, rgba(36, 36, 36, 1) 0%,rgba(15, 15, 15, 1) 100%);
	background: -ms-linear-gradient(top, rgba(36, 36, 36, 1) 0%,rgba(15, 15, 15, 1) 100%);
	background: linear-gradient(to bottom, rgba(36, 36, 36, 1) 0%,rgba(15, 15, 15, 1) 100%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#242424', endColorstr='#0f0f0f',GradientType=0 );
	box-shadow: inset 0px 1px 0px 0px #494949;
	-webkit-transition: background 0.5s ease;
	-moz-transition: background 0.5s ease;
	-o-transition: background 0.5s ease;
	transition: background 0.5s ease;
	-webkit-transition: box-shadow 0.5s ease;
	-moz-transition: box-shadow 0.5s ease;
	-o-transition: box-shadow 0.5s ease;
	transition: box-shadow 0.5s ease;
	text-align: center;
	padding-top: 1px;
	color: #ADABAB;
	float: left;
	cursor: pointer;
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
	position: relative;
	z-index: 1;
	font-weight: bold;
	font-size: 24px;
}
div.row div.is_open, div.row div.is_open:hover
{
	background: #f5f0f0;
	background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y1ZjBmMCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNjNWM1YzUiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
	background: -moz-linear-gradient(top,  #f5f0f0 0%, #c5c5c5 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f5f0f0), color-stop(100%,#c5c5c5));
	background: -webkit-linear-gradient(top,  #f5f0f0 0%,#c5c5c5 100%);
	background: -o-linear-gradient(top,  #f5f0f0 0%,#c5c5c5 100%);
	background: -ms-linear-gradient(top,  #f5f0f0 0%,#c5c5c5 100%);
	background: linear-gradient(to bottom,  #f5f0f0 0%,#c5c5c5 100%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f5f0f0', endColorstr='#c5c5c5',GradientType=0 );
	color: black;
	box-shadow: inset 1px 1px 2px 0px;
	cursor: default;
	font-weight: bold;
}
div.row div:hover
{
	background: #0f0f0f;
	background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzBmMGYwZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMyNDI0MjQiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
	background: -moz-linear-gradient(top,  #0f0f0f 0%, #242424 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#0f0f0f), color-stop(100%,#242424));
	background: -webkit-linear-gradient(top,  #0f0f0f 0%,#242424 100%);
	background: -o-linear-gradient(top,  #0f0f0f 0%,#242424 100%);
	background: -ms-linear-gradient(top,  #0f0f0f 0%,#242424 100%);
	background: linear-gradient(to bottom,  #0f0f0f 0%,#242424 100%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#0f0f0f', endColorstr='#242424',GradientType=0 );
	box-shadow: inset 0px 1px 0px 0px #494949, 0px 0px 3px 0px cyan;
	z-index: 0;
}

div.row div:hover:before
{
	border: 1px solid cyan;
	display: block;
	content: '';
	height: 128px;
	width: 122px;
	margin-top: -52px;
	margin-left: -47px;
	box-shadow: 0px 0px 3px 0px cyan, inset 0px 0px 3px 0px cyan;
	border-radius: 5px;
	position: absolute;
}

div.row
{
	width: 100%;
	height: 45px;
	clear: both;
}
.gaming div.row div.flag
{
	background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABK0lEQVQ4jWNgoBZwcHBQcXR01CZVHwucwcjo+5eFVVs7vYv1i4CC1l8mdla27++f8L27dZDz9v4px48f/46s0c3NjXvXrl1fWZAFvwnIW76RN9X695+N4bWMFQPDXwZ9lh/fvMUkbXJUDJ6e5Pj68vZfZg7Ob9yyRk8/3LnNwLArFcUABgaG/4x/fjMwMLPBBf6wcjE8U/SUY/jHIMfwl4EBhlWvz7zOwMDAwESqn9EBbQz4z8RMtAEoYcD47x8T659v17jeP7nA//Ia83cOUemPIrqGX3nkuYkygPPD40PKhybkHDhw4A9MzDQ01eCdqEniTy5Jqx+swlIsv74wcn19cZ7zy7M1KCa5ODoWujg5tRDtdigYpLFACoAH4n8mptuM//69pNRAkgEAa59hbsPHCCUAAAAASUVORK5CYII=')!important;
	background-position: 6px 7px;
	background-repeat: no-repeat;
}
.gaming div.row div.quest
{
	background-position: 7px 7px;
	background-repeat: no-repeat;
	background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC+ElEQVQ4jWWTP2xbVRjFf9+99/01TgwJCQkMbkMKqCkEKGoXBpiQEqkpZUL8y8BeRqYwMMBQRMWMGtQgJCSKYWCohARIVCCoMJUgLbSipVERTuzYcexn+713L4OlirafdLZzznC+c4TbbnZxrSTIEkoeQ9S8iICob0WkevHMC5Xb+XKLeOH08WIxXnloZqoUhSFRFDHIcmr1XerNDts7navWuTfWPz1SucNgduH0qfm58mt7y/ezfq1LrZmR2SHFaAg9QTtLo1WnnybLv3+yuHrTYHZh7a1DT8ysFEfH+eVyh73TEa8vTvL0o0WsdXzz6w6nzm5x5cYALdDtNMgG3aO/ffxcRWYX18pTE6W/Dj7+CN9daDM17lN5+2GsteS5w1qLc44b9ZSXTlxHRHDOkrRqTWvzPXp837GVw0/OHl7fyOiljt2eJQ4U9VbGiTM1HLBn0iP2hR8v92n1QGmNaB3m6eCS8QNvSZmI7XYXRECED76skTshs6C18OyBGOccm7uOTjrMLNQRottHzOhIoVxrpsM0BPqpo9vJEa15YMLnzWNjWGs590efRg/8yABQUJB0dclYB71BjnWwvZORI3i+RnuKd16ZIA6EK/9mnPy6QxB7KDV8XDFw1BsGM0hzBMfmdooTwfgKZRQLB+9iZtJgrePD7xNSZQgChdIKgJGCRZRGdZJBpd1OsM4hSlB6iKl7DM45nHNc2nQEoSGIPMLYY2TEIwxAtKkaRH3Rau8uFcNRulZQaojKzwn7pn22EjC+xgs0fuhhfE15DP5cbyFKf6Qufvb8atJPqpHOMQKIIEo4+lTMoRmfhTmfuWmD9jReqJm+10ORs/FPv3L+/QNVBeBwy4O01QzE3uz2hesp3QFs7MDfTYdRcN+oUPIyzv3QqIrSy7dsYf+LX82jzOe5GS3rsEgcGeKCR6FouLvkMVYybDV6XNtIqoNMnvnp3Qebd6xx7uWzJZQ6Ltp71XhBOS7EhJEhzS27SV4VbU6ef2//6v81/wH6bjI89FfNwgAAAABJRU5ErkJggg==');
}
#saper_game div.row .dg0
{
	text-indent: -10000px;
}
#saper_game div.row .dg1
{
	color: #008FFF;
}
#saper_game div.row .dg2
{
	color: green;
}
#saper_game div.row .dg3
{
	color: red;
}
#saper_game div.row .dg4
{
	color: blue;
}
#saper_game div.row .dg5, #saper_game div.row .dg6, #saper_game div.row .dg7, #saper_game div.row .dg8
{
	color: #860000;
}
#saper_game div.row .digX
{
	background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACSElEQVR4XpWTTYgSYRjHH79aHTVRd4VVwQzEi0rSZY+FELLSZYXORtCxU3TsFHTZ7RR2sU7SIaSgkxeFDiaCIqirLX4QiAri98HEr6dn3maGKerQCz/eQef3PM/83xn4z8UR14iDl0/gxb0TCCv/IdhcLtfdQCBwZrVa75Bg0Gg0t88vXl9+TYUKb5/DZxMHty7bMPhTtMdisXf5fH7e6/Ww1WphNpvFSCTS9fv9vWj0DL9fZfD09H6SBNVvpk6nO0kmk93tdov9fh9LpRKm02lMpVIYj8fRbDYjCajlru8PDrhHoqcWOycSiY/hcPi4WCzCZDKB6XTK9tlsBovFAtRqNRNWy0WPtk9iAZZBKBR6FgwGjwuFAjSbTWi32xKdTgcqlQorJiwn8VRegPP5fNFGowH0zEwSdum6XC7DbrcDhUIheg8IjVjgJgXmFAS5yKap1WowHA5FWeQG4RYz4DKZDCAirNdrGI1GbNzxeAzz+Rw2mw2TERFUKhVwHMf/plqtVkaxmo/YU0hoMpnQYDAgfw0AEkqlErVaLR4eHqLH4+FPZE2OhwAFwRE1cSQZrDO9QKDX64EKg9FoZFSr1W/L5TJAzobPYEl8EAQpKJqCCdQVjo6OGHa7HUjkec/L0jESr4iW8Jyso8VikUSbzQZOp5OdRL1e/0LCOQHyAkPiIXFFI0vdHA4HDys2GAwgl8tVqMhjEn787SszEl7iDQXWJXHn9XrR7XbvKNg2CRdCcJysMcgPV0Og7G1zC0UXRJPoy+7fi9JP9hILhtZZYFgAAAAASUVORK5CYII=');
	background-repeat: no-repeat;
	background-position: 7px 7px;
	text-indent: -100000px;
}
#saper_game div.row .open_lose
{
	border-color: red!important;
	text-indent: 0px!important;
	font-family: cursive;
	font-size: 39px;
	font-weight: normal;
	line-height: 24px;
	color: red;
}

Ссылка на jsfiddle

Автор: Vlad_IT

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js