Предисловие
В последнее время я начал замечать всё больше браузерных игр, которые написаны на связке html+ccs+js. Именно в такой связке, без флеша и других технологий. Не знаю, может их и раньше было много, но замечать всё больше с каждым днём я начал только сейчас. Одна 2048 сколько шуму наделала!
Вообщем, я был вдохновлён, и решил создать что-то простенькое, за пару вечеров, зато своё. За идею взял старые хорошие «пятнашки».
Единственным условием было написание всего исключительно на javascript (используя, правда, JQuery), не используя canvas, WebGL, и другие причуды технологии.
Концепт
Изначально даная затея задумывалась как чисто локальная вещь, без залива куда-либо, но чуть позже я заметил некий интерес к данной разработке в офисе, и решил что надо выкладывать. Первая идея, конечно же — обычный сайт. И вконце-концов это было сделано, и на этом бы всё и закончилось, если бы через пару дней коллега не спросил у меня: «ты писал когда-нибудь расширения для хрома?». Но обо всём попорядку.
Реализация самой игры
Внешний вид
Первоначальной идеей была реализация на html-таблице 4х4. Тут и готовые ячейки, и всё такое. На первый взгляд. При более детальной обдумке оказалось что плюсов у данной задумки никаких нет, и от идеи пришлось отказаться. В результате, я пришёл к одному блоку, внутри которого ещё 15, и у всех выставлено свойство
float:left;
Получилось так:
.game_field{ position: absolute; left: 50%; top: 50%; margin-top: -128px; margin-left: -128px; width: 256px; height: 256px; border-radius: 4px; } .block{ -ms-user-select:none; -moz-user-select:none; -khtml-user-select:none; -webkit-user-select:none; -user-select:none; float: left; width: 60px; height: 60px; margin: 2px 2px 2px 2px; background-color: #F3EDD6; border-radius: 4px; text-align: center; font-size: 250%; font-weight: bold; cursor: pointer; }
Логика
Движение
Самое сложное для меня это была логика перемещения блоков. Возможно, для опытного разработчика всё прояснилось бы мгновенно, но у меня на это ушёл целый вечер. Первой идеей была реализация столкновений: если пусто, то блок продвинется, а если нет — то останется на месте. Уже спустя некоторое время, и десятки форумов, я понял что двигаюсь в каком-то не том направлении. В результате я пришёл к такой реализации: массив на 16 ячеек с булевым значением. 0 — занято, 1 — пусто. Ну а далее всё понятно — даём блокам id с текущей позицией, сравниваем с реальным номером, для движения влево отнимаем 1, вправо — добавляем 1, вверх — минус 4. Если ячейка массива с номером в виде полученой позиции — true, значит двигаем, заменяем текущую на true, а новую на false.
Начальное расположение
Понятное дело, что начальное положение всех блоков должно быть неправильным, чтобы было, собственно говоря, во что играть. В теории: ставим все блоки на 15 позиций, потом рандомно присваиваем им номера, радуемся жизни. На практике: получаем критичный баг. Дело в том, что как оказалось, половина случайных расположений в пятнашках — принципиально непроходимые. Тоесть в результате в половине случаев мы имеем игру в которую выиграть просто невозможно. Я уверен что большинство это и так знает, но лично я — не знал. Решение пришло ввиде совета от второго коллеги: «расставляй правильно, а потом рандомно перемешивай, итераций на 400». Именно так и было реализовано начальное расположение в конце-концов.
mix : function(){ core.check_win = false; for(var i = 0; i < 600; i++){ var num = Math.floor(Math.random() * (4 - 1 + 1)) + 1; var free_pos = 0; for(var j = 1; j<=16;j++){ if(core.table_of_emptify[j] == true){ free_pos = j; break; } } switch (num) { case 1: $('#'+(free_pos-4)).trigger('click'); break; case 2: $('#'+(free_pos+4)).trigger('click'); break; case 3: $('#'+(free_pos-1)).trigger('click'); break; case 4: $('#'+(free_pos+1)).trigger('click'); break; default: break; } } core.check_win = true; }
Расширения для Chrome
После изучения нескольких статей на хабре, оказалось что для создания несложного расширения требуется всего 2 вещи — создание файла manifest.json, и… 5$. Я до сих пор не понимаю до конца логики, но для размещения своего расширения на web магазине хрома, надо единоразово уплатить 5$.
{ "manifest_version": 2, "name": "Fifteen puzzle", "version": "1.0", "description": "A famous fifteen puzzle now in you browser!", "icons": { "32": "32x32.png", "48": "48x48.png", "64": "64x64.png", "128": "128x128.png" }, "browser_action": { "default_title": "Game of 15", "default_icon": "48x48.png", "default_popup": "popup.html" } }
Так как кода сравнительно немного, то смысла заливать его на гитхаб я не вижу, и выкладываю прямо здесь:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" href="css/style.css"> <meta name="description" content="game v 0.001 alpha"> <meta name="author" content="vlreshet"> <script type="text/javascript" src="js/jquery.js"></script> <script type="text/javascript" src="js/timer.jquery.minified.js"></script> <script type="text/javascript" src="js/game.min.js"></script> </head> <body style="width:386px; height:266px; background-color:#CFF2DE;"> <div class="game_field_backdiv"></div> <div class="game_field" border="10" onselectstart="return false"> <div class="block" id="1">1 </div> <div class="block" id="2">2 </div> <div class="block" id="3">3 </div> <div class="block" id="4">4 </div> <div class="block" id="5">5 </div> <div class="block" id="6">6 </div> <div class="block" id="7">7 </div> <div class="block" id="8">8 </div> <div class="block" id="9">9 </div> <div class="block" id="10">10</div> <div class="block" id="11">11</div> <div class="block" id="12">12</div> <div class="block" id="13">13</div> <div class="block" id="14">14</div> <div class="block" id="15">15</div> </div> <div class = "start_button_field"> <div class="button start" id="button">START</div> <div class="win" style="display:none;">YOU WIN!</div> <div class='timer_place timer' id="win_time"></div> </div> <div class="driver_button_background"></div> <div class = "driver_button_field"> <div class="button" id="pause">PAUSE</div> <div class="button" id="reset">RESET</div> <div class="timer" id="timer"></div> </div> </body> </html>
.html{ -ms-user-select:none; -moz-user-select:none; -khtml-user-select:none; -webkit-user-select:none; -user-select:none; } .game_field{ position: absolute; left: 50%; top: 50%; margin-top: -128px; margin-left: -128px; width: 256px; height: 256px; border-radius: 4px; } .game_field_backdiv{ position: absolute; left: 50%; top: 50%; margin-top: -133px; margin-left: -133px; width: 266px; height: 266px; border-radius: 10px; background-color: #20C0D9; opacity: 0.7; } .block{ -ms-user-select:none; -moz-user-select:none; -khtml-user-select:none; -webkit-user-select:none; -user-select:none; float: left; width: 60px; height: 60px; margin: 2px 2px 2px 2px; background-color: #F3EDD6; border-radius: 4px; text-align: center; font-size: 250%; font-weight: bold; cursor: pointer; } table{ text-align: center; } .false{ background-color: #F7A603; } .true{ background-image: -webkit-gradient( linear, left top, left bottom, color-stop(0, #39F046), color-stop(1, #76ED7E), color-stop(1, #9AFFA4) ); background-image: -o-linear-gradient(bottom, #39F046 0%, #76ED7E 100%, #9AFFA4 100%); background-image: -moz-linear-gradient(bottom, #39F046 0%, #76ED7E 100%, #9AFFA4 100%); background-image: -webkit-linear-gradient(bottom, #39F046 0%, #76ED7E 100%, #9AFFA4 100%); background-image: -ms-linear-gradient(bottom, #39F046 0%, #76ED7E 100%, #9AFFA4 100%); background-image: linear-gradient(to bottom, #39F046 0%, #76ED7E 100%, #9AFFA4 100%); } .start_button_field{ top: 50%; margin-top: -132px; background-color: #E4DDE4; opacity: .9; position: absolute; left: 50%; margin-left: -132px; width: 264px; height: 264px; border-radius: 4px; } .button { -ms-user-select:none; -moz-user-select:none; -khtml-user-select:none; -webkit-user-select:none; -user-select:none; -moz-box-shadow:inset 0px 0px 9px 0px #c1ed9c; -webkit-box-shadow:inset 0px 0px 9px 0px #c1ed9c; box-shadow:inset 0px 0px 9px 0px #c1ed9c; background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #9dce2c), color-stop(1, #8cb82b) ); background:-moz-linear-gradient( center top, #9dce2c 5%, #8cb82b 100% ); filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9dce2c', endColorstr='#8cb82b'); background-color:#9dce2c; border-radius: 8px; border-bottom-left-radius:8px; text-indent:0px; display:inline-block; color:#ffffff; font-family:Arial; font-size:20px; font-weight:bold; font-style:normal; height:35px; line-height:35px; width:96px; text-decoration:none; text-align:center; text-shadow:1px 1px 0px #689324; } .button:hover { background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #8cb82b), color-stop(1, #9dce2c) ); background:-moz-linear-gradient( center top, #8cb82b 5%, #9dce2c 100% ); filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#8cb82b', endColorstr='#9dce2c'); background-color:#8cb82b; cursor: pointer; } .start{ position: relative; left: 50%; margin-left: -48px; top:40%; margin-top: 9px; } #win_time{ display: none; } .timer_place{ color:#19FE0B; font-family:Arial; font-size:20px; font-weight:bold; font-style:normal; height:35px; line-height:35px; width:96px; position: relative; text-decoration:none; text-align:center; left: 50%; top:40%; margin-left: -48px; margin-top: -10px; } .win{ display: none; color:#19FE0B; font-family:Arial; font-size:20px; font-weight:bold; font-style:normal; height:35px; line-height:35px; width:96px; position: relative; text-decoration:none; text-align:center; left: 50%; top:40%; margin-left: -48px; margin-top: -25px; } .driver_button_field{ display: none; position: absolute; border-radius: 4px; width: 105px; height: 110px; border-radius: 8px; left: 60%; top: 50%; margin-top: -133px; } .driver_button_background{ display: none; position: absolute; border-radius: 4px; background-color: #20C0D9; opacity: 0.7; width: 115px; height: 130px; border-radius: 8px; left: 60%; top: 50%; margin-top: -133px; } #pause{ margin-left: 10px; margin-top: 5px; } #reset{ margin-left: 10px; margin-top: 5px; } #timer{ box-shadow: 0px 0px 14px 0px #5D6FE5 inset; -ms-user-select:none; -moz-user-select:none; -khtml-user-select:none; -webkit-user-select:none; -user-select:none; border: 1px #22C3DD solid; background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #20C0D9), color-stop(1, #22C3DD) ); background:-moz-linear-gradient( center top, #20C0D9 5%, #22C3DD 100% ); filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#20C0D9', endColorstr='#22C3DD'); background-color:#20C0D9; border-radius: 8px; text-indent:0px; display:inline-block; color:#ffffff; font-family:Arial; font-size:20px; font-weight:bold; font-style:normal; height:35px; line-height:35px; width:96px; text-decoration:none; text-align:center; text-shadow:1px 1px 0px #689324; margin-left: 10px; margin-top: 5px; }
var handlersSetter = { setHandlers : function(){ $('#button').on('click', function(){ $('.start_button_field').hide('fast'); $('.driver_button_background').show('fast'); $('.driver_button_field').show('fast'); }); $('.start').on('click', function(){ $('.timer').timer('start'); $(this).addClass('hidden'); $('.start_button').hide(); handlersSetter.paused = false; }); $('#reset').on('click', function(){ core.set_default(); $('.timer').timer('start'); $('.timer').timer('reset'); $('.start_button_field').hide('fast'); $('.start').show(); $('.win').hide(); $('#win_time').hide(); }); $('#pause').on('click', function(){ if(!handlersSetter.paused){ $('.timer').timer('pause'); handlersSetter.paused = true; $('.start_button_field').show(); } }); $("body").on("click",".block",function(e){ var position = parseFloat(e.currentTarget['id']); if(core.check_top(position)){ core.replace(position, -64, 0, -4); } if(core.check_bottom(position)){ core.replace(position, 64, 0, 4); } if(core.check_right(position)){ core.replace(position, 0, 64, 1); } if(core.check_left(position)){ core.replace(position, 0, -64, -1); } }); } } var core = { check_win : false, replace : function(position, func_top, func_left, func_position){ var obj = $('#'+position); var left = obj.offset().left; var top = obj.offset().top; new_position = new Object(); new_position.top = top + func_top; new_position.left = left + func_left; core.table_of_emptify[position] = true; core.table_of_emptify[position+func_position] = false; obj.attr("id",(position+func_position)); new_position.left = left + func_left; obj.offset(new_position); core.check_pos(position+func_position); }, setEmptifyTable : function(func){ core.table_of_emptify = [false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true]; func(); }, mix : function(){ core.check_win = false; for(var i = 0; i < 600; i++){ var num = Math.floor(Math.random() * (4 - 1 + 1)) + 1; var free_pos = 0; for(var j = 1; j<=16;j++){ if(core.table_of_emptify[j] == true){ free_pos = j; break; } } switch (num) { case 1: $('#'+(free_pos-4)).trigger('click'); break; case 2: $('#'+(free_pos+4)).trigger('click'); break; case 3: $('#'+(free_pos-1)).trigger('click'); break; case 4: $('#'+(free_pos+1)).trigger('click'); break; default: break; } } core.check_win = true; }, check_top : function (position){ target_position = parseFloat(position) - 4; if(target_position > 0){ return(core.table_of_emptify[target_position]); }else{ return false; } }, check_bottom : function (position){ target_position = parseFloat(position) + 4; if(target_position <= 16){ return(core.table_of_emptify[target_position]); }else{ return false; } }, check_left : function (position){ target_position = parseFloat(position) - 1; if((target_position != 0)&&(target_position != 4)&&(target_position != 8)&&(target_position != 12)){ return(core.table_of_emptify[target_position]); }else{ return false; } }, check_right : function (position){ target_position = parseFloat(position) + 1; if((target_position != 1)&&(target_position != 5)&&(target_position != 9)&&(target_position != 13)){ return(core.table_of_emptify[target_position]); }else{ return false; } }, check_pos : function (pos){ var obj = $('#'+pos); if(obj.html() == pos){ obj.attr('class','block true'); }else{ obj.attr('class','block false'); } if(!core.check_win){ return; } if($('#15').html() == '15'){ var flag = true; for(var i = 1; i <= 15; i++){ if($('#'+i).html() != i){ flag = false; break; } } if(flag){ $('.start').hide(); $('.start_button_field').show(); $('.win').show(); $('#win_time').show(); $('.timer').timer('pause'); } } }, set_default : function(){ $('.game_field').html('<div class="block" id="1">1 </div><div class="block" id="2">2 </div><div class="block" id="3">3 </div><div class="block" id="4">4 </div><div class="block" id="5">5 </div><div class="block" id="6">6 </div><div class="block" id="7">7 </div><div class="block" id="8">8 </div><div class="block" id="9">9 </div><div class="block" id="10">10</div><div class="block" id="11">11</div><div class="block" id="12">12</div><div class="block" id="13">13</div><div class="block" id="14">14</div><div class="block" id="15">15</div>'); for(var i = 1; i <= 15; i++){ if($('#'+i).html() == i){ $('#'+i).attr('class','block true'); }else{ $('#'+i).attr('class','block false'); } } core.setEmptifyTable(core.mix); }, init : function(){ handlersSetter.setHandlers(); core.setEmptifyTable(core.mix); } }
Ссылка на расширение
Ссылка на чесно заюзаный js-плагин для реализации таймера
Ссылка на первоначальный сайт
P.S. можно собрать данное расширение вручную, вставить в Opera 20+, и оно отлично будет работать (chromium же). Расширение в Opera Store сейчас на модерации
ссылка на оригинал статьи http://habrahabr.ru/post/227547/
Добавить комментарий