0,(9) апреля

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

Особенно грустно, когда этим занимаются новостные издания, которым остальные 360 с гаком дней в году читатель привыкает верить.

Просто замечательно, когда авторы проекта продумывают шутку так, чтобы встретив её в любой другой день в году, посетитель догадаться бы, что это шутка, например, «Яндекс.Карты» сделали панораму Марианской впадины, это здорово (в специальном посте на «Хабре» собираются шутки со всего интернета).

Но моё сердце отдано картам «Гугла», эти ребята сделали карты в стиле старых «восьмибитных» игр, волшебно.

Восьмибитные карты (186.85КиБ)

Ну а я первого апреля традиционно серьёзен, как стадо бизонов.

8 комментариев

ДОСбокс и «Гопник»

Когда-то мне нравилась незамысловатая ДОС-игра «Гопник» (там аж две части было), решил показать ребятам с работы что это такое. Было бы просто это сделать, будь у меня компьютер в «Виндоуз», но у меня «Мак».

Вспомнил, что есть такая штука как DOSBox («ДОСбокс»), позволяет запускать ДОС-игры под другими операционками.

«Гопник» под «Мак» (16.50КиБ)

Чтобы её поставить под «Мак», сначала надо установить «хоумбрю» (если у вас его ещё нет), потом в консоли последовательно набрать:

brew tap homebrew/games
brew install dosbox
# долго скачиваются разные штуки, ставятся библиотеки
sed -ie 's/keyboardlayout=auto/keyboardlayout=RU/' "$(dosbox -printconf)"
# заменяем кодировку на русскую, иначе будут проблемы со шрифтами
dosbox
# запускаем ДОСБокс, там пишем:
mount c /каталог на диске где у вас игры
# переходим на диск C:
C:
# запускаем игру
gopnik.exe

Готово, теперь у вас в окне искомая игра.

12 комментариев

strrev для UTF-8

В ходе обсуждения одной книге на «Хабре» придумал как развернуть строку UTF-8. Просто так её не перевернёшь — строка многобайтная, порядок байт важен.

Вот что я придумал: перевожу в двухбайтную кодировку UTF-16 с одним порядком байт, потом «strrev» переворачивает байты, а потом я конвертирую из кодировки с обратным порядков байт:

$test = 'А роза упала на лапу Азора ウィキ';

$test = iconv('utf-8', 'utf-16le', $test);
$test = strrev($test);
// キィウ арозА упал ан алапу азор А
echo iconv('utf-16be', 'utf-8', $test);

56 комментариев

Казань ✈ Москва ✈ Бангкок → Паттайя → Бангкок ✈ Москва ✈ Казань

Ура, удалось вырваться в короткий отпуск (загруженность высокая, поэтому и пишу «вырваться»). С 7 по 15 мая будем в Паттайе (Таиланд) в отеле «Шератон» 5★. Это относительно новый отель с собственными пляжем, номер будет с видом на море.

Шератон Паттайя (113.62КиБ)

Снимок взят с сайта отеля.

15 комментариев

Переход на «Гоу 1»

С выходом первой версии языка программирования «Гоу» посмотрел как компилируются мои программы. Не компилируются никак. Весь набор утилит поменяли, теперь всё делается через утилиту «go». С одной стороны хорошо — раньше, чтобы просто запутсить код нужно было много танцев проделать, а сейчас «go run code.go», и всё, он работает.

С другой стороны, мне все make-файлы придётся переписать, надо ещё разобраться как всё теперь работает, но самое плохое вот что:

Finally, the Go tool chain (compilers, linkers, build tools, and so on) are under active development and may change behavior. This means, for instance, that scripts that depend on the location and properties of the tools may be broken by a point release.

Для тех, кто не понимает английский — набор утилит «Гоу» находится в стадии разработки и ещё может измениться. Это плохо очень. Я не хотел бы с выходом каждой новой версии смотреть не сломалось ли у меня что-то.

4 комментария

Работа с камерой и распознавание чисел в «Опере»

Распозновалка (43.68КиБ)

Я вчера заморочился на отличненько. Мне показалось скучным и грустным вводить цифры счёта за квартиру в окошко «Госуслуг» и решил сделать автоматическую распознавалку чисел, попутно изучив работу с камерой из браузера.

Доступ к камере — эксперментальный стандарт, поэтому мало кто из браузеров может им похвастаться, я использовал специальный билд «Opera-Labs-Camera», который можно взять с сайта снапшотов «Оперы». Работать мой пример, соответственно, будет только там.

АПИ оказалось несложным. Всё, что нужно сделать — соединить специальным образом тег VIDEO с источником стримового видео; потом я 10 раз в секунд забираю кадр с видео и кладу в CANVAS (на скриншоте он цветной), оттуда вырезаю небольшую область, перевожу в градации серого, беру за порог 75% от усреднённого цвета и перевожу всё в ч/б.

Больше всего из трёх часов, которые у меня ушли на это, я делал распознование цифр. Алгоритмы, которые я нашёл в интернете, были очень монструозными, да и переписывать готовое было скучно, поэтом я перепробовал несколько вариантов, которые пришли мне в голову.

В итоге, лучше всех распознаёт следующий алгоритм.

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

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

На скриншоте в большом текстовом поле записаны получившиеся комбинации. Первое — номер по порядку, далее (через двоеточие) — количество переходов по вертикали и потом описанная последовательность. Например, «ноль» записывается как «2:11» (два перехода по вертикали, потом одинаковые переходы по разу слева и справа разделительной линии).

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

9 комментариев

ФлешБэк

Все уже знают о существовании ботнета из полумиллиона «Маков»?

Проверить не заражён ли ваш «Мак» можно на специальном сайте. Я написал небольшой скрипт, который надо запускать из консоли, если лень шариться по менюшкам в поисках UUID вашей машины:

#!/bin/bash

UUID=$(/usr/sbin/system_profiler SPHardwareDataType | /usr/bin/awk '/UUID/ {print $3}')
result=$(/usr/bin/curl "http://flashbackcheck.com/check/?uuid=$UUID" 2>&-)

echo -n "Your system status is "

case "$result" in
    "")    echo NA.;;
    clean) echo -e "\033[1;32mclean.\033[0m";;
    *)     echo -e "\033[1;31minfected.\033[0m";;
esac

Либо можно сразу скачать его на запуск с «Гитхаба»:

curl -s https://raw.github.com/bolknote/shellgames/master/checkflashback.sh | /bin/bash

У меня компьютер чистый.

15 комментариев

Угадай закон

Минздравсоцразвития собирается внести законопроект о защите здоровья населения от табачного дыма, сообщается на Вести.ru… С 2014 года предлагается ввести запрет курения на рабочих местах в помещении, в общественном транспорте.

Пишет портал «ПроГород» и куча других изданий. Ммм? Что? Ещё 10 июля 2001 года был принят «Об ограничении курения табака» (№87-ФЗ) в статье №6 говорится:

Запрещается курение табака на рабочих местах, в городском и пригородном транспорте… за исключением курения табака в специально отведенных местах для курения табака

Из той же статьи:

— Мы надеемся, что Минздрав запретит еще и курение в подъездах, — сказал Дмитрий Янин, председатель правления Международной конфедерации обществ потребителей. — Пока закон не защищает нас от курящих соседей, из-за которых всем остальным приходится дышать дымом.

Как же не защищает? Курение в подъезде является нарушением п. 9.1 СанПин 2.1.2.1002-00 «Санитарно-эпидемиологические требования к жилым зданиям и помещениям». Наказание — штраф.

47 комментариев

Nuzhno vvesti bukvy

Не те буквы (17.36КиБ)

Регистрация на сайте какой-то авиакомпании (я столько билетов покупал в последнее время, что не помню какой). Ладно я догадался, что сайт за буквы только латиницу держит. А простые пользователи могут и не догадаться.

20 комментариев

UserJS для воспроизведения H.264 в «Опере»

Существует плагин «DivX web player», который отлично подменяет тег «VIDEO», в случае, если должен быть использован кодек H.264, но браузер его не поддерживает.

К сожалению, в «Опере» он почему-то не работает. Я сделал UserJS, который, пользуясь этим плагином, подменяет видеотег, если у него указан тип «video/mp4». Все атрибуты он не переносит (я просто пока не столкнулся с тем, что это нужно), буду добавлять, если столкнусь. Кроме того, я подменяю метод «canPlayType», чтобы показать, что браузер H.264 поддерживает.

Самая засада — это тег «VIDEO» без размеров. Мне для поддержки этого приходтся запускать проигрывание и когда становятся доступны медиаданные, останавливать воспроизведение и переписывать размеры видео.

// ==UserScript==
// @name        XVid patch
// @author      Evgeny Stepanischev aka Bolk
// @version     1.00
// @namespace   /files/opera-xvid-patch.js
// @modified    2012-04-14
// @include     http://*
// ==/UserScript==

!function () {
    var htmlif = function (attr, attrname) {
        return attr ? attrname + '="' + attr + '" ' : '';
    }

    var patchtag = function (o, src) {
        var html = '<embed type="video/divx" '+
        'custommode="none" ';

        if (o.preload == 'auto') html += 'bufferingMode="full" '; else
        if (o.preload == 'none') html += 'bufferingMode="null" ';

        html += htmlif(o.poster, 'previewImage')+
                htmlif(o.id, 'id')+
                htmlif(o.name, 'name')+
                htmlif(src || o.src, 'src')+
                htmlif(o.controls ? 'full' : 'null', 'mode')+
                htmlif(o.width, 'width')+
                htmlif(o.height, 'height');

        if (!o.height || !o.width) {
            html += 'onloadedmetadata="this.width=this.videoWidth;this.height=this.videoHeight" ';
            html += htmlif(!o.autoplay && 'if (!this._sized) {this.pause(); this.currentTime=0; this._sized=1}', 'onplay');

            html += 'autoPlay=true ';
        } else {
            html += htmlif(o.autoplay ? 'true' : 'false', 'autoPlay');
        }

        html += 'pluginspage="http://go.divx.com/plugin/download/"></embed>';

        o.outerHTML = html;
    };

    var patch = function () {
        window.opera.addEventListener('BeforeScript', function() {

            with (window.HTMLVideoElement)
            if (prototype._canPlayType === undefined) {
                prototype._canPlayType = prototype.canPlayType;

                prototype.canPlayType = function (type) {
                    if (type.substr(0, 9) == 'video/mp4') {
                        return 'probably';
                    }

                    return this._canPlayType(type);
                }
            }

            !function() {
                var videos = document.getElementsByTagName('VIDEO');

                for (var i = 0, vl = videos.length; i<vl; i++) {
                    if (!videos[i]) continue;

                    if (videos[i].type && videos[i].type.substr(0, 9) == 'video/mp4') {

                    } else {
                        var sources = videos[i].getElementsByTagName('source');

                        var foundSupported = false, foundMP4 = false;

                        for (var j = 0, sl = sources.length; j < sl; j++) {
                            if (videos[i]._canPlayType(sources[j].type) !== '') {
                                foundSupported = true;
                                break;
                            }

                            if (sources[j].type.substr(0, 9) == 'video/mp4') {
                                foundMP4 = sources[j].src;
                            }
                        }

                        if (!foundSupported && foundMP4) {
                            patchtag(videos[i], foundMP4);
                        }
                    }
                }
            }();
        });
    };

    for (var i = 0, l = navigator.plugins.length; i < l; i++) {
        if (navigator.plugins[i].filename == 'DivXBrowserPlugin.plugin') {
            return patch();
        }
    }
}();

Добавлено: в комментариях справдливо указали на недостаток — перехватывать надо не только BeforeScript. Исправлять не буду до первого реального случая, когда где-то этого события не будет достаточно.

13 комментариев

Чем плох ПХП

На «Хабре» появился перевод статьи «PHP: фрактал плохого дизайна». Отличная статья, расставляющая все точки над «ё» в этом вопросе. Кстати, вот штука, которую я не знал:

bolk@Bolk ~  $ php <<PHP
> <? 
> function __lambda_func() {
> };
> 
> create_function('', 'return;');
> PHP

Fatal error: Cannot redeclare __lambda_func() (previously declared in -:3) in -(5) : runtime-created function on line 1

ПХП, при использовании функции create_function сначала создаёт функцию __lambda_func (чтобы потом её переименовать), а если она уже где-то была создана, падает.

9 комментариев

Стоит ли покупать третий «Айпад»

Я покупать не буду, не вижу никакой причины для этого, но если вы раздумываете надо или нет, то принять решение поможет очень хорошая статья на «Айфонс»: «Три недели с третьим iPad. Впечатления от использования», это лучший обзор чего угодно, который я вообще когда-либо видел.

4 комментария

Пару слов о Джаве

У ДиБРа увидел прекрасное. Как вы думаете, что выведет следующий код:

public class Main {
    public static void main(String[] args) {
        Integer a = 10, b = 10; 
        Integer c = 150, d = 150;
        System.out.println(a == b);
        System.out.println(c == d);
    }
}

Как ни странно, но «true» и «false». Насколько я понял по комментариям, это происходит из-за того, в Джаве экземпляры Integer (видимо для скорости) в диапазоне [-128; 127] предопределены. Поэтому объекты «a» и «b» равны (ссылаются на один объект), а «c» и «d» нет. А на равенство значений надо проверять вызовом метода «equals».

19 комментариев

Вот что я люблю¹

Дружба дружбой (34.57КиБ)

Мне кажется это очень плохой слоган, раз он требует пояснения. Мне кажется это очень плохое пояснение, потому что оно ничего не поясняет. «Хочется съесть самому» это и есть «жалко».

3 комментария

Национальный музей РТ

Нацмузей РТ (59.07КиБ)

Невидимый поросёнок Пётр, хипстерская «Лейка», Родина-әни и карманный фонограф как бы говорят вам, что мы с женой посетили Национальный музей Республики Татарстан и впредь собираемся ходить в музеи в Казани чаще, чем никогда.

Остальные фото (без аннотаций правда) на «Яндекс.Фотках».

Оставить комментарий

Ещё недостаток «Гоу»

В отличие от многих языков, в «Гоу» символ решётки не позволяет комментировать строку. В упомянутых языках это сделано не зря. В различных ЮНИКС-подобных системах, если запускаемый файл начинается с последовательности «#!», то считается, что дальше идёт путь до интерпретатора, который умеет обрабатывать запускаемый файл. Таким образом работают, например, все шелл-скрипты.

Сам же язык, встретив первой строкой «#!/bin/какой-то путь» просто посчитает это комментарием. Так поступает Пайтон, ПХП, Перл и ещё целая куча языков. Это удобно.

В «Гоу» такого нет. Несмотря, что в первой версии появилась команда «go run», позволяющая запускать программы из командной строки, не компилируя их (на самом деле компилируя, но результат не сохраняется), я не могу указать в первой строке «#!/usr/local/bin/go run», «Гоу» меня отругает:

bolk@dhcp-174 ~ $ ./main.go 
package : 
main.go:1:1: illegal character U+0023 '#'

Прискорбно.

6 комментариев

葉片風扇沒有

Вентилятор (32.72КиБ)

Помните в интернете все с ума сходили по безлопастному вентилятору VES VD 501? Китайцы не дремлют!

610 рублей всего. Вот напись с ценника:

Фантастическая технология и дизайн, не имеющая аналогов! Вентилятор не покрывается пылью! Воздух подаётся охлаждённым! Устройство нетравматично! Вентилятор можно установить напротив себя, при этом будем видно собеседника! Сборка занимает всего полминуты!

Опечатки с ценника я заботливо перенёс. Я себе живо представляю двух китайцев, разговаривающих сквозь этот небольшой (он с ладонь размером) вентилятор и изумляющихся: «посмотри Ляо Дзянь, я тебя вижу, не то что через этот старый лопастной вентилятор!».

2 комментария

Декларация-вызов в JS

Сегодня на первой части семинара Ильи Кантора по JS присутствовал (к сожалению, на второй части не получится, буду в Москве это время). Немного поговорили о частом паттерне «объявили функцию и тут же вызвали». Используется этот приём (если кто не знаком с ДжаваСкриптом) для изоляции — в этом языке функции единственное, что может создавать зоны видимости.

Я вскользь затронул свой любимый способ — описание функции и вызов через восклицательный знак или тильду, вместо оборачивания скобками. Вообще, в JS непосредственный вызов функции сразу по месту определения возможен, только если декларация находится в контексте выражения. Для этого и нужны традиционные скобки:

(function () {
    // скобки вокруг функции говорят интерпретатору, что это выражение
})();

Вообще говоря, можно использовать любые средства, чтобы декларация стала частью выражения. Например, можно что-то прибавить к декларации. Или использовать любую унарную операцию.

Использование тильды или восклицательного знака гораздо лучше скобок по трём причинам:

!function () {
    // 
}();

Во-первых, на один байт меньше. Если таких функций много, экономия существенная. Во-вторых, скобки надо балансировать, а унарный оператор — нет.

И, в-третьих, самая главная причина. Вот такой код вызовет ошибку:

var a = 5
(function() {})()

Причём ошибку, которую найти иногда довольно трудно, да и сообщение о ней не самое дружественное: «TypeError: 5 is not a function». На самом деле, тут пропущена точка с запятой. Интерпретатор считает, что ему встретились две части выражения, между которыми пропущен какой-то знак. Эта ситуация часто встречается при автоматическом объединении двух скриптов в один.

Обычно для этого добавляют точку с запятой сразу перед такими блоками.

Но если использовать унарный оператор, то этой проблемы просто нет (поэтому, на самом деле, мы экономим два байта, ещё и точка с запятой не обязательна):

var a = 5
!function () {}()

Тут интерпретатор, встретив унарный оператор, понимает, что предыдущее выражение закончилось и началась новая конструкция.

33 комментария

Логичность шаблона «объявили и вызвали» в JS

Вчера в комменариях возникло обсуждение семантики синтаксиса «!function () {}()». Мне так кажется, что семантика здесь побоку, использовать функцию для создания зоны видимости — уже костыль (в JS 1.7 есть конструкция «let» для этого), но мне тут подумалось, что ведь можно сделать эту конструкцию семантичной, если использовать забытую конструкцию «with»:

with (function () {
    // тело функции
}) call();

На мой взгляд, весьма и весьма семантично. Что скажете?

5 комментариев

Как в JS узнать что объект — массив?

Узнал на семинаре Кантора такую штуку. Как узнать, что объект, который пришёл на вход — массив? Конечно, лучше использовать утиную типизацию, но если нам нужен именно массив, со всей полнотой методов, не проверять же каждый?

Кстати, если кто-то думает, что ответ «typeof», то он просто не знает JS. Эта конструкция вернёт «object».

Я до недавнего времени думал, что правильный способ такой:

console.log([] instanceof Array); // true

А редко его используют, потому что конструкция «instanceof» не везде реализована. Я заблуждался. Вот правильный способ:

console.log(Object.prototype.toString.call([]) == '[object Array]'); // true

И вот почему. Оказывается, у каждого фрейма своя иерархия типов, если один фрейм передаёт во второй какой-то объект, то «instanceof» даст «false», так как сравнивает c «Array», локальным для этого фрейма. Проблему иллюстрирует следующий пример:

<script>
function Up(arr, iframeWindow) {
    alert(arr instanceof Array); // false
    alert(arr instanceof iframeWindow.Array); // true

    alert(Object.prototype.toString.call([]) == '[object Array]'); // true
}
</script>
<iframe src="data:text/html, <script>parent.Up([], window)</script>"></iframe>

15 комментариев

← сюда туда →