пятница, 2 декабря 2011 г.

Товары из 90-ых

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

В девяностые годы мы радовались всему несоветскому, которое только начало без разбора завозиться в нашу страну отовсюду. И советскому, успешно маскирующемуся под западное.
Любое поколение, вырастая, начинает вспоминать свое детство и скучать по нему, каким бы трудным и тяжелым оно ни было. Сейчас стали взрослыми те, кто рос в девяностые, и окунулись в ностальгию по временам, когда деревья были большими, а нежующаяся твердокаменная жвачка Turbo - роскошной сладостью. В этом материале собрано то, что так любили есть, пить и жевать нынешние 25-30-летние.

Сетевая игра в NES (Dendy) реализованная на Flash P2P

image

С момента запуска nesbox.com прошло уже более полутора лет, все это время меня не покидала идея сделать игру для двоих через интернет. Если интересно, как мы пришли к рабочему варианту, добро пожаловать под кат.

Начну с того, как работает наш эмулятор. Для комфортной игры он должен отрабатывать ~50-60 кадров в секунду. На каждый кадр мы передаем состояние обоих джойстиков, в виде 2 байт, в котором установленный бит отвечает за одну из кнопок джойстика, а на выходе получаем заполненный экранный буфер 256x240, и звуковой ряд размером 44100/60~=735 семплов.

image

Как видно, интерфейс простейший, но чтобы заставить все это работать по сети, мы должны отправить состояние джойстика первого игрока второму игроку, получить его состояние джойстика, и после этого запустить генерацию кадра экрана со звуком. И это должно происходить 50-60 раз в секунду.

Первым прототипом была схема с примитивным С++ сервером, который получал через Flash Sockets данные о джойстиках, и пересылал их обоим клиентам. Прототип был вполне рабочим, но играть по сети могли пользователи которые находились вблизи сервера nesbox.com (ping ~ 60ms). Были мысли выложить его в open source, чтобы люди сами его собирали и запускали на своих серверах в непосредственной близости. Но тут Adobe выпускает RTMFP протокол и технологию Cirrus, которая решает все наши проблемы, и теперь мы можем подключить оба эмулятора через P2P, и пропадает проблема с пингом. По крайней мере, люди в пределах одного города могут комфортно играть вдвоём.

image

Начать работу с Flash P2P элементарно, весь процесс уже описывался на хабре: Adobe Flash Player и передача потоковых данных без участия сервера, повторяться не будем. Общий смысл такой, мы подключаемся к Cirrus серверу, получаем 256-битный id, отсылаем их друг другу. Все, теперь можно слать данные peer-to-peer, что и реализовано в нашем эмуляторе.

Чтобы опробовать данную технологию и поиграть вдвоем в интренете предлагаю выбрать один из 16000 ромов на сайте nesbox.com и запустить эмуляцию. Далеко не все ромы рабочие, поэтому выбирайте зеленые с тегом verified.

image

Выбираем two players via internet.

image

Копируем полученный урл в буфер обмена и отсылаем второму игроку. Если все в порядке с конфигурациями ваших сетей, игра начнется через несколько секунд, если нет, соединение можно протестировать тут http://cc.rtmfp.net.

Моя мечта сбылась (и не только моя, очень много людей просили об этом), теперь можно играть в браузере в игрушки денди по сети с другом!

Ниже приведу несколько ссылок на популярные игры для двух игроков. На все ваши вопросы и пожелания отвечу в комментариях.

Battle City
image


Battletoads & Double Dragon — The Ultimate Team
image


Contra
image


Chip 'n Dale Rescue Rangers
image

Расширения для Google Chrome. Часть первая. Getting started

Я хочу написать цикл статей о создании расширений для Google Chrome. К этому меня побуждает, во-первых, практическая польза самого процесса разработки и последующего использования: вы сами определяете, какие ещё задачи хотите решить не выходя из браузера и, во-вторых, отсутствие каких-либо внятных гайдов, туториалов и справочников на русском языке, за исключением, пожалуй, этой и вот этой статей на Хабре. Основная цель цикла — систематизировать разрозненную информацию и облегчить поиск потенциальным разработчикам. :)

В первой (этой, то бишь) статье, на примере простейшего расширения, будут рассмотрены все основные моменты, связанные с разработкой, отладкой и использованием расширения, конфигурационный файл manifest.json и начала chrome.* API. Первая же статья, думаю, будет не очень полезна опытным разработчикам (это дисклеймер).


Hello world


Лучшая теория — практика, а поэтому, не откладывая в долгий ящик, создаём папку hello_world, а в ней текстовый документ manifest.json и печатаем туда следующий код:
{
    "name" : "Hello world", //Название расширения
    "version" : "1.0", //Версия
    "description" : "This is a simple chrome extention" //Краткое описание
}

Это — программа минимум. Если зайти в «Гаечный ключ → Инструменты → Расширения», установить галку «Режим разработчика», нажать кнопку «Загрузить распакованное расширение...» и указать нашу папку «Hello world», то расширение появится в списке установленных, но делать, естественно, оно пока ничего не будет, потому как не умеет.

image

Учим и отлаживаем


А раз не умеет — надо научить. Отредактируем manifest.json:
{
    "name" : "Hello world", 
    "version" : "1.0", 
    "description" : "This is a simple chrome extention",
    "background_page" : "background.html"
}

И создадим файл background.html в котором будет написан сценарий, выполняемый в фоновом режиме. Например, такой:
<script type="application/javascript">
    window.onload = function() {
        window.setInterval( function() {
            console.log("Hello world");
        }, 10000);
    }
script>

Сценарий в background.html будет выполнен один раз, при старте браузера и расширения, то есть, при открытии ещё одной вкладки или окна, повторное исполнение сценария не произойдёт. В нашем случае он каждые 10 секунд будет писать в консоль сакраментальную фразу, и это, кстати, надо бы проверить.

Для отладки удобно использовать служебную страницу chrome://extensions/ со включённым режимом разработчика.

image

В принципе, она дублирует функционал страницы управления расширениями из «Гаечного ключа», но мне, субъективно, нравится больше. Как-то компактнее, что ли?

Здесь нас интересуют две позиции: строка «ID» с внутренним идентификатором расширения и подраздел «Проверить активные режимы просмотра» в котором мы видим созданный нами background.html и, щёлкнув по нему, можем проконтролировать исполняемый сценарий.

Смотрим и убеждаемся, что сценарий исправно пишет в консоль хэллоуворлды:

image

Обратите внимание на заголовок формата chrome-extension://ID/filename. Зная идентификатор расширения таким образом можно добраться до любого его файла. Опять же удобно в процессе отладки расширения.

Взаимодействие с браузером


Пока наше расширение представляет эдакую вещь в себе, исполняя в фоне некий сценарий. Для того, чтобы оно начало взаимодействовать с браузером и его компонентами нужно познакомиться с chrome.* API. Так, например, для взаимодействия с окном браузера используются методы chrome.browserAction, а значения по умолчанию задаются в manifest.json следующим образом:
{
    "name" : "Hello world", 
    "version" : "1.0", 
    "description" : "This is a simple chrome extention",
    "background_page" : "background.html",

    "browser_action" : {
        "default_title" : "Hello world!!!", //Текст, всплывающий при наведении курсора на иконку (если не задан, то всплывает название расширения)
        "default_icon" : "img/icon_world.png", //Иконка для панели расширений (по умолчанию)
        "default_popup" : "popup.html" //Всплывающее окно при клике на иконке
    }
}

Не забываем создать popup.html (пока оставим его пустым) и положить иконку в папку img, щёлкаем на «Перезагрузить» на странице chrome://extensions/ и смотрим на результат. Иконка нашего расширения появилась на панели расширений, а при клике на неё возникает пустое всплывающее окошко.

image

Иконка для тех, кто проходит по шагам:
image

Всё это управлябельно с помощью методов chrome.browserAction из сценариев:
<script type="text/javascript">
    chrome.browserAction.setTitle({title:"New title"}); //Устанавливает новый всплывающий при наведении на иконку текст
    chrome.browserAction.setPopup({popup:"new_popup.html"}); //Устанавливает новое всплывающее окно при клике на иконке
    chrome.browserAction.setIcon({path:"new_icon.png"}); //Устанавливает новую иконку
    chrome.browserAction.setBadgeText({text:"text"}); //Устанавливает текст поверх иконки
    chrome.browserAction.setBadgeBackgroundColor({color:[0,0,0]}); //Устанавливает фон текста поверх иконки
script>


Для практике давайте заставим background.html сделать что-нибудь полезное, вместо того, чтобы просто гадить в консоль. Вот, хотя бы часы. Поверх иконки будет отображаться количество минут, при наведении — время в формате ЧЧ: ММ: СС, а во всплывающем окошке — часы со стрелками.

background.html
<script type="application/javascript">
    window.onload = function() {
        window.setInterval( function() {
            var now = new Date();
            var h = now.getHours();
            var m = now.getMinutes();
            var s = now.getSeconds();
            
            var badge_text = (m < 10 ? "0" + m : m).toString();
            var title_text = (h < 10 ? "0" + h : h) + ":" + (m < 10 ? "0" + m : m) + ":" + (s < 10 ? "0" + s : s);
            
            chrome.browserAction.setBadgeText({text: badge_text});
            chrome.browserAction.setTitle({title: title_text});
        }, 1000);
    }
script>


popup.html

<html>
    <head>
        <style type="text/css">
            * { margin: 0; padding: 0; border: 0; }
            body { background: #000; }
        style>
        <script type="text/javascript">
            Clock = function() {
                this.canvas = false;
                this.pi = Math.PI;
            }

            Clock.prototype = {

                get_time: function() {
                    var now = new Date();
                    var result = {
                        milliseconds: now.getMilliseconds(),
                        seconds: now.getSeconds(),
                        minutes: now.getMinutes(),
                        hours: now.getHours()
                    }
                    return result;
                },

                init: function() {
                    this.canvas = document.getElementById("clock").getContext("2d");
                },
    
                draw: function() {		
                    var now = this.get_time();
                    var hangle = (this.pi/6)*now.hours + (this.pi/360)*now.minutes + (this.pi/21600)*now.seconds + (this.pi/21600000)*now.milliseconds;
                    var mangle = (this.pi/30)*now.minutes + (this.pi/1800)*now.seconds + (this.pi/1800000)*now.milliseconds;
                    var sangle = (this.pi/30)*now.seconds + (this.pi/30000)*now.milliseconds;
        
                    this.canvas.save();
                    this.canvas.fillStyle = "#000";
                    this.canvas.strokeStyle = "#000";
                    this.canvas.clearRect(0,0,200,200);
                    this.canvas.fillRect(0,0,200,200);
                    this.canvas.translate(100,100);
                    this.canvas.rotate(-this.pi/2);		
        
                    this.canvas.save();
                    this.canvas.rotate(hangle);
                    this.canvas.lineWidth = 8;
                    this.canvas.strokeStyle = "#ffffff";
                    this.canvas.fillStyle = "#ffffff";
                    this.canvas.lineCap = "round";
                    this.canvas.beginPath();
                    this.canvas.moveTo(-10,0);
                    this.canvas.lineTo(50,0);
                    this.canvas.stroke();
                    this.canvas.restore();

                    this.canvas.save();
                    this.canvas.rotate(mangle);
                    this.canvas.lineWidth = 4;
                    this.canvas.strokeStyle = "#ffffff";
                    this.canvas.lineCap = "square";
                    this.canvas.beginPath();
                    this.canvas.moveTo(-20,0);
                    this.canvas.lineTo(75,0);
                    this.canvas.stroke();
                    this.canvas.restore();

                    this.canvas.save();
                    this.canvas.lineWidth = 2;
                    this.canvas.strokeStyle = "#ffffff";
                    this.canvas.fillStyle = "#333";
                    this.canvas.beginPath();
                    this.canvas.arc(0,0,8,0,this.pi*2,true);
                    this.canvas.fill();
                    this.canvas.stroke();
                    this.canvas.restore();		
        
                    this.canvas.save();
                    this.canvas.rotate(sangle);
                    this.canvas.lineWidth = 2;
                    this.canvas.strokeStyle = "#ff0000";
                    this.canvas.lineCap = "square";
                    this.canvas.beginPath();
                    this.canvas.moveTo(-30,0);
                    this.canvas.lineTo(85,0);
                    this.canvas.stroke();
                    this.canvas.restore();		

                    this.canvas.save();
                    this.canvas.lineWidth = 6;
                    this.canvas.fillStyle = "#ff0000";
                    this.canvas.beginPath();
                    this.canvas.arc(0,0,3,0,this.pi*2,true);
                    this.canvas.fill();
                    this.canvas.restore();
        
                    this.canvas.save();
                    this.canvas.lineWidth = 6;
                    this.canvas.strokeStyle = "#ffffff";
                    this.canvas.beginPath();
                    this.canvas.arc(0,0,95,0,this.pi*2,true);
                    this.canvas.stroke();
                    this.canvas.restore();
        
                    this.canvas.restore();
                }
        
            }

            window.onload = function() {
                var clock = new Clock();
                clock.init();
                window.setInterval(function() {
                    clock.draw();
                }, 10);
            }		
        script>
    head>
    <body>
         id="clock" width="200" height="200">
    body>
html>


Сохраняем, перезапускаем, проверяем — красота!
image

Собственно, мы сделали простое расширение (а заодно и canvas припомнили). Для Getting Started, во всяком случае, достаточно. Осталось только привести его к годному для распространения виду — упаковать. Для этого на той же странице chrome://extensions/ давим на «Упаковка расширений...», указываем корневой каталог (тот, где лежит manifest.json), давим «Ок» и получаем файл формата *.crx на выходе. Это и есть наше упакованное расширение. Открыв его с помощью Хрома, мы установим расширение.

Упакованный пример из статьи для установки
Архив с исходниками

В следующей статье цикла я планирую подробно разобрать chrome.* API, а в дальнейшем — взаимодействие с различными сайтами и использование локальных хранилищ данных. Если вы считаете, что я что-то упустил в азах или у вас есть пожелания по поводу следующих статей цикла — прошу изложить их в комментариях.

See ya!

Десяток ресурсов, которые помогают быть дизайнером

Работать дизайнером очень интересно. Это творческая работа. И как любому творческому человеку, дизайнеру нужна муза или вдохновение.

Я уверен, что у каждого дизайнера есть набор сайтов, на которые они периодически заходят для того что бы черпать это самое вдохновение. У меня так же есть такой список. И я хочу им с вами поделиться.

Естественно, если вы расскажите о своих ресурсах, я буду благодарен.

DIY или Сделай Сам

Делаем приватный монитор из старого LCD монитора 

Вы наконец-то можете сделать кое-что со своим старым LCD монитором

День зеленого робота

Репортаж с конференции Google Mobile Day

Клич «Google Mobile Day», сотрясший воздух в конце октября, почему-то сразу вызвал в воображении образ множества разноцветных пуфиков и такое же множество шустрых молодых людей с ноутбуками, тусующихся на них. Очевидно, воображение восстановило в памяти фотографии разноцветных заморских офисов софтверного гиганта и удивительные описания ни на что не похожего стиля работы в них. Что-то в этом роде.

Как Microsoft на пенсию XP провожала

Необычное мероприятие провела компания Microsoft в конце прошлой недели. «Товарищи журналисты» были вызваны соответствующей телеграммой на торжественные проводы самой известной операционной системы софтверного гиганта — Windows XP. Убеленную сединами ОС Microsoft провожали на пенсию всем офисом, все равно, что заслуженного сотрудника. Отсюда и стиль мероприятия — этакое ретро с нотками социалистической ностальгии.

Biostar TA75A+ и Biostar TA75M+ полноразмерная и microATX-модели под Socket FM1

Компания Biostar — фактически единственная из оставшихся на рынке «классических» производителей системных плат третьего эшелона. Когда-то компаний с таким бизнесом было не меньше десятка, а сейчас даже гранды рынка (и они-то, пожалуй, в первую очередь) вовсю внедряют в свои продуктовые линейки самые разные продукты. Которые порою не имеют отношения уже не только к компонентам ПК, но и вообще к персональным компьютерам. У Biostar, конечно, тоже есть «для порядка» непрофильная продукция, например, видеокарты и мини-ПК, но основа все равно — системные платы.

25-дюймовый монитор LG DX2500 не требует специальных очков для просмотра 3D

Компания LG Electronics расширила каталог 3D-мониторов, не требующих специальных очков, 25-дюймовой моделью DX2500. Новинка использует параллаксный барьер для формирования объемного изображения и прошла сертификацию TÜV Rheinland Group.

Продажи одноплатного компьютера Raspberry Pi стоимостью $25 начнутся в декабре

Продажи одноплатного компьютера Raspberry Pi стоимостью $25 начнутся в декабре

Проект по выпуску одноплатного компьютера Raspberry Pi уверенно приближается к тому моменту, когда все желающие смогут заполучить в свои руки миниатюрное изделие на ARM-совместимом процессоре. Плата компьютера, работающего под управлением Linux, по размерам совпадает с кредиткой — 85,60 x 53,98 мм. Цена малютки, поддерживающей видео высокой четкости H.264 в формате 1080p с кадровой частотой 30 к/с, в зависимости от конфигурации, составляет $25 или $35.

Зима на нуле

Погода в начале декабря ожидается не по-зимнему теплая: температура, как прогнозируют метеорологи, будет колебаться около 0°С. Небо закроют облака, пойдет дождь с мокрым снегом.