Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 773 сообщений
Cообщения с меткой

perl - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

Вышла версия 2.0 Perl плагина для IntelliJ IDEA

Четверг, 26 Мая 2016 г. 19:32 (ссылка)






Стала доступна для загрузки вторая версия Perl плагина для IDE от JetBrains. В этой версии появилась последняя из крупных фич, которую я хотел реализовать — отладчик.

Читать дальше →

https://habrahabr.ru/post/301910/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

«Код выигрывает споры». С днем рождения, Марк

Суббота, 14 Мая 2016 г. 15:54 (ссылка)

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

— Марк Цукерберг, «Путь Хакера»



imageСегодня день рождения Марка Цукерберга (14 мая 1984). Каким он был программистом?




  • В 11 лет написал «домашний мессенджер» ZuckNet, чтоб не бегать по дому, а посылать сообщения родным через компьютер.

  • В старших классах написал Synapse Media Player — прообраз Last.fm. Алгоритм понимал музыкальные предпочтения пользователей Winamp. Microsoft and AOL предложили выкупить программу и устроиться к ним на работу. Но Марк выложил ее в свободный доступ.

  • CourseMatch помогал студентам Гарварда выбрать курсы. Он отображал сколько студентов уже записались на курсы, а так же показывал, на какие курсы записался конкретный студент.

  • FaceMash — «пузырьковая сортировка» людей по привлекательности. Он показывал 2 фотки и предлагал выбрать более симпатичную. Хабраэффект от этого сервиса обрушил сетку Гарварда. Марку выкатили выговор за то, что он использовал фотки людей без их согласия.





«Если ты ничего не разрушаешь, ты, вероятно, растешь не слишком быстро.»

— Марк Цукерберг



Под катом — «исходный код» раннего Фейсбука, письмо Марка инвесторам «Путь хакера», парочка скриншотов и видосы его лекций в Росссии.





image

В школьные годы занимался компьютерным программированием, разработал сетевую версию игры «Риск».



image

Synapse Media Player



image

«Вовочка, встань и выйди»



image

THEfacebook



Was this written by Mark Z.?
#!/usr/bin/perl



use Mysql;
use strict;
use vars qw($school_name);
use vars qw($pass);

require "./cgi-lib.pl";
#do "./school_name.pl";
do "../password.pl";

my (%input, $text, $field);
&ReadParse(\%input);

my @rawCookies = split (/; /,$ENV{'HTTP_COOKIE'});
my %cookies;

foreach(@rawCookies){
my ($key, $val) = split (/=/,$_);
$cookies{$key} = $val;
}

my $id = $input{id};
my $user = $input{user};
my $code = $input{code};
my $course = 0;#$input{course};
my @node;
my @edge;
#my $db_data = Mysql->connect("69.28.179.12", "login", "mark", $pass);
my $db_data = Mysql->connect("$cookies{host}", "$cookies{db}", "mark", $pass);
my $map;
if ($ENV{'HTTP_HOST'} =~ m/^(.*)\.thefacebook\.com/) {
$map = $1;
}
my $cookie_host = $cookies{host};
my $cookie_db = $cookies{db};
my $sql = "SELECT * FROM school_data where map='$map'";
my $retval = $db_data->query($sql);
my %rs = $retval->fetchhash();
my $host = $rs{ip};
my $dbname = $rs{db};
#my $sql = "INSERT INTO viz (user,map,host,db) VALUES ('$user', '$map','$host', '$dbname')";
#$db_data->query($sql);
my $db = Mysql->connect($host, $dbname, "mark", $pass);
#my $db = Mysql->connect("69.28.179.11", "facebook", "mark", $pass);
my $retval; my $sql;
my %privacy;
my $num_degrees = 1;
my @already_expanded;
my %cs;

sub morph {
my ($number) = @_;
return ((((($number % 7) * 13) % 17) * 19) % 23);
}

sub share_course {
my ($user1, $user2) = @_;
$sql = "SELECT count(*) as count FROM course as course1, course as course2 WHERE " .
"course1.id = '$user1' AND course2.id = '$user2' AND course1.course_id = course2.course_id";
$retval = $db->query($sql);
my %rs = $retval->fetchhash();
return $rs{count};
}

sub is_one_degree {
my ($user1, $user2) = @_;
$sql = "SELECT count(*) as count FROM friend WHERE user1 = '$user1' AND user2 = '$user2'";
$retval = $db->query($sql);
my %rs = $retval->fetchhash();
return $rs{count};
}

sub is_two_degrees {
#my ($user1, $user2) = @_;
#$sql = "SELECT count(*) as count FROM friend as f1, friend as f2 " .
# "WHERE f1.user1 = '$user1' AND f1.user2 = f2.user1 AND f2.user2 = '$user2'";
#my %rs = $retval->fetchhash();
#return $rs{count};
return 0;
}

sub is_three_degrees {
#my ($user1, $user2) = @_;
return 0;
}

sub can_see {
my ($user, $id, $type) = @_;
if ($user eq $id) { return 1; }
$sql = "SELECT house, year, " . $type . "_domain, " . $type . "_type, " .
$type . "_allow FROM info WHERE info.id = '$id'";
$retval = $db->query($sql);
my %control = $retval->fetchhash();
my $allow = $control{$type . '_allow'};
my $domain = $control{$type . '_domain'};
my $privacy_type = $privacy{$type};
if (($domain eq "" or ($privacy{email} =~ m/$domain$/)) and
($control{$type . '_type'} =~ m/-$privacy_type/)) {
if ($allow =~ m/-1/) {
$cs{$id} = 1;
return 1;
} elsif (($allow =~ m/-2/) and $privacy{year} eq $control{year}) {
$cs{$id} = 1;
return 1;
} elsif (($allow =~ m/-3/) and $privacy{house} eq $control{house}) {
$cs{$id} = 1;
return 1;
} elsif (($allow =~ m/-4/) and share_course($user, $id)) {
$cs{$id} = 1;
return 1;
} elsif (($allow =~ m/-5/) and is_one_degree($user, $id)) {
$cs{$id} = 1;
return 1;
} elsif (($allow =~ m/-6/) and is_two_degrees($user, $id)) {
$cs{$id} = 1;
return 1;
} elsif (($allow =~ m/-7/) and is_three_degrees($user, $id)) {
$cs{$id} = 1;
return 1;
}
}
return 0;
}

sub find_node {
my ($id, $type) = @_;
for (my $i = 0; $i < @node; $i++) {
if ($node[$i]{id} eq $id and $node[$i]{type} eq $type) {
return $i;
}
}
return -1;
}

sub find_connections {
my ($this, $degree, $is_course) = @_;
my ($type, $type_id);
if (!$is_course) {
$already_expanded[@already_expanded] = $this;
$sql = "SELECT * FROM friend WHERE user1 = '$this' LIMIT 0, 200";
$type = "friend";
$type_id = 1;
} else {
$sql = "SELECT * FROM course WHERE course_id = '$this'";
$type = "course";
$type_id = 0;
}
my $return = $db->query($sql);
while (my %row = $return->fetchhash()) {
my $new_id;
if (!$is_course) {
# get the new id from the friend pair
#if ($row{user1} eq $this) {
# $new_id = $row{user2};
#} else {
# $new_id = $row{user1};
#}
$new_id = $row{user2};
} else {
$new_id = $row{id};
}
if ($degree > $num_degrees or $cs{$new_id} or can_see($user, $new_id, $type)) {
# see if the id is already a node; if not, add it
my $node_exists = 0;
for (my $i = 0; $i < @node and !$node_exists; $i++) {
if ($node[$i]{id} eq $new_id and $node[$i]{type} eq 1) {
$node_exists = 1;
}
}
if (!$node_exists and $degree <= $num_degrees) {
my $next_node = @node;
$node[$next_node]{id} = $new_id;
$node[$next_node]{type} = 1;
}
# see if there's an edge between the two already; if not, add it
if ($node_exists or $degree <= $num_degrees) {
my $edge_exists = 0;
my $this_index = find_node ($this, $type_id);
my $new_index = find_node ($new_id, 1);
for (my $i = 0; $i < @edge and !$edge_exists; $i++) {
if (($edge[$i][0] eq $this_index and $edge[$i][1] eq $new_index) or
($edge[$i][0] eq $new_index and $edge[$i][1] eq $this_index)) {
$edge_exists = 1;
}
}
if (!$edge_exists) {
my $next_edge = @edge;
$edge[$next_edge][0] = $this_index;
$edge[$next_edge][1] = $new_index;
}
}
# if we want to consider friends farther out, do so now
if ($degree < $num_degrees + 1) {
my $expansion_exists = 0;
for (my $i = 0; $i < @already_expanded and !$expansion_exists; $i++) {
if ($already_expanded[$i] eq $new_id) {
$expansion_exists = 1;
}
}
if (!$expansion_exists) {
if (!$is_course or can_see ($user, $new_id, "friend")) {
find_connections ($new_id, $degree + 1, 0);
}
}
}
}
}
}

sub identify_nodes {
for (my $i = 0; $i < @node; $i++) {
if ($node[$i]{type}) {
$sql = "SELECT name FROM info WHERE id = '" . $node[$i]{id} . "'";
} else {
$sql = "SELECT name FROM course_list WHERE id = '" . $node[$i]{id} . "'";
}
$retval = $db->query($sql);
my %row = $retval->fetchhash();
$node[$i]{name} = $row{name};
}
}

if ($code == &morph($user) and &can_see($user, $id, "friend")) {
# figure out what's going into the graph
$sql = "SELECT email, house, year FROM info WHERE info.id = '$user'";
$retval = $db->query($sql);
%privacy = $retval->fetchhash();
$node[0]{id} = $id;
if ($course) {
$node[0]{type} = 0;
} else {
$node[0]{type} = 1;
}
find_connections ($id, 1, $course);
identify_nodes ();
# generate the graph file
my $o;
my $outfile = "/tmp/thefacebook-$id-graph-" . time();
open $o, "> $outfile";
# headers
print $o "graph g {\n";
print $o "start=\"yes\"\n";
print $o "size=\"20,20\"\n";
print $o "page=\"20,20\"\n";
print $o "maxiter=1000\n";
print $o "resolution=100\n";
print $o "center=true\n";
print $o "bgcolor=white\n";
print $o "title=\"A Graph\"\n";
# nodes
print $o "node [shape=box,fontname=\"Tahoma\",style=filled]\n";
for (my $i = 0; $i < @node; $i++) {
my $name = $node[$i]{name};
$name =~ s/[^A-Z0-9'. ]//gi;
my ($red, $green, $blue);
do {
$red = int(rand() * 201);
$green = int(rand() * 201);
$blue = int(rand() * 56) + 200;
} while ($red + $green + $blue < 400);
my $extra = "";
if ($i eq 0) {
$extra = ",fontsize=32,label=\"$name\"";
} else {
$extra = ",fontsize=24,label=\"$name\"";
}
printf $o "n$i [color=\"#%02x%02x%02x\"$extra]\n", $red, $green, $blue;
}
# edges
print $o "edge [len=8,color=\"#555555\"]\n";
for (my $i = 0; $i < @edge; $i++) {
my $name1 = $node[$edge[$i][0]]{name};
my $name2 = $node[$edge[$i][1]]{name};
print $o "n$edge[$i][0] -- n$edge[$i][1] [dir=both,weight=1]\n";
#print $o "$name1 -- $name2 [dir=both,weight=1]\n";
}
# footer
print $o "}\n\n";
# compile the graph and output
my $cmd = "nice neato -Tsvgz $outfile|";
print "Content-Encoding: gzip\n";
print "Content-Type: image/svg+xml\n\n";
#print "Content-Type: text/html\n\n";
my $file;
my $pid = open ($file, $cmd);
while (my $line = <$file>) {
print $line;
}
close $o;
unlink $outfile;
#print "what is up..\n";
} else {
print &PrintHeader;
print "Authentication failed. Return home.\n";
}

#print "end...\n";





Источник





Путь хакера
Взято отсюда.



Из письма Марка Цукерберга будущим акционерам Facebook





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



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



Сегодня общество достигло новой переломной точки. Большинство людей в мире имеют доступ к интернету или мобильным телефонам — новым инструментам, позволяющим делиться своими мыслями, чувствами и действиями с кем только захочется. Facebook надеется построить сервисы, которые дадут людям возможность делиться [друг с другом] и помогут им снова трансформировать многие ключевые отрасли и общественные институты.



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



Наша миссия и наш бизнес





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



Когда я писал первую версию Facebook, я делал это сам, потому что мне хотелось, чтобы он был. С тех пор большая часть идей — и программного кода — были созданы лучшими людьми, которых мы привлекли в свою команду.



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



Попросту говоря, мы не создаем сервисы, чтобы зарабатывать деньги, мы зарабатываем деньги для того, чтобы создавать лучшие сервисы.

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



Мы считаем, что, фокусируясь на своей миссии и создавая отличные сервисы, мы создадим для наших инвесторов больше стоимости в длительной перспективе. А это, в свою очередь, позволит нам и дальше привлекать лучших людей и создавать отличные сервисы. Мы не просыпаемся по утрам, думая о том, где бы добыть деньжат, но понимаем, что лучший способ выполнить свою миссию — это построить сильную и дорогую компанию.



Вот так же мы думаем и про наше IPO. Мы становимся публичной компанией ради наших сотрудников и наших инвесторов. Давая им долю в компании, мы обещали, что она будет дорого стоить и станет ликвидной, и наше IPO — подтверждение этого обещания. Становясь публичной компанией, мы обещаем то же и нашим новым инвесторам и будем работать изо всех сил, чтобы снова выполнить это обещание.



Путь хакера





Создавая сильную компанию, мы много работаем над тем, чтобы сделать Facebook лучшим местом, где лучшие люди могли бы менять мир и учиться у лучших. Мы создали уникальную культуру и систему управления, которую мы называем «путь хакера».



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



«Путь хакера» — это подход к созиданию, который включает постоянные улучшения и повторы. Хакеры верят, что все можно улучшить и ничто не может быть совершенным. Надо всего лишь исправить — часто вопреки воле людей, которые говорят, что это невозможно, и довольны статус-кво.

Хакеры стараются строить лучшие сервисы, выпуская частые релизы и обучаясь на небольших итерациях, вместо того чтобы постараться сделать все правильно с первого раза. Чтобы поддержать этот подход, мы построили тестовую среду, способную в каждый момент времени испытывать тысячи версий Facebook. На стенах нашего офиса написаны слова, призванные постоянно подгонять нас: Done is better than perfect.



Хакинг по своей природе активная и практическая работа. Вместо того чтобы днями спорить о возможностях или способах реализации новой идеи, хакеры просто сделают прототип и посмотрят, что работает, а что нет. В стенах Facebook вы часто можете услышать мантру хакера: [программный] код выигрывает споры.



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



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



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



Приведенные выше примеры адресованы программистам, но из этих принципов мы выделили пять ценностей, ключевых для развития Facebook:



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



Быстро развиваться. Быстрый рост позволяет нам делать больше и учиться быстрее. Большинство компаний замедляются по мере роста, потому что они больше боятся сделать ошибку, чем упустить хорошую возможность из-за своей медлительности. У нас есть присказка: «Двигайся быстро и разрушай». Идея в том, что если ты ничего не разрушаешь, ты, вероятно, растешь не слишком быстро.



Быть смелым. Нельзя сделать больших проектов, не рискуя. Страх перед риском мешает большинству компаний совершить смелые поступки, которые следовало бы совершить. Но в мире, который меняется с такой скоростью, отказ от риска просто гарантирует неудачу. У нас есть присказка и на эту тему: «Самый большой риск — не идти на риск». Мы поощряем принятие смелых решений, даже если иногда это значит, что приходится ошибаться.



Быть открытым. Мы верим, что открытый мир лучше, потому что хорошо информированные люди принимают лучшие решения и успевают сделать больше. Это относится и к управлению нашей компанией. Мы много работаем над тем, чтобы каждый в Facebook имел доступ ко всей возможной информации, чтобы принять наиболее правильные решения и достичь высокого результата.



Создавать социальный капитал. Еще раз: Facebook существует для того, чтобы сделать мир более связанным и открытым, а не просто как компания для зарабатывания денег. Мы ожидаем, что каждый сотрудник Facebook сфокусирован на том, как принести больше реальной пользы для мира каждым своим действием.



Оригинал письма на английском языке











Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/283544/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Не подлежит забвению

Воскресенье, 01 Мая 2016 г. 12:14 (ссылка)

Вы не находите странным черту освистывать нечто до появления некого более функционального аналога оного и проявлять к этому нечто интерес после? На протяжении всего своего существования командная оболочка Windows неоднократно подвергалась жесткой критике, дескать, ее функциональность оставляет желать лучшего, что, казалось бы, должно было сойти на нет с появлением PowerShell, призванного устранить недочеты первой и упростить жизнь разработчикам и системным администраторам. Нет, PowerShell снискал должную популярность, но появился интерес и к самой командной строке особенно после того, как «селекционерами» были открыты способы запускать командные сценарии как WS[H|F] и HTA. Собственные эксперименты и наблюдения показали, что этим дело не ограничивается.



Некоторые символы в cmd наделены особой логикой, например, двоеточие в начале команды означает метку, а потому если командный сценарий состоит из единственной строки :label или даже одного двоеточия, интерпретатор просто ее проигнорирует; если после двоеточия стоит один из знаков перенаправления потока, сути это также не изменит — интерпретатор по-прежнему будет считать данную строку меткой, что в общем-то логично. Вообще, изменяя порядок использования некоторых спецсимволов командной строки, можно добиться любопытных побочных эффектов, таких как, скажем, запуск PHP кода в виде командного сценария:



:


Или то же, но — bash:



:<nul bash "%~f0" %*
echo:End
exit /b
EOF
for i in $@;do echo $i;done


Конечно же при этом обыгрываются особенности самих вызываемых интерпретаторов, но основная суть в том, что такое вообще возможно. Возможен также и запуск сценариев Perl, Ruby или Python как командных сценариев в виду наличия у перечисленных интерпретаторов ключа -x, заставляющий последние игнорировать первую строку передаваемого на исполнение кода. Строго говоря, данное утверждение справедливо для Python, а вот Perl и Ruby с данным ключом будут игнорировать все до тех пор, пока не встретят шебанги #!perl и #!ruby соответственно.



Perl:

@echo off
echo:Start
2>nul perl -x "%~f0" %*
echo:End
exit /b
#!perl
foreach my $i (@ARGV) {print $i, "\n";}


Ruby:

@echo off
echo:Start
2>nul ruby -x "%~f0" %*
echo:End
exit /b
#!ruby
ARGV.each do |i| puts i;end


Python:

@echo off&echo:Start&python3.4m -x "%~f0" %*&echo:End&exit /b
from sys import argv
for i in range(1, len(argv)): print(i)


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



Original source: habrahabr.ru.

https://habrahabr.ru/post/282816/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

5 стадий API: что мы поняли, написав две версии

Понедельник, 25 Апреля 2016 г. 14:52 (ссылка)

Сегодня мы хотим поговорить о сокровенном — у нас есть API.



Мы писали, а затем переписывали его заново на протяжении четырех лет. И за это время прошли почти все классические стадии “принятия неизбежного”. Кроме одной — четвертой. И хотим поделиться нажитыми непосильным трудом выводами, что делать и не делать, если вы решите делать свой “мощный эпиай”.







Процесс создания API uCoz иногда напоминал сюжет сериала The Knick («Больница Никербокер») — с неудачными операциями, кишками и экспериментами на живых людях.



Стадия первая – Отрицание



По концепции пяти стадий:
На первом этапе человек отказывается принимать то, что с ним случилось.



Вообще, API для конструкторов сайтов — редкость и сейчас. А в 2010-м такого инструмента не было еще ни у кого на рынке.



С одной стороны, определенная потребность в API была: мы видели, что люди активно гуглили “скрипты для uCoz”, получали запросы напрямую — часть пользователей хотела создавать свои дополнения и модификации. С другой стороны — и это была проблема, — в те времена все ресурсы были брошены на другие проекты.



Вопрос «быть или не быть» решился просто. API потребовалось нам самим — для запуска функции поддержки PHP в конструкторе. Мы выделили одного разработчика, и он за полгода сделал наше “начальное API” — это был get-only интерфейс, к которым можно было получать данные страниц из 11 модулей. Данные отдавались только в XML. К моменту запланированного анонса PHP мы не успевали наладить еще хотя бы добавление контента, но у API были и плюсы: с ним можно было запускать пхп-скрипты в бесплатном варианте юкоза.



В общем, мы вышли к пользователям. Получилось прям по классике:







Нас не очень-то приняли… Точнее, в основном хвалили. Но за PHP. А в самом “эпиай” люди не увидели того, что представлялось им при этом волшебном слове. В техподдержку посыпались вопросы: «Как добавлять материалы? Как редактировать? Как получить в json?» А этого всего не было.



Стадия вторая – Гнев



По концепции пяти стадий:
На этом этапе проявляется агрессия ко всему окружающему миру.



К вопросу, что API нужно развивать, в компании вернулись где-то через год. В приоритетах были мобильные клиенты — и мы решили писать все заново с учетом требований ребят, делавших клиенты для iOS и Android. Начальная версия осталась жить сама по себе (и даже до сих пор жива, потому что некоторые ей все же пользуются), а мы стали подбирать исполнителя на новый проект.



“Сам себе менеджер”. В ростовский офис как раз пришел толковый парень Илья: он знал Perl, на котором была написана часть старого uCoz, а когда ему по традиции предложили несколько задач, он выбрал из них работу над API. Проблема была в том, что на время парню пришлось стать самому себе менеджером.



Тут наступил гнев: «Как выяснилось в процессе, синтаксис Perl я понимал, а вот дух — нет.







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



К моменту просветления появился менеджер и стал требовать отчет. А была готова только отдача контента по нескольким модулям… В общем, еще раз гнев, теперь с другой стороны, — и Илью перевели на новый проект. Его версии API так и не суждено было увидеть свет.





… Между стадий ...



“Идеальная обучающая задача”. К тому моменту компания пробовала открыть офис R&D в Казани. На месте нужен был носитель знания о всей системе — и появилась идея “вырастить” его через работу над API, которое затронет основные модули системы.



Так в этой истории появился Ринат:







С одной стороны, он был в меру азартен, чтобы взяться за проект (он у нас вообще немного экстремал). С другой — в работе спокоен и рассудителен: все же за плечами не только 700+ прыжков с парашютом, но и 20 лет опыта в ИТ.



Он также был знаком с Perl и имел свежий опыт работы с чужими API — интегрировал «Метрику» и GA в панель веб-мастера.



Первое, что нам пригодилось, так это его рассудительность. Ринат попросил паузу, чтобы детальнее поразбираться в запутанных “кишках” системы, — и только затем озвучить сроки и план реализации. Спустя месяц он вышел с таким предложением:



* Переписываем и дублируем часть функций чисто под API — за время создания системы в ней скопилось много старого кода. И некоторые функции, попробуй ты туда еще что-нибудь засунуть, получились бы «километровыми». Значит, нужны подчищенные двойники чисто под API.



* Используем REST — для упрощения архитектуры запроса, что поможет увеличить производительность.



* Используем Oauth 1.0a — аутентификацию, показавшуюся самой защищенной на тот момент.



* Отдаем в разных форматах — JSON, XML, Text Plain.



* Ну и: get, post, put, delete, мир, труд, май…





Третья стадия – Торг



По концепции пяти стадий:
Тут у вас появляются мысли о том, чтобы договориться с кем-то о лучшей участи



К общему пониманию, как сделать удобно, мы вроде пришли. Но дьявол крылся в деталях и спорах.



Принципиальным вопросом стала область видимости токенов. Наша серверная система, как говорит Ринат, «это законченная инкапсулированная фиговина»: на каждом сервере крутится N юзеров, а когда место на нем кончается, берется новое железо.



Ринат хотел использовать один токен, чтобы получить доступ ко всем сайтам. “Ок, давай поговорим об этом”, — и мы позвали в скайп-чат еще коллег. Как это бывает в коллективных чатах, спорили-спорили, но к коллективному решению не пришли. А в угаре разработки тема подзабылась и вновь всплыла, когда была готова интеграция для половины модулей.







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



Оформить как массив или объект? Для нас это была не проблема разряда “что правильно, а что нет”. Это была проблема деталей.



Например, мы отдаем данные в JSON — и возникает проблема с типизированными языками. Не все структуры, полученные от API, удобно распарсить — потому что увеличивается количество кода на клиентской стороне.



А ведь API ориентировано на веб-приблуды, поэтому мы прислушались к мнению разработчиков на Java и C++ и пришли к стандарту: поля отдаем в любом случае, именованные параметры дополняем кодом.



Поля и параметры. Тут торги и обсуждения шли все время реализации. Например, стоит ли выдавать пустой параметр? Поскольку мы ориентировались и под мобильную разработку с использованием API, Ринат в процессе доказал — не нужно: ведь в мобильных приложениях важен трафик.



А какие поля нужны? Тут уже мне, менеджеру проекта, нужно было искать и приводить примеры из реальной практики. Часто я брал помощь зала — опрашивал будущих пользователей API. Если присланный случай стороннего разработчика казался ценными, мы реализовывали нужные поля в самом API, чтобы люди потом не делали через костыли. А позже придумали более элегантное решение — о нем еще расскажем ниже.







Четвертая стадия –… (и о сроках)



По концепции пяти стадий:
На четвертой стадии у вас наступает депрессия. [Мы как-то миновали этот этап]



Работа, запланированная на год, заняла почти два. В процессе возникали совсем непредвиденные сложности. Мы быстро поняли, что:







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



Мы расписали гугл-док, поставив модули в алфавитном порядке. Работу по ним разделили между исполнителями пополам. Определили график — один модуль в месяц от каждого. Когда пора было приступать, второй человек ушел. А мы уже делали новый проект — конструктор uKit, на который бросили основные ресурсы. С потерей второго программиста, к сроку реализации добавились почти 7 месяцев.



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



Но оказалось, что наши обычные тестировщики не очень подходили для работы с API — ведь они “заточены” под тестинг веб-страниц. API же — тонкая вещь. Например, когда происходило изменение внутри модуля, с которым мы интегрировались, что-то могло отвалиться — но тестировщики сначала этого не замечали (потом мы их научили).



Тогда мы открыли 4 тестовых сервера и пригласили продвинутых пользователей на самых ранних этапах. Такое крауд, а потому не очень контролируемое (ты не уверен, когда человек что-то сделает и не бросит ли), тестирование тоже сказалось на увеличении сроков.





Пятая стадия – Смирение (и выводы)



По концепции пяти стадий:
По канонам, наступает согласие с неизбежным



В конце концов, мы смирились с неизбежным — API, как и ремонт, можно начать, но не так-то просто закончить. Вот несколько рецептов, как организовать процесс, чтобы вам было проще.



1. Наладьте обратную связь. Больше связи.



Помимо ветки на форуме и постов в блог, т.е. публичных средств обмена комментариями, я завел на сайте с новой документацией раздел “Лаборатория”. По сути, это была форма обратной связи, где можно было обсудить свой случай в личной переписке.



О новом API мы сообщили ранним пользователям в феврале 2015-го, а процесс выкатки всех модулей на сервера завершили лишь в текущем году. Все это время мы получали через “Лабораторию” репорты, предложения и интересные случаи от пользователей (которые я в том числе использовал в «торгах» с Ринатом).





Поток обращений снизился лишь в последние два месяца



Каждый раз, получив обращение «как сделать вот это или как тут поступить», мы выделяли время и садились смотреть, а как же лучше? Иногда из этого рождались интересные истории.



2. Лично помогите тем, кто не разобрался или [Скрипт в подарок].



Бывало, сообщение из “Лаборатории” заинтересовывало настолько, что мы начинали писать скрипт сами. А затем дарили готовое решение пользователю,



В чем профит? Как говорится, «хочешь найти баг – будь багом». А если хочешь погрузиться в проект – сделай на нем как можно больше всего, чтобы проверить работоспособность и удобство. Заодно так на бирже uScript появились первые решения с использованием uAPI — авторизация через соцсети и неглючный вывод картинок в блоке рекомендованных материалов.



3. Проведите внутренний хакатон.



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





Ринат обычно не общался напрямую с пользователями, но на хакатон к ростовским разработчикам приехал из Казани. Говорит, “за исключением троллинга, остался доволен собранными в процессе данными и критикой”.





4. Попробуйте что-нибудь автоматизировать.



Моей любимой фишкой для работы с API стало полуавтоматическое создание приложения и токена.







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



5. Советуйтесь и меняйте документацию.



Изначально мы расписывали пример запроса в PHP и CURL.





Так было. Как выяснилось в процессе, CURL никто не пользовался.



Но люди спрашивали, например, в каком формате и по какому пути отправлять запрос. Стало понятно, что первая реализация была не очевидна для всех.



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



Параллельно я прошелся по нашим программистам и спросил – документацию к каким API они считают достойным примером для подражания? Посмотрел рекомендованные примеры и с их учетом составил новую версию документации:







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



6. Реализуйте мобильность.



Во-первых, это поможет получать хорошие отзывы и расширит вашу аудиторию:



Давно была мечта о создании мобильного приложения для своего сайта. Буду изучать uAPI, чтобы наконец-таки воплотить ее в жизнь


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





Тут можно посмотреть код приложения



7. Не ограничивайте желания пользователей.



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







Наконец, мы пришли к тому, что API — это двустороннее движение. Сегодня с помощью uAPI можно сделать авторизацию для любого сайта, переносить материалы с uCoz и наоборот, использовать только нашу БД, но выводить данные на стороннем сайте. Чем больше юзкейсов вы вбираете в инструмент, тем дольше реализация. Но одновременно растет и область его применения.





P.S.



Весь процесс выкатки и боевого тестирования новой версии занял у нас 14 месяцев и 20 обновлений. Вот визуализация.



Бывает, после очередного обновления нам пишут: “Когда же вы его уже допишете?” Но процесс и правда очень сложно остановить (мы не шутили про ремонт). Иногда — по техническим причинам: скажем, когда апдейт системы требует изменений в API. А иногда — по творческим. Например, сейчас мы думаем: когда все интеграции с модулями закончены, почему бы не проработать темы изменения дизайна и настроек сайта по API?



Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/282385/

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Быстрый анализ транзитного трафика

Вторник, 05 Апреля 2016 г. 14:43 (ссылка)

Перед любым системным администратором рано или поздно возникает задача количественного анализа трафика (откуда / куда, по каким протоколам / портам, в каких объемах и т. п.), проходящего по его сети. Особенно неприятно, когда эта задача возникает спонтанно, как побочный результат DDoS-а, а денег на серьезные решения от Cisco или Arbor, как обычно, нет. И хорошо еще, если шлюзом для сети выступает сервер, на котором можно запустить tcpdump или wireshark, но что делать если:




  • шлюзом выступает устройство провайдера, а в сети есть только файл-сервер;

  • данные о трафике нужны не постоянно, а от времени к времени;

  • устройство не поддерживает возможность запуска на нем сторонних программ;

  • трафика столько, что сервер после запуска tcpdump-а «клеит ласты»;

  • или наоборот, настолько мало, что его уровень сравним с долей (хотя и значительной) обычного трафика?



Дополнительную ложку дегтя в эту «бочку меда» задачи добавляет еще и отсутствие как у tcpdump, так и у tshark чудесного ключа «сгруппировать, просуммировать / усреднить и отсортировать».



Так что, при всем нежелании изобретать велосипед, пришлось закатить рукава и написать инструмент, удовлетворяющий следующим требованиям:




  • источником данных выступает маршрутизатор или коммутатор, поддерживающий протокол sFlow;

  • коллектор (tcpdump, tshark или sflowtool) данные в формате PCAP либо пишет в файл, либо передает на STDOUT;

  • соответсвтенно, исходные данные могут быть вычитаны инструментом либо из файла, либо с STDIN-а;

  • базовой единицей для анализа является пакет, а не соединение;

  • результат должен включать информацию о направлении трафика, количестве прошедших пакетов, объеме трафика и среднем размере пакета;

  • должна быть предусмотрена возможность базовой группировки и сортировки результата;

  • ну и разные попутные мелочи;

  • и при всем этом этот инструмент не должен дублировать функционал существующих общеизвестных инструментов.



Вот исходя из этого и был написан PCAParse — максимально простой инструмент для получения обобощенной информации о проходящем по сети трафике. Для его использования, в простейшем случае, достаточно коммутатора типа D-Link DGS-3XXX или аналога других производителей и запущенного на вышеупомянутом файл-сервере sflowtool либо tcpdump-а на офисном шлюзе. Как показывает практика, эти устройства давным давно утратили статус экзотических и встретить их можно даже в небольших офисах.



Для того, чтобы было понятно, о чем идет речь, приведу небольшой пример:



Пример вывода скрипта

$ tshark -c 100 -w - | pcaparse
Filename: -
File size: -
Parsed: 100 samples in 4.90 seconds
Matched: 100 samples / 104.21kB
tcp: 20 samples / 2.04kB
udp: 76 samples / 101.79kB
icmp: 4 samples / 392B
other: 0 samples / 0B

Samples Summary Average
Destination count size size
212.XX.XXX.XX 86 102.43kB 1.19kB
tcp: 10 660B 66B
udp: 76 101.79kB 1.34kB
icmp: 0 0B 0B
other: 0 0B 0B
212.XX.XXX.XX 5 550B 110B
212.XX.XXX.XX 5 878B 176B
212.XX.XXX.XXX 4 392B 98B




Разумеется, сразу же бросается в глаза несуразное время работы, но на самом деле это — результат tshark-а, а не скрипта. Реальная его производительность на AMD A8-6600K @ 3,9 ГГц / 8 ГБ RAM составляет 15-25 килопакетов/с в зависимости от источника данных (чтение из файла, sFlow и т. д.).



Более требовательные пользователи могут затребовать более развернутую информацию:



Развернутый пример вывода скрипта

$ tcpdump -w - | pcaparse -f - -d 212.XX.XX.XX
Filename: -
File size: 32.65MB
Parsed: 280692 samples in 27.58 seconds
Matched: 4383 samples / 3.75MB
tcp: 4 samples / 513B
udp: 4378 samples / 3.75MB
icmp: 0 samples / 0B
other: 0 samples / 0B

Samples Summary Average
Destination count size size
212.XX.XX.XX 4.38k 3.75MB 897B
tcp: 4 513B 128B
80 (http) 4 513B 128B
udp: 4378 3.75MB 898B
7 (echo) 2819 2.82MB 1.02kB
3389 (ms-wbt-server) 659 670.15kB 1.02kB
5538 273 86.15kB 323B
9584 87 24.62kB 290B
18002 92 26.55kB 295B
27186 167 55.68kB 341B
32302 279 89.50kB 328B
icmp: 0 0B 0B
other: 0 0B 0B




Если принять во внимание, что дамп для примера взят для обычного web-сервера, эта информация позволяет судить о наличии атаки DoS / DDoS по udp:* на ресурс. Ну, а это знание, позволяет уже принять какие-то адекватные меры. Для удобства дальнейшей обработки данных предусмотрен parser-friendly вывод:



Parser-friendly вывод

$ pcaparse -f tcpdump-212-XX-XX-XX -d 212.XX.XX.XX -p
212.XX.XX.XX total 4382 3931684 897
212.XX.XX.XX tcp 4 513 128
212.XX.XX.XX tcp:80 4 513 128
212.XX.XX.XX udp 4378 3931171 897
212.XX.XX.XX udp:7 2819 2955528 1048
212.XX.XX.XX udp:3389 659 686237 1041
212.XX.XX.XX udp:5538 273 88222 323
212.XX.XX.XX udp:9584 87 25208 289
212.XX.XX.XX udp:18002 92 27185 295
212.XX.XX.XX udp:27186 167 57019 341
212.XX.XX.XX udp:32302 279 91644 328
212.XX.XX.XX icmp 0 0 0
212.XX.XX.XX other 0 0 0


Скрипт написан на perl-е с использованием модулей Net::PCAP и NetPacket::*, что обеспечивает достаточную производительность и кроссплатформенность. По крайней мере, на свежих linux-ах и FreeBSD проблем с запуском и работой не возникало.



Из известных минусов:




  • отсутствие поддержки IPv6 (надеюсь, пока — над этим ведется работа);

  • проверка диапазонов IP-адресов с использованием Data::Validate::IP (опять же, надеюсь, временно);

  • отсутствие опции «сделать хорошо» у tcpdump или tshark (но, надеюсь, в будущем она может там появиться, если скрипт понравится авторам и контрибьюторам упомянутых инструментов и его функционал будет туда спортирован).



P.S. постфактум оказалось, что существует аналогичный инструмент — Fastnetmon, но он, все-таки, ориентирован под «стационарное» использование, поскольку подразумевает использование коллектора данных, с которым и взаимодействует клиентская часть.



Ссылки:





Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/280986/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Погоня с препятствиями

Понедельник, 28 Марта 2016 г. 09:55 (ссылка)



Какая медлительная страна! — сказала Королева. — Ну, а здесь,

знаешь ли, приходится бежать со всех ног, чтобы только остаться

на том же месте! Если же хочешь попасть в другое место, тогда

нужно бежать по меньшей мере вдвое быстрее!



Льюис Кэрролл "Алиса в Зазеркалье"




Сегодня, я хочу рассказать об удивительной и недооценённой игре, с которой я познакомился чуть менее двух лет назад. В каком-то смысле, именно с этой игры, а также с Ура, началось моё знакомство с Дмитрием Скирюком. В те дни я только начинал интересоваться настольными играми. Мои познания были скудны и, во многом, наивны. Такие игры как "Чейз", буквально открыли для меня новый необъятный мир. Даже сейчас, работа над этой игрой, в большой степени, напоминает детективную историю. В этом отношении, игра "Chase" полностью оправдала как своё название так и сходство с псевдонимом известного американского писателя.



Игра была разработана Томом Крушевски и выпущена в продажу компанией «TSR» в 1986 году. Помимо специальной доски, у каждого из игроков имеется по 10 шестигранных игральных кубиков, но несмотря на это игра не является азартной. Кубики бросаются всего один раз, для определения очерёдности хода и в дальнейшем используются лишь в качестве фигур. Количество очков на верхней грани показывает число шагов, на которое может быть передвинут кубик. Так кубик с одним очком может быть перемещён на соседнее поле, в любом из шести направлений, с двумя очками — на два поля по прямой, с тремя — на три и т.д. Кубик должен перемещаться ровно на указанное число шагов, не больше и не меньше. В процессе перемещения, кубик не поворачивается другой стороной (количество очков на верхней грани не изменяется). Начальная расстановка показана ниже:





Подробнее о правилах
Для каждого из игроков, общее количество очков, на верхних гранях, составляет 25. Игрок обязан поддерживать эту сумму до конца игры. Игроки ходят по очереди и если один из них забирает одну (или две, такое тоже возможно) фигуры, его противник обязан добавить сумму очков, выбывших из игры, к своему кубику с минимальным количеством очков (в начале игры, это одна из единичек). Если после этого распределены не все очки, остаток распределяется далее, всегда начиная с кубика с наименьшим количеством очков. Игрок, у которого остаётся менее 5 кубиков — проигрывает, поскольку не может распределить необходимое количество очков по оставшимся на доске кубикам.





Границы доски не препятствуют движению фигур. Левая и правая границы доски «склеены» между собой, а от верхней и нижней границ фигуры отскакивают «рикошетом». Разумеется, это не означает, что фигуры движутся беспрепятственно. Фигуры не могут «перепрыгивать» друг друга, а также центральное поле "Chamber". Для взятия фигуры противника, фигура должна «встать» на неё (шахматное взятие), выполнив полное количество шагов по прямой. Ход может закончится и на фигуре своего цвета. В этом случае происходит "bumping" — фигура оказавшаяся на целевом поле смещается на один шаг, продолжая направление движения (с учётом склеенности доски и рикошетов). Если следующее поле также оказалось занято своей фигурой, "bumping" распространяется далее, до первого пустого поля или поля занятого фигурой противника (вражеская фигура забирается). Только одно препятствие может сделать такой ход невозможным — запрещено «задвигать» фигуры в центральную клетку, используя bumping.



Можно заметить, что из начальной позиции, каждый из игроков может циклически сдвинуть все свои фигуры, сходив любой из единичек в сторону двойки. Подобный ход разрешён правилами. Также допускается «обмен» очками между фигурами одного цвета, находящимися на соседних полях. Так пара из 5 и 2 может превратиться в 4 и 3 или даже в 1 и 6. Такое действие считается ходом. Не рассмотренным остался всего лишь один тип хода. Ни одна из фигур не может пройти сквозь центральное поле доски (Chamber), но она может закончить своё движение на этом поле. Если это произошло, фигура «расщепляется» на две, с сохранением суммарного количества очков. Фигура всегда разделяется таким образом, чтобы очки одной из полученных фигур превышали очки другой не более чем на 1. Общее количество фигур, у каждого из игроков, не может превысить 10 (именно на этот случай, в начале игры, каждый из игроков имеет 1 кубик в резерве).





Направления «разлёта» осколков напоминают остриё стрелы. Кубик с большим числом очков (если такой есть) всегда уходит в левую сторону. В двух особых случаях «расщепление» невозможно. Во первых, как я уже сказал выше, количество кубиков одного цвета не может превышать 10. Кроме того, совершенно очевидно, что расщепить кубик с 1 очком не удастся. В обоих этих случаях, кубик, вошедший в Chamber, выходит неизменным, по левому направлению. Каждая из фигур, покинувших Chamber, может инициировать bumping, попав на свою фигуру или взять фигуру противника (только таким способом можно взять две вражеских фигуры одновременно).



Должен сказать, что Tom Kruszewski и «TSR» сильно переоценили возможности своей потенциальной аудитории. Для массового потребителя, игра оказалась слишком сложной (шахматы не менее сложны, но к ним все привыкли). Производитель прекратил выпуск продукции и, в настоящее время, «Чейз» можно приобрести лишь с рук, на различных ярмарках, аукционах и распродажах. Тем не менее, эта игра по праву считается одной из лучших игр 20-го столетия.



Простая работа



Игра начинается с доски, а доска у Chase… своеобразная. Ранее мне ещё не приходилось делать игры на гексагональных досках и это стало первым (очень небольшим) препятствием. Это интересный момент и я хочу рассказать о нём поподробнее. Механизм описания игровых досок в ZRF хорошо продуман и позволяет реализовывать практически любые доски, при условии того, что они отображаются на плоскость и не изменяются в процессе игры.



Вот как это выглядит
(board
(image "../Images/Chase/board.bmp")
(grid
(start-rectangle 48 32 108 82)
(dimensions
("a/b/c/d/e/f/g/h/i/j/k/l/m" (60 0))
("1/2/3/4/5/6/7/8/9" (-30 52))
)
(directions (se 1 1) (w 1 0) (sw 0 1)
(nw -1 -1) (e -1 0) (ne 0 -1))
)
(kill-positions
j1 k1 l1 m1 j2 k2 l2 m2 a3 k3 l3 m3
a4 k4 l4 m4 a5 b5 l5 m5 a6 b6 l6 m6
a7 b7 c7 m7 a8 b8 c8 m8 a9 b9 c9 d9
)
)


Я не сторонник того, чтобы детали модели смешивались с вопросами визуализации, но до тех пор, пока не требуется отделить одно от другого (например отобразить доску в «честном» 3D, а не изометрии) такой подход вполне работает. Рассмотрим это описание подробнее:




  • Неотъемлемой частью описания является файл, содержащий изображение доски. Все геометрические размеры и позиции фигур привязаны к нему (именно по этой причине, большую часть дистрибутива моей реализации "Сокобана" составляют чёрные прямоугольники различных форм и размеров). Файл содержащий изображение доски в BMP-формате (ZoG понимает только этот формат) определяется ключевым словом image. Здесь можно определить сразу несколько файлов (для обеспечения возможности переключения между скинами), но лишь с идентичными геометрическими пропорциями.

  • Ключевое слово grid позволяет описать n-мерный массив позиций. В большинстве случаев, это привычная двумерная доска, но также можно определять и доски другой размерности (вплоть до пяти). Доска может состоять из нескольких grid-ов, при условии того, что обеспечивается уникальное именование отдельных позиций. При большом желании, можно даже размещать один grid поверх другого, наподобие того как это сделано в "Квантовых крестиках-ноликах".

  • Размер «ячейки» и расположение сетки определяются ключевым словом start-rectangle. Две пары целых чисел задают экранные координаты (x, y) левого верхнего и правого нижнего угла самой первой (левой верхней) ячейки.

  • Далее следует описание «измерений» (dimensions). Каждое описание содержит строку имён (из которых декартовым произведением комбинируются имена позиций), а также два целых числа. В этих числах и заключается «магия», позволяющая описывать гексагональные и изометрические доски. Это ни что иное как сдвиги, на которые смещаются очередные ячейки сетки. Обычно (для двумерных досок), в одном из измерений, ячейки смещаются на ширину ячейки по x, а в другом — на высоту ячейки по y, но дополнительно смещая эти ячейки на половину ширины по x, можно получить превосходную основу для гексагональной доски.

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

  • Чтобы получить доску требуемой формы, мы берём «прямоугольную» доску большего размера, а затем смещаем ряды на половину ячейки друг относительно друга. В результате остаются «лишние» позиции, которые необходимо «отрезать» от доски. Ключевое слово kill-positions позволяет объявить ранее определённое имя позиции недействительным. Разумеется, вместе с удаляемыми позициями разрываются и соответствующие им соединения.





Использование ключевого слова grid позволяет существенно снизить объём ручной работы при описании «типовых» досок, но такой подход не лишён определённых недостатков. Во первых, если изображение доски не рисовалось под выбранные геометрические размеры специально, оперируя лишь целочисленными координатами и смещениями, бывает сложно выровнять расположение всех позиций доски идеально. Индивидуальное описание позиций менее лаконично, но позволяет корректировать их расположение независимо друг от друга. Вместе с тем, оно требует просто убийственного объёма ручной работы (с учётом необходимости исправления всех допущенных опечаток). Чтобы как-то облегчить этот процесс, я использую grid для «чернового» описания, после чего получаю индивидуальное описание позиций, при помощи небольшого скрипта:



Скрипт
my @grid;
my %kp;
my $sx, $sy, $dx, $dy;
my $dm = 0;

while (<>) {
if (/\(start-rectangle\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\)/) {
$sx = $1;
$sy = $2;
$dx = $3 - $1;
$dy = $4 - $2;
}
if (/\(\"([^\"]+)\"\s+\((-?\d+)\s+(-?\d+)\)\)/) {
my @a = split(/\//, $1);
$grid[$dm]->{ix} = \@a;
$grid[$dm]->{x} = $2;
$grid[$dm]->{y} = $3;
$dm++;
}
if (/\(kill-positions/) {
$fl = 1;
}
if ($fl) {
if (/\s(([a-z0-9]{1,2}\s+)+)/i) {
my @a = split(/\s+/, $1);
foreach my $p (@a) {
$kp{$p} = 1;
}
}
if (/\)/) {
$fl = 0;
}
}
}

sub try {
my ($ix, $pos, $x, $y) = @_;
if ($ix < $dm) {
my $i = 0;
foreach my $p (@{$grid[$ix]->{ix}}) {
try($ix + 1, $pos . $p, $x + $i * $grid[$ix]->{x}, $y + $i * $grid[$ix]->{y});
$i++;
}
} else {
if (!$kp{$pos}) {
my $a = $sx + $x;
my $b = $sy + $y;
my $c = $a + $dx;
my $d = $b + $dy;
print " ";
printf "($pos %3d %3d %3d %3d)\n", $a, $b, $c, $d;
}
}
}

try(0, '', 0, 0);




Результат
      (positions  
(a1 48 32 108 82)
(a2 18 84 78 134)
(b1 108 32 168 82)
(b2 78 84 138 134)
(b3 48 136 108 186)
(b4 18 188 78 238)
(c1 168 32 228 82)
(c2 138 84 198 134)
(c3 108 136 168 186)
(c4 78 188 138 238)
(c5 48 240 108 290)
(c6 18 292 78 342)
(d1 228 32 288 82)
(d2 198 84 258 134)
(d3 168 136 228 186)
(d4 138 188 198 238)
(d5 108 240 168 290)
(d6 78 292 138 342)
(d7 48 344 108 394)
(d8 18 396 78 446)
(e1 288 32 348 82)
(e2 258 84 318 134)
(e3 228 136 288 186)
(e4 198 188 258 238)
(e5 168 240 228 290)
(e6 138 292 198 342)
(e7 108 344 168 394)
(e8 78 396 138 446)
(e9 48 448 108 498)
(f1 348 32 408 82)
(f2 318 84 378 134)
(f3 288 136 348 186)
(f4 258 188 318 238)
(f5 228 240 288 290)
(f6 198 292 258 342)
(f7 168 344 228 394)
(f8 138 396 198 446)
(f9 108 448 168 498)
(g1 408 32 468 82)
(g2 378 84 438 134)
(g3 348 136 408 186)
(g4 318 188 378 238)
(g5 288 240 348 290)
(g6 258 292 318 342)
(g7 228 344 288 394)
(g8 198 396 258 446)
(g9 168 448 228 498)
(h1 468 32 528 82)
(h2 438 84 498 134)
(h3 408 136 468 186)
(h4 378 188 438 238)
(h5 348 240 408 290)
(h6 318 292 378 342)
(h7 288 344 348 394)
(h8 258 396 318 446)
(h9 228 448 288 498)
(i1 528 32 588 82)
(i2 498 84 558 134)
(i3 468 136 528 186)
(i4 438 188 498 238)
(i5 408 240 468 290)
(i6 378 292 438 342)
(i7 348 344 408 394)
(i8 318 396 378 446)
(i9 288 448 348 498)
(j3 528 136 588 186)
(j4 498 188 558 238)
(j5 468 240 528 290)
(j6 438 292 498 342)
(j7 408 344 468 394)
(j8 378 396 438 446)
(j9 348 448 408 498)
(k5 528 240 588 290)
(k6 498 292 558 342)
(k7 468 344 528 394)
(k8 438 396 498 446)
(k9 408 448 468 498)
(l7 528 344 588 394)
(l8 498 396 558 446)
(l9 468 448 528 498)
(m9 528 448 588 498)
)




Это лишь половина дела! Имена позиций доски необходимо поправить, чтобы привести их в соответствие с общепринятой нотацией. Кроме того, требуется связать пары позиций направлениями, не забыв «зациклить» доску по краям. Всё вместе вылилось в немаленький объём ручной работы, но я не стал писать под это дело скрипт (хотя возможно и стоило).



Сон разума



Хоть я и познакомился с «Чейзом» довольно давно, поиграть в него, до последнего времени, никак не удавалось. Очень уж причудливая для этого требуется доска. При некоторой сноровке, можно играть на доске Сёги (9x9), но её у меня тоже не было. Обычная шахматная доска (8x8) для этой игры непригодна совершенно. Доску для «Чейза» удалось приобрести на прошлом "Зилантконе", но кубики в комплект не входили. Своё приобретение я забросил на дальнюю полку и там бы оно вероятно и провалялось, если бы в дело не вмешался случай.



Случайности не случайны
Мою дочку пригласила на день рожденья семья, с которой мы давно и крепко дружим. В качестве подарка, была выбрана настольная игра, а поскольку нескольким взрослым предстояло просидеть около трёх часов в детском кафе, их (и меня) тоже требовалось чем-то занять. В качестве возможного варианта, была предложена другая игра, но поскольку я предпочитаю игры более абстрактные, то решил с собой тоже что нибудь принести. Первоначально, я подумал об Уре, но в имевшемся у меня комплекте, его D2 «кости», выполненные в форме полукруглых палочек (более характерных для Сенета), были довольно неудобны, при броске производили много шума и могли помешать окружающим.



Тут-то я и вспомнил про «Чейз». Предстояло пополнить его комплект двадцатью игральными кубиками, но поскольку я всё равно направлялся в магазин настольных игр (за подарком), это (как мне тогда казалось) не являлось проблемой. На сайте, я присмотрел себе замечательные полупрозрачные кубики (по 70 рублей за штуку), но жизнь внесла коррективы. В магазине выяснилось, что присмотренные мной кубики имеются лишь в одном экземпляре. Что я могу сказать, Казань — не Москва, пришлось удовольствоваться бюджетным вариантом и набирать вожделенные кубики из предложенной продавцом россыпи ико-, доде- и прочих -аэдров. Красный или зелёный комплект собрать не удалось, но синие и белые (ладно, ладно, один слегка желтоватый) кубики в наличии имелись.



Правила я, разумеется, переврал (рассказывал о памяти). В моём изложении, траектории разлёта «осколков», на выходе из «репликатора», напоминали не наконечник стрелы, а скорее латинскую букву 'Y'. По всей видимости, определённую роль сыграло её сходство со схемами распада элементарных частиц. «Осколки» двигались не на одну клетку (как в оригинальном варианте правил), а в соответствии с их «номиналом». Кроме того, такой ход было гораздо легче заблокировать. Любые препятствия (будь то фигура, стоящая на пути разлёта «осколков» или наличие на доске десяти фигур) трактовались как невозможность выполнения хода. В оригинальной версии правил, заблокировать "Chamber" можно лишь установив фигуру на пути входа в него.



Другим звеном "испорченного телефона" послужил сам Дмитрий. В своём описании «Чейза» он упомянул, что фигура, выполнившая взятие, имеет право на повторный ход (по аналогии с Шашками). В первоисточнике не было ни слова об этом (о чём ему не преминул сообщить уважаемый Гест), но я, в тот момент, не обратил на это внимания. Надо сказать, идея скрестить «Чейз» с «Шашками» уже тогда вызывала много вопросов. Следовало ли распространять правило повторного хода на случай bumping-а? На «осколки», полученные при разделении фигуры? Что следовало делать если взятие выполнял каждый из осколков? А если то же с bumping-ом? Но, нет таких сложностей, которых мы не могли бы себе создать! Я с энтузиазмом принялся за работу…



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



Я не в восторге от реализации составных ходов в ZoG и вот почему. Прежде всего, в понимании ZoG частичные ходы — это именно отдельные, независимые действия. По сути, это просто набор ходов, выполняемых одним и тем же игроком, друг за другом. Мы не можем передавать какую либо промежуточную информацию между частичными ходами! Глобальные и позиционные флаги автоматически сбрасываются, в начале каждого хода. Это дьявольски неудобно, но это лишь часть беды! ZoG не может рассматривать составной ход как единую сущность (в частности, именно по этой причине пришлось вводить хардкодную опцию "maximal captures", для реализации «правила большинства». Какие-то другие идеи, не укладывающиеся в этот хардкод, реализовать уже не удастся!









Это фрагмент партии из игры "Mana", придуманной Клодом Лероем. Количество чёрточек, на каждой позиции, показывает, на сколько шагов может переместиться фигура. Должно быть выполнено точное число шагов и, при этом, в процессе движения нельзя поворачиваться назад. Тут-то нас и поджидает засада! Очень редко, но бывает так, что фигура, выполнив два шага, загоняет себя «в тупик». Она не может продолжить движение, поскольку ей мешают другие фигуры и обязана сделать ещё один шаг, поскольку должна завершить ход! А ZoG, в свою очередь, не предоставляет ровно никаких средств, чтобы решить эту проблему!



Другим ограничением является то, что составной ход может продолжать лишь та же самая фигура, которая перемещалась предыдущим частичным ходом. Именно так всё и происходит в шашках, но в «Чейзе» ситуация немного сложнее. Например, взятие может быть осуществлено при помощи bumping-а, то есть не той фигурой, которая выполняла ход! С Chamber-ходом всё ещё сложнее. Оба осколка могут взять фигуры противника и, по логике, имеют право выполнить следующий частичный ход. И обе они не являются той фигурой которая заходила в Chamber (той фигуры, на доске, вообще уже нет)!



Меньше слов - больше кода
: val ( -- n )
piece-type mark -
;

: mirror ( 'dir -- 'dir )
DUP ['] nw = IF
DROP ['] sw
ELSE
DUP ['] ne = IF
DROP ['] se
ELSE
DUP ['] sw = IF
DROP ['] nw
ELSE
['] se = verify
['] ne
ENDIF
ENDIF
ENDIF
;

: step ( 'dir -- 'dir )
DUP EXECUTE NOT IF
mirror
DUP EXECUTE verify
ENDIF
;

: bump ( 'dir -- )
BEGIN
here E5 <> verify
friend? here from <> AND IF
piece-type SWAP step SWAP
create-piece-type
FALSE
ELSE
TRUE
ENDIF
UNTIL DROP
;

: slide ( 'dir n -- )
alloc-path !
val SWAP BEGIN
step
SWAP 1- DUP 0= IF
TRUE
ELSE
my-empty? verify
SWAP FALSE
ENDIF
UNTIL DROP
from here move
+ enemy? IF
+ cont-type partial-move-type
+ ENDIF
bump enemy? IF
alloc-all
ELSE
alloc-path @ 0= verify
ENDIF
add-move
;




В конечном счёте, всё сводится к добавлению вызова partial-move-type при взятии вражеской фигуры (до выполнения bumping-а). Ограничения, о которых я говорил выше, остаются в силе. Мы не можем выполнить частичный ход, если взятие было осуществлено не той фигурой которая начала ход (в результате bumping-а или «расщепления» в Chamber), но даже в таком виде, этот код был бы неплохим решением. Если бы он заработал:





Я так и не смог расшифровать этот ребус и просто отослал код разработчику Axiom. Грег пока не ответил, но вроде бы работает над выпуском патча, который, я надеюсь, решит проблему. Странно здесь то, что частичные ходы в Axiom действительно работают! Более того, они существенно расширяют функциональность ZRF. Всё это хорошо описано в документации и используется в нескольких приложениях. Видимо, мне просто не повезло.



Поскольку частичные ходы не работали, пришлось искать другой способ решения проблемы. Если не удаётся выполнить все действия в рамках одного хода, можно попробовать растянуть их на несколько ходов! Я уже делал так в других играх, создавая на доске специальную невидимую позицию, на которой размещалась фигура-флаг. Если фигура принадлежала противнику, игрок знал, что должен пропустить свой ход. Это небольшое изменение, но оно потянуло за собой другие. Мне пришлось помечать фигуры, продолжающие ход (теперь это могли быть не только фигуры, начавшие ход), а также усложнить порядок передачи хода. В целом, это было довольно громоздкое и очень неуклюжее решение.



Результатом моих усилий стала весьма оригинальная модификация игры, к сожалению имевшая слишком мало общего с оригиналом. Кроме того, использование «сложного» порядка передачи ходов (turn-order) наотмашь било по «интеллекту» AI. Используемый им минимаксный алгоритм крайне негативно реагирует на подобные вольности, а в «иммунном» к ним search-engine (альтернативном варианте построения Axiom AI) невероятно сложно реализовать поиск в глубину.



По хлебным крошкам



Хорошо, допустим мы, своим ходом, забираем одну (или даже две фигуры) противника, после чего, распределяем полученные очки по оставшимся его фигурам, обязательно начиная с младших. Но как быть, если младших фигур несколько? Например, в самом начале игры, у каждого из игроков имеется по две «единички». Взяв любую фигуру номиналом от одного до пяти очков, мы получим два варианта распределения очков и ход игры может серьёзным образом измениться, в зависимости от того, какой из них мы выберем.



Те же и комбинаторика
Здесь, практически на ровном месте, возникает интересная комбинаторная задача. Для того, чтобы понять, какими способами (при взятии фигуры) могут распределяться очки, необходимо представлять себе все сочетания фигур (на стороне одного из игроков), способные появиться в игре. Есть всего три условия:




  1. Каждая фигура может иметь номинал от 1 до 6 очков

  2. Количество фигур не может превышать 10

  3. Суммарное количество очков всегда равно 25



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



Скрипт
my @d;
my %s;

sub out {
my ($deep) = @_;
for (my $i = 0; $i < $deep; $i++) {
print "$d[$i]";
}
print "\n";
}

sub dice {
my ($start, $deep, $sum) = @_;
if ($sum == 25) {
out($deep);
}
if ($deep < 10 && $sum < 25) {
for (my $i = $start; $i <= 6; $i++) {
$d[$deep] = $i;
dice($i, $deep + 1, $sum + $i);
}
}
}

dice(1);




Результат
1111111666
1111112566
1111113466
1111113556
1111114456
1111114555
1111122466
1111122556
1111123366
1111123456
1111123555
1111124446
1111124455
111112666
1111133356
1111133446
1111133455
1111134445
111113566
1111144444
111114466
111114556
111115555
1111222366
1111222456
1111222555
1111223356
1111223446
1111223455
1111224445
111122566
1111233346
1111233355
1111233445
1111234444
111123466
111123556
111124456
111124555
1111333336
1111333345
1111333444
111133366
111133456
111133555
111134446
111134455
11113666
111144445
11114566
11115556
1112222266
1112222356
1112222446
1112222455
1112223346
1112223355
1112223445
1112224444
111222466
111222556
1112233336
1112233345
1112233444
111223366
111223456
111223555
111224446
111224455
11122666
1112333335
1112333344
111233356
111233446
111233455
111234445
11123566
111244444
11124466
11124556
11125555
1113333334
111333346
111333355
111333445
111334444
11133466
11133556
11134456
11134555
11144446
11144455
1114666
1115566
1122222256
1122222346
1122222355
1122222445
1122223336
1122223345
1122223444
112222366
112222456
112222555
1122233335
1122233344
112223356
112223446
112223455
112224445
11222566
1122333334
112233346
112233355
112233445
112234444
11223466
11223556
11224456
11224555
1123333333
112333336
112333345
112333444
11233366
11233456
11233555
11234446
11234455
1123666
11244445
1124566
1125556
113333335
113333344
11333356
11333446
11333455
11334445
1133566
11344444
1134466
1134556
1135555
1144456
1144555
115666
1222222246
1222222255
1222222336
1222222345
1222222444
122222266
1222223335
1222223344
122222356
122222446
122222455
1222233334
122223346
122223355
122223445
122224444
12222466
12222556
1222333333
122233336
122233345
122233444
12223366
12223456
12223555
12224446
12224455
1222666
122333335
122333344
12233356
12233446
12233455
12234445
1223566
12244444
1224466
1224556
1225555
123333334
12333346
12333355
12333445
12334444
1233466
1233556
1234456
1234555
1244446
1244455
124666
125566
133333333
13333336
13333345
13333444
1333366
1333456
1333555
1334446
1334455
133666
1344445
134566
135556
1444444
144466
144556
145555
16666
2222222236
2222222245
2222222335
2222222344
222222256
2222223334
222222346
222222355
222222445
2222233333
222223336
222223345
222223444
22222366
22222456
22222555
222233335
222233344
22223356
22223446
22223455
22224445
2222566
222333334
22233346
22233355
22233445
22234444
2223466
2223556
2224456
2224555
223333333
22333336
22333345
22333444
2233366
2233456
2233555
2234446
2234455
223666
2244445
224566
225556
23333335
23333344
2333356
2333446
2333455
2334445
233566
2344444
234466
234556
235555
244456
244555
25666
33333334
3333346
3333355
3333445
3334444
333466
333556
334456
334555
344446
344455
34666
35566
444445
44566
45556
55555




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



Скрипт
my @d;
my %s;

sub out {
my ($deep) = @_;
for (my $i = 0; $i < $deep; $i++) {
print "$d[$i]";
}
print "\n";
}

sub proc {
my ($x, $r, $m) = @_;
if ($x == 0) {
$s{$r}++;
} else {
my $n = $x % 10;
for (my $i = 0; $i < $n; $i++) {
proc(int($x / 10), $r + $i * $m, $m * 10);
}
}
}

sub alloc {
my ($x, $deep, $res) = @_;
if ($x == 0) {
proc($res, 0, 1);
} else {
my $vl = 6;
for (my $i = 0; $i < $deep; $i++) {
if ($d[$i] < $vl) {
$vl = $d[$i];
}
}
if ($vl < 6) {
my $cn = 0;
my $ix = 0;
for (my $i = 0; $i < $deep; $i++) {
if ($d[$i] == $vl) {
$cn++;
$ix = $i;
}
}
my $y = $d[$ix]; $d[$ix] = 6;
$x -= 6 - $vl;
if ($x < 0) {
$x = 0;
}
alloc($x, $deep, $res * 10 + $cn);
$d[$ix] = $y;
}
}
}

sub dice {
my ($start, $deep, $sum) = @_;
if ($sum == 25) {
for (my $i = 0; $i < $deep; $i++) {
my $x = $d[$i]; $d[$i] = 6;
alloc($x, $deep, 0);
$d[$i] = $x;
}
}
if ($deep < 10 && $sum < 25) {
for (my $i = $start; $i <= 6; $i++) {
$d[$deep] = $i;
dice($i, $deep + 1, $sum + $i);
}
}
}

dice(1, 0, 0);

my $all;

foreach my $k (sort { $s{$a} <=> $s{$b} } keys %s) {
$all += $s{$k};
print "$k\t=> $s{$k}\n";
}

print "\n$all\n";


Результат
102	=> 1
331 => 1
200 => 1
...
22 => 93
5 => 106
21 => 152
20 => 152
11 => 152
10 => 220
4 => 259
3 => 584
2 => 1061
1 => 1677
0 => 2407

7954




Слева — цепочки цифр, управляющие порядком распределения взятых очков. Например, «20» означает, что мы начинаем распределение с первой попавшейся фигуры (мы начинаем их подсчёт с 0), затем, распределяем в третью из оставшихся фигур с минимальным номиналом. Очевидно, что такая схема распределения возможна лишь для раскладов, не менее чем с четырьмя «минимальными» фигурами, например «3333445» (причём, распределить таким образом получится только «четвёрку» или «пятёрку»). Результат работы скрипта показывает, что распределяя очки, каждый раз в первую попавшуюся «минимальную» фигуру, мы покроем 30% (2407/7954) всех возможных ситуаций, а используя всего лишь три схемы распределения, уже более 64%!



Специально для таких случаев, ZoG предоставляет интересную интерфейсную возможность. Выполняя ход, игрок указывает два поля: начальное и конечное. В том случае, если существует несколько различных возможных ходов, соединяющих выбранную пару полей, игроку предоставляется возможность выбора (всплывающее меню). Простейший пример — превращение пешек в Шахматах. Дойдя до последней горизонтали, пешка может превратиться в любую из фигур (от слона до ферзя) и выбор должен быть сделан игроком. Именно этой опцией я и решил воспользоваться.



За Гензель и Гретель!
Суть идеи проста — для того, чтобы ядро ZoG сочло ходы разными, достаточно, чтобы они имели разное ZSG-представление. Попросту говоря, эти ходы должны делать различные вещи. Добиться этого не сложно, необходимо, всего навсего управлять тем, к каким из фигур будут добавляться очки. Тот факт, что количество фигур (с каждой стороны) не может превышать 10, позволяет использовать удобную десятичную систему счисления. Мы уже встречались с этими числами в предыдущей врезке. Каждая отдельная цифра означает ту фигуру (с нуля, по порядку), к которой будут добавлены очки. После каждого использования, от числа отрезается один десятичный разряд. В конечном итоге остаётся 0, означающий использование первой попавшейся фигуры.



Ещё немного кода
VARIABLE	alloc-path
VARIABLE alloc-val
VARIABLE alloc-target
VARIABLE alloc-pos

: alloc-to ( pos -- )
DUP add-pos
DUP val-at 6 SWAP -
DUP alloc-val @ > IF
DROP alloc-val @
0 alloc-val !
ELSE
alloc-val @ OVER - alloc-val !
ENDIF
my-next-player ROT ROT
OVER piece-type-at + SWAP
create-player-piece-type-at
;

: alloc ( -- )
6 0 BEGIN
DUP enemy-at? OVER not-in-pos? AND IF
SWAP OVER val-at MIN SWAP
ENDIF
1+ DUP A9 >
UNTIL DROP
DUP 6 < IF
alloc-target !
alloc-path @ 10 MOD alloc-pos !
0 BEGIN
DUP enemy-at? OVER not-in-pos? AND IF
DUP val-at alloc-target @ = IF
alloc-pos @ 0= IF
DUP alloc-to
0 alloc-target !
DROP A9
ELSE
alloc-pos --
ENDIF
ENDIF
ENDIF
1+ DUP A9 >
UNTIL DROP
alloc-target @ 0= verify
alloc-val @ 0> IF
alloc-path @ 10 / alloc-path !
RECURSE
ENDIF
ELSE
DROP
ENDIF
;

: alloc-all ( -- )
0 pos-count !
here add-pos
alloc
;




Переменная alloc-path содержит нашу последовательность «хлебных крошек». Разумеется, было бы совершенно слишком расточительно определять в коде все 105 возможных управляющих последовательностей, но мы уже выяснили, что они не равнозначны. Большинство из них будут использоваться крайне редко, а всего 4 из них покроют большую часть возможных случаев. К сожалению, даже этим воспользоваться не удалось:



Хлебные крошки
: eat ( 'dir n -- )
LITE-VERSION NOT IF
check-pass
check-neg
ENDIF
+ alloc-path !
val SWAP BEGIN
step
SWAP 1- DUP 0= IF
TRUE
ELSE
my-empty? verify
SWAP FALSE
ENDIF
UNTIL DROP
from here move
LITE-VERSION NOT enemy? AND IF
from piece-type-at mark - ABS
mark SWAP - create-piece-type
ENDIF
bump DROP
here E5 <> verify
enemy? verify
LITE-VERSION NOT IF
clear-neg
set-pass
ENDIF
+ val alloc-val !
+ alloc-all
add-move
;

: eat-nw-0 ( -- ) ['] nw 0 eat ;
: eat-sw-0 ( -- ) ['] sw 0 eat ;
: eat-ne-0 ( -- ) ['] ne 0 eat ;
: eat-se-0 ( -- ) ['] se 0 eat ;
: eat-w-0 ( -- ) ['] w 0 eat ;
: eat-e-0 ( -- ) ['] e 0 eat ;

: eat-nw-1 ( -- ) ['] nw 1 eat ;
: eat-sw-1 ( -- ) ['] sw 1 eat ;
: eat-ne-1 ( -- ) ['] ne 1 eat ;
: eat-se-1 ( -- ) ['] se 1 eat ;
: eat-w-1 ( -- ) ['] w 1 eat ;
: eat-e-1 ( -- ) ['] e 1 eat ;

: eat-nw-2 ( -- ) ['] nw 2 eat ;
: eat-sw-2 ( -- ) ['] sw 2 eat ;
: eat-ne-2 ( -- ) ['] ne 2 eat ;
: eat-se-2 ( -- ) ['] se 2 eat ;
: eat-w-2 ( -- ) ['] w 2 eat ;
: eat-e-2 ( -- ) ['] e 2 eat ;

: eat-nw-3 ( -- ) ['] nw 3 eat ;
: eat-sw-3 ( -- ) ['] sw 3 eat ;
: eat-ne-3 ( -- ) ['] ne 3 eat ;
: eat-se-3 ( -- ) ['] se 3 eat ;
: eat-w-3 ( -- ) ['] w 3 eat ;
: eat-e-3 ( -- ) ['] e 3 eat ;

{moves p-moves
{move} split-nw-0 {move-type} normal-priority
{move} split-ne-0 {move-type} normal-priority
{move} split-sw-0 {move-type} normal-priority
{move} split-se-0 {move-type} normal-priority
{move} split-w-0 {move-type} normal-priority
{move} split-e-0 {move-type} normal-priority
{move} split-nw-1 {move-type} normal-priority
{move} split-ne-1 {move-type} normal-priority
{move} split-sw-1 {move-type} normal-priority
{move} split-se-1 {move-type} normal-priority
{move} split-w-1 {move-type} normal-priority
{move} split-e-1 {move-type} normal-priority
+ {move} eat-nw-0 {move-type} normal-priority
+ {move} eat-ne-0 {move-type} normal-priority
+ {move} eat-sw-0 {move-type} normal-priority
+ {move} eat-se-0 {move-type} normal-priority
+ {move} eat-w-0 {move-type} normal-priority
+ {move} eat-e-0 {move-type} normal-priority
+ {move} eat-nw-1 {move-type} normal-priority
+ {move} eat-ne-1 {move-type} normal-priority
+ {move} eat-sw-1 {move-type} normal-priority
+ {move} eat-se-1 {move-type} normal-priority
+ {move} eat-w-1 {move-type} normal-priority
+ {move} eat-e-1 {move-type} normal-priority
+ {move} eat-nw-2 {move-type} normal-priority
+ {move} eat-ne-2 {move-type} normal-priority
+ {move} eat-sw-2 {move-type} normal-priority
+ {move} eat-se-2 {move-type} normal-priority
+ {move} eat-w-2 {move-type} normal-priority
+ {move} eat-e-2 {move-type} normal-priority
+ {move} eat-nw-3 {move-type} normal-priority
+ {move} eat-ne-3 {move-type} normal-priority
+ {move} eat-sw-3 {move-type} normal-priority
+ {move} eat-se-3 {move-type} normal-priority
+ {move} eat-w-3 {move-type} normal-priority
+ {move} eat-e-3 {move-type} normal-priority
{move} slide-nw {move-type} normal-priority
{move} slide-ne {move-type} normal-priority
{move} slide-sw {move-type} normal-priority
{move} slide-se {move-type} normal-priority
{move} slide-w {move-type} normal-priority
{move} slide-e {move-type} normal-priority
-( {move} exchange-1-nw {move-type} normal-priority
- {move} exchange-1-ne {move-type} normal-priority
- {move} exchange-1-sw {move-type} normal-priority
- {move} exchange-1-se {move-type} normal-priority
- {move} exchange-1-w {move-type} normal-priority
- {move} exchange-1-e {move-type} normal-priority
- {move} exchange-2-nw {move-type} normal-priority
- {move} exchange-2-ne {move-type} normal-priority
- {move} exchange-2-sw {move-type} normal-priority
- {move} exchange-2-se {move-type} normal-priority
- {move} exchange-2-w {move-type} normal-priority
- {move} exchange-2-e {move-type} normal-priority
- {move} exchange-3-nw {move-type} normal-priority
- {move} exchange-3-ne {move-type} normal-priority
- {move} exchange-3-sw {move-type} normal-priority
- {move} exchange-3-se {move-type} normal-priority
- {move} exchange-3-w {move-type} normal-priority
- {move} exchange-3-e {move-type} normal-priority
- {move} exchange-4-nw {move-type} normal-priority
- {move} exchange-4-ne {move-type} normal-priority
- {move} exchange-4-sw {move-type} normal-priority
- {move} exchange-4-se {move-type} normal-priority
- {move} exchange-4-w {move-type} normal-priority
- {move} exchange-4-e {move-type} normal-priority
- {move} exchange-5-nw {move-type} normal-priority
- {move} exchange-5-ne {move-type} normal-priority
- {move} exchange-5-sw {move-type} normal-priority
- {move} exchange-5-se {move-type} normal-priority
- {move} exchange-5-w {move-type} normal-priority
- {move} exchange-5-e {move-type} normal-priority )
moves}




По всей видимости, в Axiom имеется ограничение на количество определяемых ходов (никак не отражённое в документации). Как я это определил? Очень просто! Когда я добавляю в код все определения, программа крэшится при старте. Если я убираю часть определений (например exchange-ходы), всё работает нормально. К сожалению, от идеи вариативного распределения очков пришлось отказаться.



Строго говоря, это не вполне корректное решение. По правилам «Чейза», распределять очки должен не тот игрок, который выполнил ход, а его противник. Я не имею ни малейшего представления, о том, как этого можно добиться, используя ZoG, но есть очень простой обходной путь. Интерфейс ZoG предоставляет удобную интерфейсную возможность редактирования доски. Используя команды всплывающего меню, игрок может удалить любую фигуру на доске или создать другую. Эта возможность незаменима при отладке и я часто ей пользуюсь. В общем, игрок которому не понравилось автоматическое распределение очков, может легко перераспределить их вручную (очерёдность хода, при этом, не нарушается). Необходимо соблюдать лишь минимальную осторожность. В процессе редактирования не следует допускать ситуации, когда у одного из игроков остаётся менее 5 фигур, поскольку в этом случае, ему будет немедленно засчитано поражение и игра будет остановлена.



… считай до одного!



Поскольку идея «вариативного» распределения съеденных очков провалилась, я вернулся к разработке игры, посредством ZRF. Axiom-реализация, в принципе, тоже работала, но ей всё ещё не хватало AI (штатным ZoG-овским Аксиома пользоваться не умеет). В целом, эта задача сводится к правильному кодированию оценочной функции (для эстетов есть ещё и "Custom Engine"), но и это — весьма не просто! Во всяком случае, стандартная оценочная функция, учитывающая мобильность и материальный баланс, в «Чейзе» проявила себя не лучшим образом.



Немножко деталей
Оценочная функция, о которой я говорю, выглядит так:

: OnEvaluate ( -- score ) 
mobility
current-player material-balance KOEFF * +
;


Самый хитрый зверь здесь — mobility. Фактически — это количество всех возможных ходов игрока, из которого вычитается количество всех возможных ходов противника. Все ходы игрока, на момент оценки позиции, уже сгенерированы — подсчитать их не сложно, а вот чтобы сгенерировать ходы противника, приходится использовать немножко аксиомовской магии:

: mobility ( -- score )
move-count
current-player TRUE 0 $GenerateMoves
move-count -
$DeallocateMoves
;


Далее, полученная «мобильность» складывается с «материальным балансом», умноженным на некоторый константный коэффициент. Материальный баланс — это просто суммарная стоимость всех своих фигур, за вычетом стоимости фигур противника. Кстати, это объясняет, почему для фигур в Axiom я выбрал такие странные числовые значения:

{pieces
{piece} p1 {moves} p-moves 6 {value}
{piece} p2 {moves} p-moves 5 {value}
{piece} p3 {moves} p-moves 4 {value}
{piece} p4 {moves} p-moves 3 {value}
{piece} p5 {moves} p-moves 2 {value}
{piece} p6 {moves} p-moves 1 {value}
pieces}


Я стремился сделать «мелкие» фигуры более значимыми, поскольку игроку действительно выгодно держать на доске как можно больше мелких фигур. В общем, в таком виде, всё это не сработало! AI вёл себя просто ужасно. Иногда складывалось впечатление, что он целенаправленно стремиться проиграть. Я думал о том как улучшить оценочную функцию, включив в неё бонусы/штрафы за взаимные угрозы фигур, образование кластеров (из фигур, стоящих вплотную друг к другу), достижимости Chamber и пр., но решил не тратить на это время, а просто переключиться на ZRF. Штатный AI ZoG-а традиционно показывает себя сильным, в подобных играх.



Оставалась всего одна мелочь — в ZRF напрочь отсутствовала арифметика! «Чейз» — такая игра, в которой постоянно приходится считать! В некоторых случаях можно выкрутится. Например, при определении поражения игрока, вместо подсчёта очков (до 25-ти) на всех фигурах, можно ограничиться стандартной проверкой количества фигур. Поскольку 25 очков заведомо невозможно разместить на 4 фигурах, и всегда можно распределить по большему количеству фигур, следующих условий завершения игры вполне достаточно:



(loss-condition (Red White) (pieces-remaining 4) )
(loss-condition (Red White) (pieces-remaining 3) )


Вторая проверка необходима, поскольку в игре возможна ситуация, когда одним ходом забираются сразу две фигуры (после расщепления фигуры в Chamber). К сожалению, есть одна задача, в которой целочисленная арифметика необходима! Разумеется, это распределение «съеденных» очков. В ZRF я не пытаюсь предложить несколько возможных вариантов распределения, на выбор. Мне необходимо просто обойти все фигуры, начиная с младших, и правильно добавить к ним ещё не распределённые очки. Вот как я это делаю:



В основном, из палок
Целые числа будем делать из булевских флагов (просто потому что больше не из чего). В ZRF-приложении их можно создать не больше тридцати двух, но нам вполне хватит четырёх (чтобы уметь считать до десяти). Макросы обеспечат (более менее) комфортную работу. Для начала, совершенно необходимо уметь обнулять «число», а также прибавлять (и отнимать) единичку:



Ноль плюс/минус один
(define clear
(set-flag $1-8 false) (set-flag $1-4 false)
(set-flag $1-2 false) (set-flag $1-1 false)
)

(define inc
(if (flag? $1-1)
(set-flag $1-1 false)
(if (flag? $1-2)
(set-flag $1-2 false)
(if (flag? $1-4)
(set-flag $1-4 false)
(if (flag? $1-8)
(set-flag $1-8 false)
else
(set-flag $1-8 true)
)
else
(set-flag $1-4 true)
)
else
(set-flag $1-2 true)
)
else
(set-flag $1-1 true)
)
)

(define dec
(if (not-flag? $1-1)
(set-flag $1-1 true)
(if (not-flag? $1-2)
(set-flag $1-2 true)
(if (not-flag? $1-4)
(set-flag $1-4 true)
(if (not-flag? $1-8)
(set-flag $1-8 true)
else
(set-flag $1-8 false)
)
else
(set-flag $1-4 false)
)
else
(set-flag $1-2 false)
)
else
(set-flag $1-1 false)
)
)




Пользоваться этим — совсем просто:



Не больше десяти!
(define not-10?
(or (not-flag? $1-8)
(flag? $1-4)
(not-flag? $1-2)
(flag? $1-1)
)
)

(define calc
(clear x)
mark START
(while (on-board? next)
next
(if friend?
(inc x)
)
)
(verify (not-10? x))
back
)




Главный цирк, как и предполагалось, начинается когда дело доходит до распределения очков по фигурам. Для начала, эти очки необходимо получить из съедаемой фигуры. Здесь подход совершенно прямолинейный. ZRF — не знает чисел, но мы-то знаем!



Инициализация
(define init
(clear $1)
(if (or (piece? p1) (piece? p3) (piece? p5))
(set-flag $1-1 true)
)
(if (or (piece? p2) (piece? p3) (piece? p6))
(set-flag $1-2 true)
)
(if (or (piece? p4) (piece? p5) (piece? p6))
(set-flag $1-4 true)
)
)




Здесь, нас подстерегает маленькая засада. Если съедаемых фигур две (такое редко, но бывает), такой код совершенно не подходит, поскольку, в самом начале, обнуляет «число». Надо научиться складывать числа! Это просто:



Отнимаем от одного - добавляем к другому
(define sum
(while (not-0? $2)
(inc $1)
(dec $2)
)
)




Осталось немного, но главное. Как добавить часть «числа» к количеству очков на фигуре? Причём, не абы как, а начиная с младших фигур?



Здесь пришлось немного подумать
(define try-alloc
(if (is-0? x)
(inc y)
else
(dec x)
)
)

(define set-piece
(if (am-i-red?)
(create White $1)
else
(create Red $1)
)
)

(define alloc-to
(clear y)
(if (piece? p1)
(try-alloc) (try-alloc) (try-alloc) (try-alloc) (try-alloc)
)
(if (piece? p2)
(try-alloc) (try-alloc) (try-alloc) (try-alloc)
)
(if (piece? p3)
(try-alloc) (try-alloc) (try-alloc)
)
(if (piece? p4)
(try-alloc) (try-alloc)
)
(if (piece? p5)
(try-alloc)
)
(if (is-0? y)
(set-piece p6)
else
(if (is-1? y)
(set-piece p5)
else
(if (is-2? y)
(set-piece p4)
else
(if (is-3? y)
(set-piece p3)
else
(set-piece p2)
)
)
)
)
)

(define alloc
(if (not-0? x)
mark ST
(while (on-board? next)
next
(if (and enemy? (piece? $1) (not-0? x)
(not-position-flag? is-captured?))
(alloc-to)
)
)
back
)
)

(define alloc-all
(alloc p1) (alloc p2) (alloc p3) (alloc p4) (alloc p5)
)




При выполнении alloc-all, в x находится количество ещё не распределённых очков (максимум — 12, если съели две шестёрки). Пока в x не 0, пытаемся его распределить, начиная с p1 и до p5 (в шестёрки, очевидно, распределить уже ничего не удастся). Ищем фигуру требуемого номинала на доске и вызываем alloc-to. Здесь и начинается магия. Распределяем очки по одной единичке, в зависимости от типа фигуры (в p1 лезет 5 единичек. в p2 — 4 и т.д.). Не пытаемся анализировать, хватает ли в x единичек, а просто добавляем все распределяемые единички к ещё одной переменной — y. Это и есть переполнение (очевидно оно не может превышать 4), если оно не нулевое, просто корректируем тип фигуры.











В результате, вся наша «ненормальная арифметика» работает с вполне приемлемой производительностью и AI ничуть не страдает. Надо сказать, что не всегда подобные эксперименты бывают столь же удачны. Например, эту версию калькулятора (напомню, что никакой арифметики в ZRF нет) можно рассматривать исключительно как шутку. Его производительность просто ужасна! Но в нашем случае, «ненормальное программирование» показало себя лучшим из возможных решений.





Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/278853/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Вышла версия 1.4 Perl плагина для IntelliJ IDEA

Понедельник, 21 Марта 2016 г. 21:14 (ссылка)


Perl*




Новая версия Perl плагина для продуктов JetBrains стала доступна для скачивания из официального репозитория. Главные фичи: оптимизация и поддержка HTML::Mason.



А теперь, немного подробнее:



Что новенького?



Поддержка HTML::Mason



Как оказалось, есть масса проектов, написанных в старые добрые времена на фреймворке HTML::Mason и граждане активно просили сделать его поддержку. Итог:




  • Парсинг компонент HTML::Mason

  • Конфигурация для задания имен autohandler и dhandler, папок для компонент, расширений файлов, глобальных переменных и кастомных тэгов.

  • Авто-дополнение и подсветка синтаксиса

  • Корректное разрешение переменных в соответствии со структурой скомпилированного компонента

  • Корректное разрешение иерархии компонент и ее визуализация

  • Разрешение, навигация и рефакторинг компонент, методов и локальных компонент при вызове из шаблонов



Новые фичи




  • Метки

    • Разрешение, авто-дополнение, навигация и рефакторинг меток (deprecated использование меток не поддерживается и не планируется)

    • Инспекции для необъявленных и неиспольземых меток


  • Here-docs

    • Авто-дополнение маркеров here-doc возможными вариантами для инжектирования других языков

    • Поддержка последовательных here-doc в одной строке

    • Поддержка backref here-doc

    • Автоматическое отключение инжектирования языков в here-doc при наличии интеполируемых элементов теперь можно настроить

    • Форматирование теперь корректно работает с here-doc инжектированных другими языками


  • Модификатор регулярных выражений n из perl 5.22

  • Обычные строки теперь можно вручную инжектировать поддерживаемыми языками



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



Что дальше?



Интеграция perldoc, Perl::Critic, Perl::Tidy и дебаггер. Не уверен что все будет в следующем релизе, но это то, что я хочу сделать.



Ссылки





Приятного кодинга!



Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/279821/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<perl - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda